手把手教你做蓝牙小车(三)

第6节 马达

要说蓝牙小车哪个模块最重要,多数人一定会以为是马达。
之前说过,为了防止开发板被电流击穿,控制马达时要增加一块扩展板。
所以,控制马达,只对扩展板编程,而不需要对马达编程。
此外,扩展板厂家会提供通过扩展板控制马达的代码。

综上,对开发人员来说,马达,只要确认存在就可以了。

6.1 扩展板

Arduino开发板只提供了一些基础、通用的接口,针对一些常见的特殊功能,Arduino专门为其推出了扩展板。

6.1.1 官方扩展板

Arduino官方目前总共推出了5款扩展板。
分别是
Arduino Motor Shield

Arduino Proto Shield

Arduino Ethernet Shield

Arduino GSM Shield

Arduino WiFi Shield 101

其中的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 PinMappingPWM两篇文档。


/*******************************************************************/
* 版权声明
* 本教程只在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 测试

上传程序到开发板,可以观察到马达先向前转,速度从慢到快,又从快到慢。
接着马达向后转,速度从慢到快,又从快到慢。


/*******************************************************************/
* 版权声明
* 本教程只在CSDN安豆网发布,其他网站出现本教程均属侵权。

*另外,我们还推出了Arduino智能硬件相关的教程,您可以在我们的网店跟我学Arduino编程中购买相关硬件。同时也感谢大家对我们这些码农的支持。

*最后再次感谢各位读者对安豆的支持,谢谢:)
/*******************************************************************/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值