上一篇文章介绍了编码马达的原理以及特性,本篇话不多说,直接开始实用。
马达控制
观察以下编码马达的接口图片:(原谅我这样拍照,实在懒得把马达拆下来了)
总共六根线,其中两根是控制马达转速以及转向的,也就是说和普通马达完全相同,你需要找到这两根线,应该会有注明,例如我的马达上标注为:马达线1
和马达线2
。将这两根线接入马达驱动板,这里我推荐TB6612模块,小巧又强大,也不算很贵,但是使用时一定要小心,我买回来的第一个,刚一接线就烧了(不怪人家,是我把12v接上了5v的口)。
关于TB6612的使用,这里简单介绍一下,马达的两根线应该接入AO1和AO2(或BO1和BO2),PWMA(或PWMB)接入Arduino的某一个PWM端口,这个是管马达速度的。UNO的PWM口其实蛮少的,做三轮车很吃紧,我直接用了Mega。AIN1和AIN2(或BIN1和BIN2)分别接入Arduino的非PWM的数字口,这两个是管马达转向的。剩下的口一定要注意,千万别接错,否则就像我一样,VM和随便一个GND分别接大电源的正负极,大电源电压尽量不要高于12V,不要低于8V,最好用个稳压模块。VCC和另外一个GND口分别接入Arduino的+5v和GND口。STBY可以直接接一个+5V口,也可以接入数字口然后给高电平,这是使能端。
ok,终于把这个复杂的接口讲完了,其实这一部分普通马达和编码马达都是一样的,代码也都一样,下面是我的习惯写法:
void MotorControl(float sp){
if (sp > 0) {
digitalWrite(MOTOR_PIN1, HIGH);
digitalWrite(MOTOR_PIN2, LOW);
analogWrite(MOTOR_SP, sp);
} else if (sp < 0) {
digitalWrite(MOTOR_PIN1, LOW);
digitalWrite(MOTOR_PIN2, HIGH);
analogWrite(MOTOR_SP, -1 * sp);
} else {
digitalWrite(MOTOR_PIN1, LOW);
digitalWrite(MOTOR_PIN2, LOW);
}
}
其中MOTOR_SP是PWM口,而其他两个则为控制转向的接口。这些都和普通马达一样,也很好理解,这里不再详细解释。
推荐你把它写成个头文件放库里,反正只要是做小车,都会用到它。
读取码盘数据
接下来这部分才是使用编码马达的“重头戏”:读取码盘的数据。根据之前我们理解的码盘的原理,我们只需要测量A相和B相的频率以及相位差,即可测算出马达的转速和转向。马达的另外四根线,一根是A相,一根是B相,分别接入Arduino的中断端口(Mega的中断口有2, 3, 18, 19, 20, 21;UNO只有2,3,如果是三轮车,看上去只能用Mega了)和其他数字口,另外两根则是编码器的5V和GND,接Arduino的5V和GND即可。
实际上码盘返回的是计数,频率需要自己测算,这就涉及到Arduino的一个功能:中断。就好比你正在你家看电影,做着自己的事情,突然你朋友敲门来找你玩,于是你暂停电影,去给朋友开门并把他打发走,又回来继续看电影。这就是中断。Arduino可以运行中,因为某些触发时间而暂停,然后去执行其他任务,执行结束后在回来继续当前的程序。
Arduino中有一个函数是
attachInterrupt(digitalPinToInterrupt(pin),ISR,mode);
其中各参数分别为:
- digitalPinToInterrupt(pin):中断端口号(并不是真正的端口号)
- ISR:中断时要执行的程序
- mode:有以下三种值:
- LOW:在引脚为低电平时触发
- HIGH:在引脚为高电平时触发
- CHANGE:在引脚电平状态改变时触发
如果学过数电,可以结合触发器触发条件的知识理解一下上面的触发是怎么个原理。
这里有个很狗的一点是,中断端口号并不是真正的引脚编号,也就是你插了Mega的19引脚,实际上你应该写4号,对应关系如下:
或者也可以直接写digitalPinToInterrupt(19),但无形中多写了好多哇…(狗头)
我们的思路是,当A相的引脚发生变化时(CHANGE),中断主程序,根据我们上一篇文章所说的原理:
当A相上行时可以看到B相是高电平,我们定义这时电机为正转,反转时,它们的相位差是原来的相反数,也就是负九十度。这时,A相上行时B相为低电平,这样即可简单地判断出电机转动方向。
也就是说,要判断此时电机的转向,我们需要先判断A相是上行还是下行,之后在判断此时B相是高电平还是低电平,这个其实很好判断,几个if
完事。当我们判断出电机是正转时,我们就让计数变量加1,证明码盘扫过一格,否则减1,即反转扫过一格。代码如下:
void Count() {
if (digitalRead(pinA) == LOW) {
if (digitalRead(pinB) == HIGH) pps++;
else if (digitalRead(pinB) == LOW) pps--;
} else {
if (digitalRead(pinB) == HIGH) pps--;
else if (digitalRead(pinB) == LOW) pps++;
}
}
其中pps即为计数变量,它的含义其实就是码盘转过的格数,如果你知道码盘一圈有几格(比如我的电机是390,一般这个参数会被称为精度),那你就可以算出目前电机转过的角度。
注意把attachInterrupt()放在setup里,比如我的A相接了19,对应的中断口号为4,则我就在setup里这么写:
attachInterrupt(4, Count1, CHANGE);
好了,至此你就已经可以顺利读出码盘数据了,不过不知道你有没有发现这个问题,有了码盘转动的数据,我们也只能算出位移
,如何计算速度
?
在这里,我们使用另外一种中断:定时中断,也就是每隔一段时间,我就中断主函数,去算一下速度和位移,这会用到一个库:MsTimer(这个库可能需要在网上下载导入,直接百度一波,找不到可以给我留言,留下qq号我发你)。该库中有一个函数是这样的:
MsTimer2::set(t, SpeedDetection);
其中,t为时间间隔,也就是每隔这么多时间中断一次,第二个参数为中断函数名,也就是中断后去执行该函数的内容。看名字我已经取好了:SpeedDetection,速度检测,顾名思义这个函数就是测速度的。
测速度的方法也很简单,我们先用两次间隔的pss之差计算出电机转角,再用该转角除以时间间隔t,即可求出角速度。
float SpeedDetection() {
detachInterrupt(4); //先停止另一种中断,以免中断套中断(禁止套娃)
velocity = (float)pps * (1000 / t) / 780.0; //测量速度
m += pps * (1000 / t) * 360 / 780; //测量转角
// Serial.print("Omega:");
// Serial.print(velocity);
// Serial.print("r/s Theta:");
// Serial.print(m);
// Serial.print("° ");//串口输出调试信息
}
pps = 0; //每次该函数最后将pps清零。
attachInterrupt(4, Count1, CHANGE); //不要忘了再打开另一种中断。
}
该函数中的速度和转角计算公式正是考验你数学功底的时候,值得注意的是,虽然我的电机精度为390(也就是码盘一周有390个豁),但是我们在每次A相电平状态改变时,不管上行还是下行都会记一次数,相当于一个豁口我们计两次数,因此真正的精度其实是780。这是在计算时应该注意的。其他没有什么难理解的地方了。
当然之后还需要在setup里写:
MsTimer2::set(t, SpeedDetection);
MsTimer2::start(); //计时开始的意思
OK,大功告成,接下来我们就可以利用起这些数据做些闭环控制算法了,当然最常见的就是PID,这个我留到下一篇文章讲(有可能又会鸽好久,很尴尬,太忙了)。
下面附上整个源码(三轮版本)
#include <motor.h> //这是我自己写的,在github上https://github.com/mond538/motor
#include <PinChangeInt.h> //引脚中断库
#include <MsTimer2.h>
//-------------端口定义---------------
#define pinA_1 19 //A相
#define pinB_1 29 //B相
#define MOTOR1_SP 13 //PWM端口
#define MOTOR1_PIN1 27 //IN1
#define MOTOR1_PIN2 28 //IN2
#define pinA_2 2
#define pinB_2 23
#define MOTOR2_SP 11
#define MOTOR2_PIN1 24
#define MOTOR2_PIN2 25
#define pinA_3 3
#define pinB_3 26
#define MOTOR3_SP 12
#define MOTOR3_PIN1 30
#define MOTOR3_PIN2 31
//------------------------------------
motor Motor1(pinA_1, pinB_1, MOTOR1_SP, MOTOR1_PIN1, MOTOR1_PIN2);
motor Motor2(pinA_2, pinB_2, MOTOR2_SP, MOTOR2_PIN1, MOTOR2_PIN2);
motor Motor3(pinA_3, pinB_3, MOTOR3_SP, MOTOR3_PIN1, MOTOR3_PIN2);
//motor类是我自己在motor.h中定义的类,参数均为端口。
volatile long m[3] = {0, 0, 0};
int pps[3] = {0, 0, 0};
float velocity[3] = {0, 0, 0};
int t = 50;
int adjust(int sp) {
//限制马达最大和最小速度。
int max_sp = 150;
if (sp >= max_sp) sp = max_sp;
else if (sp <= -1 * max_sp) sp = -1 * max_sp;
if (sp >= -10 && sp <= 10) sp = 0;
return sp;
}
float SpeedDetection() {
detachInterrupt(4);
detachInterrupt(0);
detachInterrupt(1);
for (int i = 0; i < 3; i++) {
velocity[i] = (float)pps[i] * (1000 / t) / 780.0;
m[i] += pps[i] * (1000 / t) * 360 / 780;
// Serial.print("M");
// Serial.print(i + 1);
// Serial.print(": ");
// Serial.print(velocity[i]);
// Serial.print("r/s ");
// Serial.print(m[i]);
// Serial.print("° ");
}
for (int i = 0; i < 3; i++) pps[i] = 0;
attachInterrupt(4, Count1, CHANGE);
attachInterrupt(0, Count2, CHANGE);
attachInterrupt(1, Count3, CHANGE);
}
void setup() {
pinMode(pinA_1, INPUT);
pinMode(pinB_1, INPUT);
pinMode(MOTOR1_SP, OUTPUT);
pinMode(MOTOR1_PIN1, OUTPUT);
pinMode(MOTOR1_PIN2, OUTPUT);
pinMode(pinA_2, INPUT);
pinMode(pinB_2, INPUT);
pinMode(MOTOR2_SP, OUTPUT);
pinMode(MOTOR2_PIN1, OUTPUT);
pinMode(MOTOR2_PIN2, OUTPUT);
pinMode(pinA_3, INPUT);
pinMode(pinB_3, INPUT);
pinMode(MOTOR3_SP, OUTPUT);
pinMode(MOTOR3_PIN1, OUTPUT);
pinMode(MOTOR3_PIN2, OUTPUT);
Serial.begin(9600);
attachInterrupt(4, Count1, CHANGE);
attachInterrupt(0, Count2, CHANGE);
attachInterrupt(1, Count3, CHANGE);
MsTimer2::set(t, SpeedDetection);
MsTimer2::start();
}
void loop() {
MotorControlTri(1, 1, 1);
delay(t);
//随便跑一跑做调试
}
void MotorControlTri(float sp1, float sp2, float sp3) {
sp1 = adjust(sp1);
sp2 = adjust(sp2);
sp3 = adjust(sp3);
Motor1.MotorControl(sp1);
Motor2.MotorControl(sp2);
Motor3.MotorControl(sp3);
//该MotorControl方法也是在我自己写的库函数中,参数为速度,正负代表方向
}
void Count1() {
if (digitalRead(pinA_1) == LOW) {
if (digitalRead(pinB_1) == HIGH) pps[0]++;
else if (digitalRead(pinB_1) == LOW) pps[0]--;
} else {
if (digitalRead(pinB_1) == HIGH) pps[0]--;
else if (digitalRead(pinB_1) == LOW) pps[0]++;
}
}
void Count2() {
if (digitalRead(pinA_2) == LOW) {
if (digitalRead(pinB_2) == HIGH) pps[1]++;
else if (digitalRead(pinB_2) == LOW) pps[1]--;
} else {
if (digitalRead(pinB_2) == HIGH) pps[1]--;
else if (digitalRead(pinB_2) == LOW) pps[1]++;
}
}
void Count3() {
if (digitalRead(pinA_3) == LOW) {
if (digitalRead(pinB_3) == HIGH) pps[2]++;
else if (digitalRead(pinB_3) == LOW) pps[2]--;
} else {
if (digitalRead(pinB_3) == HIGH) pps[2]--;
else if (digitalRead(pinB_3) == LOW) pps[2]++;
}
}
下一篇:PID算法简介