ルンバをESP32でBluetooth通信で動かす方法

ルンバを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コマンドが届けられ、ボーレートがきちんと変更される。

④ 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;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」を送信して、初期化する(数秒かかる)