蝦咪喜Blumoduino? 它是Arduino UNO+Bluetooth+L293D的整合版,成本台幣700元以內,可以驅動6顆馬達(4顆DC馬達與2顆伺服馬達)。為什麼取這種怪名子? 咳,這要稍微解釋一下…
坊間有在賣所謂的Motoduino (不含藍芽模組約600元台幣,應該是台灣自己做的而不是義大利原廠出的),它應該是參考Arduino Duemilanove (2007年推出的版本; UNO則為2010年推出的版本)並結合L293D馬達驅動晶片的一塊整合板,可以驅動2顆DC馬達,適合應用在如遙控車、自走車、機器人控制等… (還蠻熱門的!)
Source: Motoduino Lab
安照這個命名的邏輯,Blu是義大利文的藍色(表示有藍芽功能),Moto是驅動電機(L293D),但Mo-to讀音不乾脆,因此只取前半截讀音,而Duino是義大利的一個知名市鎮(Arduino的由來),如此形成Blu-mo-duino…
好了,這不是重點! 為什麼要搞一塊這種版子? 想「自做」飛行器,但都還不會走路怎麼可能飛上天呢? 因此「自做」飛行器前,還是先DIY一個手機App藍芽遙控車好了! 上一回我們簡單討論了馬達與其驅動電機板之後,所以這回先兜一個機電控制板。
如圖所示,若將L293D直接與Arduino UNO堆疊起來,所有的I/O埠都被L293D給佔滿了(包括重要的Rx/Tx與VDD/GND),因此我們必須把這4個Pin腳想辦法接出來。
歐吉尚成本有限,不想用焊的(想重複使用),因此花了約10塊錢剪了幾條跳線用的電線、端子跟塑膠端子套,做成4-Pin的端子母接頭給藍芽模組用(HC-06)。
小心將Rx/Tx與VDD/GND這4個接腳從Arduino UNO板子接頭拉出來,拔線時注意不要讓過多金屬外露,以免不小心短路。
之後在套上L293D就大功告成了! 整合Arduino UNO、L293D、具有藍芽端子的樂高積木。當然更高竿的可以多花個10塊錢買塊「萬用版」自己裁切,並把他焊成下公上母的三明治夾心擴充板,更像樂高(但原廠要價150元台幣,當你是盤子,我的方法較便宜、輕、又不耗時)。
HC-06與手機的藍芽通訊實測結果不錯,我在家裡每個房間關上房門都還Sense得到藍芽信號(~15公尺)。
BOM表(把虛擬通路也一起放進來Survey, 相同型號)
項目(不含運費)
|
台灣(台幣)
|
淘寶(人民幣)
|
Arduino UNO R3
|
290~450
|
12~40
|
Bluetooth HC-06
|
210~350
|
17~30
|
L293D Board (H-橋式電機驅動器)
|
180~250
|
12~21
|
端子與線材
|
<10
|
NA
|
L293D驅動程式: DC馬達測試
本程式利用鍵盤將使用者指令透過UART傳回Arduino,指令定義為: f(正轉),b(反轉)與r(停止)。驅動程式部份是買L293D板子時廠商附贈的範例,經歐吉尚研判(讀過網路上數個不同版本之後)應該都是從Adafruit推出的AF_DCMotor Library抄出來的,該程式完整的定義了將L293D堆疊在Arduino板上之後所有接腳與74HCT595移位暫存器(Shift Register)的控制信號的連接。下圖為廠商提供的電路板接線,但部分接線定義不太正確,經過測試,正確的定義以驅動程式的描述為主。
程式中也貼心的提到,「善待您的馬達,180度反轉向時請讓馬達稍微休眠一下」,其實也應該稍微能減緩因電感造成的瞬間電壓雜訊反過來干擾Arduino板。L293D可以同時驅動4顆DC馬達,其中函式motor可以選擇欲驅動的馬達編號、轉向與轉速,而最後透過函式motor_output將使用者命令轉換成對74HCT595移位暫存器(Shift Register)的控制信號。
轉向是利用對H-橋式電機信號的高低變換達成(例如1號碼達,改變M1A /M1B信號的電位),而轉速的部份是透過PWM信號來控制,其範圍從最小0到最大255。(細節請參閱: 實作App遠程遙控門禁管制)
// Arduino
Pins // 74HCT595N Pins
#define MOTORLATCH 12 // DIR_LATCH 12
#define MOTORCLK 4
// DIR_CLK 11
#define MOTORENABLE 7 // DIR_EN 13
#define MOTORDATA
8 // DIR_SER 14
#define MOTOR1_PWM 11 // PWM
#define MOTOR2_PWM 3 // PWM2B (M2)
#define MOTOR3_PWM 6 // PWM
#define MOTOR4_PWM 5 // PWM0B (M3)
#define SERVO1_PWM 10 // PWM1B (SER1)
#define SERVO2_PWM 9 // PWM
// 74HCT595N Pins
#define MOTOR1_A 2
#define MOTOR1_B 3
#define MOTOR2_A 1
#define MOTOR2_B 4
#define MOTOR3_A 5
#define MOTOR3_B 7
#define MOTOR4_A 0
#define MOTOR4_B 6
#define FORWARD 1
#define BACKWARD 2
#define BRAKE 3
#define RELEASE 4
void setup() {
Serial.begin(9600);
}
void loop() {
char key = 0;
if ( Serial.available()
) {
key = Serial.read();
}
// 善待您的馬達,180度反轉向時請讓馬達稍微休眠一下
motor(1, RELEASE,
0);
delay(1000);
switch ( key ) {
case 'f': // 正轉
motor(1, FORWARD, 50);
delay(5000);
break;
case 'b': // 反轉
motor(1, BACKWARD, 50);
delay(5000);
break;
case 'r':
default:
break;
}
}
void motor(int
nMotor, int command, int speed) {
int motorA,
motorB;
if (nMotor >=
1 && nMotor <= 4) {
switch (nMotor) {
case 1:
motorA = MOTOR1_A;
motorB = MOTOR1_B;
break;
case 2:
motorA = MOTOR2_A;
motorB = MOTOR2_B;
break;
case 3:
motorA = MOTOR3_A;
motorB = MOTOR3_B;
break;
case 4:
motorA = MOTOR4_A;
motorB = MOTOR4_B;
break;
default:
break;
}
switch (command) {
case FORWARD:
motor_output (motorA, HIGH, speed);
motor_output (motorB, LOW, -1); // -1: no PWM
set
break;
case BACKWARD:
motor_output (motorA, LOW, speed);
motor_output (motorB, HIGH, -1); // -1: no PWM
set
break;
case BRAKE:
motor_output (motorA, LOW, 255); // 255: fully
on.
motor_output (motorB, LOW, -1); // -1: no PWM
set
break;
case RELEASE:
motor_output
(motorA, LOW, 0); // 0: output floating.
motor_output (motorB, LOW, -1); // -1: no PWM
set
break;
default:
break;
}
}
}
void motor_output (int
output, int high_low, int speed) {
int motorPWM;
switch (output) {
case MOTOR1_A:
case MOTOR1_B:
motorPWM = MOTOR1_PWM;
break;
case MOTOR2_A:
case MOTOR2_B:
motorPWM = MOTOR2_PWM;
break;
case MOTOR3_A:
case MOTOR3_B:
motorPWM = MOTOR3_PWM;
break;
case MOTOR4_A:
case MOTOR4_B:
motorPWM = MOTOR4_PWM;
break;
default:
speed = -3333;
break;
}
if ( speed !=
-3333 ) {
shiftWrite(output, high_low);
// set PWM only if it is valid
if (speed >= 0 && speed <= 255) {
analogWrite(motorPWM, speed);
}
}
}
void shiftWrite(int output, int
high_low) {
static int
latch_copy;
static int
shift_register_initialized = false;
// Do
the initialization on the fly, at the first time it is used.
if
(!shift_register_initialized) {
// Set pins for shift register to output
pinMode(MOTORLATCH, OUTPUT);
pinMode(MOTORENABLE, OUTPUT);
pinMode(MOTORDATA, OUTPUT);
pinMode(MOTORCLK, OUTPUT);
// Set pins for shift register to default value (low);
digitalWrite(MOTORDATA, LOW);
digitalWrite(MOTORLATCH, LOW);
digitalWrite(MOTORCLK, LOW);
// Enable the shift register, set Enable pin Low.
digitalWrite(MOTORENABLE, LOW);
// start with all outputs (of the shift register) low
latch_copy = 0;
shift_register_initialized = true;
}
//
The defines HIGH and LOW are 1 and 0.
// So
this is valid.
bitWrite(latch_copy, output, high_low);
shiftOut(MOTORDATA,
MOTORCLK, MSBFIRST, latch_copy);
delayMicroseconds(5); // For safety, not
really needed.
digitalWrite(MOTORLATCH, HIGH);
delayMicroseconds(5); // For safety, not
really needed.
digitalWrite(MOTORLATCH, LOW);
}
|
Blumodunio驅動程式: 整合藍芽、DC馬達與伺服馬達測試
驅動程式的部份直接利用AF_Motor Library,這樣程式碼可以寫得更精簡(我們要的是「開發新的創意」,基礎的部份就交給廠商去傷腦筋就好了!)。AF_Motor函式庫很方便,使用者可以直接控制馬達,而不用管底層信號的連接,且PWM信號還提供多種解析度可以使用。依照使用手冊說明,較高頻可以減少DC馬達運轉時的嗡嗡聲(我用減速馬達確實是如此),但可能減少扭力(我感覺不出來)。
各位有沒有發現,我們的程式又更進化了?(同樣的程式) 這一回程式最大的不同就是,我直接將藍芽控制改成為(萬用的)UART串列控制,若藍芽模組沒接時還可以改由鍵盤控制(透過RS232串列埠),這樣Debug比較方便。
唯一要注意的是,因為L293D直接插在Arduino上,藍芽只能接實體的Tx/Rx信號,無法透過軟體模擬,因此藍芽模組必須在程式碼燒錄完成後(透過USB)才能接上! 此外,Arduino polling速度比9600bps快太多了,在接收藍芽命令(串列訊號)時,我們刻意多等了5m s讓Rx有充分的時間做完。
唯一要注意的是,因為L293D直接插在Arduino上,藍芽只能接實體的Tx/Rx信號,無法透過軟體模擬,因此藍芽模組必須在程式碼燒錄完成後(透過USB)才能接上! 此外,Arduino polling速度比9600bps快太多了,在接收藍芽命令(串列訊號)時,我們刻意多等了
#include
<Servo.h>
#include
<AFMotor.h> // 利用AF_Motor函式庫
#define
MAX_UARTCMDLEN 128
#define
SERVO1_PWM 10 // Pin10/SERVO1_PWM, and
Pin9/SERVO2_PWM for L293D motor shield
// up to 128 bytes UART command received from the Android system
byte
uartCmdBuff[MAX_UARTCMDLEN];
int
uartCmdLen = 0; // received BT command length
// The valid values for speed are between 0 and 255.
AF_DCMotor
motor(1,MOTOR12_2KHZ); // DC減速馬達,
1KHZ會有鳴叫聲
Servo
servo; // 伺服器馬達, 紅色(VDD), 棕色(GND), 橙色(Signal)
void
setup() {
Serial.begin(9600); // 帶軟體燒錄完成才能接藍芽模組(實體Tx/Rx)
servo.attach(SERVO1_PWM);
servo.write(0); // 設置旋轉的角度
motor.run(RELEASE);
}
void
loop() {
if ( listenSerialCmd()>0 )
executeSerialCmd();
}
void
executeSerialCmd() {
char cmd[MAX_UARTCMDLEN];
char* p;
int value = 0; // angle or speed value
// parse UART command
sprintf(cmd,"%s",uartCmdBuff);
if ( (p=strchr(cmd,','))==NULL )
return;
*p = '\0';
// get speed or angle
value = atoi(p+1);
Serial.print("Dir:"); Serial.print(cmd);
Serial.print(", Value:"); Serial.println(value);
// Be friendly to the motor: stop it before
reverse.
// DC馬達的轉速反應與程式的RPM不是線性的
switch ( cmd[0] ) {
case 'a':
servo.write(value); // 設置旋轉的角度
break;
case 'f': // DC motor forward
motor.run(RELEASE);
motor.setSpeed(value);
motor.run(FORWARD);
break;
case 'b': // DC motor backward
motor.run(RELEASE);
motor.setSpeed(value);
motor.run(BACKWARD);
break;
case 'x':
default:
motor.run(RELEASE);
break;
}
}
int
listenSerialCmd() {
char tmp;
uartCmdLen = 0;
memset(uartCmdBuff,0,MAX_UARTCMDLEN);
while( Serial.available()>0 ) {
if( (tmp=Serial.read())=='O' ){
uartCmdLen = 0;
}
uartCmdBuff[(uartCmdLen++)%MAX_UARTCMDLEN] = tmp;
delay(5); // wait RX signal
}
return uartCmdLen;
}
|