susutex’s blog

自転車、電子工作、プログラミングとかについて書くかもしれないブログ

chibi:bitをmbedで開発してやっつけでBLE心拍計としてサイコンアプリに認識させるところまで

nRF51822のボードとしても使えそうだなと思いながら昨年夏にMFT2016で買ったまま放置していたchibi:bitテスト版。Micro:bitのエディタでサンプル書き込んだだけで放置していたが、工作意欲が少しわいてきたのでmbedを使ってBLEデバイスを開発する方法を調べることにした。

まずはblinkyの実行

BBC micro:bit | Mbed
にmicro:bitの説明がある。USBにつなぐと特に作業なしにMBEDのMSDが認識された。
いつもの手順でmbed compilerから新規プロジェクトを作成してblinkyを書き込むと一番左上のLEDが点滅を始めた。
よくあるmbedのblinkyサンプルはmyledのみの操作だが、コメントにもあるとおりmicro:bitのLEDはマトリクスなのでcol0の操作が追加されている。

BLEのサンプルを捜して実行してみる。

BLEの規格とかよくわからないのでまずはサンプルを捜してそれを追うところからスタートすることにする。
mbedのライブラリとしてBLE_APIというのがある
BLE_API - a mercurial repository | Mbed
このページは情報が少ないがsummaryのページへのリンクがあるのでそっちに飛ぶ。
Bluetooth Low Energy | Mbed
Summaryのページの下にはサンプルになりそうなコードが並んでいる。シンプルそうなのでBLE_Buttonというコードを試してみることにした。
BLE_Button - a mercurial repository | Mbed
とりあえずインポートしてビルドしてみるが、"BUTTON1"が定義されていないと言われる。micro:bitのページのピンアウトを見るとmicro:bit上のBUTTON AはP5らしいのでincludeの下あたりに以下を追加する。

#define BUTTON1 p5

またその上に

DigitalOut  led1(LED1);

の記述があるが、blinkyの内容からわかるようにこれではだめなので、この行をコメントアウトして以下二行を追加する。

DigitalOut col0(P0_4, 0);
DigitalOut led1(P0_13);

BLEの接続先としてはiPhoneにBLExplorerというアプリがあるのでそれを試してみる。
BLExplorer on the App Store

f:id:susutex:20170510215238p:plain
Buttonというデバイスが見える。

f:id:susutex:20170510215237p:plain
Buttonをタップするとこの画面に移動する。

f:id:susutex:20170510215236p:plain
A000をタップするとこの画面に移動する。よくわからないがとりあえずBLEのデバイスとしては動作しているようだ。

ためしにchibi:bitのスイッチAを押してみるが値が変化する様子はない。そもそもスイッチAの操作を認識しているのかわからない。
コードを眺める限り、main中の

if (buttonState != IDLE) {

から始まるコードがスイッチを操作した際に実行されるように見えるので、このif文の中に

led1=!led1;

を追加して実行しスイッチを押してみるがLEDの動作が変わる様子がない。スイッチを認識していないようだ。

micro:bitは新しいプラットフォームなのでライブラリが古いのかと思いすべてのライブラリを最新にしてから再度ビルドしてみる。すると次は"p5"が見つからないと言われた。いろいろ調べた結果、先ほど追加したBUTTON1のdefineを

#define BUTTON1 P5

と訂正するとビルドが通った。大文字と小文字の違いだった。このバイナリを書き込むとスイッチ操作でLEDがトグルするようになったのでスイッチが正しく認識されていることが確認できた。

f:id:susutex:20170510215235p:plain
なぜかデバイス名がnRF5xに。
スクショを撮り忘れたが、スイッチを押しながらA000をタップするとA001の下の数字が00から01に変化するので、ひとまずスイッチが押されていることをBLE経由で通知できているようだ。

BLEのGATTへの理解を深めるためにBattery Serviceを実装してみる

どこかで以前見たうろ覚えの知識では、BLE機器はBLE規格上にあるGATTなるもので、そのデバイスがあらかじめ規格上で定められたどのような装置なのかを定義し、規格の内容に沿って通信をすると見たことがある。

「BLE GATT 規格」で検索すると以下のページがヒットした。
blog.fenrir-inc.com
Generic Attribute (GATT) Profileの章を見ると、一つのデバイスの中に複数のサービスがあり、その中には複数のCharacteristicsがある、という構造らしい。

GATT Service」で検索すると以下のページがヒットする。
www.bluetooth.com
"Heart Rate"とか"Cycling Power"とか"Cycling Speed and Cadance"とかある。たとえば私が使用しているチェーンリング型パワーメーターはパワーとケイデンスが計れるので、"Cycling Power"と"Cycling Speed and Cadance"というサービスを持った一つのデバイスということになるのだろう。

さて、mbed nRF51822ではこれをどのように記述しているのだろうか。「nrf51822 mbed」で検索してみると以下のサンプルコードを見つけた。

bril-tech.blogspot.jp
ここのサンプルコードではBattery ServiceなるServiceを実装しているらしい。
規格上はほかにもGeneric Access, Generic Attributeなるサービスも実装が必要らしいが、それらはライブラリ側ですでに実装されているらしい。

https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.battery_service.xml&%20u=org.bluetooth.service.battery_service.xml
Assigned Number: 0x180Fとあるので何かの値を 0x180Fとするとbattery_serviceというServiceを実装できると思われる。

下を見ていくとService Characteristicsの中にBattery LevelがMandatoryとある。つまりbattery_serviceというサービスにはBattery LevelというCharacteristicsを必ずぶら下げる必要があるようだ。

https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.battery_level.xml
Assigned Number: 0x2A19とあるので何かを0x2A19にセットするとbattery_levelというCharacteristicsを実装できると思われ、その中身はバッテリレベルを0~100であらわす8bitの変数らしい。

Battery Serviceを実装したサンプルとBLE_Buttonを眺めながらどこを変更すればよいか考える。

どうやらServiceの実装はButtonService.hの中に記述されているように見える。以下二行

const static uint16_t BUTTON_SERVICE_UUID              = 0xA000;
const static uint16_t BUTTON_STATE_CHARACTERISTIC_UUID = 0xA001;

を試しに

const static uint16_t BUTTON_SERVICE_UUID              = 0x180F
const static uint16_t BUTTON_STATE_CHARACTERISTIC_UUID = 0x2A19

に変更してビルドしてみる。もともとは規格上で未定義のUUIDを用いたServiceとCharacteristicsでデータを渡していたようだ。
コードを追う限りCharacteristicsとして渡しているボタンのデータはもともと8bitなのでUUID以外はこのままで問題ないだろう。

ちなみに

const static uint16_t BUTTON_SERVICE_UUID = GattService::UUID_BATTERY_SERVICE;
const static uint16_t BUTTON_STATE_CHARACTERISTIC_UUID = GattCharacteristic::UUID_BATTERY_LEVEL_CHAR;

としても同じ結果となる。読みやすくするためにはこちらのほうがいいだろう。

f:id:susutex:20170510215234p:plain
ServiceがA000からBattery Serviceに変わった

f:id:susutex:20170510215304p:plain
CharacteristicsがA001からBattery Levelに変わった。

f:id:susutex:20170510215305p:plain
下の数値はボタンのときと同じくスイッチを押していないときは00、押しているときは01になる。
適当ではあるが、とりあえずGATTにしたがってBattery Serviceを実装することができたようだ。

Heart Rateサービスを実装してiPhoneのサイコンアプリに心拍形として認識させてみる。

Battery Serviceと同じようにHeart Rate Serviceを実装すればiPhoneのアプリからは心拍系として見えるようにできるはずだ。

https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.heart_rate.xml
MandetoryのCharacteristicsはheart_rate_measurementのみなのでそれだけ実装することにしてみる。

https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.heart_rate_measurement.xml
heart_rate_measurementの中身は先頭8bitの"Flags"で変化するらしい。最小であれば"Flags"と"Heart Rate Measurement Value (uint8)"のみの合計16bitが中身になる。データサイズが変わるためUUIDを変更するだけではうまくいかない。

結局"class ButtonService"を以下のように変更した。変更を最小にしてどこを変えたか比較しやすくするために変数名などはそのままにしている。

class ButtonService {
public:
    const static uint16_t BUTTON_SERVICE_UUID              = GattService::UUID_HEART_RATE_SERVICE;
    const static uint16_t BUTTON_STATE_CHARACTERISTIC_UUID = GattCharacteristic::UUID_HEART_RATE_MEASUREMENT_CHAR;
    
    uint8_t heart_rate_measurement_data[2];

    ButtonService(BLE &_ble, bool buttonPressedInitial) :
        ble(_ble), buttonState(BUTTON_STATE_CHARACTERISTIC_UUID, (uint16_t*)heart_rate_measurement_data, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY)
    {
        heart_rate_measurement_data[0]=0;   //Flags=0
        heart_rate_measurement_data[1]=100;   //Heart Rate Measurement Value (uint8) = 100[bpm]
        
        GattCharacteristic *charTable[] = {&buttonState};
        GattService         buttonService(ButtonService::BUTTON_SERVICE_UUID, charTable, sizeof(charTable) / sizeof(GattCharacteristic *));
        ble.gattServer().addService(buttonService);
    }

    void updateButtonState(bool newState) {
        uint8_t new_heart_rate_measurement_data[2]={0,100};
        if(newState==1){
            new_heart_rate_measurement_data[1]=200;
        }
        ble.gattServer().write(buttonState.getValueHandle(), (uint8_t *)&new_heart_rate_measurement_data, sizeof(uint16_t));
    }

private:
    BLE                              &ble;
    ReadOnlyGattCharacteristic<uint16_t>  buttonState;
};

無事ビルドできたので書き込んでみる。
f:id:susutex:20170510215303p:plain
f:id:susutex:20170510215302p:plain
f:id:susutex:20170510215301p:plain
ServiceがHeart Rateに変わり、CharacteristicsもHeart Rate Measurementに変化した。hexだが下位8bitがスイッチの状態によって100 or 200に変化する。

f:id:susutex:20170510215503p:plain
f:id:susutex:20170510215502p:plain
f:id:susutex:20170510215501p:plain
iPhone用のサイコンアプリであるPowertap mobileからもnRF5xが心拍計として認識され、スイッチの状態によって心拍数の表示が100 or 200に変化する。

まだちょっとだけサービスの実装方法がわかったというだけ。実際の動作を見ながらだと規格の意味もわかりやすいのでいろいろいじりながら何ができるかさらに理解を深めていくとしよう。消費電力も気になるが、自作パワーメーターのデータをサイコンアプリに飛ばしてログをとることも可能になりそうだ。加速度センサもついてるので、電池つけて後輪ハブに巻きつければスピードセンサとして使うこともできそう。