自作電子小物/TIPS/Bluetooth-LEスタック/ペリフェラル向け/STM32版
自作電子小物/TIPS/Bluetooth-LEスタック/ペリフェラル向け/STM32版
マイクロコントローラ評価ボードのSTM32F4-Discoveryと、Bluetooth-USBドングルで動作する、Bluetooth4.0 LE(Low Energy)対応のソフトウエア・スタック・ライブラリです。ATT(Attribute protocol)と、クラッシックの一部(SDP、RFCOMM、A2DP/sink)をサポートしています。BluetoothLEではセントラル、ペリフェラル、オブザーバ、ブロードキャスターという立ち位置を定義していますが、これはペリフェラル(周辺機器)向けの機能で、お手軽に組込み用途に利用できます。PIC32MX版もあります。
Bluetooth-le Stack for Peripheral on STM32
2013年5月17日
1. 外部仕様
(1)Bluetoothサポート概要
仕様準拠 :Bluetooth バージョン4.0のペリフェラル機能の一部(BR/EDR + LE)
デバイス検索機能 :検出可能なデバイスとなります、デバイスを探す事はできません(Scan enable、Advertise)
サービス情報照会機能 :SDP (Service Discovery Protocol) およびATT (Attribute protocol) の情報提供機能のみ
相手のサービスを検索する事はできません
認証・暗号化機能 :あり
設定の変更で、暗号化・認証を要求するようになります
認証機能はパスキー入力などのサブルーチンをアプリケーション側に用意する必要が有ります
LEのプライバシー機能は、ランダムデバイスアドレスの受け入れ(チェック)のみ可能
SecurityManager/ATTのデータ署名機能はありません
キーは電源が切れたら消失します
誤り検出、再送機能 :なし
アトリビュート照会機能:ATTで小物内データの検索および更新機能、通知機能あり(Notify,Indicate)
相手のATTデータにアクセスする機能はありません
仮想シリアル通信 :RFCOMMでコネクション待ち
音声系機能 :A2DP/Sink機能、PCMデータ出力、SBCのみ対応
(2)動作条件
対応CPUボード: STM32F4-Discovery
USBドングル: 市販品はおおむね使用可能
多重動作: マルチタスク不可
開発環境:System Workbench for STM32 / Ac6 Tools(略称SW4STM32)
ライブラリ:STM32CubeMX
使用プログラムメモリ量: 約100kB
(USBスタック、Cランタイム等全て含んだサンプルの全サイズ、コンパイラ最適化は-Osを使用)
2. ダウンロード
SW4STM32プロジェクトファイル:
スタック本体とサンプルプログラムが含まれます。
前版(v0.3)からの変更点:
A2DPのサポート
STM32CubeMX(コード生成ツール&ライブラリ)の適用
権利関係
GPL v3に準じますが、暗号化に関する部分にオープンソース rijndael-alg-ref.c を使っていますので、ご利用の際には注意願います。
Bluetooth仕様の情報源については、次の公開情報を元にしています。私の英語読解力の問題があり、誤解の可能性が高いので、重要な場面では使わないようにして下さい。
●BLUETOOTH SPECIFICATION Version 4.0
Bluetooth SIG 開発者向けサイトdeveloper.bluetooth.orgの公式仕様書
●ETSI TS 101 369 V6.3.0 (1999-03) Technical Specification
(GSM 07.10 version 6.3.0 Release 1997)
European Telecommunications Standards Institute
RFCOMM仕様で参照されているドキュメント
●CD00289278 UM1021 USB On-The-Go host and device library
STM公式サイトで公開しているユーザマニュアル
●Federal Information Processing Standards Publication 197 (FIPS-197)
ADVANCED ENCRYPTION STANDARD (AES)
Rijndael Algorithm: http://csrc.nist.gov/archive/aes/rijndael/wsdindex.html
Reference source code: rijndael-alg-ref.[ch] rijndael-api-ref.[ch]
3. サンプルの動かし方
ダウンロードしたファイルをSW4STM32のホームディレクトリに展開し、プロジェクトを開いてください。ビルドしてF4Discoveryを接続して実行してみてください。緑LEDが2秒周期で点滅します。マイクロUSBコネクタにBluetooth-USBドングルを接続すれば、サンプルプログラムの機能を確認できるはずです。
サンプルプログラムは、以下の機能を持ちます。
・プログラムが動いていれば、「緑LED」が定期的に点滅を繰り返します。処理負荷により周期が変ります。
(1)RFCOMMの機能
1秒間隔で”Hello world.”のメッセージが表示されます。
(a)WindowsPCの場合
Bluetoothのブラウズやデバイス設定等の機能を使うと、「SYM32F4-Discovery」という名前が出ているはずです。
これでペアリングを行ってみて下さい。認証聞かれたら「無し」を指定。
作られた新しいCOMポートにハイパーターミナル等で接続すると”Hello world”の文字が出てくるでしょう。
(b)Macの場合
「Bluetoothデバイスを設定」もしくは「Bluetooth環境設定」でペアリングを行って下さい。
ターミナルを開き、”ls /dev/cu.*”コマンドで新しく出来たシリアルポートを確認し、
”cat /dev/cu.XXXXXXX”コマンドでアクセスして下さい。
”screen /dev/cu.XXXXXXX”コマンドもあります。終了はCtrl+a+\です。
(2)A2DP/sinkの機能
PCやスマートフォンから「Bluetooth4P」という名前のデバイスが出てきますので接続します。成功すれば、「青LED」が点灯します。Discoveryボード上のヘッドフォンジャックに、PC/スマートフォンの音源が出力されます。
(3)ATTの機能
ATTは、GATT標準の「Bluetooth心拍計」をエミュレートしています。
(a)WindowsPCの場合
ツール見つけられず。
(b)iPhone(iOS)の場合
標準Bluetooth心拍計をサポートするアプリケーションソフトウエアは、あまりないようですが、iOSは心拍計だけは標準サポートされており、iPhoneをお持ちの方は簡単に確認する事が出来ます。
設定のBluetoothを開くと1分ぐらいで「Heart Rate Sensor」が出てくるはずです。タップすると特に応答なくペアリング完了。心拍データを確認するには標準インストールされている「ヘルスケア」アプリケーションを使います。(残念ながらiPadにはありません)
バイタルをタップすると心拍数が60〜90まで1秒間隔で変化するのを見る事が出来ます。特に設定は必要はなく、データソースに自動的にデバイスが追加されます。(”BLE ATT SAMPLE”は開発中の名前)
他のアプリケーションとしては、ツールがいくつか有ります。LightBlue、BLExplr、BTLExplorer、SmartDiscoverなどが「App Store」で無料でダウンロードできます。出来が良いのがLightBlueで、以下スクリーンショットです。
起動してしばらくすると、ペリフェラルデバイス「Heart Rate Sensor」がいるはずです。中を確認すると「Heart Rate」のサービスが出てくるはずです。GATT仕様通り心拍計の3つの属性(キャラクタリスティクス)があります。
①Heart Rate Measurement
心拍数[BPM]
直接読みだす事はできませんが、Notify機能(Listen for notification)で値を確認できます。
②Body Sensor Location
これは測定部位を表し2はWristを示します。固定値です。
③Control Point
①の値をリセットする指示。本サンプルでは本来の機能は有しませんが、
0x01を書込むとDiscovery上の「オレンジLED」が点灯し、それ以外の値だと消える動作となります。
(c)Macの場合
Xcodeサンプルプログラム:GATT対応Bluetooth心拍計ビューア
入手容易なアプリケーションは見つけられませんでしたが、Xcodeのサンプルコード「HeartRateMonitor」がありましたので、動作確認に使えます。
Apple Developerからソースコードをダウンロードして、xcodeで開いて以下のコード修正をしてビルドして下さい。
//#import <IOBluetooth/IOBluetooth.h>
#import <CoreBluetooth/CoreBluetooth.h>
IOBluetooth.hをCoreBluetooth.hに変更するだけです。このサンプルは良くできていて、心臓が鼓動します。また、他のサンプルとしてBluetooth温度計「HealthThermometerClient」もあります。それに対応する為のATTデータベースのソースファイルも本サンプルに入れてあります。
Bluetooth Explorer:Apple提供の開発ツール
Xcodeの追加ツールですが、Apple Developerのダウンロードから個別にインストールする必要が有ります。(多分、単独でも動かせると思われます)Additional_Tools_for_Xcode もしくは Hardware I/O tools for xcode という名前です。Bluetooth Explorerを起動し、Devices -> Low Energry Devicesを選択すると画面が出て来ます。
LightBlue:無料で使えるATTツール
iOS用の物と同じで、App Storeから無料でダウンロード可能です。macOS版の出来はイマイチです。
4. アプリケーション適用
ユーザアプリケーションで、本スタックを利用するための設定方法や、インタフェース(API)を説明します。
(1)共通
(a)名称変更
外部からBluetoothデバイスを検索した時に最初に表示される「名前」はソースコード上にハードコーディングされています。これを修正する事で、サンプルの名前「Bluetooth4P」を好きな名前に変更できます。動的に変更する機能はありません。名前はBR/EDRとLEで別々に設定できます。ソースファイルは「bt_config.h」で、#define定義を変更します。
#define BT_LOCAL_NAME 12,'B','l','u','e','t','o','o','t','h','4','P','¥0'
最初の数字はデータのバイト長です。必ず‘\0’を付加して下さい。
#define BT_LE_SCAN_RESPONSE 32,31,12,GAP_AD_Type_Shortened_local_name,\
'B','l','u','e','t','o','o','t','h','4','P',\
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
最初の数字はデータ全体のバイト長で32固定です。2番目の数値は有効なデータのバイト長です。以降、GAPのADフォーマット仕様に従います。
(b)BR/EDRとLEの機能無効化
どちらかの機能を使いたくなければ、外部から見えなくする事が出来ます。これは、スキャンを出さないようにする、アドバタイズしないようにするというやりかたでの対応となります。 ソースファイルは「bt_config.h」で、#define定義をするか/しないかの指定で変わります。
#define BT_INIT_DETECT_ENABLE // BR/EDR Inquiry scan enable
#define BT_INIT_LE_DETECT_ENABLE // LE advertise enable
BT_INIT_LE_DETECT_ENABLEを無効にしても、APIでアドバタイズをオン/オフさせる方法も有ります。
(c)プログラムインタフェース(API)
(2)RFCOMM
(a)概要
プログラムインタフェースは単純です。データが受信されたらAPIがコールバックされます。送信したければAPIをコールするだけです。設定としては、認証・暗号化があります。通常は、セントラル側で必ずペアリング操作が行われますので、最低でも暗号化はされます。しかし、デフォルトでは認証なし(不特定の相手がペアリングされてしまう)のため、注意を要します。この辺りは(4)セキュアシンプルペアリング の設定を行う事で認証を要求するようにできます。
(b)プログラムインタフェース(API)
(3)A2DP/Sink
(a)概要
Bluetoothクラッシックには、2種類の音声系プロファイルがあります。一つは携帯電話用途のハンドセットやマイク付きイヤホンなどに使われるHands-Free Profile(HFP) やHeadset Profile (HSP) で、携帯電話品質の低ビットレートの音声を対象にしています。もう一つは、Advanced Audio Distribution Profile (A2DP) で、音楽を聴くに耐える品質を持ったものです。
本スタックは、A2DPのシンク(吸い込み)方向のみの機能をサポートしています。吸い込みとは、音声データを受け取る機能で、スピーカやヘッドフォンを実装するときに使います。逆はソースで、マイクやCDプレーヤなどの音源を送り込む時の機能です。なお、Codecは「SBC」を内包しており、API上はPCMデータですので、とても扱いやすくなっております。
基本的は、接続されればFIFOバッファにPCMデータが流れ込んできますので、オーバフローしないよう、逐次データを取り出して処理します。
(b)設定
系統(エンドポイント)の設定値は、ソースコードの「bt_a2dp_sink.h」にあります。初期値は、エンドポイント数:1系統、FIFOバッファの幅=1024サンプル、FIFOバッファの深さも=4、デフォルトのサンプリング周波数44100Hz、チャネル数=2(ステレオ)です。サンプル解像度(ビット深度)は16ビット固定です。
(c)プログラムインタフェース(API)
APIはありません。エンドポイントのメモリテーブルを参照/変更する事で、A2DPの機能を利用する方式です。
接続状態:
inUseが1になるか、stateがA2DP_STATE_DISCONNECTだと、接続されている事を表します。
FIFOバッファ:
バッファ本体名がpcmBufferで、書き込み位置ポインタと読み出し位置ポインタで仮想的にリング構造を実現する方式です。ポインタが、書き込み位置pcmWritePos>読み出し位置pcmReadPos、の条件になったらデータが出てきたという事です。また、pcmWritePos<(pcmReadPos+バッファ深さ)になったらオーバンラン/データ消失したという事ですので、強制的に追いつくようにして下さい。位置ポインタはリセットしない事で、大小の判定が容易になります。ただし、オーバフローした場合は問題がでますが、数千時間に1回の頻度、ちょっとノイズが出る程度の事なので、無視しても良いと思います。実際のバッファ位置は、ポインタ値%バッファ深さです。(%=剰余)
(4)ATT
(a)概要
そもそもATTとはキー/値の単純な対応表です。これにキーとキーの間の関係ルールをかなり細かく統一(GATT)する事で、温度や湿度、重量、速度、電圧、電流などの一般的なデータ値の利用がしやすくなります。例えばGATTに準拠したBluetooth温度計(サービスと呼ぶ)デバイスを作れば、それを利用するスマートフォンアプリケーションは既に存在する可能性が高いでしょう。新しいデータ形式は各自自由に作る事も許されているので、独自データも扱えます。この場合は、提供側デバイスも、利用側アプリケーションも自分で作成する必要があります。
何はさておき、ATTデータベースを設定する必要が有ります。このデータベースはアプリケーションからAPIを通してアクセスするのと、外部からも自由にアクセスされるという単純な仕組みです。外部から値が変更された時にコールバックされるAPIもありますし、変更したら自動的に相手に通知されるようにもできるので、タイミング取りも可能になっています。
ATTは何もしなければ、暗号化も認証もされません。これらを適用したければデータベースの属性に追加設定を行う必要が有ります。また(5)セキュリティマネージャーの設定も行って下さい。
(b)代表サービスUUIDの設定
アドバタイズ(広告)メッセージにサービスUUIDを仕込むには、ソースファイル「bt_config.h」を編集します。
#define BT_LE_ADVERTISING 32,11,\
2,GAP_AD_Type_Flags,(GAP_AD_Flags_Simultaneous_LE_BR_EDR_Host | \
GAP_AD_Flags_Simultaneous_LE_BR_EDR_Controller | \
GAP_AD_Flags_LE_General_Discoverable_Mode), \
3,GAP_AD_Type_16bit_Service_UUIDs_Complete,HCI_UINT16_DATA(GATT_services_heart_rate), \
3,GAP_AD_Type_16bit_Service_UUIDs_Complete,HCI_UINT16_DATA(GATT_services_device_information), \
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
アドバタイズデータその物の定義になります。1つ目の数値はデータ全体のバイト長で固定的に32を指定します。2つ目の数値は有効なデータバイト長です。この例では2つの16ビットUUIDのサービスを指定しています。他の例は、サンプルソースファイルを参考にして下さい。
(c)データベース作成
データベースはデバイス上のRAMに展開しています。基本的にはキー/値の対応表でキーは静的なものになります。動的に変更が可能なのは値のみです。キーと値の初期値はソースコードでハードコーディングします。
データベース本体のソースファイルは「bt_att_data.c」です。記述の仕方については、GATT(*1)仕様に従って組立てて行きます。ゼロから作るのは、かなり大変なので、サンプルを元に追加/削除するのが楽だと思います。3種類ほどサンプルファイルを入れてあります。
bt_att_data.Hart_Rate.c GATT心拍計
bt_att_data.Health_Thermometer_secure.c GATT体温計(認証付き)
bt_att_data.Private_Sample.c 独自デバイス
仕様書は、基本部分のサービスしか記述されていませんので、全ての定義が示されている「https://www.bluetooth.com/specifications/gatt」を参照する必要が有ります。
*1 BLUETOOTH SPECIFICATION Version 4.0「Volume 3 Core System Package [Host volume], Part G Generic Attribute Profile (GATT)」
(d)プログラムインタフェース(API)
bt_attDbNotification()はユーザアプリケーションで用意する必要があり、通信相手が更新しても自分が更新しても区別なく呼び出されます。必ず以下のように bt_att_serverNotificationQueueAdd(通知依頼)を呼ぶようにして下さい。入れないとDB更新しても通知されなくなります。
void bt_attDbNotification(unsigned short handle, void *value, size_t valueLen){
bt_att_serverNotificationQueueAdd(handle);
switch(handle) { // User action
case <X>:
break;
case <Y>:
break;
default:
break;
}
return;
}
(5) SSP:セキュアシンプルペアリング (BR/EDRの認証・暗号化)
(a)概要
事実上必須のセキュリティ機能です。ペアリングには不特定の相手を許すものと、ランダムに生成されたパスコードを入力してもらう事により相手を特定する2種類があります。両方とも暗号化はされます。何が違うかと言えば、デバイス側が不特定な相手とのペアリングを望まないという場合です。デフォルトは前者の設定にしています。以下の説明は、後者(認証と呼ぶ)の対応を行う為のものです。
(b)ポリシー設定
① サービス(プロファイル/プロトコル)毎に、認証が必要かを設定します。
② デバイスにパスコードを表示したり入力したりする装備が有るかどうかを設定します。
③ 最後に、プログラムインタフェースでパスコードの表示、入力する処理を作って下さい。
最初に①サービス内容を定義しているソースコードを修正します。ソースファイル「bt_config.h」の、#defineを修正して下さい。RFCOMMしか用意していません。コメントを入れたり抜いたりすれば良いです。
//#define BT_RFCOMM_PERMISSION BT_L2CAP_SERVICE_PERMISSIONS_Allow
#define BT_RFCOMM_PERMISSION BT_L2CAP_SERVICE_PERMISSIONS_Encryption_Required
//#define BT_RFCOMM_PERMISSION BT_L2CAP_SERVICE_PERMISSIONS_Authentication_Required
次に②を定義しているソースコードを修正します。 ソースファイル「bt_config.h」の、BT_SSP_IO_CAPABIRITY #define行のどれかを選択します。実際の動きは、相手側の表示・入力機能の有無によって組み合わせ(*1)で決まります。
//#define BT_SSP_IO_CAPABIRITY HCI_Parm_IO_Capability_NoInputNoOutput
#define BT_SSP_IO_CAPABIRITY HCI_Parm_IO_Capability_DisplayOnly
//#define BT_SSP_IO_CAPABIRITY HCI_Parm_IO_Capability_DisplayYesNo
//#define BT_SSP_IO_CAPABIRITY HCI_Parm_IO_Capability_KeyboardOnly
組み合わせ表を自分の処理部分のみ書き出しました。これに従ってAPIが呼ばれます。自動応答の部分は、必ずOKを返すようにしておきます。NGをかえさなければペアリングOKとなり、緑部分は認証もOKとなります。
自分 \ 相手
DisplayOnly
DisplayYesNo
KeyboardOnly
NoInputNoOutput
DisplayOnly
NumericComperision
自動応答
NumericComperision
自動応答
PasskeyEntry
表示のみ
NumericComperision
自動応答
DisplayYesNo
NumericComperision
自動応答
NumericComperision
表示と確認応答
PasskeyEntry
表示のみ
NumericComperision
自動応答
KeyboardOnly
PasskeyEntry
キー入力
PasskeyEntry
キー入力
PasskeyEntry
キー入力
NumericComperision
自動応答
NoInputNoOutput
NumericComperision
自動応答
NumericComperision
自動応答
NumericComperision
自動応答
NumericComperision
自動応答
*1 BLUETOOTH SPECIFICATION Version 4.0「Volume 3 Core System Package [Host volume], Part C Generic Access Profile」Table 5.6: IO Capability Mapping to Authentication Stage 1
(c)プログラムインタフェース(API)
(6)SM:セキュリティマネージャー(LEの認証・暗号化)
(a)概要
BR/EDRのペアリングと、LEでペアリングは別物です。LEの場合、Security Managerというソフトウエアが担います。デフォルトではノーセキュリティです。サービス(アプリケーション)からセキュリティ要因で拒否されたら、SMにペアリングを要求し、整った所で、再度サービス要求すると言う流れになっています。
唯一のLEサービスであるATTの場合、アトリビュート単位にアクセス権限(暗号化/認証)を持っており、ペアリング状態を確認して、拒否応答を行います。あとはSMチャネルを通して相手からペアリング要求されますので、SMが双方のパスコードの表示・入力機能の組み合わせに従い、ペアリング操作方法が決まります。
(b)ポリシー設定
① ATTデータベース上のアトリビュートに、暗号化/認証が必要かを設定します。
② デバイスにパスコードを表示したり入力したりする装備が有るかどうかを設定します。
③ 最後に、プログラムインタフェースでパスコードの表示、入力する処理を作って下さい。
最初に①ATTデータベースを定義しているソースコードを修正します。ソースファイル「bt_att_data.c」の、テーブル初期値を修正して下さい。 保護したいアトリビュートのプロパティに以下のいずれかを加えます。
・ATT_DB_PROPERTIES_ENCRYPTION(暗号化)
・ ATT_DB_PROPERTIES_AUTHENTICATION(暗号化と認証<相手を特定>)
次に②を定義しているソースコードを修正します。 ソースファイル「bt_config.h」の、BT_SMP_IO_CAPABIRITY の#defineのどれかを選択します。実際の動きは、相手側の表示・入力機能の有無によって組み合わせ(*1)で決まります。
//#define BT_SMP_IO_CAPABIRITY SMP_IO_Capability_NoInputNoOutput
#define BT_SMP_IO_CAPABIRITY SMP_IO_Capability_DisplayOnly
//#define BT_SMP_IO_CAPABIRITY SMP_IO_Capability_KeyboardOnly
//#define BT_SMP_IO_CAPABIRITY SMP_IO_Capability_KeyboardDisplay
組み合わせ表を自分の処理部分のみ書き出しました。これに従ってAPIが呼ばれます。JustWorksはAPは呼ばれません。APIでNGを返さなければペアリングOKとなり、 緑部分は認証もOKとなります。
自分 \ 相手
Display
Only
Display
YesNo
Keyboard
Only
NoInput
NoOutput
Keyboard
Display
Display
Only
JustWorks
JustWorks
PasskeyEntry
表示のみ
JustWorks
PasskeyEntry
表示のみ
Display
YesNo
JustWorks
JustWorks
PasskeyEntry
表示のみ
JustWorks
PasskeyEntry
表示のみ
Keyboard
Only
PasskeyEntry
キー入力
PasskeyEntry
キー入力
PasskeyEntry
キー入力
JustWorks
PasskeyEntry
キー入力
NoInput
NoOutput
JustWorks
JustWorks
JustWorks
JustWorks
JustWorks
Keyboard
Display
PasskeyEntry
キー入力
PasskeyEntry
キー入力
PasskeyEntry
表示のみ
JustWorks
PasskeyEntry
キー入力
「表示のみ」は、パスキー値を決められますので固定値を返すようにしておけば、自動応答でも認証OKにさせる事が可能です。もちろん、相手にはその固定値を教えておく必要はあります。
*1 BLUETOOTH SPECIFICATION Version 4.0「Volume 3 Core System Package [Host volume], Part H Security Manager Specification」Table 2.4: Mapping of IO Capabilities to STK Generation Method
(c)プログラムインタフェース(API)
(d)ペアリングデータの保存
取り交わされたキー情報は、デバイスの電源が切れたら消失しますので、再度、ペアリング操作を要します。ペアリングデータを保存したい場合は、「デバイス管理テーブル」をバックアップして下さい。
スタック内部の動作について興味がある方は、「自作電子小物/TIPS/Bluetooth-LEスタック/ペリフェラル向け/設計内部情報」をご覧ください。