第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 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 測試
上傳程序到開發板,可以觀察到馬達先向前轉,速度從慢到快,又從快到慢。
接著馬達向後轉,速度從慢到快,又從快到慢。
Tags: 開放平臺 驅動器 第三方 好奇心 程序員
文章來源: