2014年6月18日水曜日

BluetoothSMARTデバイスをmbed で開発する(4)



はじめに


これまで3回に渡ってnRF51822ブランクモジュールをARM mbed 対応機器にするための方法について掲載してきましたが、今回から mbedによるBLEプログラミング実践編をスタートいたします。

関連記事
BluetoothSMARTデバイスをmbed で開発する(1)
BluetoothSMARTデバイスをmbed で開発する(2)
BluetoothSMARTデバイスをmbed で開発する(3)
BluetoothSMARTデバイスをmbed で開発する(5)
BluetoothSMARTデバイスをmbed で開発する(6)
BluetoothSMARTデバイスをmbed で開発する(7)
BluetoothSMARTデバイスをmbed で開発する(8)

ARMは、ARM Limited(またはその子会社)のEUまたはその他の国における登録商標です。mbedは、ARM Limited(またはその子会社)のEUまたはその他の国における商標です。All rights reserved.

注意事項 - 本記事に掲載されているソースコードについて

本記事の掲載時点ではまだnRF51822 向けのmbed ライブラリコードが安定しておらず、頻繁に更新が行われています。連載中は仕様変更に追従できるよう心掛けておりますが、連載終了後にビルドが通らない等の事象が起きる場合がありますので予めご了承ください。

本記事でのBluetooth/BLE表記について

本記事におけるBluetooth/BLEの表記について、mbedプロジェクトでの表記がBLEであることから mbedに関する部分についてはBLEと表記し、デバイスに関する部分はBluetooth-SIGの勧告に従い BluetoothSMARTとしています。

mbed 開発環境でのBLEプログラミング

今回はBLEプログラミング実践編一回目としてmbedプロジェクトの作成から動作までの手順を順番に進めていきます。


新規プロジェクトの作成とmbed,BLEライブラリのインポート

この部分は定石+αの手順になりますので手早く進めます。

まず mbed IDEで新規プロジェクトを作成します。
この部分は他のmbed対応ボードと同じ手順になります。


次にmbedとBLEライブラリをインポートします。
nRF51822ではmbed以外にBLEライブラリも必須になることを注意してください
http://mbed.org/teams/Nordic-Semiconductor/code/nRF51822/


http://mbed.org/teams/Bluetooth-Low-Energy/code/BLE_API/


mbed


デバイスとしての骨組みを作る

今回は一番シンプルなBLEデバイスとしてバッテリ電圧を取得するだけのデバイスを作成することにします。このようなデバイスを作成するためにはBluetooth-SIGが定義する次の3つのBLEサービスを実装する必要があります。

Generic Access<UUID=0x1800> org.bluetooth.service.generic_access
Generic Attribute<UUID=0x1801> org.bluetooth.service.generic_attribute
Battery Service<UUID=0x180F> org.bluetooth.service.battery_service

なお mbed版 nRF51822ではライブラリ側に"Generic Access"と"Generic Attribute"が組み込まれているため、この部分のカスタマイズの必要が無ければ実装の必要がありません。実際に実装が必要となるのはデバイス接続待ちの際に出力されるAdvertisingパケットと、セントラル(host)からのGATTアクセスに対する通信部分になります。

サンプルコード

今回作成したコードをサンプルとして掲載しました
mbedで main.cppファイルを作成しコードを書き込んで保存して下さい。

#include "mbed.h"
#include "BLEDevice.h"

#define NEED_CONSOLE_OUTPUT 0
#if NEED_CONSOLE_OUTPUT
Serial  pc(USBTX, USBRX);
#define DEBUG(...) { pc.printf(__VA_ARGS__); }
#else
#define DEBUG(...) /* nothing */
#endif /* #if NEED_CONSOLE_OUTPUT */
 

BLEDevice ble;

DigitalOut led_grn(LED1);
DigitalOut led_red(LED2);
AnalogIn   ana_bat(p6);
Ticker     flipper;
Serial     pc(USBTX, USBRX);

void tickerCallback(void);


#define DEVICE_NAME "ABCDEF"

/* Complete list of 16-bit Service IDs */
uint16_t    uuid16_list[] = {GattService::UUID_BATTERY_SERVICE};

/* Battery Level Service */
uint8_t            batt      = 72; /* Battery level */
GattCharacteristic battLevel(GattCharacteristic::UUID_BATTERY_LEVEL_CHAR, (uint8_t *)&batt, sizeof(batt), sizeof(batt),
                           GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ);

/* GattService Infomation */
GattCharacteristic *BattChars[] = {&battLevel };
GattService        BattService(GattService::UUID_BATTERY_SERVICE , BattChars, sizeof(BattChars) / sizeof(GattCharacteristic *));


void ConnectionCallback(Gap::Handle_t handle, Gap::addr_type_t type, const Gap::address_t addr,
                        Gap::addr_type_t addr_type_townAddrType, const Gap::address_t ownAddr, const Gap::ConnectionParams_t *params)

{
    DEBUG("Connected!\n\r");
    led_red = 1;
}



void DisconnectionCallback(Gap::Handle_t handle, Gap::DisconnectionReason_t reason)
{
    led_red = 0;
    DEBUG("Disconnected!\n\r");
    DEBUG("Restarting the advertising process\n\r");
    ble.startAdvertising();
}

void ConnectTimeoutCallback(void)
{
}


int main(void)
{
    led_red = 0;
    led_grn = 1;
    flipper.attach(&tickerCallback, 0.5);

    /* Initialise the nRF51822 */
    ble.init();

    /* Setup The event handlers */
    ble.onConnection(ConnectionCallback);
    ble.onDisconnection(DisconnectionCallback);
    ble.onTimeout(ConnectTimeoutCallback);

    /* Advertising Payload */
    ble.accumulateAdvertisingPayload((GapAdvertisingData::Flags)(GapAdvertisingData::LE_GENERAL_DISCOVERABLE | GapAdvertisingData::BREDR_NOT_SUPPORTED));
    ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS,
                            (uint8_t *)uuid16_list, sizeof(uuid16_list));

    ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME,
                                        (const uint8_t *)DEVICE_NAME, 
                                        strlen(DEVICE_NAME));
    ble.setAdvertisingInterval(160); /* 100ms; in multiples of 0.625ms. */
    ble.startAdvertising();
 
    ble.addService(BattService);

    for (;; ) {
        ble.waitForEvent();
    }
}

void tickerCallback(void)
{
    led_grn = !led_grn;
    
    /* Update battery level */
    batt = ana_bat.read_u16() >> 2; // nRF51822 ADC is 10 bit resolution
    ble.updateCharacteristicValue(battLevel.getHandle(),
                                        (uint8_t *)&batt,
                                        sizeof(batt));

}

サンプルコードのコード説明

BLEデバイス定義
mbedでBLEを使うために必要な定義とコードになります。

#include "BLEDevice.h"
BLEライブラリの定義をインポートする
BLEDevice ble;
BLEライブラリの使用を宣言します
ble.init();
BLEライブラリとnRF51822デバイスを初期化します
※2014/6/10頃まではnrf51822ライブラリを直接使用するようになっていましたが現在はデバイスを抽象化したBLEライブラリを使うようなりました。サンプルコードにおいて新旧のライブラリの利用が混在していますので注意して下さい。


Advertising

セントラル(host)にBLEデバイスとして認識してもらうためのコードです。

ble.accumulateAdvertisingPayload(
    (GapAdvertisingData::Flags)(GapAdvertisingData::LE_GENERAL_DISCOVERABLE |
     GapAdvertisingData::BREDR_NOT_SUPPORTED )
  );
このデバイスの属性をAdvertisingパケットに載せる宣言を行います。
今回設定したパラメータは次の通りです。
BREDR_NOT_SUPPORTED -- Bluetooth ClassicはサポートせずBLEのみ動作可能
LE_GENERAL_DISCOVERABLE -- セントラルから検出可能とする
この設定を行うとAdvertisingパケットの赤枠部分に反映されます。






ble.setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED);
デバイスがセントラル(host)接続可能であることをAdvertisingパケットヘッダに設定します。
この設定を行うとAdvertisingパケットヘッダ部分に設定が反映されます。




uint16_t    uuid16_list[] = {GattService::UUID_BATTERY_SERVICE};
ble.accumulateAdvertisingPayload(
    GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS,
    (uint8_t *)uuid16_list, 
    sizeof(uuid16_list)
  );
Advertisingパケットにこのデバイスに搭載されている機能(BLE service)を載せるために、該当するUUIDをデータ列として登録します。
この設定を行うとAdvertisingパケットの赤枠部分に反映されます。


この図では16bitUUID(0x180F)が1個だけ設定されています。UUIDが複数ある場合はUUIDデータが列挙された形でAdvertisingパケットに載ります。


#define DEVICE_NAME "ABCDEF"
ble.accumulateAdvertisingPayload(
    GapAdvertisingData::COMPLETE_LOCAL_NAME,
    (const uint8_t *)DEVICE_NAME, 
    strlen(DEVICE_NAME)
  );
デバイス検索時に表示される名前を設定します。
この設定を行うとAdvertisingパケットの赤枠部分("ABCDEF")が追加されます。


もし文字列やUUID部分が長くてAdvertisingパケットが29バイトを超えそうな場合はGeneric AccessのDevice name characteristicに完全なデバイス名を設定したうえでCOMPLETE_LOCAL_NAMEの代わりにSHORTENED_LOCAL_NAMEを指定し、この部分にはデバイス名の短縮文字を設定するようにします。



ble.setAdvertisingInterval(160); /* 100ms; in multiples of 0.625ms. */
Advertisingパケットの送信周期を設定します。設定は0.625ms単位で行い、設定値が160の場合は送信周期100mSecとなります



ble.startAdvertising();
Advertisingパケットの送信を開始します。Advertisingパケットはセントラルから接続されるか、タイムアウト(デフォルト180秒)となるまで出力され続けます。


・BLEサービス定義(GATT設定)
セントラルからアクセス可能な GATTとCharacteristicの定義を行います。
これらの定義はAdvertising開始からセントラルからの接続までに行う必要があります。

uint8_t batt = 72; /* Battery level */

GattCharacteristic battLevel(
  GattCharacteristic::UUID_BATTERY_LEVEL_CHAR,
  (uint8_t *)&batt,
  sizeof(batt),
  sizeof(batt),
   GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY |
   GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ
  );
バッテリ残量の Characteristicテーブルと初期値を定義します。



GattCharacteristic *BattChars[] = {&battLevel };
GattService BattService(
  GattService::UUID_BATTERY_SERVICE,
  BattChars,
  sizeof(BattChars) / sizeof(GattCharacteristic *)
  );
バッテリ残量サービスに必要なデータ構造を定義します。



ble.addService(BattService);
BLE/GATTスタックにサービスを登録します。



・デバイス動作定義
BLEデバイス動作に対する処理が必要な場合はコールバック関数を定義します。

ble.onConnection(ConnectionCallback);
ble.onDisconnection(DisconnectionCallback);
ble.onTimeout(ConnectTimeoutCallback);

・GATTアクセス処理
Battery Level Serviceのように非同期の読み出しのみを行う場合はセントラルからのGATTアクセスはnRF51のBLEスタックが自動的に応答するためコードを記述する必要はありません。デバイス側のCharacteristic値を更新したい場合は次のコードを実行します。

ble.updateCharacteristicValue(battLevel.getHandle(), (uint8_t *)&batt, sizeof(batt));
もし同期処理が必要な場合はコールバック関数を追加し応答コードを記述するようにします。


・その他
今回のコードにはBluetoothSMASTモジュールの動作状況が判るよう2個のLEDを点灯するようなコードが入れてあります。

DigitalOut led_grn(LED1);
DigitalOut led_red(LED2);

led_grn = 1;
led_red = 1; 
LED1は1秒間隔で点滅し、LED2はセントラルとの接続中に点灯します。


・バッテリ容量測定処理
main関数内で0.5秒間隔のタイマーを登録し、タイマーハンドラ内でAD変換を行います。
また取得したバッテリ容量値をバッテリーサービスのCharacteristic値に上書きします。

AnalogIn   ana_bat(p6);
Ticker     flipper;

flipper.attach(&tickerCallback, 0.5);


void tickerCallback(void)
{
    led_grn = !led_grn;
    
    /* Update battery level */
    batt = ana_bat.read_u16() >> 2; // nRF51822 ADC is 10 bit resolution
    ble.updateCharacteristicValue(battLevel.getHandle(),
                                        (uint8_t *)&batt,
                                        sizeof(batt));

}


Tips:  nRF51822のバッテリ電圧測定方法について
nRF51-SDKのサンプルにあるbattery-serviceのコードを見たところ、nRF51822ではバッテリ電圧を次のような方法で測っているようです。電圧リファレンスを内蔵バンドギャップ(1.2V)に指定し、アナログ入力をVddの1/3にしてAD変換を行いバッテリ電圧を取得するような実装になっています。
この方法だとデバイス動作範囲の電圧ならばデバイス単体で測ることができます。


それに対してmbedライブラリではADCの基準電源をVddから取るような実装になっています。しかし、この方法ではデバイスの電源電圧(Vdd)が変動するとAD変換の基準電圧も変わってしまうため電源がバッテリ直結の場合はバッテリ電圧の変動により正しく電圧を取得することができません。
なにかと不便ですのでmbedチームにライブラリの改修を期待したいところです。


そのため今回はバッテリ電圧を測る代わりとして Analog入力に外部回路から可変電圧を印加して疑似的なバッテリ電圧とするようにしました。電源電圧を半固定抵抗で分圧したものをAnalog入力(6)に入力しています。




動作確認

ビルド済みのバイナリをmbedインターフェイス経由で書き込みます。
スマートフォンのBluetooth設定で"ABCDEF"という名前のデバイスが検出されていれば正しく動作しています。またPacketSnifferを用いてパケットモニターを行うと、次のようなAdvertisingパケットが出力されているはずです。




Android SDKに付属するサンプルコード(sdk\samples\android-19\connectivity\BluetoothLeGatt)を実行することにより今回作成したBLEサービスとそのCharacteristic値が取得することができます。ブランクモジュールのAnalog入力に接続された電圧を変化させるとCharacteristic値に反映されます。


まとめ

今回はmbedプロジェクトの新規作成からAndroid端末によるデータ取得までの手順を紹介いたしました。mbedを使うことによりBluetoothSMARTデバイス開発の敷居が低くなってきたことがご理解頂けたかと思います。

次回は実践的なBLEプロファイルの実装方法とAndroidアプリについて掲載する予定です。



関連記事
BluetoothSMARTデバイスをmbed で開発する(1)
BluetoothSMARTデバイスをmbed で開発する(2) BluetoothSMARTデバイスをmbed で開発する(3)

BluetoothSMARTデバイスをmbed で開発する(5)
BluetoothSMARTデバイスをmbed で開発する(6)
BluetoothSMARTデバイスをmbed で開発する(7)
BluetoothSMARTデバイスをmbed で開発する(8)



140 180 Bluetooth , mbed , Nordic , nRF51822 , ツール作成 , 技適

記載されている会社名、および商品名等は、各社の商標または登録商標です。

0 コメント:

コメントを投稿

Related Posts Plugin for WordPress, Blogger...