社内勉強会でやったことを一応ノウハウとして残しておくために書きました。Qiitaは見る専だったのですが、これを機にちょっとずつ書いていければいいかなーと
#やりたいこと
秋月のRN4020を使ったBLEモジュール("http://akizukidenshi.com/catalog/g/gK-11102/" )を使って、外部デバイスとarduino間でデータをやりとりさせたい。
#開発当初の話
「なんか無線通信考えたときBLE?っていうのがいいらしいし?とりあえずシリアル通信みたいに適当なフォーマット考えてデータ送受信すればいけるでしょ〜〜」
って考えてたけど思っていたのと違ってて全然簡単じゃなかった。
BLEの説明はわざわざこんな記事に書いてあるのを見るより以下を参考にしたほうがいいです。
アプリケーション開発視点でのBLE通信 - Qiita
開発視点の超簡単BLE入門
BLEについて今更調べてみた - Qiita
最低限の説明をすると、BLEではProfileという独自の通信ルールをもとに
- 接続要求をだしたりうけとったり
- Characteristicという仮想のレジスタみたいなデータにR/Wしたり
などをします。
前者周りを定義するのがGAP、後者周りを定義するのがGATTです。(BLEわかんないってなったらこの2つを調べてみるといいかも)
なんでこんな面倒くさい仕組みで通信しなきゃいけないのか?っていうのはBLEが省エネルギーを第一として作られている規格だから。
より興味ある人は上で紹介した記事を読んだり、これ→(https://www.amazon.co.jp/Bluetooth-Low-Energy%E3%82%92%E3%81%AF%E3%81%98%E3%82%81%E3%82%88%E3%81%86-Make-PROJECTS/dp/4873117135 )とか読んだりするといいんじゃないかな。(僕も図書館で借りて必要な箇所を抜き出しながら読んだだけだけど、内部でやられてることが細かく書かれていて、本格的にBLEアプリの開発を考えてる人にはいいと思う)
ここではとりあえず動かすのが目的なのであまり電力効率がどうのとか考えずすすめます。
#必要な物
- Arduino Uno
- BLEモジュール("http://akizukidenshi.com/catalog/g/gK-11102/" )
- 確認用のセントラル端末(今回はiPadを使用)
#ゴールまでのフロー
外部端末からRN4020にR/Wアクセスしたり、hostPCからRN4020へ設定ができるようになっています。
これをGAP上の役割で例えると以下の通りになります。
ペリフェラル
RN4020+arduino
セントラル
iPad(LightBlue)
はじめにarduinoからUART経由でRN4020のセットアップを行います。hostPC(今回はMac book)にもUSB-Serialがつながっていますが、これは主に確認用として使います。
RN4020内で独自のServiceおよびCharacteristicを立ち上げて、外部の端末からデータを書き込みできるようにします。Arduinoはその書き込みをイベントとして検知してなんらかのアクション(今回はLED点灯消灯とhostPCへのイベントメッセージ送信)を起こす、というところまでがゴールです。
#RN4020スタートアップ
BLEモジュールとarduinoを次の写真のようにつなげます。
ペリフェラル側の接続図
注意点として、PCをUSBでつないでSerial通信する場合はarduinoのURATのRXおよびTX(0pin,1pin)は外しておく必要があります。そうしなければボード書き込み時にバッティングしてエラーになってしまうからです。
→https://garretlab.web.fc2.com/arduino_reference/language/functions/communication/serial/index.html
そのため、代わりにRN4020 <-> arduino間の通信は標準のSoftwareSerialを使用します。
プログラムはここをめちゃくちゃ参考にしました。
https://ogapsan.com/archives/766
RN4020モジュールはMicroChip社が規定したコマンドをUART経由で送ることで制御を行います。具体的には以下のような種類のコマンドがあります。
• Set/Get コマンド
• アクション コマンド
• キャラクタリスティック アクセス コマンド
• プライベート サービスの設定コマンド
• Microchip MLDP コマンド
• RN4020 スクリプト実行コマンド
• リモートコマンド
• DFU コマンド
初めて使う人は秋月が出してる日本語のマニュアルに目を通してください。
http://akizukidenshi.com/download/ds/microchip/70005191A_JP.pdf
とりあえず勘所を掴むために、PCからRN4020に直接書き込めるようにしつつ、RN4020からのメッセージをPC側に受け取れるようにしてみましょう。
#include <SoftwareSerial.h>
#define PIN_MY_SERIAL_RX 6
#define PIN_MY_SERIAL_TX 7
#define PC_WRITE 0x08
#define PC_READ 0x02
#define PC_NOTIFY 0x10
//初期化
SoftwareSerial mySerial(PIN_MY_SERIAL_RX, PIN_MY_SERIAL_TX); // RX, TX
void setup() {
Serial.begin(115200); // ハードウェアシリアルを準備
Serial.println("!!BLE SETTING START!!");
Initialize();
Serial.println("!!BLE SETTING END !!");
}
void loop() {
delay(10);
EchoPrint();
WriteSerialPathThrough();
}
void Initialize() {
mySerial.begin(115200);
/**************************************************************
子機:ペリフェラル
**************************************************************/
//出荷時設定
mySerial.println("SF,1");
//ボーレート設定
mySerial.println("SB,1");
//Private Serviceリセット
mySerial.println("PZ");
//Reboot
mySerial.println("R,1");
//ソフトウェアシリアル変更
mySerial.end();
mySerial.begin(9600);
//エコー
mySerial.println("+");
//Device Name
mySerial.println("SDM,Arduino_nano");
/**************************************************************
SS:サービス設定
SetRegister
0x00000001 : User
**************************************************************/
mySerial.println("SS,00000001");
/**************************************************************
SR:機能設定
SetRegister
0x20000000 : パワーサイクル、再起動、切断後にアドバタイズ開始
0x10000000 : シリアル非同期転送サービス
0x04000000 : No Direct Advertisement
0x02000000 : UART Flow
**************************************************************/
mySerial.println("SR,36000000");
//Reboot
mySerial.println("R,1");
delay(500);
Check_CMD();
}
//RN4020 コマンド受付チェック
void Check_CMD() {
String buff;
int i = 0;
while (1) {
if (mySerial.available()) {
buff = mySerial.readString();
}
if ((buff.indexOf("CMD") >= 63) || (buff.indexOf("CMD") == 0)) {
i = 0;
break;
}
if (i >= 1000) {
Serial.println("Reset");
mySerial.println("R,1");
i = 0;
}
i++;
delay(1);
}
}
//PC->RN4020 コマンドWrite
void WriteSerialPathThrough() {
String TD;
if (Serial.available()) {
TD = Serial.readString();
Serial.print(TD);
mySerial.print(TD);
}
}
//RN4020 エコー確認
void EchoPrint(){
String RD;
if (mySerial.available()){
RD = mySerial.readString();
Serial.print(RD);
}
}
上記コードをarduinoに書き込んだら、試しにArduinoIDEのシリアルモニタからD
コマンドを入力してデバイス情報を読み取ってみます。
こういう感じで表示されたら、hostPC<->Arduino<->RN4020のパスが通っているところまで確認できています。(なんか先頭文字化けしてるけど・・・)
他にもマニュアルを見ながらいろいろやって見るのがいいかもしれません。
RN4020の動作確認が終わったら、とりあえず独自のServiceとCharacteristicを立ち上げるまで自動でやってくれるように実装します。
#define FINSNAP_SERVICE_UUID "93765f06422711e9b210d663bd873d93" /*Set Target Service UUID*/
#define FINSNAP_CHARACTERISTIC_TORIGGER_EVENT_UUID "93766366422711e9b210d663bd873d93" /*Set Characteristc of Target Service UUID*/
...
mySerial.println("SR,36000000");
/*カスタムPriveteService立ち上げ*/
Register_Private_Service(FINSNAP_SERVICE_UUID);
/*カスタムPriveteCharacteristic設定*/
Register_Private_Characteristic(FINSNAP_CHARACTERISTIC_TORIGGER_EVENT_UUID, PC_WRITE | PC_READ, 1);
//Reboot
mySerial.println("R,1");
...
//PS登録
void Register_Private_Service(String uuid) {
String cmd = "PS,";
cmd.concat(uuid);
mySerial.println(cmd);
}
//PC登録
void Register_Private_Characteristic(String uuid, int property, int bytes) {
char uuid_char[128];
char cmd[255] = "";
uuid.toCharArray(uuid_char, uuid.length() + 1);
sprintf(cmd, "PC,%s,%02x,%02x", uuid_char, property, bytes);
mySerial.println(cmd);
}
FINSNAP_SERVICE_UUID
とFINSNAP_CHARACTERISTIC_TORIGGER_EVENT_UUID
は独自ServiceとCharacteristicを立ち上げるために必要な16byteのUUIDを定義しています。
実装前にOnline UUID Generatorなどのサイトを利用して必要なだけUUIDを生成します。
実際に独自サービスが立ち上がったかを確認するためにLS
コマンドを入力してみます。
設定したServiceUUID
設定したCharacteristicUUID,ハンドラ,property,byte数
...
という風にデータが表示されていればOKです。
#キャラクタリスティック値Writeイベント検知
あとは外部端末からCharacteristicに値を書き込んで、それをarduinoが何らかの方法で受け取れればいいのですが、ここですごく詰まってしまいました。
というのも、SUR
コマンドを使ってarduinoが常にポーリングし続けるようにすればいいかと考えたんですが、RN4020からエコーされるRead値が文字化けしてしまうみたいで…
色々と試したものの一向にうまくいかなかったのでどうしようかと思ったんですが、マニュアルに次の記述があるのをみつけました。
図 3-7 では、プライベート キャラクタリスティック
0x111213141516171819101A1B1C1D1E1F にアプリケーションから値 0x3412
( リトル エンディアン ) を書き込んでいます。RN4020 モジュールのターミナル
エミュレータには、キャラクタリスティック
0x111213141516171819101A1B1C1D1E1F( ハンドル 0x001E) に値 0x3412 が書
き込まれた事を示す以下のステータス メッセージが表示されます。
WV,001E,1234
このステータスメッセージをそのまま書き込みイベント用メッセージとしてarduinoに解釈させてしまえばいいじゃん、っていうのが方法です。
正直ゴリ押しもいいとこかなと思うので、うまい方法あったら教えてください。。。(本当はPIOあたりを使えたらよかった)
WV,[Target Handle Value],[Target Write Value],
というフォーマットでメッセージが出力されるので、コマンドとTarget Handle Valueが一致していれば書き込みイベントとして受け取るようにします。ハンドルは先程LS
コマンドで確認したものを設定します。
#define FINSNAP_CHARACTERISTIC_TRIGGER_EVENT_HANDLE "000B" /*Set Characteristc of Target Service Handle*/
int getBLEAccess(){
String RD;
String cmd_key="WV,";
cmd_key.concat(FINSNAP_CHARACTERISTIC_TRIGGER_EVENT_HANDLE);
if (mySerial.available()) {
RD = mySerial.readString();
if (RD.indexOf(cmd_key,0) != -1) {
String BTwdata = RD.substring(8, 10);
return BTwdata.toInt();
}
return -1;
}
return -1;
}
あとはloop関数内部でずっとポーリングし続けていればコマンドの受信を検知できるようになります。(※ちなみに上記関数はEcho_Printとバッティングするので、検知する場合はコメントアウトしてください。)
諸々踏まえてLightBlueから、RN4020のキャラクタリスティック値を書き換えたときのテストを行います。
0x0がWriteされたときは青色のLEDが点灯し、それ以外がWriteされたときは赤色LEDが転送するようにします。-1の場合はコマンドが取得できなかった場合です。
できました
#まとめ
これだけのことをするのに数日かけてしまったのは反省点。作業の大部分がRN4020の設定に依存しているものなので、勉強会にいる電子工作つよつよマンたちに質問なげにくかったのも難しいところだった
RN4020のコマンドとか設定を探すのはわりと面倒くさいけど、センサをそのままBluetooth化できたり使いみちは多そうなので、電子工作苦手な人もとりあえずやってみたらいいんじゃなかろうか
次はiPadのLightBlueから制御していた部分をRaspberryPiのpythonスクリプト上から制御できるようにしたい