Arduino控制带编码器的直流电机速度

Arduino DC Motor Speed Control with Encoder, Arduino DC Motor Encoder

作者

How to control dc motor with encoder:

步进电机伺服电机 相比,直流电机的使用频率更高。这三种电机的结构各不相同。步进电机和伺服电机的设计方式使我们可以控制它们的位置。我们可以控制正反方向的步进。伺服电机可以在 0 至 180 度之间移动,因此可以移动到 0 至 180 度之间的任何位置。同样,步进电机可以精确控制步进,这也是步进电机用于数控机床、3d 打印机等的原因。

另一方面,直流电机通电后会立即开始旋转,并持续旋转,除非使用特定技术,否则无法准确控制其位置。你无法像控制步进电机和伺服电机那样百分之百地控制直流电机,但如果添加一个编码器,就能真正改变整个游戏规则。有了编码器,你就可以跟踪电机的转数、它所走过的距离,这样你就可以建立一个很好的反馈系统,用来控制直流电机。然后,您就可以让直流电机停在您希望它停的位置。使用编码器控制直流电机并不那么简单,你不能一开始就在直流电机上添加一个编码器**,然后开始控制直流电机,要使用编码器,你需要一个控制器,控制器将读取编码器,然后根据程序员编写的预定义指令相应地控制直流电机。我知道初学者更喜欢使用 Arduino Uno、Arduino mega 和 Arduino Nano,所以我会从 Arduino Uno 开始,同样的连接和程序你也可以在 Arduino Nano 和 Arduino Mega 上尝试。

要开始使用,您需要 Arduino Uno、一个电机驱动器、一个直流电机,当然还有一个编码器。 为了读取编码器,我们将把编码器的输出引脚与 Arduino 的中断引脚 2 和 3 连接起来。 编码器的电源线将与 Arduino 的 5V 和 GND 连接。 为了简化操作,我将从简单的示例代码开始,其中我将把引脚 2 和 3 用作普通的数字引脚,我们不会激活中断,然后在第二个示例代码中,我们将使用中断。 事不宜迟,让我们开始吧

DC Motor with Encoder + Arduino, Circuit Diagram:

从下面的电路图中可以看到,这种直流电机内置了编码器。 因此,你完全可以决定是将其用作简单的直流电机,还是使用编码器,或者同时使用电机和编码器。 如电路图所示,电源线(红线和黑线)没有连接,但编码器线已经连接。 在第一个示例中,我们将只使用编码器来了解基础知识,这样你就会很容易理解编码器的工作原理。 因此,我们将用手旋转电机轴来查看触发信号。
在这里插入图片描述
编码器的工作原理是观察连接在电机轴上的磁铁所产生的磁场变化,当电机旋转时,编码器输出将周期性地触发。 当磁铁顺时针旋转时,输出端 "a "将首先触发,反之,当逆时针旋转时,输出端 "b "将触发。 这样,您就能准确知道电机轴的旋转方向。 在需要控制直流电机正反转的情况下,这将非常方便。

Arduino DC Motor Encoder
让我们编写一个非常简单的程序,了解编码器的工作原理以及如何读取编码器的输出。

Arduino Encoder Code:

#define ENCA 2 // pin2 of the Arduino
#define ENCB 3 // Pin3 of the Arduino
int ENCA_DATA;
int ENCB_DATA;

void setup() {
  Serial.begin(9600); // Activates Serial communication
  pinMode(ENCA,INPUT); // sets pin2 as the input
  pinMode(ENCB,INPUT); // sets pin3 as the input
}

void loop() {
  ENCA_DATA = digitalRead(ENCA); 
// We simply read Pin2 of the Arduino and store the result in variable ENCA_DATA
  ENCB_DATA = digitalRead(ENCB); 
// We simply read Pin3 of the Arduino and store the result in variable b
  Serial.print(ENCA_DATA*5); 
  Serial.print(" ");
  Serial.print(ENCB_DATA*5);
  Serial.println();
}
Arduino Encoder Code Explanation:

这个基本程序不需要任何库。 首先,我定义了 Arduino 的 2 号和 3 号引脚。 我将这两个引脚称为 ENCA 和 ENCB。 接下来,我定义了两个变量 ENCA_DATA 和 ENCB_DATA。

#define ENCA 2
#define ENCB 3

int ENCA_DATA;
int ENCB_DATA;

接下来,我们需要告诉 Arduino 是否要使用串行通信? 定义引脚将用作输入还是输出?

void setup() {
	Serial.begin(9600);
	pinMode(ENCA,INPUT);
	pinMode(ENCB,INPUT);
}

void setup() 函数中,选择 9600 作为波特率。 接下来,使用 pinMode() 函数将两个引脚 ENCA 和 ENCB 设置为输入。

void loop() {
ENCA_DATA = digitalRead(ENCA);
ENCB_DATA = digitalRead(ENCB);

我们使用 digitalRead() 函数读取 ENCA 引脚,并将值存储到变量 ENCA_DATA,在 ENCB 上所做的完全相同。

Serial.print(ENCA_DATA*5);
Serial.print(" ");
Serial.print(ENCB_DATA*5);
Serial.println();

}

接下来,使用了 Serial.print() 和 Serial.println() 函数。 在括号内,将数值乘以 5以便更容易读取图表。 代码上传后,你可以继续打开串行监视器,选择 9600 波特率。 现在,你可以开始旋转带有编码器的直流电机轴。 编码器信号会随着电机轴的旋转而变化,使用串行绘图仪更容易理解这些变化。 因此,顺时针旋转电机轴时,输出 "a "被触发;同样,逆时针旋转电机轴时,输出 "b "被触发。 这段代码与电机轴的位置无关,其目的只是帮助你理解这两个输出 "a "和 "b "是如何触发的。 现在我们来测量直流电机轴的位置。 硬件方面没有任何变化。

Position of the encoder Arduino Code:


#define Encoder_output_A 2 // pin2 of the Arduino
#define Encoder_output_B 3 // pin 3 of the Arduino
// these two pins has the hardware interrupts as well.
int Count_pulses = 0;
void DC_Motor_Encoder();
void setup()
{
  Serial.begin(9600);               // activates the serial communication
  pinMode(Encoder_output_A, INPUT); // sets the Encoder_output_A pin as the input
  pinMode(Encoder_output_B, INPUT); // sets the Encoder_output_B pin as the input
  attachInterrupt(digitalPinToInterrupt(Encoder_output_A), DC_Motor_Encoder, RISING);
}

void loop()
{
  Serial.println("Result: ");
  Serial.println(Count_pulses);
}

void DC_Motor_Encoder()
{
  int b = digitalRead(Encoder_output_B);
  if (b > 0)
  {
    Count_pulses++;
  }
  else
  {
    Count_pulses--;
  }
}
Position of the encoder Arduino Code Explanation:

这段代码是我上面解释过的代码的修改版。 做了一些改动, 引脚 2 和引脚 3 的连接方式保持不变。

#define Encoder_output_A 2 // pin2 of the Arduino
#define Encoder_output_B 3 // pin 3 of the Arduino
// these two pins has the hardware interrupts as well.

我定义了一个全局变量 Count_pulses,初始值为 0。 由于这是一个全局变量,因此我可以从 Arduino 代码的任何地方访问该变量。
int Count_pulses = 0;

void setup() {
Serial.begin(9600); // activates the serial communication
pinMode(Encoder_output_A,INPUT); // sets the Encoder_output_A pin as the input
pinMode(Encoder_output_B,INPUT); // sets the Encoder_output_B pin as the input
attachInterrupt(digitalPinToInterrupt(Encoder_output_A),DC_Motor_Encoder,RISING);
}

这次添加了attachInterrupt(digitalPinToInterrupt(Encoder_output_A),DC_Motor_Encoder,RISING);这行代码。 attachinterrupt() 函数用于激活硬件中断。 attachinterrupt() 函数将三个参数作为输入。 第一个参数是引脚 digitalPinToInterrupt(Encoder_output_A),第二个参数是函数名称,每次当 Arduino 的Encoder_output_A引脚(即引脚 2)发生中断时,该函数就会执行。 第三个参数是告诉我们是在上升沿还是下降沿采取行动。 这样,我们的中断设置就完成了。我们在 loop() 函数中只使用了两行代码,第一行代码打印文本 Result 和 “Serial.println(Count_pulses);”,后者打印存储在变量 Count_pulses 中的值。

void loop() {
	Serial.println(“Result:);
	Serial.println(Count_pulses);
}

DC_Motor_Encoder() 函数是一个用户自定义函数,它没有返回类型,也不将任何参数作为输入。 当 Arduino 的引脚 2 发生中断时,该函数将被执行。 因此,在该函数中,我们只需读取 Encoder_output_B 引脚的值,并将其存储在变量 b 中。接下来,我们使用 if 条件来检查是否检测到信号,然后将 Count_pulses 递增 或递减 。

void DC_Motor_Encoder(){
	int b = digitalRead(Encoder_output_B);
	if(b > 0){
		Count_pulses++;
	}
	else{
		Count_pulses–;
	}
}

上传代码,打开串行监视器,开始旋转编码器。 顺时针和逆时针旋转电机轴。 在一个方向上,数值会增加,在另一个方向上,数值会减少。

Driving the Motor with Encoder and Arduino:

现在,我相信你已经完全了解了编码器的工作原理、编码器输出的触发方式以及如何编写简单的代码来计数脉冲。 到目前为止,我们都是通过手动旋转直流电机轴来触发编码器输出,现在要想自动完成所有操作,就需要连接电机驱动器,以便读取编码器的位置测量值。 首先将直流电机导线连接到电机驱动器电路的输出端。 电机驱动器还需要适当的电源,您需要根据直流电机的规格来选择。 接下来,将电机驱动器的地线连接到 Arduino 的地线上,电机驱动器的 PWM 输入应连接到 Arduino 的 PWM 引脚上,这里我使用的是第 5 引脚。

在这里插入图片描述

另外两个电机驱动器引脚可以连接到 Arduino 其余的任何数字引脚上。 在编写控制算法之前,让我们先测试一下电机驱动器,定义连接到电机驱动器的引脚。

Driving the Motor with Encoder, Arduino Code:

#include <Arduino.h>

#define ENCA 2
#define ENCB 3
#define PWM 5
#define IN2 6
#define IN1 7

int pos = 0;

void setMotor(int dir, int pwmVal, int pwm, int in1, int in2);
// void setMotor(int, int, int, int, int);
void readEncoder();

void setup()
{
  Serial.begin(9600);
  pinMode(ENCA, INPUT);
  pinMode(ENCB, INPUT);
  attachInterrupt(digitalPinToInterrupt(ENCA), readEncoder, RISING);
}

void loop()
{
  setMotor(1, 25, PWM, IN1, IN2);
  delay(200);
  Serial.println(pos);
  setMotor(-1, 25, PWM, IN1, IN2);
  delay(200);
  Serial.println(pos);
  setMotor(0, 25, PWM, IN1, IN2);
  delay(20);
  Serial.println(pos);
}

void setMotor(int dir, int pwmVal, int pwm, int in1, int in2)
{
  analogWrite(pwm, pwmVal);
  if (dir == 1)
  {
    digitalWrite(in1, HIGH);
    digitalWrite(in2, LOW);
  }
  else if (dir == -1)
  {
    digitalWrite(in1, LOW);
    digitalWrite(in2, HIGH);
  }
  else
  {
    digitalWrite(in1, LOW);
    digitalWrite(in2, LOW);
  }
}

void readEncoder()
{
  int b = digitalRead(ENCB);
  if (b > 0)
  {
    pos++;
  }
  else
  {
    pos--;
  }
}

定义一个可以设置电机方向和速度的函数非常有用。 我在这里编写的 setMotor 函数接口使用最后三个输入中定义的引脚来设置电机的方向和速度。 如果方向整数为 1,那么通过向驱动器的输入引脚写入高-低组合,电机将单向旋转。 如果将顺序颠倒为低-高组合,电机将向另一个方向旋转。 在循环函数中,可以调用 setMotor 函数来驱动电机,同时将位置写入串行线。

使用编码器反馈回路控制直流电机:*

到目前为止,我们已将控制器电机驱动器和电机连接成一个环路,但还没有使用编码器的位置信号来控制电机位置。 我们将使用反馈回路。 在反馈回路中,控制部件通常被称为设备(电机),这里指的是电机和电机驱动器。 我们用来测量位置的传感器(编码器)就是编码器。 为了实际控制电机的位置,需要为其提供一个目标位置,然后求出目标位置与测量位置之间的差值,其结果就是误差,通常写为 e(t)。
Arduino DC Motor Encoder
既然误差已经计算出来,那么就可以使用控制器来计算发送给设备(电机)的控制信号。 控制信号经过配置后,来减小误差。 在本项目中,我们将使用 PID 控制算法来生成控制信号 u(t)。PID 控制信号由比例项、导数项和积分项三个项的总和构成,这就是 PID 的含义。 比例项最为重要,因为它直接负责减少误差,而导数和积分项通常用于平滑控制系统的响应。 三个常数 kp、ki 和 kd 决定了每个项在控制回路中的体现程度;您可以调整这些常数来调整响应。 您可以使用简单的有限差分近似估算误差的积分和导数。 积分项计算误差随时间的累积,导数项计算误差随反馈控制回路变化的速度。 完成后,您就可以编写代码来控制电机的位置了。

DC Motor control with Encoder Feedback, Arduino Code:


#define ENCA 2
#define ENCB 3
#define PWM 5
#define IN2 6
#define IN1 7

int pos = 0;
long prevT = 0;
float eprev = 0;
float eintegral = 0;

void setMotor(int dir, int pwmVal, int pwm, int in1, int in2);
void readEncoder();

void setup()
{
  Serial.begin(9600);
  pinMode(ENCA, INPUT);
  pinMode(ENCB, INPUT);
  attachInterrupt(digitalPinToInterrupt(ENCA), readEncoder, RISING);
  Serial.println("target pos");
}

void loop()
{

  // set target position
  int target = 1200;
  // target = 250*sin(prevT/1e6);

  // PID constants
  float kp = 1;
  float kd = 0.025;
  float ki = 0.0;

  // time difference
  long currT = micros();
  float deltaT = ((float)(currT - prevT)) / (1.0e6);
  prevT = currT;

  // error
  int e = pos - target;

  // derivative
  float dedt = (e - eprev) / (deltaT);

  // integral
  eintegral = eintegral + e * deltaT;

  // control signal
  float u = kp * e + kd * dedt + ki * eintegral;

  // motor power
  float pwr = fabs(u);
  if (pwr > 255)
  {
    pwr = 255;
  }

  // motor direction
  int dir = 1;
  if (u < 0)
  {
    dir = -1;
  }

  // signal the motor
  setMotor(dir, pwr, PWM, IN1, IN2);

  // store previous error
  eprev = e;

  Serial.print(target);
  Serial.print(" ");
  Serial.print(pos);
  Serial.println();
}

void setMotor(int dir, int pwmVal, int pwm, int in1, int in2)
{
  analogWrite(pwm, pwmVal);
  if (dir == 1)
  {
    digitalWrite(in1, HIGH);
    digitalWrite(in2, LOW);
  }
  else if (dir == -1)
  {
    digitalWrite(in1, LOW);
    digitalWrite(in2, HIGH);
  }
  else
  {
    digitalWrite(in1, LOW);
    digitalWrite(in2, LOW);
  }
}

void readEncoder()
{
  int b = digitalRead(ENCB);
  if (b > 0)
  {
    pos++;
  }
  else
  {
    pos--;
  }
}

首先要定义全局存储变量,用于保存周期内的值,这些值将用于积分和导数的有限差分估算。 在loop()函数中需要做的第一件事是为控制环路设置一个目标值;当测量位置变得更接近目标值时,控制信号将随时间而调整。 接下来,定义 PID 控制算法中使用的常数,将 kp 设为 1,kd 和 ki 设为 0。 我们需要计算时间差 t,首先使用 micros() 函数记录当前时间(以微秒为单位),然后计算当前时间与前一时间的差值(以秒为单位),注意是浮点运算而不是整数运算;完成计算后,将当前时间存储到前一时间变量中备用。

在循环的下一次迭代中,误差被计算为目标位置和测量位置之间的差值。在这里,由于电机引线的接线方式不同,我将顺序颠倒了一下,如果您发现控制算法不起作用,可以尝试像我一样转换误差项的符号。 这个信号将告诉工厂电机的旋转方向和速度,并将信号发送给电机。 我们需要通过计算 PWM 信号作为控制信号 U 的浮点绝对值,将其转换为速度和方向起始信号。

此外,您还需要将 PWM 信号的上限设定为 255,因为这是我们可以写入的最大值。接下来,通过计算控制信号 u 的符号以及从控制信号中计算出的电机速度和方向来确定方向,调用 setMotor 函数写入电机驱动器,以完成循环功能,存储之前的误差值,并将目标位置和测量位置打印到串行通信端口,这样您就可以测试您的控制算法在使用这些参数时的性能如何。 在达到目标位置后,我看到了一点过冲。 换句话说,电机转得太快,必须反转方向才能达到目标位置。减少过冲的方法之一是增加导数项,在这里我将 kd 设为 0.025,这足以完全消除该系统的过冲。 一旦您的系统能够实现恒定目标,请尝试设置一个随时间变化的目标,在这里我设置了一个正弦目标,具体取决于您的目标和负载条件,您需要进一步调整 PID 参数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蔚蓝慕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值