第6节 马达
要说蓝牙小车哪个模块最重要,多数人一定会以为是马达。
之前说过,为了防止开发板被电流击穿,控制马达时要增加一块扩展板。
所以,控制马达,只对扩展板编程,而不需要对马达编程。
此外,扩展板厂家会提供通过扩展板控制马达的代码。
综上,对开发人员来说,马达,只要确认存在就可以了。
6.1 扩展板
Arduino开发板只提供了一些基础、通用的接口,针对一些常见的特殊功能,Arduino专门为其推出了扩展板。
6.1.1 官方扩展板
Arduino官方目前总共推出了5款扩展板。
分别是
其中的Arduino Motor Shield就是专为马达设计的扩展板。
6.1.2 第三方扩展板
Arduino是开放平台,所以有不少第三方设计者为Arduino设计扩展板。
我们今天要使用的是下面这款双L293D芯片的马达扩展板。
作为一个软件程序员,我们只需要知道L293D是一种“H桥电机驱动器”就足够了。
如果你还有更多好奇心,可以参看L293D的datasheet文件
之所以选择这块马达扩展板,而没有选择官方推出的扩展板。
一个原因是这块扩展板有两个L293D,它支持同时控制四个车轮。四轮驱动,好牛X的感觉:)
另一个原因是这个开发板便宜。
6.2 马达和车轮
马达采用比较常见的这种
车轮只要能和马达匹配就行
因为不会针对这两个设备编程,所以没有太多要求。
6.3 代码分析
厂家提供了一些扩展板相关的代码,我把相关的三个文件打包放在安豆网方便大家下载。
6.3.1 扩展板库文件
下载解压后,打开MotorTest目录,可以看到三个文件。
AFMotor.cpp,AFMotor.h是扩展板的库文件。
Arduino有两种方法使用库文件。
第一种方法是把它们加入Arduino库文件目录中。
把库文件打成zip包,直接打包文件或放在目录下打包都可以。
选择“菜单 项目->加载库->添加一个.ZIP库”,文件就被加到了”c:\Users\UserName\Documents\Arduino\libraries\”目录。
所以你也可以直接拷贝这两个文件到以上目录下。
第二种方法是把这两个文件和使用他们的ino文件放在一起。
使用时根据相对路径调用库文件。
所有arduino模块,厂家都会提供库文件,这个库文件相当于sdk。
大多数时候,我们不需要了解它的详细实现过程,只要知道它提供哪些接口,怎么使用就可以了。
6.3.2 扩展板测试程序
MotorTest.ino是这款马达扩展板的演示程序。
我们需要详细了解这个文件。
6.3.2.1 引用头文件
第5行
#include <AFMotor.h>
如果你没把AFMotor.h放在arduino库文件目录下,这里要改成""
引用头文件。
6.3.2.2 生成马达对象
从这张图中可以看到,这款开发板可以控制的四个马达,接口分别被标注为M1、M2、M3、M4。
第7行
AF_DCMotor motor(4);
生成一个控制M4号马达的对象。
6.3.2.3 设置马达速度
第14行
motor.setSpeed(200);
从函数名判断是设置马达的速度,代码理解到这个程度就足够了。
当然,如果你非常有好奇心,我还是有必要满足一下。
看看setSpeed函数的实现:
void AF_DCMotor::setSpeed(uint8_t speed) {
switch (motornum) {
case 1:
setPWM1(speed); break;
case 2:
setPWM2(speed); break;
case 3:
setPWM3(speed); break;
case 4:
setPWM4(speed); break;
}
}
我们传入的参数是200,即speed=200
。
初始化AF_DCMotor对象时,指定了M4号马达,此处的motornum等于4
。
setSpeed最终执行的是case 4: setPWM4(200);
。
再看看setPWM4函数的实现:
inline void setPWM4(uint8_t s) {
#if defined(__AVR_ATmega8__) || \
defined(__AVR_ATmega48__) || defined(__AVR_ATmega88__) || defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__)
// use PWM from timer0A on PB3 (Arduino pin #6)
OCR0B = s;
#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
// on arduino mega, pin 6 is now PH3 (OC4A)
OCR3A = s;
#elif defined(__PIC32MX__)
// Set the OC2 (pin 5) PMW duty cycle from 0 to 255
OC2RS = s;
#else
#error "This chip is not supported!"
#endif
}
我手头这块Mega板对应的宏定义是__AVR_ATmega2560__
,即执行了代码OCR3A = s;
。
你如果不确定哪个宏定义对应你的开发板,可以用这个方法。
每一个#if下都胡乱写一些代码,不相同即可。
inline void setPWM4(uint8_t s) {
#if defined(__AVR_ATmega8__) || \
defined(__AVR_ATmega48__) || defined(__AVR_ATmega88__) || defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__)
// use PWM from timer0A on PB3 (Arduino pin #6)
OCR0B = s;
abc
#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
// on arduino mega, pin 6 is now PH3 (OC4A)
OCR3A = s;
def
#elif defined(__PIC32MX__)
// Set the OC2 (pin 5) PMW duty cycle from 0 to 255
OC2RS = s;
ghi
#else
#error "This chip is not supported!"
#endif
}
编译一下,编译器会告诉你某行代码出错了。我这里提示def
出错了。
我就确定我的开发板对应的是__AVR_ATmega1280__
或defined(__AVR_ATmega2560__
。
OCR3A = s
这行代码做了什么?简单说,就是告诉Arduino,输出电压按时间分成255份,s份输出1,其他的输出0。
OCR3A = 200
就是200份输出1,55份输出0。整体看,Arduino就输出了一个5(v)*200/255~=4(v)
的电压。
还不满意我这个解释?
那就自己学习Mega PinMapping和PWM两篇文档。
/*******************************************************************/
* 版权声明
* 本教程只在CSDN和安豆网发布,其他网站出现本教程均属侵权。
/*******************************************************************/
6.3.2.4 马达初始状态设置为停止
第16行
motor.run(RELEASE);
先看看RELEASE的定义,在AFMotor.h中一共定义了四条命令。
// Constants that the user passes in to the motor calls
#define FORWARD 1
#define BACKWARD 2
#define BRAKE 3
#define RELEASE 4
接着分析run函数的实现,先看函数的后半段。
void AF_DCMotor::run(uint8_t cmd) {
...
switch (cmd) {
case FORWARD:
latch_state |= _BV(a);
latch_state &= ~_BV(b);
MC.latch_tx();
break;
case BACKWARD:
latch_state &= ~_BV(a);
latch_state |= _BV(b);
MC.latch_tx();
break;
case RELEASE:
latch_state &= ~_BV(a); // A and B both low
latch_state &= ~_BV(b);
MC.latch_tx();
break;
}
}
先看看_BV
是个啥东东。
#define _BV(bit) (1 << (bit))
,作用就是把1左移bit位。
我们继续分析刚才发出的RELEASE命令,
latch_state &= ~_BV(a);
,把latch_state的第a位清零。
latch_state &= ~_BV(b);
,把latch_state的第b位清零。
MC.latch_tx();
调用AFMotorController类latch_tx函数。
回过头再分析run函数的前半段
void AF_DCMotor::run(uint8_t cmd) {
uint8_t a, b;
switch (motornum) {
case 1:
a = MOTOR1_A; b = MOTOR1_B; break;
case 2:
a = MOTOR2_A; b = MOTOR2_B; break;
case 3:
a = MOTOR3_A; b = MOTOR3_B; break;
case 4:
a = MOTOR4_A; b = MOTOR4_B; break;
default:
return;
}
...
}
继续查看MOTOR1_A的定义
#define MOTOR1_A 2
#define MOTOR1_B 3
#define MOTOR2_A 1
#define MOTOR2_B 4
#define MOTOR4_A 0
#define MOTOR4_B 6
#define MOTOR3_A 5
#define MOTOR3_B 7
结合这个定义,我们可以得出结论:
latch_state变量的2,3位对应MOTOR1,也就是扩展板上看到的M1。
1,4位对应M2,
5,7位对应M3,
0,6位对应M4。
继续分析latch_tx
函数
void AFMotorController::latch_tx(void) {
uint8_t i;
//LATCH_PORT &= ~_BV(LATCH);
digitalWrite(MOTORLATCH, LOW);
//SER_PORT &= ~_BV(SER);
digitalWrite(MOTORDATA, LOW);
for (i = 0; i < 8; i++) {
//CLK_PORT &= ~_BV(CLK);
digitalWrite(MOTORCLK, LOW);
if (latch_state & _BV(7 - i)) {
//SER_PORT |= _BV(SER);
digitalWrite(MOTORDATA, HIGH);
} else {
//SER_PORT &= ~_BV(SER);
digitalWrite(MOTORDATA, LOW);
}
//CLK_PORT |= _BV(CLK);
digitalWrite(MOTORCLK, HIGH);
}
//LATCH_PORT |= _BV(LATCH);
digitalWrite(MOTORLATCH, HIGH);
}
这段代码根据latch_state各个位的状态开或关MOTORDATA引脚。
如果不详细学习这个硬件知识,完全搞不懂这是在做什么。
目前只要知道通过这些操作,马达是可以被有效控制的就足够了。
6.3.2.5 马达前进
...
motor.run(FORWARD);
for (i = 0; i < 255; i++) {
motor.setSpeed(i);
delay(10);
}
for (i = 255; i != 0; i--) {
motor.setSpeed(i);
delay(10);
}
...
经过刚才的分析,这一块的代码就很简单了。
motor.run(FORWARD)
让车轮向前转
第一个for循环i逐渐变大,motor.setSpeed(i)
使车轮速度越来越快。
第一个for循环i逐渐减小,motor.setSpeed(i)
使车轮速度越来越慢到最后停止。
6.3.2.6 马达后退
motor.run(BACKWARD);
for (i = 0; i < 255; i++) {
motor.setSpeed(i);
delay(10);
}
for (i = 255; i != 0; i--) {
motor.setSpeed(i);
delay(10);
}
其他代码比较简单,就不再分析了。
6.4 连接模块
蓝牙模块还是5.2节的连接方法。
这里要注意扩展板和Mega的连接方式。扩展板没有Pin脚的这头和Mega没有Pin脚的这头放一边,扩展板0 Pin脚和Mege 0 Pin脚重合,图中黄线所示。
6.5 测试
上传程序到开发板,可以观察到马达先向前转,速度从慢到快,又从快到慢。
接着马达向后转,速度从慢到快,又从快到慢。