2017/09/24

BLE Nano2ではじめてのBLE通信!

どうも、クラゲです。
今回はBLE Nano V2にちょっとした回路を追加して、BLE通信でPCやスマホ側へ情報を送信したり、逆にPCやスマホ側からハードウェアを制御したりします!

今後様々な応用例も出していきますが、今回の回路をベースとして展開してゆきます
今回できることは、ハード側の半固定抵抗という小さなボリュームのツマミを回すと、PCやスマホ画面の数値が変更することと、PCやスマホ画面からLEDの点灯/消灯制御です。
BLE Nano2やmbed自体の開発が初めてという人はこちらを先に目を通しておいてください。

【 概要 】


注意事項

クラゲは、2017年9月現在のnRF52向けのmbed-osライブラリはバグがあると推測しています。
BLE通信が途中で切れたり、アナログ電圧入力の読み込みが不安定だったりします。BLE Nano2のVDD電圧をテスターで測定してみると結構電圧降下が大きいことから、無駄な消費電流もありそうです。
色々試してみましたが、現状は解決方法は無さそうです。ライブラリの更新に期待しましょう!
とりあえず、機能的にはそれなりに動くことは確認していますので、上記を考慮の上、読み進めてください。

必要なハードウェア部品

BLE Nano以外に必要となる周辺部品です。
全て秋月電子のみで購入することが可能です。

ミニブレッドボード
http://akizukidenshi.com/catalog/g/gP-05155/
ミニである必要はないです。ブレッドボードがあれば何でも良いです。

ジャンパワイヤー 7本以上
http://akizukidenshi.com/catalog/g/gC-05371/
オスーオスのジャンパワイヤであれば何でも良いです。URL先は20本セットの商品です。

半固定ボリューム10kΩ
http://akizukidenshi.com/catalog/g/gP-08012/
ツマミを回すことにより、抵抗値を変えることのできる抵抗です

LED
http://akizukidenshi.com/catalog/g/gI-11655/
もし、お手持ちのLEDがあれば、それでも大丈夫だと思います

抵抗220Ω
http://akizukidenshi.com/catalog/g/gR-25221/
もし手持ちに47Ω〜470Ωくらいの抵抗があれば、それでもいいです

ボタン電池 CR2032
http://akizukidenshi.com/catalog/g/gB-05694/
小型化のためにボタン電池を使います。近くのスーパーなどで売っているボタン電池でも問題ありません

CR2032ボタン電池ホルダー
http://akizukidenshi.com/catalog/g/gP-00706/
ボタン電池のケースです

mbedファームウェア書き込み

BLE Nanoにファームウェアを書き込みます

こちらからプログラムをimportして書き込んで下さい。
https://os.mbed.com/users/electricbaka/code/BLENano2_SimpleTemplate/
このプログラムはBLEnanoの製造元RedBearLab作成のBLENano_SimpleControlsをベースに改良したものです。

簡単にプログラムの説明をします
急いでいる人は飛ばして次の項目に進んじゃってOKです

//------------------------------------------------------------
//Definition
//------------------------------------------------------------ 
#define TXRX_BUF_LEN 20                              //max 20[byte]
#define DEVICE_LOCAL_NAME "BlueJelly"     
#define ADVERTISING_INTERVAL 160      //160 * 0.625[ms] = 100[ms]
#define TICKER_TIME 200000          //200000[us] = 200[ms]
#define DIGITAL_OUT_PIN P0_29
#define ANALOG_IN_PIN1 P0_5
#define ANALOG_IN_PIN2 P0_4

TXRX_BUF_LENはBLEのデータバッファサイズでMAX20Byteと決まっていますので、20のままを推奨とします
DEVICE_LOCAL_NAMEは、Scanしたときに見つかる名前です。好きな文字列に変えてもらって構いません。
ADVERTISING_INTERVALはAdvertisingのインターバルで、短いほど見つかりやすくなります
TICKER_TIMEは、ハードウェア側で電圧値を読んでBLE送信するタイミングです。短いほど高頻度で情報を送信しますが、電力としては長い方がお得です
DIGITAL_OUT_PINでは、LEDを制御するためのピンを定義しています
ANALOG_IN_PINでは、どこで電圧値を読み込むのかのピンを定義しています。ANALOG_IN_PIN1とANALOG_IN_PIN2の2つを定義しました。

//------------------------------------------------------------
//Object generation
//------------------------------------------------------------ 
BLE ble;
DigitalOut      LED_SET(DIGITAL_OUT_PIN);
AnalogIn        ANALOG1(ANALOG_IN_PIN1);
AnalogIn        ANALOG2(ANALOG_IN_PIN2);

デジタル出力ピンをDIGITAL_OUT_PINにしてLED_SETという名前に設定しています。
アナログ入力ピンの1つをANALOG_IN_PIN1にしてANALOG1という名前に設定しています
もう一つをANALOG_IN_PIN2にしてANALOG2という名前に設定しています

//------------------------------------------------------------
//Service & Characteristic Setting
//------------------------------------------------------------ 
//Service UUID
static const uint8_t base_uuid[] = { 0x71, 0x3D, 0x00, 0x00, 0x50, 0x3E, 0x4C, 0x75, 0xBA, 0x94, 0x31, 0x48, 0xF1, 0x8D, 0x94, 0x1E } ;
 
//Characteristic UUID
static const uint8_t tx_uuid[]   = { 0x71, 0x3D, 0x00, 0x03, 0x50, 0x3E, 0x4C, 0x75, 0xBA, 0x94, 0x31, 0x48, 0xF1, 0x8D, 0x94, 0x1E } ;
static const uint8_t rx_uuid[]   = { 0x71, 0x3D, 0x00, 0x02, 0x50, 0x3E, 0x4C, 0x75, 0xBA, 0x94, 0x31, 0x48, 0xF1, 0x8D, 0x94, 0x1E } ;
 
//Characteristic Value
uint8_t txPayload[TXRX_BUF_LEN] = {0,};
uint8_t rxPayload[TXRX_BUF_LEN] = {0,};
 
//Characteristic Property Setting etc
GattCharacteristic  txCharacteristic (tx_uuid, txPayload, 1, TXRX_BUF_LEN, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ);
GattCharacteristic  rxCharacteristic (rx_uuid, rxPayload, 1, TXRX_BUF_LEN, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY| GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ);
GattCharacteristic *myChars[] = {&txCharacteristic, &rxCharacteristic};
 
//Service Setting
GattService         myService(base_uuid, myChars, sizeof(myChars) / sizeof(GattCharacteristic *));

ServiceUUIDとCharacteristicUUIDの設定です。
base_uuidというServiceが1つで、その下にtx_uuidとrx_uuidというCharacteristicが入っています。
これらのUUIDはReadBear Lab社のサンプルプログラムのUUIDと同じままにしています。

713D0000-503E-4C75-BA94-3148F18D941E
713D0003-503E-4C75-BA94-3148F18D941E
713D0002-503E-4C75-BA94-3148F18D941E

必要があれば変更してください。
ちなみに、この形式で手入力するのが大変なので、UUID変換ツールを作成しました。ご活用ください。

コメント文の “Characteristic Property Setting etc” の下では、それぞれのCharacteristicに対し、プロパティを設定してます。
tx_uuidはWRITEとREAD、rx_uuidはNOTIFYとREADをプロパティとして設定しています。

//======================================================================
//onDisconnection
//======================================================================
void disconnectionCallback(const Gap::DisconnectionCallbackParams_t *params)
{
    ble.startAdvertising();
}

切断時の処理です。Advertiseを開始します。

//======================================================================
//onDataWritten
//======================================================================
void WrittenHandler(const GattWriteCallbackParams *Handler)
{   
    uint8_t buf[TXRX_BUF_LEN];
    uint16_t bytesRead;
 
    if (Handler->handle == txCharacteristic.getValueAttribute().getHandle()) 
    {
        ble.readCharacteristicValue(txCharacteristic.getValueAttribute().getHandle(), buf, &bytesRead);
        memset(txPayload, 0, TXRX_BUF_LEN);
        memcpy(txPayload, buf, TXRX_BUF_LEN); 
 
        if(buf[0] == 1)
            LED_SET = 1;
        else
            LED_SET = 0;
    }
}

セントラル(PC・スマホ側)からWRITEが来た時のイベント処理です
readCharacteristicValueで、txCharacteristicからValueを読み込みbufに代入しています。
memsetにて配列txPayloadをオール0で初期化しています
memcpyにて配列txPayloadにbufをコピーしています
ここの関数自体ではtxPayloadは使いませんが、セントラルからREADされたときに備えてこのようにしています。

buf[0]が1の場合はLED_SETを1にしてLEDを点灯させ、それ以外の場合はLED_SETを0にしてLEDを消灯させています。

//======================================================================
//onTimeout
//======================================================================
void m_status_check_handle(void)
{   
  uint8_t buf[2];
 
  //Read Analog port
  float s1 = ANALOG1;
  float s2 = ANALOG2;
  uint16_t value = s1/s2 * 1024;
 
  buf[0] = (value >> 8);
  buf[1] = value;
 
  //Send out
  ble.updateCharacteristicValue(rxCharacteristic.getValueAttribute().getHandle(), buf, 2);
}

変数s1, s2にアナログ読み込みした値を入力します。
s1には半固定抵抗で分圧された電圧値が入力されます。
s2には電源電圧値が入力されます。
s2に対するs1の割合を見ることで、半固定抵抗のツマミの位置を得ることができます。
本来、Vrefの設定をVddに変更すればs1のみで位置を得ることができますが、nRF52のVref設定レジスタが不明のためこのような対応にしています。
0〜1.0の小数値で入るので、1024倍して0〜1024の整数値に変換します
buf[0]には下位8bitを入力し、buf[1]には上位8bitを入力
最後にupdateCharacteristicValueにて、rxCharacteristicの値をbufで更新しています。

//======================================================================
//convert reverse UUID
//======================================================================
void reverseUUID(const uint8_t* src, uint8_t* dst)
{
    int i;
 
    for(i=0;i<16;i++)
        dst[i] = src[15 - i];
}

AdvertiseするときにUUIDを逆にして渡す必要があります。
元のプログラムは手動で書いていましたが、ここでは逆に書く関数を用意しました。

//======================================================================
//main
//======================================================================
int main(void)
{
    uint8_t base_uuid_rev[16];
 
    //Timer Setting [us]
    Ticker ticker;
    ticker.attach_us(m_status_check_handle, TICKER_TIME);
 
    //BLE init
    ble.init();
 
    //EventListener
    ble.onDisconnection(disconnectionCallback);
    ble.onDataWritten(WrittenHandler);

Timer Settingにて、m_status_check_handleをTICKER_TIME(=200ms)の間隔でタイマー呼び出しする設定を行っています。
EventListenerにて、切断時と書き込み時に呼ぶ関数をここで定義しています。

    //------------------------------------------------------------
    //setup advertising 
    //------------------------------------------------------------
    //Classic BT not support
    ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED);

クラシックBTはサポートせず、BLEデバイスとして認識してもらいます

    //Connectable to Central
    ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);

デバイスがセントラルに接続可能であることを設定しています

    //Local Name
    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME,
                                    (const uint8_t *)DEVICE_LOCAL_NAME, sizeof(DEVICE_LOCAL_NAME) - 1);

LOCAL NAMEの設定です。
sizeofで -1 しているのは、終端記号を含めないようにしているためです。(BLEでは終端不要)

    //GAP AdvertisingData                                
    reverseUUID(base_uuid, base_uuid_rev);  
    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_128BIT_SERVICE_IDS,
                                    (uint8_t *)base_uuid_rev, sizeof(base_uuid));

今回のUUIDは16bit短縮ではなく128bitであることを示しています。
サービスのUUIDをリバースしたものを第2の引数に入れ、第3の引数はそのサイズを設定します。

    //Advertising Interval 
    ble.setAdvertisingInterval(ADVERTISING_INTERVAL);

ADVERTISING_INTERVAL(=100ms)でAdvertiseパケットの送信周期を設定しました

    //Add Service
    ble.addService(myService);
 
 
    //Start Advertising
    ble.startAdvertising();

サービスを登録し、Advertiseを開始します。

    //------------------------------------------------------------
    //Loop
    //------------------------------------------------------------
    while(1)
    {
        ble.waitForEvent(); 
    }
}

最後は無限ループでBLEイベント待ちです。

電子回路作成

ブレッドボードに回路を組んでいきます。
もちろん、ハンダごてや工具は一切不要です!
ジャンパー線はたったの7本です。色はお好みでOKです

結線図はこの通りです。(結線図は fritzing を使って描いています)
気をつける点としてはまずLEDの向き。足の短いほうが上図の足がまっすぐな方、つまり左側です。
電池ホルダーの + と - のジャンパー線は若干斜めになります。
抵抗は向きはないので逆向きでもOK

回路が組み終わったら、いよいよ実行確認です

既存スマホアプリで実行確認

BLE対応しているスマホやタブレットを用意しましょう。
(Macしかない人はLightBlueというアプリをインストールしてみてください。操作説明は省略しますが、ほぼ直感で使えると思います)

iOS端末の場合は”nRF Connect”、Android端末の場合は”nRF Connect for Mobile”という無料アプリをインストールします
以降では、iPod Touchでの実行画面を例に説明します。他のOSだったとしても、ほぼ同じ操作で出来ます。

アプリを起動したら、SCANを行います。
mbedプログラムで設定したDEVICE_LOCAL_NAMEが見つかるはずです。
(mbedプログラムを変更していなければ、”BlueJelly”という名前が出てきます。もしくは”nRF5_MCU0”と出る場合もあります。)
見つかったら CONNECTをタップします

Unknown Serviceがあると思いますので、それをタップします
ちなみにUUIDはmbedプログラムで設定した 713D0000-503E-4C75-BA94-3148F18D941E と一致していると思います。

Unknown Characteristicが2つ出てくると思います。それぞれのUUIDはmbedプログラムで設定したtx_uuidとrx_uuidと一致しています。
tx_uuid : 713D0003-503E-4C75-BA94-3148F18D941E
rx_uuid : 713D0002-503E-4C75-BA94-3148F18D941E
図の赤い丸はRead、青い丸はWrite、緑の丸はNotifyを意味しています

ブレッドボード回路の半固定抵抗ツマミを少し回してからrx_uuidをReadしてみてください
(rx_uuidはRaedとNotifyがある方です)
16進数表示なので分かりづらいですが、Valueが変化したのが分かると思います。

今度はNotifyを押してみてください

バツ印が付いているときがNotify中を示しています。このときにツマミを回すとリアルタイムでValueが変化すると思います。
ちょっとタップにくいですが、もう一度NotifyをタップするとNotifyがストップします。

最後にWriteをタップしてみましょう

ポップアップが表示されます。
1をWriteするのですが、入力値としては “01”と入力しないとエラーになります。
SENDをタップして、回路に繋げたLEDが点灯すれば成功です

消灯するには”00”を入力します。

オリジナルアプリで実行確認

先ほどは既存のスマホアプリを使ってBLE通信しました。これだと操作も面倒だし、応用が利きません。
そこでオリジナルアプリを作りたくなるのですが、開発方法としていくつかあります。
もっともポピュラーなのはAndroidまたはiOSのネイティブアプリ開発ですが、クラゲにとっては非常に面倒に感じます。
一番簡単なアプリ開発は、BlueJelly.jsを使うことです。BlueJellyを使えば、AndroidStudioやXcodeなどは一切使わずに、テキストエディッタのみで開発できます。

クラゲのブログページではBlueJellyに関する開発方法やBLE知識などたくさん公開していますで、そちらを参考にサクッとWEBでプログラミングしちゃいましょう!
http://jellyware.jp/kurage

以上、BLE Nano2ではじめてのBLE通信でした!