BlueTooth Low Energy v4.2を使ってみる。nRF52832

nRF52832 と、BLE v4.2 の最大の魅力は、パケットサイズを大きくとれること
UARTで相互通信
nRF52832 には、CPUを介さないで周辺回路を制御するPPIという機能がある。PPIでADCを動かす
PPIでADCを動かし、GPIOの制御を追加。 2017.11.9 プロジェクトソース追加
SDK14.0では、周辺回路のクロックソースが、デフォルトで内臓RC発振になっている! 2017.11.12
1パケット244バイトで、インターバルを50msで通信してみる 2017.11.19
PPIで内臓ADCを動かし、BLEでデータを送信する。 2017.12.8


 BLE( BlueTooth Low Energy )は、低消費電力を売りものにした通信規格であるため、従来のBlueToothほどのデータ通信の速度を出せず、用途によっては 「もう少し早ければ」という壁にさえぎられることがありました。スペック的には、7.5msの通信インターバルで、20バイトが最高の速度なので、133x20で、1秒間で、2660バイトが限界でした。( 実際に、この速度を出すのは不可能です。というのは、これはエラーなく通信できた場合のみです。)

 このような速度の限界を突破できる規格が、v4.2で、2014年12月に発表されました。パケットサイズが約10倍となり、無線キャリアで送信できる速度も1Mbpsから、最大2Mbpsになったようです。チップメーカーからこの規格のICが出てきたのは、最近のことで、評価基板付き、日本国内技適認証付きでは、2017年4月ごろです。( ノルディックの、nRF52832 を使った 太陽誘電のBLEモジュール )

 ノルディックからは、nRF52 DKという評価基板が、先に入手できていましたが、これには、日本の技適の認証はありません。あくまでもテスト用です。この評価基板は、DigiKeyなどから入手でき、値段もそれほど高くないので、nRF52832 関連の開発をするなら、1枚あるといろいろと役に立ちます。ノルディックのSDK( 最新は、V14.0 )に同梱されているサンプルソフトは、この nRF52 DK で、修正なしに動作するようになっています。
 以下は、nRF52 DK



 nRF52 DK は、押しボタンや、LEDなど、サンプルソフトをそのまま動作可能ですが、別の部品を接続するには不便なので、次のような評価基板を用意しました。太陽誘電株式会社のBLE評価基板です。nRF52 DK よりかなり高価(約2倍)ですが、それなりの使い勝手があります。

 上側の P のラベルを貼ったのが、EBSHJNZXZで、C のラベルを貼ったのが、EBSHCNZXZです。違いは、BLEモジュールの大きさと、外部で使えるピンの数です。いづれもFTDIの、FT232RQを介して、USB経由で、PCのCOMポートでUART送受信できます。UARTに接続しているnRF52832 のピンは、nRF52 DK と同じになっているので、サンプルソフトの、ble_app_uart と、ble_app_uart_c を使って、相互にUARTで無線送受信可能です。( 1文字単位に送るわけではなく、LFコード[0x0A]で区切られたパケット単位 )



nRF52832 と、BLE v4.2 の最大の魅力は、パケットサイズを大きくとれること

 このパケットサイズを大きくすることを、DLEと呼んでいますが、BLEv4.1までは、23バイトでしたが、BLE v4.2では、これが、最大247バイトになって、ユーザーが扱えるパケットサイズは、最大244バイトになり、実に12倍まで1パケットで送れます。
 この「最大」ということがミソで、100バイトにしたいなら、その設定にすることができます。ble_app_uart という名のサンプルソフトの、main.c には、nrf_ble_gatt_att_mtu_periph_set(nrf_ble_gatt_t * p_gatt, mtusize) 関数があって、この関数で、最大パケットサイズを設定できます。使いたいペーロードの最大サイズを100バイトにしたいなら、mtusize = 103と設定します。

 SDK14.0( nRF5_SDK_14.0.0_3bcc1f7 )の、examples フォルダには、ble_central と、ble_peripheral のフォルダがあり、これらのフォルダには、それぞれ、セントラル側のサンプルソフトと、ペリフェラル側のサンプルソフトがまとめられており、その中に、UART で相互通信可能な、ble_app_uart_c と、ble_app_uart があります。この2個のサンプルソフトを使って、上の写真で紹介した、太陽誘電の評価基板、EBSHJNZXZ と、EBSHCNZXZ で、相互にUARTで無線通信させることができます。なぜ最初にUARTのサンプルソフトを使うのかというと、どちらの基板にも、UARTとして設定されるポートがFTDIと接続されていて、容易にPCのターミナルソフトで動作確認ができるからです。

UARTで相互通信
 サンプルソフトの、ble_app_uart_c と、ble_app_uart を、それぞれ、KEIL ( ここで使ったのは、やや古いバージョンのIDEで、v5.12 )でコンパイルしますが、ble_app_uart_c のソースは、すこし改造します。改造といっても、エコーバック機能を止めるだけです。

 以下は、ble_app_uart_c の、main.c の、87行です。オリジナルでは、1と設定されているので、これを0に変更します。


 ペリフェラルの改造は、デフォルトでは、ペイロードサイズが、61バイト(設定値としては64)なので、これを、128バイトまで送れるようにします。設定は、131となります。変更箇所は、main.c の、void gatt_init(void) 関数内で、以下です。


 この設定で、128バイトまで1パケットで送れます。

 その他、動作中のメモリや、チップの周辺回路のレジスタの値を読めるように、モニタを実装します。モニタを実装すると、常時CPUが動作するため、スリープにはならないので、モニタには、終了コマンドを入れて、スリープできる機能も入れておきます。

 こちらが、ペリフェラルのソース一式です。→ ble_app_uart_TAIYO.lzh ファイル一式 ble_app_uart_TAIYO.lzh
 解凍して、ble_app_uart_TAIYO フォルダを、nRF5_SDK_14.0.0_3bcc1f7\examples\ble_peripheral\ フォルダに置きます。

 以下は、テラタームでのスクリーンショットです。

 ペリフェラル側、EBSHJNZXZに、解凍した、nrf52832_xxaa.hex を書いてリセットスタートすると、
UART Start!00
とターミナルに表示されるので、ここで、コントロールCを入力すると、
nRF52832 BLE SoC 2017.10.8-1
NRF52832-Bug>
 と表示され、コマンドを入力して、メモリの表示などができます。


nRF52832 には、CPUを介さないで周辺回路を制御するPPIという機能がある。PPIでADCを動かす

 nRF52832 には、いわゆるDMAとシーケンサを組み合わせたような機構が、各周辺回路に個別に実装されていて、無線の対応で割り込みサービスが遅れて正確なタイミング設定ができない問題を解消してくれます。この機構を、「PPI」と呼びます。PPIとは、Programmable peripheral interconnect のことで、CPU動作から独立した、簡単なハードウエアシーケンサです。ハードウエアで制御するため、時間間隔などは正確にタイマーに追従します。
 なぜこのような機能を入れたのかは、最優先のBLEの制御によって、ユーザーに渡される割り込みのタイミングが大きくずれてしまうので、そのずれを解消するためと推測されます。実際、nRF51822 では、タイマー割込みを使っても、1ms程度のジッターが発生してしまい、正確さが要求される用途には使えませんでした。

 時間を正確に決められるといっても、繰り返し動作のような使い方で重宝するのですが、ADコンバーターを正確なインターバルで動作させる、正確なインターバルでSPIを動作させる、正確なインターバルでGPIOを操作する、などが可能になります。また、これらの周辺回路操作を複数個、同じタイマーのカウント数の違いで、個別に操作タイミングを設定できたりします。

 SDK14.0には、nRF52832 の、PPI機能を使ったサンプルが、
nRF5_SDK_14.0.0_3bcc1f7\examples\peripheral フォルダに、いくつか用意されていて、
 その中の、saadc というフォルダにある、ADコンバーターをPPIで制御するサンプルを使ってみます。そのままでも動作しますが、ADコンバーターのサンプリングを、1秒に1000回として、2個用意された読み取りバッファーを、200回のサンプル単位に、バッファーの先頭部分をUARTでヘキサで出すように改造してみます。オリジナルでは、デバッグ用にヘキサでUARTで出すようになっていますが、このあたりは、別途にUART出力を実装します。そのために、peripheral\uart フォルダにある、UARTのサンプルを、saadc サンプルに移植します。

 主な変更点は以下
  1. uart 機能を追加。サンプルソフトの uart から必要な部分をコピーして移植。
  2. 4ビットのデータをヘキサ文字に変換して、UARTで出力する関数。uint8_t tohexcode(uint8_t d) を追加。
  3. nrf_drv_timer_ms_to_ticks(&m_timer, 400); を、nrf_drv_timer_us_to_ticks(&m_timer, 1000); に変更。400ms から、1ms に。
  4. #define SAMPLES_IN_BUFFER 5 を、#define SAMPLES_IN_BUFFER 200 に変更。サンプル数は、200回。
  5. void timer_handler(nrf_timer_event_t event_type, void * p_context) 関数内で、ポートに、Lowパルスを出す。
  6. void saadc_callback(nrf_drv_saadc_evt_t const * p_event) 関数内に、バッファーの先頭のデータをヘキサでUARTで出す処理を追加。
  7. NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN0); を、NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN1); にして、P0.03 (AN1) の電圧を変換します。
 これらの変更をしたプロジェクトファイル一式です。→  saadc_modify.lzh
 解凍して、saadc_modify フォルダを、nRF5_SDK_14.0.0_3bcc1f7\examples\peripheral\ フォルダに置きます。

 以下は、ボリュームで適当な電圧を作って P0.03 ピンに入力し、ターミナルに、UARTで変換結果を表示させたものです。ADコンバーターは、10ビットで動作しているので、ボリュームを回すと、0付近 〜 0x3FF付近の値に変化します。



PPIでADCを動かし、GPIOの制御を追加。

 さて、SDK14.0のサンプルソフトでは、周辺回路の単体動作を、PPIで制御させる例はあるのですが、複数の周辺回路を、関連付けて動作させる例が見当たらないようです。この要求は、内蔵ADCでは、変換を始める前に、ポートをLow,Highさせるとか、外部のICにタイミングを通知するのに必要になることがあります。また、SPIや、I2Cも、PPIで制御できますが、外部に接続した、複数の異なるシリアルデバイスを、関連付けて制御したい、ってこともあると思われます。
 この要求を満たすには、PPIの制御がどのように行われるのか、分析しないと判りませんが、とりあえず、内臓ADCを、PPIで制御し、関連付けたポートに、Low,Highを出すものを作成してみました。saadc のサンプルを元に、GPIOを追加するには、いくつかファイルとか、インクルードパスとか、を追加し、sdk_config.h への #define 文の追加が必要になってきて、一筋縄には動作しませんでした。

 以下はその備忘録です。
 手順
  1. GPIOに関するソースパス、nRF5_SDK_14.0.0_3bcc1f7\components\drivers_nrf\gpiote を、インクルードパスに追加。
  2. nRF_Drivers ソース欄に、nRF5_SDK_14.0.0_3bcc1f7\components\drivers_nrf\gpiote の、nrf_drv_gpiote.c を追加。
    このとき、nrf_drv_gpiote.c には、どんな関数があるか一通りながめておく。
  3. SDK14.0の、ペリフェラルのサンプル、nRF5_SDK_14.0.0_3bcc1f7\examples\peripheral\gpiote の、main.c で、GPIOの使い方を確認する。このサンプルは、GPIOを、PPIで制御するサンプルなので、使い方が判る。
  4. saadc に、GPIOのサンプルを、同じタイマーで動作するように、改造しながら、移植する。
 ・・・ と、こんな具合なのですが、4番目で、どう移植するのかに、一番時間がかかりました。
 以上の手順をふまえ、saadc のサンプルに、gpiote のサンプルを、同じタイマーを使って、PPI制御して、ADコンバーターが動作する前に、ポート P0.28 に、Low のパルスを出すようにしたものです。ADコンバーターサンプリングインターバルは、1msと短くして、ADCの結果のバッファーも、200個を2面用意し、片方に変換データが書かれているときには、もう一方のバッファーから読み出せるようにしています。

 また、この bitcraft URLのあちこちで使っている、モニタ機能を入れ、UART経由でメモリの内容の表示や変更ができるようにしています。
 SAADCとGPIOをPPIで制御したプロジェクトファイル一式です。→  saadc_modify_gpio_mon.lzh  このソースでは、BLEは使っていません。
 解凍してできるフォルダ、saadc_modify_gpio_mon を、nRF5_SDK_14.0.0_3bcc1f7\examples\peripheral フォルダ以下にコピーすると、
nRF5_SDK_14.0.0_3bcc1f7\examples\peripheral\saadc_modify_gpio_mon\pca10040\blank\arm4 に、saadc_pca10040.uvprojx という名のKEILのプロジェクトファイルがあるので、これを、KEILのIDEで開くと、変更、再コンパイルが可能です。

 以下は、P0.20 でトリガし(赤色)、ADCサンプリングの前の100μ秒のLowパルス(青色)P0.28 を観測したもの。

 赤色のLowパルスは、ADコンバーター200回読み取りの結果の割り込み処理です。

SDK14.0 では、周辺回路のクロックソースが、デフォルトで内臓RC発振になっている!

 PPIでGPIOを操作できてるか確認のためオシロで観察していると、1msのインターバル時間が、わずかに変動しているのが判りました。オシロで確認できるほどなので、どれくらいなのか正確に測ってみることにしました。こちらの、→ GPSモジュール AE-GYSFDMAXB の、1PPS出力を調査 で1秒インターバルの測定で使ったFPGA(ARTY)で測定すると、約±1%のジッターがあるのが判りました。つまり、0.99ms 〜 1.01msの間で常にランダムに変動しているのです。PPIは、ハードウエアによる制御なので、カウンタに正確に追従しているはずなのに、このジッターは何なのか、調査してみました。その結果、ノルディックのデベロッパーゾーンにその回答がありました。→NORDIC DEVELOPER ZONEで、PWM frequency accuracy で検索。

先ほどの、プロジェクトソース saadc_modify_gpio_mon.lzh の、mai.n.c の、以下の部分の、コメントとしているところは、ジッターをほとんど無しにする設定です。この設定は、ノルディックのメーリングリストQ&Aで見つけたものです。→PWM frequency accuracy


NRF_CLOCK->TASKS_HFCLKSTART = 1; と、その次の行のコメントを外すと、正確な1msになりました。

1パケット244バイトで、インターバルを50msで通信してみる

 通信のテストに行う構成は、以下です。PCには、244バイトの文字列( 最後の2バイトは、CR と LF コード)を任意のインターバルでUARTで送信するソフトと、UARTで受信するターミナルソフトを用意。

 このテストに使った、プロジェクトファイル一式です。→  ble_app_uart_pkt244_7_5ms.lzh  起動後、ソフトデバイスを起動する前にモニタが走り、NRF52832-Bug>t (enter) で、デフォルトのMTUサイズ、247を確認でき、NRF52832-Bug>t92 (enter) で、MTUサイズを、146に設定できます。NRF52832-Bug>q (enter) で、モニタが終了し、ソフトデバイスの設定を行い、アドバタイジングを始め、再びモニタが走ります。セントラルと接続できると、再び、NRF52832-Bug>q (enter) でモニタを終了すると、UARTで受信したデータをセントラルに送れます。
 セントラル側は、こちら →  ble_app_uart_c_mtumax247_7_5to75ms.lzh

 BLE v4.2 で、SDK14.0のUARTサンプルソフトを使うと、パケットサイズ(UART等で実際に送れる1パケットのペイロードサイズ)を最大244バイトにはできますが、実際にこれを行うと、以下のようになります。コネクションインターバルは、7.5msと、最短時間としています。
青:UARTで244バイト受信後、244バイトを送信する、ble_nus_string_send 関数を呼び出す前後で、ポートをLowにした信号
赤:ble_evt_handler のイベント通知で、p_ble_evt->header.evt_id が、BLE_GATTS_EVT_HVN_TX_COMPLETE の時、Lowパルスをポートに出した信号

カーソルは、送信したあと、送信完了の通知がくるまで、約30msであるのを示しています。
また、青のLowパルスのインターバルが、約50msになっており、50msインターバルで送信していることを示しています。

 青、赤信号とも、約7.5msインターバルで、急激にすこし電圧が下がっていますが、これは、BLEモジュールの電源供給ラインに20オームの抵抗を直列に挿入して、無線が動作しているときの電流増加が判るようにしたものです。信号を拡大すると、送信と、受信操作の区別も判ります。
 送信完了のイベント通知、BLE_GATTS_EVT_HVN_TX_COMPLETEが来るまで、この電圧低下の波形が何回かあり、カーソル位置の例では、5回となっています。そのうち最初の3回は、Lowになっている( 正確には、何回か短くLow,Highと変化 )期間が長く、そのあとかなり短いLow、そして、BLE_GATTS_EVT_HVN_TX_COMPLETE のイベント通知のときの、短い波形となっています。

この波形を見る限り、244バイトが、1パケットで送られているようには見えません。ソフトデバイスでは、送信相手がデータを正常に受け取れなかった場合、再送信を行うようになっていますが、このテストでは、送信相手をごく近くに置いているため、(約8cm)電波品質の悪さが再送信につながっているとは思えません。

そこで、パケットサイズを、もう少し短くして同じようなテストをしてみました。ペイロードは、143バイトです。(MTU設定は、146)


この波形を見ても、143バイトが、1パケットで送られているようには見えません。図では、3パケットすべてが2回で送られています。 

 パケットサイズを、最大244バイトにできるはずなのですが、何等かの理由で、143バイトでも1回で送られていないようです。


いろいろ設定値を変え1パケット143バイトで、通信してみた

 そこで、いろいろ設定値を変え、sdk_config.h での設定値、#define NRF_SDH_BLE_GATT_MAX_MTU_SIZE 247 のところを、小さくしていき、146という設定だと、143バイトのペイロードを1回で送られることが判りました。
 以下は、sdk_config.h での設定値を、#define NRF_SDH_BLE_GATT_MAX_MTU_SIZE 146 とし、main.c の void gatt_init(void)関数 で、nrf_ble_gatt_att_mtu_periph_set(&m_gatt, 146);としたものです。


 この図では、4パケットの送信で、すべて1回で送信しているのが判ります。BLE_GATTS_EVT_HVN_TX_COMPLETE の通知も、1回の送信のあと、次のコネクションインターバルの時に発生しています。
 そして、この図から推定されることとして、送信インターバルを、もっと短く、25msでも連続で送れるように思われます。

PPIで内臓ADCを動かし、BLEでデータを送信する。

 これを行う前に、まず、UARTで受信したデータをBLEで送るプロジェクトに、PPIを使って、ADCを駆動する機能を追加します。BLEで送信するプロジェクトは、いろいろ設定値を変え1パケット143バイトで、通信してみたを使い、それに、PPIでADCを操作する機能を追加します。→PPIでADCを動かし、GPIOの制御を追加。
 ここで、PPIでADCを操作する部分をBIOSとして分離し、ADC以外のデータ取得方法になったとき、変更しやすいようにします。名称は、hal_ppi_adc.c などと、ハードウエア寄りのモジュール名とします。

 全体の構成としては、モニタを実装してPPIで読み取ったADCのデータを表示したり、ヘキサコードなどに変換して、BLEで送ったりできるようにします。ノルディックのサンプルソフトは、送受信双方を、ble_app_uart, ble_app_uart_c として、ノルディックのUARTのUUIDを使います。

 追加した関数。PPIでのADC読み取りバッファーは、100サンプルに変更しています。また、コネクションインターバルを、10msとしています。
  1. hal_ppi_adc.c PPIを使って、ADCで電圧を読み取る。ハードウエアレイヤー
  2. hal_send_timer.c 10msのタイマーを動作させ、割り込み処理関数を登録。ハードウエアレイヤー
  3. app_ble_send_data.c 上記の、割り込み関数の実行部と、ADCで読んだデータをBLEで送信する。アプリケーションレイヤー
  4. dbgmon.c テスト用のモニタ
 上記ファイルを追加したもの。パケットサイズは、131バイトとなっています。→ ble_app_uart_pkt131_adc100ms_10ms.lzh
 BLEで送信するとき、ADCで読んだ10ビットデータのMSB6ビットは不要なので、バイト列に詰めて、パケットサイズを小さくしています。また、ペリフェラルでは、UARTのデータはBLEで送信しません。

送信側で、上記のようにパックした100サンプル(125バイトになる)に、ヘッダーのSTX,4バイトのシーケンス番号、125バイトのパックデータ、LF として送信するので、受信側が出力するUARTデータを、ターミナルで内容を確認することはできません。

 送信側(ペリフェラル)の動作と操作は以下のように進みます。
  1. nRF52832 BLE SoC pkt131 2017.12.8-1
    NRF52832-Bug>00 とUARTで通知がくるので、ctrl-C を入力すると、
  2. NRF52832-Bug> とモニタのプロンプトが出るので、ここで、q (enter) とターミナルから入力します。(この時点でアドバタイジングを開始)
    すると、UART Start!00 とターミナルに通知がくるので、再び、ctrl-C を入力すると、
  3. nRF52832 BLE SoC pkt131 2017.12.8-1
    NRF52832-Bug>00 と再び同じ通知がくるので、ctrl-C を入力すると、
  4. NRF52832-Bug> とモニタのプロンプトが出て、ADCで読み取ったデータを、セントラルに送信する操作ができるようになります。
  5. セントラル側とのコネクションができても、何も通知しませんが、送信操作で確認します。
ADCで読んだ100サンプルのデータを送信する。
NRF52832-Bug>a1 (enter) で、100サンプルデータを送信します。送り終わると以下のように結果が出ます。
TX complete time = 10 ms
NRF52832-Bug>

ADCで読んだ100サンプルのデータを指定回数送信する。指定回数は、ヘキサで指示します

上の例は、ペリフェラルと、セントラルの距離が約80cmですが、操作しているノートパソコンのWiFiでファイル転送している場合は以下のようになります。WiFiでのデータ転送レートは、1秒で約10MB。負荷としては中程度です。

 この結果から、近くでWiFiがそこそこ動作している場合は、ソフトデバイスでの再送信回数が増加し、BLEの電波が妨害されているのが判ります。

ADCで読んだ100サンプルのデータを指定回数だけ、app_ble_send_data.c の、app_send_timer_event_handler で送信する。















準備中


ホームに戻る


inserted by FC2 system