ルンバをESP32でBluetooth通信で動かす方法
更新情報: 最終更新日 2019/07/24 記事を追加
2019年度のアルバイト学生さんがまとめてくれた内容です。間違い等があるかも知れません。お気づきの点がございましたら、ご連絡下さい。
PCからESP32を介して、Bluetooth通信でルンバを動かす方法について書く。
環境
- ルンバ:iRobot Create 2
- ESP32:ESP32-DevKitC ESP-WROOM-32開発ボード
(秋月電子通商ページ(通販コード:M-11819)) - ESP32のコードエディタ(バージョン):ArduinoIDE(Arduino 1.8.9)
- ライブラリ:Arduino-esp32(バージョン 1.0.2)のBluetoothSerial
- PCのOS:Windows10
ルンバのマニュアル:https://www.irobotweb.com/-/media/MainSite/PDFs/About/STEM/Create/iRobot_Roomba_600_Open_Interface_Spec.pdf?la=en
ルンバを動かすにあたり、必要な知識は5つ!
これらの説明の後に、全体のプログラムコードと手順を書く。
ルンバのシリアルポート
ルンバのシリアルポートは7ピンあるが、機能は5種類。
- ピン1(, 2):電源供給に使うピン。ルンバの充電状況によって出てくる電圧値が異なるので、レギュレータを挟んで5Vに整えてからESP32の5Vピンにつなぐ。(PCとESP32 を有線でつなぐ場合は、つながない)
- ピン3:ルンバへのインプット。ESP32からの波形をそのまま入れてもよい(HIGHと認識される最低電圧が2.0Vのため)し、トランジスタなどでレベル変換して5Vの波形にしてから入れてもよい。
- ピン4:ルンバからのアウトプット。抵抗を挟むなどして5Vから3.3Vに落とし、ESP32に入れる。
- ピン5:ボーレート変更用のピン。Sleep状態からルンバを起こすのにも使う。ピン3と同様に、ESP32から3.3Vの波形をそのまま入れるか、5Vにレベル変換して入れる。
- ピン6(, 7):グラウンド。ESP32のGNDピンとつなぐ。
全体の回路としては例えばこんな感じになる。
- 使用している電子部品
ルンバのOIモード4つ(Manual p.7)
ルンバのOpen Interface(OI)モードには、4つのモードがある。
- Off Mode
- バッテリー交換後や電源を入れたときのモード
- 115200 bps でのStartコマンドのみ受け付ける
- (Manual p.4の方法でデフォルトのボーレートを変更した場合は、19200bpsでのStartコマンド)
- Passive Mode
- センサー値が受け取れる
- モーター等のアクチュエータが動かせない
- 5分間入力がないと、節電のためSleep状態になる。BRCピンへのパルス入力でSleepから起こせる。
- Safe Mode
- タイヤが浮いているなどの一部の状況を除いて、ルンバをコントロールできる
- 充電されない
- 使い終わったら、ルンバを持ち上げてタイヤを浮かせ、Passive Modeにする。これにより、充電ドックに入れると充電されるようになる。
- Full Mode
- ルンバをコントロールできる
- 充電されない
つまり、ルンバを走らせたいなら Safe Mode にする!充電したいなら Passive Mode にする!ということ。
ルンバに送るコマンドの仕組み
ESP32からルンバへ送る命令には、コマンドを使う。
コマンドでは、最初に 1 byte の opcode(コマンド番号)を送る。コマンドによっては、後に続けて補足情報 1~4 byte を送るものもある。
コマンドはルンバのマニュアルに載っている。
例)Drive Directコマンド(Manual p.14, Opcode: 145)の場合
(マニュアル p.14より)
文章から、このコマンドは左右のタイヤについて別々に速度(単位 mm/s)を指定して動かすコマンドだとわかる。
上部のOpcodeとData Bytesより、このコマンドは 5 byte から成るとわかる。中央部のSerial sequenceを見ると、 5 byte の内訳がわかる。
- 1 byte 目 Opcode の「145」
- 2 byte 目 右タイヤの速度の上位 1 byte
- 3 byte 目 右タイヤの速度の下位 1 byte
- 4 byte 目 左タイヤの速度の上位 1 byte
- 5 byte 目 左タイヤの速度の下位 1 byte
このように、コマンドでは 16 bit の数値を上位 8 bit と下位 8 bit に分けて送る。コマンドを送る度に逐一分けるのは面倒なので、関数化する。
HardwareSerial Roomba(2); // ESP to Roomba, rx:16, tx:17 void roomba_send_num(int num){ //numを二つの8bitに変換してルンバに送信 Roomba.write(hex_convert_to8_high(num)); Roomba.write(hex_convert_to8_low(num)); } //整数を8bitに分けるプログラム ビットシフトを使っている unsigned int hex_convert_to16(int a, int b){ return (unsigned int)(a << 8)|(int)(b); } unsigned int hex_convert_to8_high(int a){ return (unsigned int)(a >> 8)&0x00FF; } unsigned int hex_convert_to8_low(int a){ return a^(hex_convert_to8_high(a) << 8); }
これらの関数を用いて、先ほどの Drive Direct コマンドも関数化してしまうとこんな感じ。
void roomba_drive(int right,int left){ //直進 Roomba.write(byte(145)); roomba_send_num(right); //Velocity right roomba_send_num(left); //Velocity left delay(100); }
ルンバの主なコマンド
ルンバを前進・後進、回転させるにあたり、このページに記載のコードでは次のコマンドを用いている。
- Startコマンド (Manual p.8, Opcode: 128)
- OIをスタートする命令。
- 他のすべてのコマンドよりも先に送らなくてはいけない。
- Baudコマンド(Manual p.9, Opcode: 129)
- ボーレートを変える命令。
- ルンバのボーレートはデフォルトで115200bpsであるが、ESP32は115200bpsで通信できない。そのため、Baudコマンドでルンバのボーレートを変える。
- デフォルトのボーレートを115200bpsから19200bpsに変える手法が他にある。(Manual p.4)
- Safeコマンド(Manual p.10, Opcode: 131)
- ルンバのOIモードをSafe Modeにする命令。
- 起動時や充電後などのPassive ModeからSafe Modeに変えるのに使う。
- Drive Directコマンド(Manual p.14, Opcode: 145)
- 左右のタイヤのそれぞれに対して速度を指定し、走らせる命令。
- 左右同じ値を指定することで直進
- Driveコマンド(Manual p.13, Opcode: 137)
- 速度と半径を指定して回転させる命令。
- 速度に0を指定することで、移動中のルンバをストップさせるのに使用。
他にも、音を鳴らすなど色々なコマンドがある。
ルンバにコマンドを送る前に必要な処理
① Passive ModeのSleepから起こす
- "To disable sleep, pulse the BRC pin low"(Manual p.7, Passive 内参照)
- このページのプログラムでは500msのlowパルスを送信している
② ボーレート変更(115200 → 19200)
(任意。このページのプログラムでは行わない。19200 bps以外のbaud rateにするなら手順⑤で115200から直接変えてOK)
- "After turning on Roomba, wait 2 seconds and then pulse the BRC pin low three times."(Manual p.4, Method 2 内参照)
←重要!すべてのコマンドよりも前に送る必要あり
③ StartコマンドでOI開始- Startコマンド(Manual p.8, Opcode: 128)
- Baudコマンド含め、全コマンドよりも前にStartコマンドを送るのがとても大切。
- × Baudコマンド → Startコマンド
- そもそもOIが開始されていないのでBaudコマンドが受け付けられない。ボーレートが変更されないので、変更後のボーレートでStartコマンドを送っても正しく届かない。
- 〇 Startコマンド → Baudコマンド
- OIが開始されてからBaudコマンドが届けられ、ボーレートがきちんと変更される。
- × Baudコマンド → Startコマンド
④ Safe Modeにする
- Safeコマンド(Manual p.10, Opcode: 131)
- ルンバを走らせることができるようになる
⑤ ボーレート変更
(任意。このページのプログラムでは、115200 → 9600。)
- Baudコマンド(Manual p.9, Opcode: 129)
- ボーレート変更後、一旦ルンバとのシリアル通信を切って再度接続
⑥ ①,③,④を再度行う
(⑤のボーレート変更を行った場合のみ)
ルンバを動かすプログラム
プログラムは次の2つのファイルに分かれている。
- roomba_esp_Bluetooth.ino : メイン部分
- roomba_function.ino : ルンバへの命令の関数群
roomba_esp_Bluetooth.ino:
/* roomba_esp_Bluetooth.ino(最終更新日:2019/07/23) */ /* ESP32とBluetooth通信してルンバを動かすプログラム。 * roomba_function.ino と同じフォルダに入れてください。 * BluetoothSerial bt: PC to ESP32 * HardwareSerial Roomba: ESP32 to Roomba */ #include <BluetoothSerial.h>; /* Bluetooth通信 */ BluetoothSerial bt; // PC to ESP. Alternative to "Serial" after Serial.begin() const char* bt_name = "ESP32_BluetoothSerial"; /* ESP32とルンバの通信 */ HardwareSerial Roomba(2); // ESP to Roomba, rx:16, tx:17 int ddPin = 4; int iB; int v = 100; // 速度 void setup() { Roomba.begin(115200); // ルンバはデフォルトが115200bps Serial.begin(9600); bt.begin(bt_name); pinMode(ddPin, OUTPUT); bt.println("start"); wakeUp(); // Passive ModeでのSleepから起こす startSafe(); // StartコマンドでOI開始 & Safe Modeにすることで、移動指示可能にする //baud rateの変更(115200→9600) Roomba.write(129); // 必ずStartコマンド(128)の後に行う! Roomba.write(byte(5)); //9600に変更 Roomba.end(); //一旦切る Roomba.begin(9600); //9600でスタート wakeUp(); // Passive ModeでのSleepから起こす startSafe(); // StartコマンドでOI開始 & Safe Modeにすることで、移動指示可能にする bt.println("setup completed -------"); } void loop() { // ESP32 to Roomba if (bt.available() &amp;amp;amp;gt; 0){ iB = bt.read(); if (iB == 'a'){ roomba_drive(v, v); /*Roomba.write(145); Roomba.write(255); Roomba.write(56); Roomba.write(255); Roomba.write(56);*/ bt.println("ahead"); }else if(iB == 'b'){ roomba_drive(-v, -v); bt.println("back"); }else if(iB == 'c'){ roomba_moter_stop(); bt.println("stop"); }else if(iB == 'r'){ roomba_drive_turn_clockwise(v); bt.println("turn right"); }else if(iB == 'l'){ roomba_drive_turn_counterclockwise(v); bt.println("turn left"); }else if(iB == 's'){ startSafe(); bt.println("safe"); }else if(iB == 'p'){ startPassive(); bt.println("passive"); }else if(iB == 'w'){ setup(); bt.println("wakeup"); } } }
roomba_function.ino:
/* roomba_function.ino */ /* ルンバへの動作命令などの関数群。 * roomba_esp_Bluetooth.ino と同じフォルダに入れてください。 */ void wakeUp(void){ //起動 digitalWrite(ddPin, HIGH); delay(100); digitalWrite(ddPin, LOW); delay(500); digitalWrite(ddPin, HIGH); delay(2000); } void startSafe(){ // Startコマンド(128)でOI開始 & Safe Modeにする(131) Roomba.write(128); //start Roomba.write(131); //safe mode bt.println("changed to SAFE mode"); delay(100); } void startPassive(){ //passivemodeに移行 Roomba.write(128); //start delay(100); //bt.println("startPassive開始したよ"); } void roomba_drive(int right,int left){ //直進 Roomba.write(byte(145)); roomba_send_num(right); //Velocity right roomba_send_num(left); //Velocity left delay(100); } void roomba_moter_stop(){ //モーターを止める Roomba.write(137); roomba_send_num(0); //Velocity 0mm/s roomba_send_num(0); //Radius 0 速度が0なのでなんでも良い //bt.println("モーターを止める"); delay(100); }; void roomba_drive_turn_counterclockwise(int num){ //反時計回り 引数は速さ Roomba.write(137); roomba_send_num(num); //Velocity 100mm/s roomba_send_num(1); //Radius 1 //bt.println("反時計回り"); delay(100); }; void roomba_drive_turn_clockwise(int num){ //時計回り 引数は速さ Roomba.write(137); roomba_send_num(num); //Velocity roomba_send_num(-1); //Radius //bt.println("時計回り"); delay(100); }; void roomba_send_num(int num){ //numを二つの8bitに変換してルンバに送信 Roomba.write(hex_convert_to8_high(num)); Roomba.write(hex_convert_to8_low(num)); } //整数を8bitに分けるプログラム ビットシフトを使っている unsigned int hex_convert_to16(int a, int b){ return (unsigned int)(a << 8)|(int)(b); } unsigned int hex_convert_to8_high(int a){ return (unsigned int)(a >> 8)&0x00FF; } unsigned int hex_convert_to8_low(int a){ return a^(hex_convert_to8_high(a) << 8); }
このプログラムでは、ルンバとESP32の通信にHardwareSerialを使っている。
ESP32-DevKitCでは、IO16,IO17ピンがUART2のrx, txにデフォルトで設定されている。HardwareSerial Roomba(2); と定義すると、IO16ピンで受信、IO17ピンで送信することができる。
ルンバをBluetooth通信で動かす手順
上記のプログラム(roomba_esp_Bluetooth.ino, roomba_function.ino)を用いる。 (Unityを通じても、ArduinoIDEのシリアルモニタを通じてもルンバを動かすことができる。Unityを用いる場合は、Unity内にESP32とのシリアル通信のプログラムが必要。また、Unityで通信するときにArduinoIDEのシリアルモニタを開いてはいけない(Port busyになる)。)
① ルンバを充電する
- 充電ドックに入れて、ルンバを充電する。
- ルンバはPassive Modeでないと充電されない。Passive Modeにするには、ルンバを持ち上げてタイヤを浮かせるのが手っ取り早い。
② ESPの回路と、ESP32-ルンバのコネクタを用意する
- ESP32が出した3.3V波形を、5Vにレベル変換してルンバに送る
- ルンバは0-5VのTTL通信(Manual p.3),ESP32は3.3V通信
- ルンバからの電源は、レギュレータで5Vにする
- 整流されておらず、バッテリー残量によって電圧値が変わる
- 「ESP32→ルンバ」が2ピン(BRC, RXD),「ルンバ→ESP32」が1ピン(TXD),ルンバからの電源供給ピン(Vpwr, GND)
ESPにプログラムを書きこむ
③④ ESPをルンバとつなぐ
- PCとUSBケーブルでつないだまま、ESP32をルンバにつながないように注意
- USBケーブルを抜いてから、ルンバとコネクタでつなぐ
- ESP32にPCとルンバの双方から電源供給されてしまうため
- つないだときに、ルンバが「ピッ」と音を鳴らすことがある
- Startコマンド(Manual p.8)により、Off ModeからPassive Modeに変わったときに鳴る
- すでにPassive Modeだと鳴らない
⑤ BluetoothでESP32と接続する
- ⑥で「Port busy」といわれてシリアルモニタを開けなくならないように、ArduinoIDEを先に開いた状態で、Bluetooth通信をつなぐ
- PCのBluetoothをオンにし、Bluetoothデバイスの追加から「ESP_BluetoothSerial」を探して接続
⑥ ArduinoIDEでシリアルモニタを開く
- ESP32をBluetooth接続するとCOMポートが2つ追加されるので、シリアルモニタを開くCOMポート番号はどちらなのかを確認する。確認方法は次の手順。
- Windowsの「設定」→「デバイス」(→「Bluetoothとその他のデバイス」)→「その他のBluetoothオプション」(関連設定のリスト内に青い字で書いてある。PCのBluetoothがオンになっていないと押せない)
- Bluetooth設定のウインドウが開く
- COMポートのタブを開き、ポートの名前を確認する。末尾に'ESP32SPP'とついている方が、シリアルモニタを開くときのCOMポート。
(下の図の例では、COM9)
⑦ シリアル通信でESPに命令
- 「a」送信で、ESP32から「ahead」という返信が返ってきてルンバが前進する(「b」→後進,「r」→右回り,「l」→左回り,「c」→停止)
- 動かない場合は「w」を送信して、初期化する(数秒かかる)