目的
ロボットハンドを遠隔操作するためのデータグローブを新しく作るにあたって、seeed xiao nrf52 BLE senceという親指サイズなのにbluetoothとマイクと6軸センサーを積んだマイコンを買った。
これを機に今までのちょっと怪しい315Mhzの無線モジュールを卒業して技適の通ったBLE(Bluetooth Low Energy)通信で遠隔操作をしようと思う。
はじめに
まず、ロボットハンド側もBLE通信に対応させるためseeed xiao ESP32を購入した。この二つの間でデータのやり取りをする。
BLE通信にはGAT( General Advertising Profile)とGATT(General Attribute Profile)通信があり、前者はペアリングせずに一方向通信をするが、後者はペアリングしたセントラル-ペリフェラル間でのみ双方向通信ができる。今回の使用範囲では正直ブロードキャストで十分だが、構想ではいつか双方向が必要になるので今回は2つのマイコン間で自動でペアリングをして通信をする。
しかしネットで探してもスマホとマイコン間の通信ばかりでマイコン間のものが少なかった。見つけてもESP間であり、nrf52では使えないライブラリだったので両方で使えたarduinoBLE.hを用いる。
BLEとは
概要
BLEはBluetoothの規格の一つであり、Bluetooth4.0以降から追加された電力消費が少ない通信方式のこと。普通のものに比べて何十分の1という消費電力で済む。
ワイヤレスイヤホンのような通信量が多く、リアルタイム性が求められるものには向かないが、今回のような数バイトのデータをやり取りする程度なら十分な性能。
Bluetoothにはセントラルとペリフェラルがあり、セントラルはスマホ、ペリフェラルはイヤホンやセンサーなどの役割を持つ。
通信のしくみ
まず、ペリフェラルが自分の「サービス名」と「UUID」という固有のアドレスをブロードキャストという方式で周囲にばらまく。これをアドバタイズといい、セントラルがこれを受信するとスマホでいう周囲のBluetoothデバイス欄に表示される。ちなみにこの時の電波の強さもわかるのでイヤホンを見つけるアプリはこれを使っている。
ペリフェラルは複数のCharacteristicをもち、これらを包括した名前をサービスという。Characteristicは通信構造のことであり、データグローブで例えると、5つの指の曲げ具合の数値を2つのCharacteristicに格納し、DataGroveというサービスがその2つのCharacteristicを持っている。
送受信したいデータを入れる箱が「Characteristic」、その箱たちが入ったトラックが「サービス」というイメージだ。
ArduinoBLEライブラリ
導入経緯
今回はどちらもseeed XIAOを使っているが、マイコンがESPとnrf52なので使えるBLE通信ライブラリがArduinoBLE.hに限られた。そのためこのライブラリを使うが、今回はseeedのボードマネージャーやライブラリの導入は省く。
ただし、ESPのBLEライブラリと競合してコンパイルエラーが出るので、ArduinoBLEはボードのライブラリフォルダに入れておく。
ArduinoBLEのリファレンスもあるが、実際に使わないと分かりにくいのでスケッチ例を参考にする。
追記:このリファレンスは決して参考にしてはならない。これのせいで開発が数ヶ月遅れてしまった。これを参考にせず、直接ライブラリの中身を見よう。
解説
まず、実際に通信に成功し、実使用しているプログラムを書く。
ペリフェラル側
#include <ArduinoBLE.h>
const int ledPin = LED_BUILTIN; // set ledPin to on-board LED
const int buttonPin = 2;
int oldButtonState = LOW;
BLEService DataGrove("19B10010-E8F2-537E-4F6C-D104768A1214"); // create service
// create switch characteristic and allow remote device to read and write
BLECharacteristic thumbCharacteristic("19B10011-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite, 32);
BLECharacteristic indexCharacteristic("b6fe67e7-8cb2-47a5-99da-24e8cee99b56", BLERead | BLEWrite, 32);
float finger[5];
void setup() {
Serial.begin(115200);
// while (!Serial);
Serial.println("start");
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);
delay(1000);
digitalWrite(ledPin, HIGH);
// configure the button pin as input
pinMode(buttonPin, INPUT);
pinMode(8, OUTPUT);
// begin initialization
if (!BLE.begin()) {
Serial.println("starting Bluetooth® Low Energy module failed!");
while (1);
}
// set the local name peripheral advertises
BLE.setDeviceName("DataGrove");
BLE.setLocalName("DataGrove");
// set the UUID for the service this peripheral advertises:
BLE.setAdvertisedService(DataGrove);
// add the characteristics to the service
// ledService.addCharacteristic(ledCharacteristic);
// ledService.addCharacteristic(buttonCharacteristic);
DataGrove.addCharacteristic(thumbCharacteristic);
DataGrove.addCharacteristic(indexCharacteristic);
// DataGrove.addCharacteristic(middleCharacteristic);
// DataGrove.addCharacteristic(ringCharacteristic);
// DataGrove.addCharacteristic(pinkyCharacteristic);
// add the service
BLE.addService(DataGrove);
// thumbCharacteristic.writeValue(0);
// indexCharacteristic.writeValue(0);
// start advertising
BLE.advertise();
// BLE.begin();
Serial.println("Bluetooth® device active, waiting for connections...");
}
void loop() {
// poll for Bluetooth® Low Energy events
// BLE.poll();
BLEDevice central = BLE.central();
if (central) {
Serial.print("Connected to central: ");
// print the central's MAC address:
Serial.println(central.address());
// while the central is still connected to peripheral:
while (central.connected()) {
digitalWrite(5, HIGH);
digitalWrite(6, HIGH);
int sensor[5] = {123, 124, 125, 126, 127};
digitalWrite(ledPin, LOW);
sensor[0] = analogRead(0);
sensor[0] = constrain(sensor[0], 430, 530);
sensor[0] = map(sensor[0], 530, 430, 0, 180);
sensor[1] = analogRead(1);
sensor[1] = constrain(sensor[1], 180, 390);
sensor[1] = map(sensor[1], 390, 180, 0, 180);
sensor[2] = analogRead(2);
sensor[2] = constrain(sensor[2], 220, 420);
sensor[2] = map(sensor[2], 420, 220, 0, 180);
sensor[3] = analogRead(3);
sensor[3] = constrain(sensor[3], 170, 300);
sensor[3] = map(sensor[3], 300, 170, 0, 180);
sensor[4] = analogRead(4);
//sensor[4]++;
sensor[4] = constrain(sensor[4], 200, 480);
sensor[4] = map(sensor[4], 480, 200, 0, 180);
uint32_t bend[5];
bend[0] = sensor[0] | sensor[1] <<8 | sensor[2] <<16 | sensor[3] << 24;
// bend[0] = sensor[0] | sensor[1] <<8;
bend[1] = sensor[4];
for (int i = 0; i < 5; i++) {
// sensor[i]=analogRead(i);
// bend[i]=sensor[i];
Serial.print(i);
Serial.print(": ");
Serial.print(sensor[i]);
Serial.print(" ");
}
Serial.println();
thumbCharacteristic.writeValue(bend[0],true);
indexCharacteristic.writeValue((byte)sensor[4]);
// middleCharacteristic.writeValue(bend[2]);
// ringCharacteristic.writeValue(bend[3],true);
// pinkyCharacteristic.writeValue(bend[4],true);
// delay(10);
}
if (!central.connected()) {
digitalWrite(ledPin, HIGH);
digitalWrite(5, LOW);
digitalWrite(6, HIGH);
}
}
else {
digitalWrite(ledPin, HIGH);
digitalWrite(5, LOW);
digitalWrite(6, HIGH);
}
// when the central disconnects, print it out:
Serial.print(F("Disconnected from central: "));
Serial.println(central.address());
}
やっていること
- セントラルを探す
- DataGloveという名前のサービスを見つけたら接続する
- 5つの曲げセンサーの値を読む
- 4つのデータをバイトシフトののち32byteのint型変数にまとめる
- 小指用のCharacteristicも作成し送信
以上を繰り返している。
詳説
ここからは細かい解説をしていく。まず、
ここでサービス名を設定する。括弧の中の文字列はUUIDといい、このサービス固有のものを書く必要がある。UUIDに関してはUUID生成とかで検索すればそれ用のサイトが出てくるので、ランダム生成したものを入れる。
ここでは二つのキャラクターを生成している。括弧の中は左から、UUID,プロパティ,データの最大サイズとなっている。
プロパティはBLEBroadcast、BLERead、BLEWriteWithoutResponse、BLEWrite、BLENotify、BLEIndicateがあるが、ここではデータの読み書きができればいいのでBLERead | BLEWriteとした。
どうやらArduinoBLEの仕様上、32byteまでしかデータが送れないようなので最大で設定。次に
ここではBLEの初期化をしている。成功すればループを抜ける。
ここではデバイス名とサービス名を設定している
今回は同じ名前にしている。
ついで、
ここでキャラクターとサービスを追加している。
ここでアドバタイズしている。初めの方で述べたが、これで周りに自分のデバイス名やサービス名、キャラクター名とそのUUIDをばらまいている。ここまでで通信の前に行う準備が完了した。
通信
ここからは通信に入る。
まず通信をしているloop内の構造を大まかに記述する。
まず、データグローブがセントラルと接続できればシリアル通信でセントラルMACアドレスを送信する。その後接続しているうちは、曲げセンサーの値を処理して送信を続ける。もし接続が途切れれば、切断されたとシリアル通信で送信する。LEDは接続確認用につけてある。
ここからは細かい解説を入れる。
ここで接続したセントラルにcentralと名付ける。これ以降はセントラル関連の関数はcentral.~()と書く。
セントラルのMACアドレスを表示する。MACアドレスとはデバイス固有のIDのことを指す。
ここではキャラクターに数値を送信している。ただし、ここでの書き方には注意が必要だ。ArduinoBLEのリファレンスでは
としか書かれていないが、実際には送るデータやデータの大きさによって少し変わる。ライブラリーの中身を見てみると、
といろいろな種類の引数がある。文字を送りたいときや、数値のサイズによって引数を上記に合わせて適切に書かないとエラーが出るので注意が必要。
これを書いてくれなかったせいで、1バイトのデータしか送れないと勘違いして5つのキャラクターを作ってBLE通信したら8Hzくらいしか出なかった
おまけ
ビットシフト
ここでデータを送る際、データをまとめて送りたいときにはビットシフトが便利です。具体的に書いていく。
例えば100,110、120、130という4つのデータがある。これを4つのキャラクターで送ると処理が重くなってしまうので、一つのキャラに収めたいところだ。1つのキャラで送れる最大のデータ量は32byte、つまり4,294,967,296までしか表せない。3桁ずらして足すと100,110,120,130となり、超過してしまう。ここで活躍するのがビットシフトだ。
ビットシフトとは二進数で表した数値の桁をずらしていく操作のことをいう。100は2進数では01100100という8桁の数になる。1バイト、つまり256までの数は8桁内に収まるので、32byte中に4つ1バイトデータを入れることができます。この操作をarduinoで書くと次のようになります。
これで4つのデータを一つにまとめることができた。あとはこれを受け取り側で4つに分解する方法である。arduinoには標準でデータのうち下1バイト、つまり下4桁のみを読む機能と続く4桁を読む機能があり、それぞれ
とかく。これを用いて、先ほど用意した複合データを4つに分解するには
と書ける。8~12桁を読んでくれる関数はないので、途中で16桁右にずらして再び読んでいる。
これでfinger[0]に100、finger[3]に130が代入される。通信するのは結構必須のテクニックなので覚えておいて損はないはずだ。5つのデータを送りたかったので結局2つのキャラを作らなければならなかったのは内緒
思っていたより長くなったので、セントラル編は次の記事にする。
Discussion
コメントはまだありません。
ログインするとコメントできます!