Serialization
概要
nRF51に直接実装せずに外部MCUなどからシリアル通信で制御する方式をBLE serializationと呼ぶ。
ドキュメントで紹介されている方法は開発ボードを二台接続して、ApplicationBoard(制御する側)とConnectivityBoard(制御される側)に分けて実装を行う方法になる。
実際はApplicationBoard側はnRF51を使用せずにARMを直で使用することが多くなるはず。
接続はUART, SPI, HCIが利用できる。
ApplicationBoardではSoftdeviceを使用しないが専用ライブラリによりSoftdeviceを使用した時と同じコードでConnectivityBoardを制御することができる。
ConnectivityBoardには、Softdeviceと接続用のアプリケーションをインストールする。
Arduinoとつなぐ
ArduinoをApplicationBoardとしてつなぐ場合は専用ライブラリが利用できないので、直にシリアル通信を行う必要がある。
また、Arduinoはフローコントロールができないなど、シリアル通信の設定も変える必要もある。
接続アプリのコンパイル
接続アプリはコード量が多いのでKeilの無料版でばビルドできないので、今回はGCCを用います。
こちらからArm用のGCCをダウンロードし、任意の場所へ置く。
下記のGNU_INSTALL_ROOTにGCCの場所を記載する。
- [SDK]/components/toolchain/gcc/Makefile.posix
下記のヘッダにシリアル通信の設定があるので、Arduinoに合わせた設定にする。
- [SDK]/components/serialization/common/ser_config.h
/** UART transmission parameters */ //#define SER_PHY_UART_FLOW_CTRL APP_UART_FLOW_CONTROL_ENABLED //#define SER_PHY_UART_PARITY true //#define SER_PHY_UART_BAUDRATE UART_BAUDRATE_BAUDRATE_Baud1M #define SER_PHY_UART_FLOW_CTRL APP_UART_FLOW_CONTROL_DISABLED #define SER_PHY_UART_PARITY false #define SER_PHY_UART_BAUDRATE UART_BAUDRATE_BAUDRATE_Baud115200
接続アプリは利用するSoftdevice毎に下記フォルダに用意されているので、移動してmakeを実行するとhexファイルが生成される。
- [SDK]/examples/ble_central_and_peripheral/ble_connectivity [S130]
- [SDK]/examples/ble_peripheral/ble_connectivity [S110]
- [SDK]/examples/ble_central/ble_connectivity [S120]
開発ボードにSoftdeviceと先ほどのhexファイルを書き込む。
接続
表の通りにGPIOを接続する。
開発ボード | Arduino |
---|---|
GND | GND |
P0.12 | P2 |
P0.13 | P3 |
Arduinoプログラム
iBeaconを発信するプログラム。
#include <SoftwareSerial.h>
SoftwareSerial serial(2, 3);
int state = 0;
int count = 0;
void setup() {
// BLEとの通信用
serial.begin(115200);
// ログ出力用
Serial.begin(9600);
Serial.write("*Start!\n");
// BLEを有効化
Serial.write("sd_ble_enable()\n");
sd_ble_enable();
}
void loop() {
while (serial.available()) {
Serial.print(serial.read(), HEX);
if (count++ > 6) {
Serial.write("\n");
count = 0;
switch(state++) {
case 0:
Serial.write("*sd_ble_gap_adv_data_set()\n");
sd_ble_gap_adv_data_set();
break;
case 1:
Serial.write("*sd_ble_gap_adv_start()\n");
sd_ble_gap_adv_start();
break;
case 2:
Serial.write("*Finish!\n");
break;
}
} else {
Serial.write(",");
}
}
}
// データ送信
void send_data(byte *data) {
int len = data[0] + 2;
for (int i=0; i<len; i++) {
serial.write(data[i]);
}
}
// BLEを有効化
void sd_ble_enable() {
byte data[] = {
// パケットサイズ
0x08, 0x00,
// タイプ(0:コマンド, 1:レスポンス)
0x00,
// コマンドタイプ
0x60,
// コマンドの内容
0x01, 0x00, 0x00, 0x00, 0x00, 0x00
};
send_data(data);
}
// Advertisingのデータを設定
void sd_ble_gap_adv_data_set() {
byte data[] = {
0x24, 0x00, 0x00, 0x72, 0x1e, 0x01,
// Header
0x02, 0x01, 0x06, 0x1a, 0xff, 0x4c, 0x00, 0x02, 0x15,
// UUID
0x01, 0x12, 0x23, 0x34, 0x45, 0x56, 0x67, 0x78, 0x89, 0x9a, 0xab, 0xbc, 0xcd, 0xde, 0xef, 0xf0,
// Major
0x01, 0x02,
// Minor
0x03, 0x4,
0xc3, 0x00, 0x00};
send_data(data);
}
// Advertising開始
void sd_ble_gap_adv_start() {
byte data[] = {
0x0c, 0x00, 0x00, 0x73, 0x01,
// ADV_NONCONN_IND
0x03,
0x00, 0x00, 0x00,
// Interval(0.625 ms units)
0xa0,
0x00, 0x00, 0x00, 0x00};
send_data(data);
}
パケットフォーマット
例)sd_ble_enableのパケットフォーマット
他のパケットフォーマットはこちら
サイズ | タイプ | コマンド | コマンド | パラメータ |
---|---|---|---|---|
2Byte | 1byte | 1byte | 1byte | 5byte |
0x08, 0x00 | 0x00 | 0x60 | 0x01 | 0x00... |
以降8Byte | コマンドタイプ | sd_ble_enableを表す | BLEを有効化する | 5byte |
byte data[] = {
// パケットサイズ
0x08, 0x00,
// タイプ(0:コマンド, 1:レスポンス)
0x00,
// コマンドタイプ
0x60,
// コマンドの内容
0x01, 0x00, 0x00, 0x00, 0x00, 0x00
};
スキャンする
準備
Advertisingパケットをスキャンするにはsd_ble_gap_scan_startを利用する。
これはS110のソフトデバイスでは対応していないので、S130を書き込む。
Arduinoプログラム
#include <SoftwareSerial.h>
SoftwareSerial serial(2, 3);
int state = 0;
int count = 0;
uint8_t buff[255];
int buff_pos = 0;
int data_len = 0;
int data_com = 0;
void setup() {
// BLEとの通信用
serial.begin(115200);
// ログ出力用
Serial.begin(38400);
Serial.write("*Start!\n");
// BLEを有効化
Serial.write("sd_ble_enable()\n");
sd_ble_enable();
}
// パケットの内容をパース
void parse(uint8_t data) {
buff[buff_pos++] = data;
if (buff_pos == 2) {
data_len = buff[0] + (buff[1] << 8);
Serial.write("len:");
Serial.print(data_len, DEC);
Serial.write("\n");
}
if (buff_pos == 3) {
Serial.write("type:");
Serial.print(data, HEX);
Serial.write("\n");
}
if (buff_pos == 4) {
data_com = data;
Serial.write("command:");
Serial.print(data, HEX);
Serial.write("\n");
}
if (buff_pos > 4) {
Serial.print(data, HEX);
} else {
return;
}
if (data_len == buff_pos -2) {
buff_pos = 0;
data_len = 0;
state++;
Serial.write("\n");
} else {
Serial.write(",");
}
}
void loop() {
if (serial.overflow()) {
// SoftwareSerialはバッファが64byteしかなくオーバーフローしてしまうので、回避策が必要。
Serial.println("\nSoftwareSerial overflow!");
delay(100);
exit(1);
}
while (serial.available()) {
parse(serial.read());
if (state == 1 && buff_pos == 0) {
Serial.println("sd_ble_gap_scan_start()");
sd_ble_gap_scan_start();
}
}
}
// データ送信
void send_data(byte *data) {
int len = data[0] + 2;
for (int i=0; i<len; i++) {
serial.write(data[i]);
}
}
// BLEを有効化
void sd_ble_enable() {
byte data[] = {
// パケットサイズ
0x08, 0x00,
// タイプ(0:コマンド, 1:レスポンス)
0x00,
// コマンドタイプ
0x60,
// コマンドの内容
0x01, 0x00, 0x00, 0x00, 0x00, 0x00
};
send_data(data);
}
// Scan開始
void sd_ble_gap_scan_start() {
byte data[] = {
// パケットサイズ
0x0b, 0x00,
// タイプ(0:コマンド, 1:レスポンス)
0x00,
// コマンドタイプ
0x86,
// コマンドの内容
0x01, // present
0x00, // Active Scanning
0x00, // Whitelist
0x40, 0x06, // Interval(0.625 ms units)
0x50, 0x00, // Scan window(0.625 ms units)
0x00, 0x00 // Timeout(No timeout)
};
send_data(data);
}
改良
SimpleFIFOを利用してSoftwareSerialのオーバーフローを回避する。
#include <SimpleFIFO.h>
SimpleFIFO<uint8_t,255> sFIFO;
// ~~略~~
void loop() {
// シリアルから読み取ったデータはすぐにQueueに入れる
while (serial.available()) {
sFIFO.enqueue(serial.read());
}
// シリアルからの読み込みが無いタイミングで処理する
if (sFIFO.count() > 0) {
parse(sFIFO.dequeue());
if (state == 1 && buff_pos == 0) {
Serial.println("sd_ble_gap_scan_start()");
sd_ble_gap_scan_start();
}
}
}
より見やすく
parse関数の内容を変更してBLE_GAP_EVT_ADV_REPORTのイベントをより詳細に表示するように改良。
// ~~略~~
if (buff_pos > 4) {
if (data_com == 0x1b) {
// BLE_GAP_EVT_ADV_REPORT
if (buff_pos > 16) {
Serial.print(data, HEX);
} else if (buff_pos == 16) {
Serial.write("handle:");
Serial.print(buff[5], HEX);
Serial.print(buff[6], HEX);
Serial.write("\n");
Serial.write("type:");
Serial.print(buff[7], HEX);
Serial.write("\n");
Serial.write("address:");
Serial.print(buff[8], HEX);
Serial.print(buff[9], HEX);
Serial.print(buff[10], HEX);
Serial.print(buff[11], HEX);
Serial.print(buff[12], HEX);
Serial.print(buff[13], HEX);
Serial.write("\n");
Serial.write("rssi:");
Serial.print(buff[14], HEX);
Serial.write("\n");
return;
} else {
return;
}
} else {
Serial.print(data, HEX);
}
} else {
return;
}
// ~~略~~