2014/09/20

[nrf51]はじめてのBLEアプリ - (1)サービスをつくる

今まで、nRF51822やBLEのことを調べてきたが、結局アプリを作るに至っていない。
ごにょごにょ言っていないで、動くものを作ろう。


参考にするのは、アプリケーションノートnAN-36。
4章をやっていく。
開発環境は、GCC(gcc-arm-none-eabi-4_8-2014q2-20140609-win32)+Eclipse(Luna)。
GCCが4.8なので、そのままだとコンパイルエラーが発生すると思う。
http://hiro99ma.blogspot.com/2014/06/blebvmcn5102-bk_28.html

 

まず、アプリのテンプレートをコピーする。
今回はS110(Peripheral)なので、SDKをインストールした中にあるs110のものをとってきた。
misファイルでインストールした場合だと、たぶんこんな感じの場所に入っている。
C:\Nordic Semiconductor\nRF51 SDK_v6.1.0.0\Nordic\nrf51822\Board\nrf6310\s110\ble_app_template

コピーする先のフォルダは、多少手間を減らすのであればテンプレートと同じフォルダ内に置くのがよい。
そうではない場合、Makefileの変更が発生する。
armフォルダを削除して、gccの中にあるファイルをMakefileにリネーム。
テキストエディタで開いて、SDK_PATHを変更する。

SDK_PATH = C:/NORDIC~1/NRF51S~1.0/Nordic/nrf51822/

なんで8.3形式かというと、フォルダ名にスペースが入っているからだ。
やりようはあるのかもしれんが、めんどくさいので dir /xでいちいち確認している。
msiじゃなくてzipを解凍した方がよいかも。msiでインストールするとスタートメニューに登録されるんだけど、そのパスがインストールしたパスになってるわけでもないし。。。

Eclipseでデバッグすることも考えると、環境変数に置いておいた方が楽そうだ。
もちろん、パスはスペースが入っていないところにする、という前提だが。
(Eclipseは、か、GDBか、はわからないが、そっちはスペースが入ったパスでもよさそうだった。)

 

ここで一度、Makefileのあるフォルダでコマンドプロンプトを開き、make cleanが通ることを確認しよう。
次に、makeしよう。
そうすると、コンパイルが始まるのだが、SDKをインストールしたままだとコンパイラの場所を正しく指していないかもしれない。

ツール類は、

$(GNU_INSTALL_ROOT)/bin/$(GNU_PREFIX)-gcc

のような名前で実行される。
定義しているのは、Makefile.windowsなので、これを書き換える(GNU_VERSIONは使ってないような気がする)。
$(SDK_PATH)\nrf51822\Source\templates\gcc\Makefile.windows

もう一度makeして、最後にhexファイルができたら、とりあえず環境は整ったことになる。

人によっては、Makefileのこれが気になるかもしれない。

BOARD := BOARD_NRF6310

$(SDK_PATH)\nrf51822\Include\boards.hで使っていて、これがないとコンパイルエラーになる。
たぶんNordic社のサンプルボードなんだろうが、気になる人はここを変更するか、boards.hをincludeしないようにするとよかろう。
テンプレートではこれを使っているので、今回は自分用のビルドが通るまでは残しておく。

これで、ひとまず自分用のテンプレートができたことになるので、記念に残しておこう。
そういえば、元のテンプレートはCopyrightがNordicになっているから、自分のものにしておいた方がよかろう。
ああ、Makefileの「OUTPUT_FILENAME」は、何か名前を付けた方がよいかも。
なお、未設定にしてもビルドは通って「_s110_xxaa.hex」みたいな名前がつくから、気になるなら対策しよう。


サービスの作成

アプリの前に、サービスを作る。

テンプレートをコピーした後、ble_basを持ってきて、それをカスタマイズするようになっている。
なぜBASかというと、内容がシンプルだからだそうだ。
ファイルはここにある。

ソース:$(SDK_PATH)\Source\ble\ble_services\ble_bas.c
インクルード:$(SDK_PATH)\Include\ble\ble_services\ble_bas.h

ファイルとして、ble_bas_cもあるのだが、これはBASのクライアント側。
バッテリー情報を提供するのがble_basでサーバ側と考えてよいだろう。

サンプルは、LEDの点灯制御と、ボタンの押下状態取得だ。
他の例を考えようとしたが、結局のところ基本は「制御をしてもらう」「結果や状態を教えてもらう」なので、悔しいがまねをしておこう。
(名前だけでも変えておくか、と「I/O Service」にしたが、略称が「ios」になるので、なんかやだな。)

image

 

構造体を、3つほど用意する。

  • データ保持用。thisみたいなもの。
  • 初期化用。thisの初期化に使う。
  • イベントデータ用。イベントが発生したときに、値を詰めてコールバックするときに使う。

初期化用は、なくてもよい。
ble_basというか、S110でのお作法みたいなもので、init関数を作るときにアプリが初期値を入れて渡せるようにしているだけだ。
使うのもそこだけなので、だいたい呼び出し元がローカル変数で設定して渡すだけのようだ。

データ保持用は、これは呼び出し元でメモリを保持する。
だから、ローカル変数じゃなくてグローバルにする。まあ、staticがほとんどだろうが。

イベントデータ用は、SoftDeviceから通知されたとき、呼び出し元にコールバックの引数として渡すようだ。
だから、コールバック元、つまりble_xxxのイベントハンドラがローカル変数で設定してコールバックするだけのようだ。

 

 

サービスの初期化

UUIDを生成する。
これには、nRFgo Studioを使う。
カーソルを「nRF8001 Configuration」にしておかないと、メニューのnRF8001 Setupが使えない。

image

image

生成は簡単なのだけど、これをどうしたらいいんだ・・・。
以下、最終的にはやらなくてよかったのだけど、せっかくやったので残しておく

「GATT Services」というタブでやる?
image

右の「Add new templete」を選んで、

image

真ん中右くらいにある「Add new characteristic templete」を選んで、

image

「Base」でさっき作ったUUIDを選び、空いたところになにやら入力。
inputとoutputを作ると、templete欄に入るので、そこからMandatoryにD&Dし、適当に入力。

image

で、前の画面に戻ってtemplete欄からI/O ServiceをD&D。
image

・・・それで??
気を落ち着けてメニューを見てみると、ソースファイルを生成してくれるような項目が出てきた。
実行すると・・・よくわからないソースファイルが出てきた。

PDFの例と見比べて、以下のような関係になっていればよさそうだ。
BLE_LBSのUUIDが書いてあればいいのにと思ったが、前の章に書かれていたようだ。。

生成されたUUID:87C9xxxx-CBA0-7D7D-F1B5-E1635787F177
XXX_UUID_BASE:{ 0x77,0xf1,0x87,0x57,0x63,0xe1,0xb5,0xf1,0x7d,0x7d,0xa0,0xcb,0x00,0x00,0xc9,0x87 }

base UUIDはバイト単位で書くので自分でリトルエンディアンにし、16bit UUIDはそのまま書けばよいようだ。
というわけで、nRFgo StudioはUUID生成だけの目的で使えばよいようだ(PDFもそうなってる)。

さて、base UUIDはnRFgo Studioが生成してくれるとして、16bit UUIDはどうやって決めるとよいのだろうか?
nAN-36の2.2.4.2節では、Bluetooth Core Specificationでは特に決められていない、と書いてある。
そういうのが一番困る・・・。
なんとなく、0x0000とか0xFFFFは避けたくなってきた。
どういう無線なのかわからないけど、同じデータが続くとまずい、とかいうことはアプリ層では考えなくてよいと思うので、やっぱり適当に割り振ろう。

#define IOS_UUID_SERVICE        (0x0001)
#define IOS_UUID_CHAR_INPUT     (0x0002)
#define IOS_UUID_CHAR_OUTPUT    (0x0003)

 

init処理では、base UUIDをsd_ble_uuid_vs_add()で登録する。
そうすると、8bitのUUID typeなる値が返される。
以後はこのtype値と、16bit UUID値を使って操作することになるようだ。

まず、UUID typeとIOS_UUID_SERVICEをble_uuid_tの変数に突っ込んで、sd_ble_gatts_service_add()で登録。
これで、空のサービスが登録されたことになるそうだ。
「空の」というのは、キャラクタリスティックが登録されていない、ということだ。

image

 

キャラクタリスティックの登録

まず、出力方向のキャラクタリスティックを用意する。
中身はわかっていないが、こういうことをやっていると思われる。

  • Attributeメタデータの設定(cccd_md)・・・NotifyかIndicateをサポートする場合
    • ReadとWriteの動作
  • キャラクタリスティックメタデータの設定(char_md)
    • Read可能
    • Notify可能
  • UUIDの設定(ble_uuid)
    • サービスのUUIDタイプ値
    • 16bit UUID値
  • Attributeメタデータの設定(attr_md)
    • ReadとWriteの動作
  • Attributeの設定(attr_char_value)
    • 値のサイズ

これらとサービスのハンドラなどをsd_ble_gatts_characteristic_add()に与えて、登録。
登録すると、キャラクタリスティックのハンドラが取得できる。

image

CCCDは、NotifyかIndicateをサポートする場合、つまりクライアント側に通知を行うキャラクタリスティックの場合に必要となる。

ここは出力方向のキャラクタリスティックの説明になったが、入力のみや入出力の場合でも同じだ。
その違いは、Attributeメタデータ設定で行っている。

ここで、サービスの初期化は終わりだ。

image

 

スタックイベントのハンドリング

BLEスタックからの通知は、サービスにではなくアプリで受けとるようにする。
アプリがあれこれ初期化するときに、BLEスタックの初期化も行う。
softdevice_ble_evt_handler_set()でアプリのイベントハンドラを登録し、そのハンドラが呼ばれたときにサービスのハンドラを呼び出してもらうように書くようだ。

PDFのサンプルでは、イベントタイプとして3つ用意されている。

  • BLE_GAP_EVT_CONNECTED
  • BLE_GAP_EVT_DISCONNECTED
  • BLE_GATTS_EVT_WRITE

上2つがGAPイベント(enum BLE_GAP_EVTS)で、最後のはGATT Serverイベント(enum BLE_GATTS_EVTS)。
LEDの点灯制御要求がWriteになる。

引数から、渡されたデータをこんな感じでコールバックする。

ble_gatts_evt_write_t *p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;
p_ios->evt_handler_in(p_ios, p_evt_write->data[0]);

なお、ble_gatts_evt_write_tのdataは、uint8_t data[1]で定義してあるので、malloc()か何かでメモリが取られてるんじゃなかろうかね。

PDFの例だとLEDの点灯制御なので、ble_lbsサービスでLED点灯まで制御するかと思ったが、そうではなくアプリにコールバックするだけだ。
プロファイルとかサービスとかは、あくまでプロトコルスタックで、機能のデバイスドライバではないのだ。
製品として「このプロファイルに対応しています」という場合には、アプリまで含んだ機能の説明になるのだろう。
まあ、「このヘッドセットはA2DPに対応しています(音は聞こえません)」などとなってたら売れないだろうから当然ですな。

 

逆方向の、nRF51822からClientに通知したい場合は、sd_ble_gatts_hvx()を使う。
値の設定だけしたい(Notifyを投げたくない)ときは、sd_ble_gatts_value_set()を使うそうだ。

そういえば、Clientが値を取得したいときの動作が書かれていない。
BLE_GATTS_EVTSにもないので、SoftDeviceが自動的にさばいてくれるのかな?

image

 

これで、サービスの説明は終わりのようだ。
次回、アプリを作って動作確認だ。

0 件のコメント:

コメントを投稿

コメントありがとうございます。
スパムかもしれない、と私が思ったら、
申し訳ないですが勝手に削除することもあります。