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

第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 测试

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

接着马达向后转,速度从慢到快,又从快到慢。

时间: 2024-10-24 13:15:44

手把手教你做蓝牙小车(三)的相关文章

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

第1节 选择Arduino开发板 1.1 Arduino是什么 对Arduino,官方有一堆解释. 作为一个软件程序猿,在我眼里,Arduino是学习"可怕硬件"的一个便捷通道.它把复杂的硬件名称,属性给我们隐藏起来,只需要一些简单的软件知识,就可以学习硬件开发. 1.2 怎么选择Arduino开发板 1.2.1 官方版本还是兼容版本 Arduino是开源项目,硬件结构,软件设计都开源. 所以不存在盗版的问题. 官方版质量肯定是杠杠的,但价格也贵. 两者价格数字差不多,一个卖人民币,一

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

第5节 BTChat 本节开始介绍Arduino蓝牙模块,配合Android应用,实现一个蓝牙聊天应用. 5.1 什么是蓝牙 简单说就是一种不同设备之间点对点通讯的技术. 有大篇大篇的蓝牙各种协议,各种规范... 本课程只讲用到的内容,不展开更多内容了. 5.2 SDP Service Discovery Protocol,简称SDP,是允许设备发现其他设备所支持服务的协议. 蓝牙协议给每个服务分配一个UUID,用来区分各种服务. SDP的UUID是00001101-0000-1000-8000

手把手教你做蓝牙聊天应用(一)-设计方案

前言 通过"计算器"和"视频播放器"我们已经能够开始开发一些比较像样的应用了. 今天,我们将开始制作一个"蓝牙聊天"应用.这个应用其实很简单,没有炫酷的界面,就是一对一.通过蓝牙连接两台设备,让两个人互相发送信息. 可别觉得它太无聊.没有什么实用性,其实我们正是想通过它让你开始接触网络编程(蓝牙和wifi都是无线连接技术,它们的程序设计方法和思路非常的相似). 另外,学会了使用蓝牙,就为大家打开了技术开发的另一扇大门-物联网,现在很多物联网硬件都

手把手教你做蓝牙聊天应用(三)-获取要连接的设备

第3节 获取要连接的设备 这一节我们开始设计蓝牙聊天应用的界面.根据之前的规划,连接管理将放在单独的ConnectionManager模块当中,所以每当要使用连接功能的时候,我们就暂时把它空着,等到ConnectionManager开发完成之后再加进来. 这里我们将完成下面的界面设计, 3.1 主界面 主界面是一个独立的Activity-ChatActivity,它要实现三个主要功能, 当蓝牙没有开启或者设备不能被发现的时候,请求用户打开对应的功能: 下方有输入框输入要发送的文字内容,点击按钮后

手把手教你做蓝牙聊天应用(二)-设计方案

第2节 设计方案 功能确定后,就要开始围绕功能进行功能的验证.界面设计的规划.以及程序结构的规划了. 2.1 技术验证 选定了现阶段要完成的核心功能后,我们首先需要对它们做技术上的验证,看看用什么样的方法能实现它们.在进行技术验证的同时,也能让我们发现很多我们在头脑风暴阶段没有意识到的现实问题. 现在的手机和移动设备已经把蓝牙作为了标准配置,它常常用到与周边小设备的数据连接上,例如蓝牙自拍杆,蓝牙音箱,蓝牙键盘,运动手环等等,可以看出这都是一些不需要太大数据量传输而需要保持长时间数据通信的设备.

手把手教你做蓝牙聊天应用(五)-界面使用ConnectionManager

第5节 界面使用ConnectionManager ConnectionManager已经设计完成了,它的价值需要在ChatActivity中体现出来. 5.1 监听ConnectionManager 实现对ConnectionManager各个状态的监听,当ConnectionManager的状态有变化.收到发送的数据时,需要让ChatActivity知道,它才能将各种变化反应到用户界面上. 5.1.1 创建监听器 ConnectionManager定义了ConnectionListener接

手把手教你做蓝牙聊天应用(四)-蓝牙连接模块

第4节 蓝牙连接模块 蓝牙连接的管理模块需要为ChatActivity提供于连接相关的所有功能,要设计的方便使用,并尽量隐藏连接的细节. 4.1 对外接口 我们首先来看看ConnectionManager需要向Chat Activity提供哪些接口. 监听.当应用运行起来后,聊天应用需要启动对其它蓝牙设备的监听,迎接随时可能到来的连接请求.所以ConnectionManager需要提供启动监听-startListen()和停止监听-stopListen()的两个接口: 主动连接.应用搜索到可连接

手把手教你做蓝牙聊天应用(六)-界面优化

第6节 应用的美化与完善 现在,我们还可以为聊天应用加上多国语言的支持和关于界面,把使用到的颜色和尺寸定义到资源文件当中,这样一来,安豆的蓝牙聊天应用就算是比较完整的完成了. 这两部分在以前"计算器"章节中,已经介绍过了,大家就自己动手吧. 这一节,我们将重点介绍聊天文字的背景图片是如何制作的. 6.1 9Patch图片的原理 观察一下安卓系统中需要经常用到的图片,可以发现: 很多要使用透明效果的地方在转角处: 很多图片不同的地方只在靠近边缘的地方,内部区域几乎都是一样的: 为此安卓系

手把手教你做关键词匹配项目(搜索引擎)---- 第三天

第三天 小王(运营总监)看到小丁丁整天都在淘宝.百度.魔方.拍拍上面淘关键词,每天花费的时间好长,工作效率又低,拿着这个借口来找到我. 说到:小帅帅,你看小丁丁每天都在淘宝.百度.魔方.拍拍上面淘关键词花费的时间好长,你能不能帮帮忙,看看能不能让系统自己做啦,这样可以节省好多人力,带来的效益多高.(0 其实就是为了掩饰他们懒惰 0) 小帅帅一听到可以带来的效益好高,王总还求着我呢 ,马上 两眼冒着星光,是该好好体现, 解决这个问题就可以体现出我的价值. 小帅帅拍着胸膛保证到:王总,这个小KS啦,