手機加速度感測器與藍芽控制科學怪蟲

前一陣子也看到有位工程師(不折不扣的Mirai Suenaga)放棄其千萬年薪(應該是勇敢的去開創自己的新世界,賺更多),並立志要將他心中的Mirai Suenaga變成智能娃娃(Small Doll),其過程感人肺腑(害歐吉尚也想買一個)




根據資策會主計處的產業研究報告,台灣於2014年出生率與死亡率交叉,至2030年後將出現18萬名勞工的短缺這對許多亞洲國家(日本首當其衝)而言,為了舒緩未來短缺的勞動人口與高齡照護等工作,機器人工業幾乎是不得不的趨勢! 當你邁入高齡,陪在你身旁的肯定是RobiDoll之類的,可能不會是自己孩子喔?!

因此,歐吉尚認為這個發明(Smart Doll)相當不得了首先呢,將微縮的個人電腦(PC)人型化、變成寵物或是玩偶,再結合語多種感測器(如溫/濕度、ImageIrDABluetoothGPSAccelerator)WebCam、影像/語音識別等等,讓日常生活中許多控制變的更互動、豐富而有趣。

想想看,將來GPS、時鐘、媒體儲存播放系統等等將不再只是平面的冰冷顯示器,而是一位會用溫柔語音告訴你氣候、時間、新聞、與一天行程,並使用豐富的肢體動作指引你汽車方向、走過頭或走錯路還會罵你(喔不是安慰你)的可愛少女。平時她會用內建的Camera幫你拍照、錄影、紀錄你平常沒注意的生活點滴,還會自動幫你Summarize你每日、每月或每年的精彩剪輯,與你分享(如果有投影機的話)。出國旅遊時,她可以幫你翻譯,告訴你哪裡有好玩的,大眾運輸怎麼搭較省錢,一邊呱噪的講解當地的歷史人文,當一位稱職的導遊。她跟你一起去上課(幫你筆記),一起去購物(告訴你哪裡買更便宜),直到你生活跟本少不了她!

 
Source: Smart Doll (圖為Smart Doll發明人Danny Choo)


好了做完白日夢又離題了各位有沒有發覺當我們在指責別人或大環境如何、如何的時候,別人是怎麼充分利用時間,如何去努力實踐自己理想並改善人們生活方式的嗎? 所以趕快加把勁,讓自己跟上世界的脈動吧!

Basic StampArduino微控制器與許多搭配的感測器火紅之後,Raspberry Pi(算是一款價格低廉、功能齊全的PC)BeagleBone(根本是一款內含Linux的強大PC)IoT應用開發板如雨後春筍出現,連微軟都出來攪局了。最近,又看到淘寶有在賣用Raspberry Pi板組裝的「Programmable DIY Robot」。

Source: RAPIRO


因此,繼上一回使用手機內建的加速度儀(Acceleration Sensor),透過藍芽並迅速產生對應需要較敏捷操控訊號的裝置(例如四驅車或四軸飛行器等)相關的手機App撰寫之後,今天重點擺在如何實作具有「類關節」,可用手機加速度儀App透過藍芽控制的生物。而「機器蟲」又是入門之中最簡單的,只要兩顆伺服馬達(~90元台幣/每顆)、一些膠帶再剪一些鐵絲即可搞定!




一些前置作業(知識):




BOM (把虛擬通路也一起放進來Survey)
項目(不含運費)
台灣(台幣)
淘寶(人民幣)
Arduino UNO R3
290~450
12~40
Bluetooth HC-06
210~350
17~30
L293D Board (H-橋式電機驅動器)
180~250
12~21
2顆伺服馬達
70~90(每顆)
12~16(每顆)
美勞或植栽用鋁線(書局有賣)
20
NA




科學怪蟲組裝測試:
將兩顆伺服馬達以90度相位差對接,如圖,左側(Severo1)規劃為前腳,右側(Severo2)規劃為後腳。其中,因為馬達的組成多少有些電磁干擾,建議使用厚一點的泡棉隔開,以免誤動作。之後再以膠帶補強接點。
 

 


身體完成之後,用美勞或植栽用的鋁線製作蟲的四肢。我買的線太細了,因此將其纏繞並彎曲成彈性與剛性適中的狀態,才能夠支撐Blumodunio外加電池的龐大身軀跟重量。下圖為綁上四肢後的側視圖,其中呈狗腿式彎曲的是蟲的後腿,較短的則是前腳。
 

下圖分別為為府視與底部(可以看清楚蟲的屁股弧度與兩顆伺服電機的配置),其中,鋁絲做的四肢是用實心線(用剝皮鉗去皮)綑綁固定住的蟲的背部(上方)貼了一塊厚泡棉,用來拖住以Blumodunio板的身體與大腦(Arduino程式)
 


 
蟲的四肢最後再纏上電火布(黑色),以增加其腳步運行時的磨擦力,最後來一張前視定裝照。




Android App手機程式規劃
沿用之前的活用手機加速度感測器與藍芽控制的程式,懶的寫程式的朋友可以使用下面的QR Code下載執行檔,安裝方式請參閱前幾回的Android與Arduino的藍芽通訊




Arduino Uno端系統與程式規劃--科學怪蟲的四肢活動測試(使用手機App控制前)
Arduino系統端的程式則分析上述的「加速度值」來判斷要前進(手機前傾)或後退(手機後傾)。各別對應為兩顆伺服馬達的轉速與轉角。比較麻煩的是,蟲四肢的形狀除了要能支撐重量,也跟程式規劃伺服馬達運轉角度有關,這裡最花時間,可能要調整蠻多次才能符合需求(至少要能前進跟後退)Arduino程式、蟲腳的形狀、伺服馬達的的左/右擺幅也要配合程式運作,不斷的修改與調整。

函式bugInit用來規劃伺服馬達運轉角度,前腳使用Servo1,用來將身體略為抬起,因此角度不宜過大,而後腳使用Servo2,用來將身體推進(或退),所以角度會大一些。程式透過序列埠讀取指令,「i」表示初始化(bugInit)、「f」表示前進(bugForward)、「b」表示後退(bugBackward)而「x」則表示暫停。當蟲停止後,須重設(bugInit)之後才能前進或後退。至於序列埠與藍芽控制的部分,請參考活用手機加速度感測器與藍芽控制,不再贅述。

Arduino程式--科學怪蟲的四肢活動測試(使用手機App控制前) 
#include <SoftwareSerial.h>
#include <Servo.h>

#define MAX_UARTCMDLEN 128

// Pin10/SERVO1_PWM, and Pin9/SERVO2_PWM for L293D motor shield
#define SERVO_FRONT 9
#define SERVO_BACK 10
#define BUG_FORWARD 1
#define BUG_BACKWARD 2

// 伺服器馬達 紅色(VDD), 棕色(GND), 橙色(Signal)
Servo frontServo;  // front leg
Servo backServo;  // back leg

// up to 128 bytes UART command received from the Android system
byte uartCmdBuff[MAX_UARTCMDLEN];
int uartCmdLen = 0; // received BT command length

// define bug's movement
int frontRUp;
int frontLUp;
int backRForward;
int backLForward;

void setup() {
    Serial.begin(9600); // connect to the HC_06, after downloading the software
    bugInit(10,30); // 初始化蟲的前後腳轉動角度大小
}

void loop() {
    if ( listenSerialCmd()>0 )
        executeSerialCmd();
}

void executeSerialCmd() { // update bug's status
    char cmd[MAX_UARTCMDLEN];
    char* token = NULL;
    char* p = NULL;
           
    // parse UART command
    sprintf(cmd,"%s",uartCmdBuff);
    if ( (p=strchr(cmd,','))==NULL )
        return;
    *p = '\0';
   
    switch( cmd[0] ) {
        case 'i':
            bugInit(10,20);
            break;
        case 'f':
            bugForward(500);
            break;
        case 'b':
            bugBackward(500);
            break;
        case 'x':
            bugStop();
            break;
        default:
            break;
    }
    delay(200);
}

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;
}

void bugInit(int afront,int aback) {
    frontServo.attach(SERVO_FRONT);
    backServo.attach(SERVO_BACK);
    frontServo.write(90);
    backServo.write(90);
    frontRUp = 90-afront;
    frontLUp = 90+afront;
    backRForward = 90-aback;
    backLForward = 90+aback;
}

void bugStop() {
    frontServo.write(90);
    backServo.write(90);
    delay(500);
    frontServo.detach();
    backServo.detach();
}

void bugForward(int dvalue) {
    int backDelay = (500-dvalue)<200?200:(500-dvalue);
    int frontDelay = (200-dvalue/2)<100?100:(200-dvalue/2);
    backServo.write(90-30); // R
    delay(backDelay);
    frontServo.write(90-10); // R
    delay(frontDelay);
    backServo.write(90+40); // L
    delay(backDelay);
    frontServo.write(90+10); // L
    delay(frontDelay);
}

void bugBackward(int dvalue) {
    int backDelay = (500-dvalue)<200?200:(500-dvalue);
    int frontDelay = (200-dvalue/2)<100?100:(200-dvalue/2);
    backServo.write(90+40); // 90+aback
    delay(backDelay);
    frontServo.write(90-10);
    delay(frontDelay);
    backServo.write(90-30); // 90-aback
    delay(backDelay);
    frontServo.write(90+10);
    delay(frontDelay);
}






Arduino Uno端系統與程式規劃--使用手機加速度儀與藍芽控制
沿用之前的活用手機加速度感測器與藍芽控制,手機傳給Arduino的指令格式也不變,藍芽控制指令由偵測加速度儀變化產生,以「accel」命令開頭緊接著xyz三軸的加速值(目前z軸不使用),格式如下:

<Command>,<xAccel -10~10>,<yAccel -10~10>,<zAccel>
例如:
accel,0,-5,9   xyz三軸的加速值分別為0,-5,9 (手機往前傾)
accel,0,5,9    xyz三軸的加速值分別為0,5,9 (手機往後傾)
accel,-5,0,9   xyz三軸的加速值分別為-5,0,9 (手機往右傾)
accel,5,0,9    xyz三軸的加速值分別為5,0,9 (手機往左傾)


Arduino程式--科學怪蟲的四肢活動測試(使用手機App控制前) 
#include <SoftwareSerial.h>
#include <Servo.h>

#define MAX_UARTCMDLEN 128

// Pin10/SERVO1_PWM, and Pin9/SERVO2_PWM for L293D motor shield
#define SERVO_FRONT 9
#define SERVO_BACK 10
#define BUG_FORWARD 1
#define BUG_BACKWARD 2

// 伺服器馬達 紅色(VDD), 棕色(GND), 橙色(Signal)
Servo frontServo; // front leg
Servo backServo; // back leg

// up to 128 bytes UART command received from the Android system
byte uartCmdBuff[MAX_UARTCMDLEN];
int uartCmdLen = 0; // received BT command length

// define bug's movement
int frontRUp;
int frontLUp;
int backRForward;
int backLForward;
int bugDir = BUG_FORWARD; // bug's move direction
int bugSpeed = 10;
int gAccel[3] = {0,0,0}; // accel x,y,z values

void setup() {
    Serial.begin(9600); // connect to the HC_06, after downloading the software
    bugInit(10,30);
}

void loop() {
    if ( listenSerialCmd()>0 )
        executeSerialCmd();
    moveBug();
}

void executeSerialCmd() { // update bug's status
    char cmd[MAX_UARTCMDLEN];
    char* token = NULL;
    char* p = NULL;
    int accel[3] = {0,0,0}; // accel x,y,z values
    int bugSpeed = 0;
           
    // parse UART command
    sprintf(cmd,"%s",uartCmdBuff);
    if ( (p=strchr(cmd,','))==NULL )
        return;
    *p = '\0';
    Serial.println(cmd);
   
    // check if the accel command, the format is <accel,x,y,z,>
    if ( strcmp(cmd,"accel") )
        return;
   
    // get speed
    for ( int ii=0; ii<3; ii++ ) {
        token = p+1;
        if ( (p=strchr(token,','))==NULL )
            break;
        *p = '\0';
        accel[ii] = atoi(token);
    }
   
    if ( !(accel[0]!=gAccel[0] || accel[1]!=gAccel[1]) )
        return; // no change (ignore z axis)
   
    gAccel[0] = accel[0];
    gAccel[1] = accel[1];
    gAccel[2] = accel[2];
     
    // update speed, forward/backward depends on the yAccel
    bugSpeed = abs(accel[1])*10; // 0~200
  
    // update bug's status
    bugDir = accel[1]<0?BUG_FORWARD:BUG_BACKWARD; // update direction 
    if ( bugDir==BUG_FORWARD )
        bugForward(bugSpeed);
    else
        bugBackward(bugSpeed);
   
    delay(200);

  #if 0 // debugging
    switch( cmd[0] ) {
        case 'i':
            bugInit(10,20);
            break;
        case 'f':
            bugForward(500);
            break;
        case 'b':
            bugBackward(500);
            break;
        case 'r':
            bugRight();
            break;   
        case 'l':
            bugLeft(500);
            break;        
        case 'x':
            bugStop();
            break;
        default:
            break;
    }
    delay(200);
  #endif
}

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;
}

void bugInit(int afront,int aback) {
    frontServo.attach(SERVO_FRONT);
    backServo.attach(SERVO_BACK);
    frontServo.write(90);
    backServo.write(90);
    frontRUp = 90-afront;
    frontLUp = 90+afront;
    backRForward = 90-aback;
    backLForward = 90+aback;
}

void bugStop() {
    frontServo.write(90);
    backServo.write(90);
    delay(500);
    frontServo.detach();
    backServo.detach();
}

void bugForward(int dvalue) {
    int backDelay = (500-dvalue)<200?200:(500-dvalue);
    int frontDelay = (200-dvalue/2)<100?100:(200-dvalue/2);
    backServo.write(90-30); // R
    delay(backDelay);
    frontServo.write(90-10); // R
    delay(frontDelay);
    backServo.write(90+40); // L
    delay(backDelay);
    frontServo.write(90+10); // L
    delay(frontDelay);
}

void bugBackward(int dvalue) {
    int backDelay = (500-dvalue)<200?200:(500-dvalue);
    int frontDelay = (200-dvalue/2)<100?100:(200-dvalue/2);
    backServo.write(90+40); // 90+aback
    delay(backDelay);
    frontServo.write(90-10);
    delay(frontDelay);
    backServo.write(90-30); // 90-aback
    delay(backDelay);
    frontServo.write(90+10);
    delay(frontDelay);
}




電池的部分,一般的9V方電是疊層電池,容量很小且不適合大電流放電,大多用於一些小電流高電壓的場合,例如室內無線電話機。9V電池短路電流一般200mA(好的能達到1A),而1.5V的五號電池約3A,因此當需要啟動電流較大的終端設備時,9V電池支持不了那麼大電流,而且會很快沒電,因此建議買一個四顆1.5V串接的電池盒來接。

覺得這隻蟲過大嗎大家有福了,最近 Kickstarter 發表了「晶片般大小的Arduino」,比官方的Nano還小! 而且竟然還內建OLED顯示器! 正式迎接IoT時代,百家爭鳴,各式各樣紛紛出籠的「改變群眾生活方式的創意」。不禁要問:「你Arduino了沒?!

Source KickStarter