BlueTooth Low Energyを使ってみる。nRF51822 (BVMCN5103)
SDK V6.0 nRF51822 RAM 16kB
使用するBluetooth Low Energy評価基板
Nordic の SDKのサンプルソフト
サンプルソフトを改造
UARTを使ってモニタを移植
タイマーを実装し、割り込み処理
1ms精度でタイマー同期
Soft Device ( s110 ) を使ってスマートフォンとUART通信
Soft Device ( s110, s120 ) を使って nRF51822 相互にUART通信 セントラル側は こちらを元に変更しています
nRF51822 を使って相互にUART通信。 ペリフェラルの名前とMACアドレス表示 2016.6.10 追加
nRF51822 を使って相互にUART通信。通信インターバル変更 2016.6.12 追加
nRF51822 を使って相互にUART通信。2台のペリフェラルとペアリング 2016.6.24 追加
nRF51822 を使って相互にUART通信。2台のペリフェラルと相互通信 プロジェクトソース一式追加 2016.7.10
2台のペリフェラルと相互通信をテストする環境をFPGAで作成 2016.7.19 修正 追加
Micro Blaze のGCCコンパイラで、-Os オプションでバグ発生 2016.7.19 修正 追加
SDK V10.0 nRF51822 RAM 32kB
SDK v10.0.0 を試す。使用デバイスは、BVMCN5103-CFAC-BK (nRF51822 CFAC ROM256k, RAM32k) 2017.1.25 追加
使用するBluetooth Low Energy評価基板
現在簡単に入手できるBLE(Bluetooth Low Energy)評価基板は、Nordic の、nRF51822 を使ったモジュール、BVMCN5103-BK だろうと思う。
テキサスインスツルメンツにもあるが、開発環境が、IARのようなので、無償で開発環境が使える、nRF51822 を使ったものにした。こちらだと、KEILの評価版開発環境で、32kbまで使えます(デバッガーが32kbまで)。GCCだと、制限がなくなりますが、環境の構築に、GCCの専門知識が必要なので、当面、KEILのお世話になります。
写真は、BVMCN5103-BK KIT PLUS で、左の2枚のBVMCN5103 と、右はプログラマー。BVMCN5103 には、Nordic の、nRF51822 が使われていて、アンテナ付きのモジュールになっており、日本国内で使うことができます。
これらの基板のほか、あると重宝するのが、Nordic 社からでている、nRF51-DK という評価基板です。
nRF51-DK のメーカーURL:nRF51-DK
アプリケーションプログラムの書き換えには、トランジスタ技術の付録基板がライターとして使えますが、これでは、Nordic のソフトデバイスを書けないので、ソフトデバイス( SoftDevice s110,s120 etc.) を書き換えるには、BVMCN5103-BK KIT PLUS のライターを使います。
まず、Nordic 社のURLからファイルをダウンロード
まずはユーザー登録から。
Nordic で検索すると、Nordic 社のURLが筆頭にでるはずで、その右側の上の方に、MyPage というプルダウンメニューがあり、Create a Nordic MyPage account を選択して、登録します。
登録した後、nRF51 SDK をたどると、以下のような FTPでダウンロードするようなURLが現れます。
SDK には、V4 から、V9 までありますが、古いものも含め全部ダウンロードしといた方がいいでしょう。このURLでの解説で使うのは、主に V6 です。
ここから、SDK のほか、pieces/ をたどると、SoftDevice なるもののダウンロードができますが、用途に合わせて、使い分けが必要になります。BVMCN5103 を、無線の端末として使うのなら(つまり通信相手のホストがスマートフォンで、出来合いのアプリで操作する)通常 SoftDevice の書き換えは必要ないようです。
pca10000 という評価基板でそのまま動作するものとして、SDK( nrf51_sdk_v6_0_0_43681 )に含まれるサンプルの、ビーコン、心拍数センサー、UART送受信端末などがあります。しかし、pca10000 という評価基板は入手困難なので、BVMCN5103 で動作させるには、押しボタンスイッチや、LEDを追加する必要があります。使っているチップが、nRF51822 と、同じなので、ポートの設定などを書き換えるかすれば、サンプルソフトが動作します。動作するにしても、通信相手は、スマートフォンなどになるものが多いためため、スマートフォン側に、受信ソフトが無いと、動いたか否かの確認をする手段がありません。
サンプルソフトの多くは、nRF51822 を通信端末、スマートフォンなどを通信センターにしたものですが、これらはすべて、ノルディックが用意したソフトデバイス( Soft Device )を、APIで呼び出すことで通信をおこなうようになっています。Soft Device は、ROMの0番地から90kBほどの領域にあり、割り込み処理なども管理下に置きます。このため、通信を行いながらタイマー割り込みなどを使う場合は、正確なインターバルにならない場合があります。というのは、Soft Device の通信処理が精密な時間管理のもとで行われるため、ユーザーが使う割り込み処理が後回しにされるからです。タイマー割り込みのインターバルが短いと( たとえば10msぐらい )、後回しにされるだけでなく、1秒で100回割り込みがあるはずのところ、95回とかになってしまうこともあります。ですから、正確な短い割り込み処理が必須のアプリケーションを、 nRF51822 で兼用することはできません。
サンプルソフトには、Soft Device を使うものと、そうでないものがあり、多くは Soft Device を使うものですが、2個の nRF51822 間で通信するには、Soft Device を使わないサンプルソフトの方が簡便で、すぐに動作確認ができます。ただ、こちらは、照明器のリモコンのような単純な通信を想定したもので、数ビットの情報を伝えるためだけのもので、通信相手が情報を受け取れたか否かの確認は、照明が点灯したかどうか、人間の判断にゆだねるものです。つまり、無線通信のデータが確実に相手に送れたかどうかは、サンプルソフトでは確認しません。操作側は、データを送りっぱなしなのです。
SDK( nrf51_sdk_v6_0_0_43681 )のインストールフォルダの中に、下図の様に、pca10001 フォルダに、button_radio_example と、led_radio_example があって、これらが、送信( button_radio_example )と受信( led_radio_example )です。送信側は押しボタンの状態を読んでON,OFFのデータを受信側に送り、受信側では、送られてきたデータに基づいてLEDを、ON,OFFするサンプルソフトです。
これらサンプルソフトでは、双方が、nRF51822 なので、スマートフォンなどは使いません。
このフォルダには、ほかに blinky_example( いわゆるLチカ )や、uart_example ( ターミナルソフトでエコーバックなど )などがあり、無線を使わずCPUの動作確認ができます。
いづれも、pca10001 と呼ばれている評価基板ではそのまま動作するもののようですが、今回入手した、BVMCN5103-BK KIT 評価基板では、ユニバーサル基板に取り付けるか、ブレッドボードに差し込んで、押しボタンスイッチなり、LEDなりを付けて動作させます。
サンプルソフト、button_radio_example フォルダの、main_tx.c を見ると、押しボタンポートの設定が2か所( BUTTON_0, BUTTON_1 )ありますが、これを、1個の押しボタンだけにします。というのは、BUTTON_1 で送信データを2にしているにも関わらず、受信側ソースを見ても、送られてきたデータの判定で、LEDの点灯状態を切り替える処理が無いからです。
BUTTON_0で示されているポートは、このフォルダのファイルを見ても設定らしきものが無いので、探すと nrf51_sdk_v6_0_0_43681\nRF51822\Include\bords\pca10001.h に #define BUTTON_0 16 と、16番ピンということが判ります。
サンプルソフト、main_tx.c のオリジナルは以下です。押しボタンポートは入力なので、ポート設定はプルアップを有効にしているだけです。
上記ソースを、BUTTON_0 が押されているか、否かだけの処理に変更し、さらに、BUTTON_0 の設定や、プルアップ設定を固定値にします。設定値を、インクルードするファイルに書くと、同じボードを使ってソフトを書く場合、共通に使えるので便利なのですが、ソースを別のパソコン上でも同じように使って同じ結果を出そうとすると、インクルードファイルを含めた開発環境のツリー構造もろともコピーしなければならず、バックアップファイルも大きくなってしまうので、できるだけ、ソースファイルを置くフォルダに、すべてを含むようにします。ボード依存の部分は、アプリケーションのソースと一体にしておくほうが、後で見てわかりやすいし、インクルードファイルのコピー漏れの事故を防ぎます。
以下は、改造後の動作テストしたソースです。
/* Copyright (c) 2009 Nordic Semiconductor. All Rights Reserved.
*
* The information contained herein is property of Nordic Semiconductor ASA.
* Terms and conditions of usage are described in detail in NORDIC
* SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT.
*
* Licensees are granted free, non-transferable use of the information. NO
* WARRANTY of ANY KIND is provided. This heading must NOT be removed from
* the file.
*
*/
/** @file
*
* @defgroup nrf_dev_button_radio_tx_example_main main.c
* @{
* @ingroup nrf_dev_button_radio_tx_example
*
* @brief Radio Transmitter Example Application main file.
*
* This file contains the source code for a sample application using the NRF_RADIO peripheral.
*
*/
#include
#include
#include "radio_config.h"
#include "nrf_gpio.h"
//#include "boards.h"
#include "bvmcn5103.h"// BVMCN test board
static uint8_t packet[4]; ///< Packet to transmit
void init(void)
{
/* Start 16 MHz crystal oscillator */
NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;
NRF_CLOCK->TASKS_HFCLKSTART = 1;
/* Wait for the external oscillator to start up */
while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0)
{
}
// Set radio configuration parameters
radio_configure();
// Set payload pointer
NRF_RADIO->PACKETPTR = (uint32_t)packet;
nrf_gpio_range_cfg_input(BUTTON_START, BUTTON_STOP, BUTTON_PULL);
nrf_gpio_range_cfg_output(LED_START, LED_STOP);
}
/**
* @brief Function for application main entry.
* @return 0. int return type required by ANSI/ISO standard.
*/
int main(void)
{
init();
uint8_t btn0_nstate; // Store new (current) state of button 0
// uint8_t btn1_nstate; // Store new (current) state of button 1
// uint8_t btn0_nstate = nrf_gpio_pin_read(BUTTON_0); // Store old (previous) state of button 0
// uint8_t btn1_ostate = nrf_gpio_pin_read(BUTTON_1); // Store old (previous) state of button 1
while (true)
{
uint8_t btns = '0';
btn0_nstate = nrf_gpio_pin_read(BUTTON_0);
// btn1_nstate = nrf_gpio_pin_read(BUTTON_1);
if ((btn0_nstate == 0))
{
btns |= 1;
}
// if ((btn1_ostate == 1) && (btn1_nstate == 0))
// {
// btns |= 2;
// }
// btn0_ostate = btn0_nstate;
// btn1_ostate = btn1_nstate;
// Place the read buttons in the payload, enable the radio and
// send the packet:
packet[0] = btns;
NRF_RADIO->EVENTS_READY = 0U;
NRF_RADIO->TASKS_TXEN = 1;
while (NRF_RADIO->EVENTS_READY == 0U)
{
}
if ((btn0_nstate == 0))
{
nrf_gpio_pin_clear(LED_1);
nrf_gpio_pin_set(LED_1);
}
else
{
nrf_gpio_pin_set(LED_1);
nrf_gpio_pin_clear(LED_1);
}
NRF_RADIO->TASKS_START = 1U;
NRF_RADIO->EVENTS_END = 0U;
while (NRF_RADIO->EVENTS_END == 0U)
{
}
NRF_RADIO->EVENTS_DISABLED = 0U;
// Disable radio
NRF_RADIO->TASKS_DISABLE = 1U;
while (NRF_RADIO->EVENTS_DISABLED == 0U)
{
}
}
}
/**
*@}
**/
改造箇所は、以下ですが、無線関連の設定は、一切変更なしです。
#include "boards.h" をコメントアウトし、#include "bvmcn5103.h" に変更。bvmcn5103.h は、pca10001.h からコピーしてこれも改造。
while (true) のすぐあと、uint8_t btns = 0; を
uint8_t btns = '0'; に変更。これは、受信側で、データの判定を、0 ではなく、'0' となっているため。
これは、ノルディックのサンプルソフトのバグです。
btn1_nstate = nrf_gpio_pin_read(BUTTON_1); をコメントアウトするなど、BUTTON_0 のみで操作し、受信側では、BUTTON_0 が押されている間、LEDが点灯するように改造。
送信側で、BUTTON_0 が押されている間、送信側にもLEDを付けて点灯するように改造。
これには、init(void) 関数に、nrf_gpio_range_cfg_output(LED_START, LED_STOP); を追加。LED_1 で示されるポートを出力ポートにしています。
LEDの点灯には工夫を凝らし、BUTTON_0 が押されている間、LEDは点灯しますが、一瞬消灯しています。反対にBUTTON_0 が離されているときは、一瞬点灯しています。これは、オシロで、送信開始タイミングを見るためです。
次に、受信側の改造をしますが、オリジナルのソースは以下です。
/* Copyright (c) 2009 Nordic Semiconductor. All Rights Reserved.
*
* The information contained herein is property of Nordic Semiconductor ASA.
* Terms and conditions of usage are described in detail in NORDIC
* SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT.
*
* Licensees are granted free, non-transferable use of the information. NO
* WARRANTY of ANY KIND is provided. This heading must NOT be removed from
* the file.
*
*/
/** @file
*
* @defgroup nrf_dev_led_radio_rx_example_main main.c
* @{
* @ingroup nrf_dev_led_radio_rx_example
* @brief Radio Receiver example Application main file.
*
* This file contains the source code for a sample application using the NRF_RADIO peripheral.
*
*/
#include
#include
#include "radio_config.h"
#include "nrf_gpio.h"
#include "boards.h"
static uint8_t volatile packet[4]; ///< Received packet buffer
void init(void)
{
/* Start 16 MHz crystal oscillator */
NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;
NRF_CLOCK->TASKS_HFCLKSTART = 1;
/* Wait for the external oscillator to start up */
while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0)
{
}
nrf_gpio_range_cfg_output(LED_START, LED_STOP);
// Set radio configuration parameters
radio_configure();
}
/**
* @brief Function for application main entry.
* @return 0. int return type required by ANSI/ISO standard.
*/
int main(void)
{
init();
while (true)
{
// Set payload pointer
NRF_RADIO->PACKETPTR = (uint32_t)packet;
NRF_RADIO->EVENTS_READY = 0U;
// Enable radio and wait for ready
NRF_RADIO->TASKS_RXEN = 1U;
while (NRF_RADIO->EVENTS_READY == 0U)
{
}
NRF_RADIO->EVENTS_END = 0U;
// Start listening and wait for address received event
NRF_RADIO->TASKS_START = 1U;
// Wait for end of packet
while (NRF_RADIO->EVENTS_END == 0U)
{
}
// Write received data to LED0 and LED1 on CRC match
if (NRF_RADIO->CRCSTATUS == 1U)
{
switch (packet[0])
{
case '0':
nrf_gpio_pin_set(LED_0);
nrf_gpio_pin_clear(LED_1);
break;
case '1':
nrf_gpio_pin_set(LED_1);
nrf_gpio_pin_clear(LED_0);
break;
}
}
NRF_RADIO->EVENTS_DISABLED = 0U;
// Disable radio
NRF_RADIO->TASKS_DISABLE = 1U;
while(NRF_RADIO->EVENTS_DISABLED == 0U)
{
}
}
}
/**
*@}
**/
以下は、改造後の受信側の、main_rx.c です。
/* Copyright (c) 2009 Nordic Semiconductor. All Rights Reserved.
*
* The information contained herein is property of Nordic Semiconductor ASA.
* Terms and conditions of usage are described in detail in NORDIC
* SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT.
*
* Licensees are granted free, non-transferable use of the information. NO
* WARRANTY of ANY KIND is provided. This heading must NOT be removed from
* the file.
*
*/
/** @file
*
* @defgroup nrf_dev_led_radio_rx_example_main main.c
* @{
* @ingroup nrf_dev_led_radio_rx_example
* @brief Radio Receiver example Application main file.
*
* This file contains the source code for a sample application using the NRF_RADIO peripheral.
*
*/
#include
#include
#include "radio_config.h"
#include "nrf_gpio.h"
//#include "boards.h"
#include "BVMCN5103.h"
static uint8_t volatile packet[4]; ///< Received packet buffer
void init(void)
{
/* Start 16 MHz crystal oscillator */
NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;
NRF_CLOCK->TASKS_HFCLKSTART = 1;
/* Wait for the external oscillator to start up */
while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0)
{
}
nrf_gpio_range_cfg_output(LED_START, LED_STOP);
// Set radio configuration parameters
radio_configure();
}
/**
* @brief Function for application main entry.
* @return 0. int return type required by ANSI/ISO standard.
*/
int main(void)
{
init();
//nrf_gpio_pin_set(LED_1);
while (true)
{
// Set payload pointer
NRF_RADIO->PACKETPTR = (uint32_t)packet;
NRF_RADIO->EVENTS_READY = 0U;
// Enable radio and wait for ready
NRF_RADIO->TASKS_RXEN = 1U;
while (NRF_RADIO->EVENTS_READY == 0U)
{
}
NRF_RADIO->EVENTS_END = 0U;
// Start listening and wait for address received event
NRF_RADIO->TASKS_START = 1U;
// Wait for end of packet
while (NRF_RADIO->EVENTS_END == 0U)
{
}
// Write received data to LED0 and LED1 on CRC match
if (NRF_RADIO->CRCSTATUS == 1U)
{
switch (packet[0])
{
case '1':
nrf_gpio_pin_clear(LED_1);
nrf_gpio_pin_set(LED_1);
break;
case '0':
nrf_gpio_pin_set(LED_1);
nrf_gpio_pin_clear(LED_1);
break;
}
}
NRF_RADIO->EVENTS_DISABLED = 0U;
// Disable radio
NRF_RADIO->TASKS_DISABLE = 1U;
while(NRF_RADIO->EVENTS_DISABLED == 0U)
{
}
}
}
/**
*@}
**/
こちらも、LEDの点灯の部分で、受信判定直後に、Lowか、Highのパルスがでるようにしています。
送信側の、main_tx.c と、受信側の、main_rx.c で、共通に使っているボード依存のヘッダーファイル、BVMCN5103.h は以下です。
/* Copyright (c) 2012 Nordic Semiconductor. All Rights Reserved.
*
* The information contained herein is property of Nordic Semiconductor ASA.
* Terms and conditions of usage are described in detail in NORDIC
* SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT.
*
* Licensees are granted free, non-transferable use of the information. NO
* WARRANTY of ANY KIND is provided. This heading must NOT be removed from
* the file.
*
modified by bitcraft
*/
#ifndef BVMCN5103_H
#define BVMCN5103_H
#include "nrf_gpio.h"
#define LED_START 18
#define LED_0 3
#define LED_1 19
#define LED_STOP 19
#define BUTTON_START 16
#define BUTTON_0 16
#define BUTTON_1 17
#define BUTTON_STOP 17
#define BUTTON_PULL NRF_GPIO_PIN_PULLUP
#define RX_PIN_NUMBER 13
#define TX_PIN_NUMBER 12
#define CTS_PIN_NUMBER 1
#define RTS_PIN_NUMBER 18
#define HWFC true
#define SPIS_MISO_PIN 20 // SPI MISO signal.
#define SPIS_CSN_PIN 21 // SPI CSN signal.
#define SPIS_MOSI_PIN 22 // SPI MOSI signal.
#define SPIS_SCK_PIN 23 // SPI SCK signal.
#define SPIM0_SCK_PIN 23u /**< SPI clock GPIO pin number. */
#define SPIM0_MOSI_PIN 20u /**< SPI Master Out Slave In GPIO pin number. */
#define SPIM0_MISO_PIN 22u /**< SPI Master In Slave Out GPIO pin number. */
#define SPIM0_SS_PIN 21u /**< SPI Slave Select GPIO pin number. */
#define SPIM1_SCK_PIN 16u /**< SPI clock GPIO pin number. */
#define SPIM1_MOSI_PIN 18u /**< SPI Master Out Slave In GPIO pin number. */
#define SPIM1_MISO_PIN 17u /**< SPI Master In Slave Out GPIO pin number. */
#define SPIM1_SS_PIN 19u /**< SPI Slave Select GPIO pin number. */
// serialization APPLICATION board
// UART
// this configuration works with the SPI wires setup
#define SER_APP_RX_PIN 20 // UART RX pin number.
#define SER_APP_TX_PIN 22 // UART TX pin number.
#define SER_APP_CTS_PIN 23 // UART Clear To Send pin number.
#define SER_APP_RTS_PIN 21 // UART Request To Send pin number.
// SPI
#if 0
#define SER_APP_SPIM0_SCK_PIN 20 // SPI clock GPIO pin number.
#define SER_APP_SPIM0_MOSI_PIN 17 // SPI Master Out Slave In GPIO pin number
#define SER_APP_SPIM0_MISO_PIN 16 // SPI Master In Slave Out GPIO pin number
#define SER_APP_SPIM0_SS_PIN 21 // SPI Slave Select GPIO pin number
#define SER_APP_SPIM0_RDY_PIN 19 // SPI READY GPIO pin number
#define SER_APP_SPIM0_REQ_PIN 18 // SPI REQUEST GPIO pin number
#else
#define SER_APP_SPIM0_SCK_PIN 23 // SPI clock GPIO pin number.
#define SER_APP_SPIM0_MOSI_PIN 20 // SPI Master Out Slave In GPIO pin number
#define SER_APP_SPIM0_MISO_PIN 22 // SPI Master In Slave Out GPIO pin number
#define SER_APP_SPIM0_SS_PIN 21 // SPI Slave Select GPIO pin number
#define SER_APP_SPIM0_RDY_PIN 25 // SPI READY GPIO pin number
#define SER_APP_SPIM0_REQ_PIN 24 // SPI REQUEST GPIO pin number
#endif
// serialization CONNECTIVITY board
// UART
#if 0
#define SER_CON_RX_PIN 22 // UART RX pin number.
#define SER_CON_TX_PIN 20 // UART TX pin number.
#define SER_CON_CTS_PIN 21 // UART Clear To Send pin number. Not used if HWFC is set to false.
#define SER_CON_RTS_PIN 23 // UART Request To Send pin number. Not used if HWFC is set to false.
#else
// this configuration works with the SPI wires setup
#define SER_CON_RX_PIN 20 // UART RX pin number.
#define SER_CON_TX_PIN 22 // UART TX pin number.
#define SER_CON_CTS_PIN 21 // UART Clear To Send pin number. Not used if HWFC is set to false.
#define SER_CON_RTS_PIN 23 // UART Request To Send pin number. Not used if HWFC is set to false.
#endif
//SPI
#if 0
#define SER_CON_SPIS_SCK_PIN 20 // SPI SCK signal.
#define SER_CON_SPIS_MISO_PIN 16 // SPI MISO signal.
#define SER_CON_SPIS_MOSI_PIN 17 // SPI MOSI signal.
#define SER_CON_SPIS_CSN_PIN 21 // SPI CSN signal.
#define SER_CON_SPIS_RDY_PIN 19 // SPI READY GPIO pin number.
#define SER_CON_SPIS_REQ_PIN 18 // SPI REQUEST GPIO pin number.
#else
#define SER_CON_SPIS_SCK_PIN 23 // SPI SCK signal.
#define SER_CON_SPIS_MOSI_PIN 22 // SPI MOSI signal.
#define SER_CON_SPIS_MISO_PIN 20 // SPI MISO signal.
#define SER_CON_SPIS_CSN_PIN 21 // SPI CSN signal.
#define SER_CON_SPIS_RDY_PIN 25 // SPI READY GPIO pin number.
#define SER_CON_SPIS_REQ_PIN 24 // SPI REQUEST GPIO pin number.
#endif
#define SER_CONN_ASSERT_LED_PIN LED_0
#endif
以下は、動作させて、LEDの部分と、無線電波の様子をオシロで観察したものです。
黄色:送信側LED ボタンを押していないとき。TASKS_START = 1U にしてから、High、送信終了でLow。
水色:受信側LED 受信完了で、Highのパルス
桃色:2.4GHzの電波が出ている状況。この画面キャプチャーは取引会社のオシロを借りて撮ったものです。
帯域2.5GHzというスペックです。450万ほどしたそうな。
まず、UARTの動作を確かめる
当URLでは、使用するマイコンなどには、UARTで操作できるモニタを移植して、動作確認するだけでなく、動作中のマイコンのメモリやポートを参照できるようにしていますが、nRF51822 にも移植してみます。制御マイコンは Cortex-M0 なので、UARTの入出力ができれば移植は簡単に行えます。ただ、モニタを移植すると、押しボタンスイッチの状態を監視するには、それなりの工夫が必要です。というのは、サンプルソフトの送信プログラムでは、押しボタンの状態を読んですぐに送信という、永久ループになっているので、UARTからのコマンドが読めなくなってしまいます。
そこで、モニタプログラムでUARTの受信チェックをポーリングするところで ( getcon() 関数 )、押しボタンスイッチを監視する処理も入れ、UARTから受信が無い間は、押しボタンの状態に応じて無線で送信する処理をするという流れにします。モニタの処理のほとんどの時間は、UARTからの受信待ちになるので、この間で無線処理をすませます。
UARTを操作するには、nRF51822 のマニュアルを頼りに、ポートをたたくという作業になるのですが、SDK( nrf51_sdk_v6_0_0_43681 )の中には、uart_example フォルダに、main.c という名のサンプルソフトがあるので、これを利用します。
uart_example フォルダの、main.c を見ると、UARTの初期設定と思われる関数を呼び出しています。106行に、
simple_uart_config(RTS_PIN_NUMBER, TX_PIN_NUMBER, CTS_PIN_NUMBER, RX_PIN_NUMBER, HWFC);
の記述があります。シンボル名から、どうやらUARTのピンを指定しているのが推測できますが、いわゆるボーレートとか、ビット数、パリティーなどの設定は、main.c の中には、見当たりません。この simple_uart_config のあと、uart_start(); を呼び出して、その中で、simple_uart_putstring を実行して、文字列を出力しています。では、simple_uart_config とか、simple_uart_putstring はどこに記述されているのか、uart_example フォルダを見ても無いので、main.c のインクルードファイル、"simple_uart.h" を探して中身を見ても手がかりはありません。そこで、uart と名の付くファイルを探すと、nrf51_sdk_v6_0_0_43681\nRF51822\Source\simple_uart というフォルダがあって、そこに simple_uart.c というソースファイルがあります。ここに、simple_uart_config と、simple_uart_putstring の関数がありました。この、simple_uart.c には、ほかにも、1文字出力とか、1文字入力待ちなどがあり、それらは、直接UARTデバイスのレジスタを直接操作しているので、これらを、UARTのBIOSとして使えそうです。
送信テストプログラム、main_tx.c に、simple_uart_config と、simple_uart_putstring を、main() の起動時に実行させ、指定ポートから、UART信号が出るか、確認します。ここで、TX_PIN_NUMBER とかを、ヘッダーから参照するのではなく、以下のように、直接数値で書いてみます。
simple_uart_config( 16, 12, 1, 13, 0);// HWFC は1だと、RTS,CTSが有効になるので、0にして無効にします。
送信:12 ( P0_12 )、 受信:13 ( P0_13 )
また、simple_uart.c を、main_tx.c のあるフォルダにコピーし、107行の、UART_BAUDRATE_BAUDRATE_Baud38400 を UART_BAUDRATE_BAUDRATE_Baud115200 に変更して、ボーレートを115200に変えます。コピーした、simple_uart.c は、KEILのプロジェクトファイル管理の( Project タブ ) app を右クリックして、add Existing Files to Group 'app' を選択してソースとして有効化します。
改造して、UARTを有効化し、起動時に、Start: と出力し,その後、UARTターミナルソフトから1文字送信すると、エコーバックします。
simple_uart.c には、UARTから受信があったか、否かを判定する関数がないので、simple_uart_rxready() なるものを追加します。UARTから受信が無いと0をリターンし、あれば1をリターンします。
// simple_uart.c に追加
uint8_t simple_uart_rxready(void)
{
return (NRF_UART0->EVENTS_RXDRDY & 1);
}
改造した、main_tx.c
/* Copyright (c) 2009 Nordic Semiconductor. All Rights Reserved.
*
* The information contained herein is property of Nordic Semiconductor ASA.
* Terms and conditions of usage are described in detail in NORDIC
* SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT.
*
* Licensees are granted free, non-transferable use of the information. NO
* WARRANTY of ANY KIND is provided. This heading must NOT be removed from
* the file.
*
*/
/** @file
*
* @defgroup nrf_dev_button_radio_tx_example_main main.c
* @{
* @ingroup nrf_dev_button_radio_tx_example
*
* @brief Radio Transmitter Example Application main file.
*
* This file contains the source code for a sample application using the NRF_RADIO peripheral.
*
*/
#include
#include
#include "radio_config.h"
#include "nrf_gpio.h"
#include "simple_uart.h"
//#include "boards.h"
#include "bvmcn5103.h"// BVMCN test board
static uint8_t packet[4]; ///< Packet to transmit
extern uint8_t simple_uart_rxready(void);
void init(void)
{
/* Start 16 MHz crystal oscillator */
NRF_CLOCK->EVENTS_HFCLKSTARTED = 0;
NRF_CLOCK->TASKS_HFCLKSTART = 1;
/* Wait for the external oscillator to start up */
while (NRF_CLOCK->EVENTS_HFCLKSTARTED == 0)
{
}
// Set radio configuration parameters
radio_configure();
// Set payload pointer
NRF_RADIO->PACKETPTR = (uint32_t)packet;
nrf_gpio_range_cfg_input(BUTTON_START, BUTTON_STOP, BUTTON_PULL);
nrf_gpio_range_cfg_output(LED_START, LED_STOP);
}
/**
* @brief Function for application main entry.
* @return 0. int return type required by ANSI/ISO standard.
*/
int main(void)
{
init();
uint8_t btn0_nstate; // Store new (current) state of button 0
// uint8_t btn1_nstate; // Store new (current) state of button 1
// uint8_t btn0_nstate = nrf_gpio_pin_read(BUTTON_0); // Store old (previous) state of button 0
// uint8_t btn1_ostate = nrf_gpio_pin_read(BUTTON_1); // Store old (previous) state of button 1
simple_uart_config(16, 12, 1, 13, 0);
simple_uart_putstring((const uint8_t *)" \n\rStart: ");
while (true)
{
uint8_t btns = '0';
btn0_nstate = nrf_gpio_pin_read(BUTTON_0);
// btn1_nstate = nrf_gpio_pin_read(BUTTON_1);
if ((btn0_nstate == 0))
{
btns |= 1;
}
// if ((btn1_ostate == 1) && (btn1_nstate == 0))
// {
// btns |= 2;
// }
// btn0_ostate = btn0_nstate;
// btn1_ostate = btn1_nstate;
// Place the read buttons in the payload, enable the radio and
// send the packet:
packet[0] = btns;
NRF_RADIO->EVENTS_READY = 0U;
NRF_RADIO->TASKS_TXEN = 1;
while (NRF_RADIO->EVENTS_READY == 0U)
{
}
if ((btn0_nstate == 0))
{
nrf_gpio_pin_clear(LED_1);
}
else
{
nrf_gpio_pin_set(LED_1);
}
NRF_RADIO->TASKS_START = 1U;
NRF_RADIO->EVENTS_END = 0U;
while (NRF_RADIO->EVENTS_END == 0U)
{
}
NRF_RADIO->EVENTS_DISABLED = 0U;
if ((btn0_nstate == 0))
{
nrf_gpio_pin_set(LED_1);
}
else
{
nrf_gpio_pin_clear(LED_1);
}
// Disable radio
NRF_RADIO->TASKS_DISABLE = 1U;
while (NRF_RADIO->EVENTS_DISABLED == 0U)
{
}
if(simple_uart_rxready())
{
simple_uart_put(simple_uart_get());
}
}
}
/**
*@}
**/
UARTの動作を確かめられたら、モニタを追加
モニタでは、UARTの入力チェックで、コマンド待ちでループしてしまうので、無線の受信待ちでのループと両方の待ち状態を維持するには、ちょっとした工夫が必要になります。
無線の受信待ち中、もし無線を受信できない場合、UARTの受信チェックができなくなるので、無線受信待ちのループの中に、UARTの受信チェックを入れ、もしUARTからの受信があると、無線の受信待ちのループから抜け出るようにする。
無線の受信待ち中に抜け出る場合、再開できるように、フラグを1にし、UARTの受信処理が済んだ後、再びUART受信待ちに来た場合、フラグが1だと、無線受信待ちループの場所にジャンプする。
上記のジャンプの前に、フラグを消しておき、無線受信ループを再開する。
以上の処理をする、モニタのUART受信部分は以下です。
char getcon()
{
while (NRF_UART0->EVENTS_RXDRDY != 1)// 1文字入力があるまで、無線受信
{
if(waitrcvradiof)// 無線受信待ちループ中に、UART受信で抜け出た
{
waitrcvradiof = 0;// clear flag
goto WAITRADIOF;// 無線受信待ちループからUART受信で抜けたので、再開
}
// Set payload pointer
NRF_RADIO->PACKETPTR = (uint32_t)packet;
NRF_RADIO->EVENTS_READY = 0U;
// Enable radio and wait for ready
NRF_RADIO->TASKS_RXEN = 1U;
while (NRF_RADIO->EVENTS_READY == 0U)
{
}
NRF_RADIO->EVENTS_END = 0U;
// Start listening and wait for address received event
NRF_RADIO->TASKS_START = 1U;
// Wait for end of packet
WAITRADIOF:// 無線受信待ちループの再開位置
while (NRF_RADIO->EVENTS_END == 0U)// このループは無線受信が無いと永久に終わらない
{
if(NRF_UART0->EVENTS_RXDRDY == 1)
{
waitrcvradiof = 1;// 無線受信待ちフェーズを記憶
NRF_UART0->EVENTS_RXDRDY = 0;
return (uint8_t)NRF_UART0->RXD;// とりあえずUART受信の処理
}
}
// Write received data to LED0 and LED1 on CRC match
if (NRF_RADIO->CRCSTATUS == 1U)
{
switch (packet[0])
{
case '1':
nrf_gpio_pin_set(LED_1);
// nrf_gpio_pin_clear(LED_1);
break;
case '0':
nrf_gpio_pin_clear(LED_1);
// nrf_gpio_pin_clear(LED_0);
break;
}
}
NRF_RADIO->EVENTS_DISABLED = 0U;
// Disable radio
NRF_RADIO->TASKS_DISABLE = 1U;
while(NRF_RADIO->EVENTS_DISABLED == 0U)
{
}
}
NRF_UART0->EVENTS_RXDRDY = 0;
return (uint8_t)NRF_UART0->RXD;// UART受信データをリターン
}
送信側は、UART受信待ちの間、送信を行うだけなので、フラグを設けなくても送信しながらモニタのUART受信の両方を処理できます。
以下は、送信側での、getcon() です。
char getcon()
{
uint8_t btn0_nstate; // Store new (current) state of button 0
while (NRF_UART0->EVENTS_RXDRDY != 1)
{
uint8_t btns = '0';
btn0_nstate = nrf_gpio_pin_read(BUTTON_0);
if ((btn0_nstate == 0))
{
btns |= 1;// '1'
}
// Place the read buttons in the payload, enable the radio and
// send the packet:
packet[0] = btns;
NRF_RADIO->EVENTS_READY = 0U;
NRF_RADIO->TASKS_TXEN = 1;
while (NRF_RADIO->EVENTS_READY == 0U)
{
}
if ((btn0_nstate == 0))
{
nrf_gpio_pin_clear(LED_1);
}
else
{
nrf_gpio_pin_set(LED_1);
}
NRF_RADIO->TASKS_START = 1U;
NRF_RADIO->EVENTS_END = 0U;
while (NRF_RADIO->EVENTS_END == 0U)
{
}
NRF_RADIO->EVENTS_DISABLED = 0U;
if ((btn0_nstate == 0))
{
nrf_gpio_pin_set(LED_1);
}
else
{
nrf_gpio_pin_clear(LED_1);
}
// Disable radio
NRF_RADIO->TASKS_DISABLE = 1U;
while (NRF_RADIO->EVENTS_DISABLED == 0U)
{
}
}
NRF_UART0->EVENTS_RXDRDY = 0;
return (uint8_t)NRF_UART0->RXD;
}
送信側のソースです。すべてを含んでいませんが、残りは、SDK( nrf51_sdk_v6_0_0_43681 )の中にあります。
送信側ソース button_radio_moni.lzh
受信側のソースです。すべてを含んでいませんが、残りは、SDK( nrf51_sdk_v6_0_0_43681 )の中にあります。
受信側ソース led_radio_moni.lzh
モニタを実装したあと、タイマーで割り込みを発生させて、100msのインターバルで、送信を行ってみます。モニタを実装した段階では、無線の送信は、UARTの受信待ちの間、最短時間のループで送信するため、チャンネルを占有するようになってしまいます。BLEでは、できるだけチャンネルを占有しないように、チャンネルを切り替えて通信するようにしているので、今までのプログラムでは、その定石に違反してしまいます。ノルディックが提供しているソフトデバイスも、チャンネルを占有しないような、周波数ホッピングと呼ばれる方法で操作されています。周波数ホッピングの実装は簡単ではないので、せめて送信インターバルを長くして、他のBLE機器への電波妨害を低減します。
100msのインターバルで送信する機能を追加するにあたり、以下の機能も入れます。
1msで割り込みを発生させ、ms単位の正確なカウンタを実装。1000でクリアし、秒カウンタを更新。
100msで送信タイムフラグを立て、1ms間隔で8回送信するような、カウンタを設定する。
100ms毎に、8回送信するという動作にする。これは、次の機能、「1ms精度でタイマー同期」を実現するための準備。
通信内容(ペイロード)を8バイトまで増やす。これは、送信側と受信側と、同時に変更しないと、通信できない。
増やした通信内容(ペイロード)にはまだ何も入れませんが、後に1msカウンタの値を入れる。
通信内容(ペイロード)の増加設定は、radio_config.c の、#define PACKET1_STATIC_LENGTH と、PACKET1_PAYLOAD_SIZE を8にする。
以上のような機能を入れたものが、以下のソースです。実行ファイル(HEX)も同梱しています。
送信側のソースです。すべてを含んでいませんが、残りは、SDK( nrf51_sdk_v6_0_0_43681 )の中にあります。
送信側ソース button_radio_moni_timer.lzh
受信側のソースです。すべてを含んでいませんが、残りは、SDK( nrf51_sdk_v6_0_0_43681 )の中にあります。
受信側ソース led_radio_moni_timer.lzh
以下は、button_radio_moni_timer.lzh を走らせて、PCとターミナルソフトで接続し、ワークメモリ部分の先頭から、128バイトをダンプしたものです。メモリダンプのあと、0x2000201C番地を、ロングワードで表示しています。( コマンドは、 sl20002000(enter) ) この番地は、タイマー割り込み処理 void TIMER2_IRQHandler(void) で、1msでカウントアップしている変数、timer2intcount の番地です。
UARTは、TTLレベルのシリアル信号を、USBでPCと接続しています。
USB-serial と BVMCN5103 との接続
信号名
BVMCN5103 ピン
nRF51822 port
USB-serial ピン( 秋月電子通商 )
TXD ( nRF51822 )
6
P0_12
5 ( RXD )
RXD ( nRF51822 )
4
P0_13
4 ( TXD )
LED_1 ( nRF51822 )
1
P0_19
-
BUTTON_0 ( nRF51822 )
2
P0_16
-
送信側と、受信側の1msカウンタの値を、無線を使ってほぼ同じ値に維持するテストです。まず1msカウンタの値を使って、1秒周期で点滅するLEDを追加します。カウンタが500になると点灯し、1000になるとカウンタをクリアすると同時に、LEDも消灯するようにすると、両方同時に電源を入れた場合は、ほぼ同期して点滅しますが、長時間そのままにしておくと、同期がずれてきます。双方に周波数精度の高い水晶発振器があっても、数時間もすれば肉眼でもずれがわかります。BVMCN5103 のスペックを見ると、最大30ppmの精度の水晶が使われているみたいですが、30ppmだとすると、約3万秒で最大1秒の差があるということなので1日86400秒では3秒近いずれが生じます。約8時間で1秒、つまり4時間で、点滅の状態が反転状態になるほどずれることになります。30ppmというのは、温度変化を考慮した最大値なので、双方が同じ温度なら、もっと精度が高くなります。
ではどれほどずれるのか、まず1msカウンタの値でLEDを点滅させ、テストしてみると、1時間で、LED点灯開始に、20msの差ができました。これは、1日ではほぼ0.5秒の差になり、約5ppmの相互精度でした。
測定は、2台同時に電源ONし、ほぼ同期している状態で点滅LEDの出力ポートをオシロで時間差を確認します。
次に、LEDの点灯時刻が早い方に USB-serial でターミナルで操作できるようにします。
NRF51822-Bug>sl4000a540 ( enter ) で、
4000A540 00003E80 と表示されるので、ここを、3E90 に変更します。
すると、カウンタがマッチする時間が、1/1000 ほど伸び、1秒が、1.0001秒になり、オシロで観察すると、点灯開始が遅れてゆき、やがて点灯開始がぴったり一致するところまで来ます。
ここで、0x4000A540 を、3E80 に戻すと、そこで1秒に戻り、しばらく同期したままになります。
1時間ほど放置していると、20msほどずれ、送信側と受信側の水晶の精度差が確認できます。
ここで、0x4000A540番地ですが、TIMER2 のコンペアレジスタのアドレスで、カウンタがこの値になると、0クリアされ、1ms割り込みが発生しします。
1msでカウントするカウンタを実装し、LEDを点滅させる送信側と、受信側のスースです。
このソースから、いままでのものから、無線部分の設定( radio_config.c )を変更しています。
NRF_RADIO->MODE = (RADIO_MODE_MODE_Nrf_2Mbit << RADIO_MODE_MODE_Pos); から、
NRF_RADIO->MODE = (RADIO_MODE_MODE_Ble_1Mbit << RADIO_MODE_MODE_Pos); に変更。BLEで普通に使われているビットレート。
NRF_RADIO->FREQUENCY = 7UL; から、NRF_RADIO->FREQUENCY = 4UL; 4 で、2404MHzになり、BLEのch1になります。
送信側のソースです。すべてを含んでいませんが、残りは、SDK( nrf51_sdk_v6_0_0_43681 )の中にあります。
送信側ソース button_radio_1msdata.lzh
受信側のソースです。すべてを含んでいませんが、残りは、SDK( nrf51_sdk_v6_0_0_43681 )の中にあります。
受信側ソース led_radio_1msdata.lzh
1msでカウントするタイマーでLEDの点滅ができたところでさらに改造
受信した送信側の1msカウンタの値で、LEDを点灯するタイミング付近( 500〜508 )の受信データのとき、受信側の1msカウンタの値と比較すると、50X以下と、50X以上の場合があります。送信側は、500,501,502,503,504,505,506,507,508のとき送信するので、これと比較して、小さいと、受信側が遅れているので、1msのインターバルを少しだけ小さくして、LED点灯を早めます。逆に、大きいと受信側が進んでいるので、インターバルを1msより長くして、LEDの点灯を遅らせます。
これらの処理をするにあたり、送信側で、押しボタンを押したとき、packet[0] が、'1' になっているので、このとき受信側で、インターバルの設定を変えてタイマーを調整します。大きな差があれば、タイマーの調整値を大きくして、早く点灯タイミングがずれるようにします。
どれくらいずらせるかは、ずれの大きさによって変化させ、LED点灯タイミングが近づいてくると、タイマーの調整値を小さくします。
2ms以内になると、1msのインターバルを、1.0000625msか、0.9999375msにして、ゆっくり変化するようにします。
ちょうど一致している場合、1msのインターバルを1msちょうどにします。
送信側の押しボタンが離されると、1msのインターバルは、1msに戻します。
受信側のソースです。すべてを含んでいませんが、残りは、SDK( nrf51_sdk_v6_0_0_43681 )の中にあります。
受信側ソース led_radio_adjust.lzh
Soft Device ( s110 ) を使ってスマートフォンとUART通信
ここまでの例では、 Soft Device を使わない通信でしたが、今度は、 Soft Device のプロトコルスタックを使って、信頼性の高い通信のサンプルを動かしてみます。
SDK( nrf51_sdk_v6_0_0_43681 )のフォルダをたどると、
nrf51_sdk_v6_0_0_43681\nRF51822\Board\pca10001\s110\experimental\ble_app_uart
というフォルダがあり、ここにUARTで、スマートフォンと通信できるサンプルがあります。experimental というフォルダ名があるので、サンプルとしては、まだ確定して提供していないということですが、正常に動作するようです。nRF51822 はペリフェラルの設定です。スマートフォン側のアプリは、ノルディックからダウンロードできます。
スマートフォン側のアプリ android nRF Toolbox for BLE
このソフトを起動すると、以下のような操作画面になります。 ( android )
nRF51822 側では、以下の s110_ble_app_uart.lzh を実装して電源を入れ、一番下の、9ピンのDSUBコネクタのアイコンをタップすると、スマートフォンの画面は以下のようになります。画面の中央の、AVALABLE DEVICES: の、Nordic_UAR1 は、ソースの、main.c に記述しています。また、その下の、CA:A1:EB:B6:E3:69 は、nRF51822 のフラッシュメモリの中に書かれている、MACアドレスです。(MACアドレスは、フラッシュメモリに書かれていてライターでも書き換えることはできません。アプリケーションから読み出しは可能です。)
ここで、Nordic_UAR1 をタップしてしばらくすると、Connected to CA:A1:EB:B6:E3:69 と、接続完了のメッセージが出て、UARTの送受信できることが報告されます。
ここで、パソコンのターミナルソフトから、"abcdefg" (enter) と入力すると、"abcdefg " received となって、スマートフォンで受信したことがわかります。
ここで、"abcdefg " と、"g" の右がスペースのように見えますが、実は、( enter ) の、0x0D です。ソースの、main.c を見ると、送信するとき、( enter ) の、0x0D も含めて送信しているからです。
送信側 ( peripheral ) のソースです。すべてを含んでいませんが、残りは、SDK( nrf51_sdk_v6_0_0_43681 )の中にあります。
送信側ソース s110_ble_app_uart.lzh
このソースファイルは、TOPフォルダが s110 になっており、nrf51_sdk_v6_0_0_43681\nRF51822\Board\pca10001\s110\( ここ )に置きます。KEIL のプロジェクトファイルには、ファイルの存在場所のツリー構造も含んでいるので、ソースとプロジェクトファイルの設置場所を合わせておかないと、起動時に「ファイルが無い」というマークが付いてしまい、新たにファイルの存在場所を指定しないと、build できません。
SDK( nrf51_sdk_v6_0_0_43681 )のサンプルソフトから、少し変更しています。
UARTのポートは、いままで解説してきたソースに合わせています。
UARTポートは、USB-serial 変換で接続し、パソコンのターミナルソフトで送受信を確認します。
通信相手は、上述の スマートフォン用アプリを立ち上げ、接続しますが、CONNECTION開始画面で、Nordic_UAR1 になるように、機器名を、Nordic_UART から、Nordic_UAR1 に変更しています。( main.c の 52行 )
Soft Device ( s110, s120 ) を使って nRF51822 相互にUART通信
通信相手がスマートフォンではなく、2台の nRF51822 間で、UARTの通信を行います。SDK( nrf51_sdk_v6_0_0_43681 )には、nRF51822 相互のUART通信例がなく、SDK( nrf51_sdk_v6_0_0_43681 )に HRM セントラルの例を見つけたので、それと、HRM ペリフェラル(心拍数計測のペリフェラル)とで通信します。通信プロトコルは、HRM なのですが、ペイロードとして使える20バイトのパケットに、UARTで送りたい文字を乗せると、通信できます。最新のSDK( nRF51_SDK_10.0.0_dc26b5e )には、ble_app_uart_c なるフォルダに、UARTセントラルのサンプルがあるようですが、これはまたあとでテストしたいと思います。
心拍数計測のセントラルのサンプルは、
nrf51_sdk_v6_0_0_43681\nRF51822\Board\nrf6310\s120\experimental\ble_app_hrs_c\ フォルダにあります。
これを改造して、心拍数のデータではなく、UARTのデータを送受信しますが、普通のUARTの1バイト単位の通信を行うとパフォーマンスが20分の1に低下してしまいます。これは、心拍数のデータを送る場合でも、ユーザーが使えるペイロードは20バイトあるので、ここに、心拍数のデータを載せても、せいぜい2バイトに収まり、残り18バイトはごみになります。UARTの場合、送りたいデータが1バイト単位なので、1バイトの送信要求があると、直ちに送る必要があり、残り19バイトの空きが、無駄になってしまうからです。
そこでADコンバータから読んだデータを、定期的に送るような場合を想定し、1サンプル毎に送るのではなく、20バイト分溜まった時点で送るようにします。BLEの無線伝送能力の上限に近い20パケット/秒で送信し、400バイト/秒の無線伝送ができる雛形を作ってみました。
以下が、20バイト単位で、UARTで相互伝送できる、nRF51822 を使った、ペリフェラルと、セントラルのプロジェクト一式です。
セントラル側のソースは、 こちらを元に変更しています
送受信ソース BVMCN5103_uart_peri_cent.lzh UARTのポート送信:P0_12, 受信:P0_13
以下は、UARTのポートを、送信:P0_13, 受信:P0_12 に変更したもの。基板を作成するとき、以下の割り当てになってしまった。
送受信ソース BVMCN5103_uartx_peri_cent.lzh
このファイルは、解凍して以下の位置に置きます。場所を間違えると、KEILでプロジェクトを開いても、ファイルの所在が判らないマークが付き、コンパイルができません。
nrf51_sdk_v6_0_0_43681\nRF51822\Board\( ここに、BVMCN5103 フォルダを置く )
UARTからのデータを伝送中の、送信側と、受信側の消費電流
消費電流を測定するには、電流プローブが必要ですが、ここでは、BVMCN5103 の電源供給ピンに、3.3V電源を直接つなぐのではなく、9Ωの抵抗を間に入れて、その間の電圧をオシロで観察します。BVMCN5103 は、電波を使っている間、18mAほどの電流が流れるので、9Ωだと、160mVほどの電圧になり、様子を観察できます。
以下は、4chのオシロを借りて測定したスクリーンショットです。かなりノイズが重畳していますが、様子は判ります。
黄色:送信側の9Ωの両端電圧
桃色:送信するデータを受け取るUART信号
水色:受信側の9Ωの両端電圧
緑色:受信して出力したUART信号
このスクリーンショットは、パソコンからUARTで、1パケット20バイトを、送信側に1秒に20回送っているときのものです。インターバルは50msなのですが、パソコン側での時間管理が正確には行えないので、インターバルは、30msから66msの間で変動しています。総計では1秒に約20回になるはずで、長時間(1時間)でのずれは起らないようにしています。( MFC の、OnTimer 関数を20msで起動し、処理は、timeGetTime();を使って、50msインターバルにしている)
無線を使うインターバルが30msで一定で、送信側、受信側ともに同期しているのを観察できます。つまり、ノルディックの SoftDevice を使うと、BLEの通信は、30msインターバルで行われ、お互いにそのタイミングから外れないことです。UARTで通信データを送信側に送らなくても、無線通信は常に30msインターバルで行われており、その場合、受信側はUARTに何も出力しません。
電波を使っているときは、18mAほど流れますが、平均すると、送信側で約1.3mA、受信側で約2mAとなり、この電流は1秒に400バイトのデータを伝送してもあまり増加しません。受信側が大きいのは、UARTで出す処理を、ここではポーリングで行っているからと思われます。送信側ではUARTを割り込み処理にしているので、受け取ればCPUが低消費電力モードに移行するからと推測されます。(power_manage(); 関数を呼び出している )
nRF51822 を使って相互にUART通信 ペリフェラルの名前とMACアドレス表示
nRF51822 で相互通信する場合、いままでの例では、複数のペリフェラルの中、特定のペリフェラルだけ選択して、ペアリングするという機能が無いので、どのペリフェラルとつながるか予測できません。特定のペリフェラルだけとペアリングするには、MACアドレスか、ペリフェラルの名前で区別する方法があります。MACアドレスで区別する場合、あらかじめMACアドレスを、セントラル側が知っておく必要があります。ペリフェラルの名前で区別する場合は、ペリフェラル側のソフトで個別に名前を設定できなければなりません。
ペリフェラルの名前は、スマートフォンでは、ペリフェラルの名前とMACアドレスの表示で確認 のように表示されるのですが、これを、nRF51822 のセントラル側で確認できるようにします。
スマートフォンでは、以下のように、接続(ペアリング)のまえに選択、確認画面が出る。
ペリフェラルで、名前を設定しているところ。main.c の設定している部分。
52行
#define DEVICE_NAME "Nordic_UAR1" /**< Name of device. Will be included in the advertising data. */
157行から179行のGAPのパラメター設定部分で、52行で設定した、DECICE_NAME を設定
static void gap_params_init(void)
{
uint32_t err_code;
ble_gap_conn_params_t gap_conn_params;
ble_gap_conn_sec_mode_t sec_mode;
BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode);
err_code = sd_ble_gap_device_name_set(&sec_mode,
(const uint8_t *) DEVICE_NAME,
strlen(DEVICE_NAME));
APP_ERROR_CHECK(err_code);
memset(&gap_conn_params, 0, sizeof(gap_conn_params));
gap_conn_params.min_conn_interval = MIN_CONN_INTERVAL;
gap_conn_params.max_conn_interval = MAX_CONN_INTERVAL;
gap_conn_params.slave_latency = SLAVE_LATENCY;
gap_conn_params.conn_sup_timeout = CONN_SUP_TIMEOUT;
err_code = sd_ble_gap_ppcp_set(&gap_conn_params);
APP_ERROR_CHECK(err_code);
}
このソースでは固定ですが、static 変数で作成し、nRF51822 以外の装置から、何らかの情報を読んで、起動( gap_params_init(); )前に 名前を変更すれば、"Nordic_UAR1" 以外の名前にすることができるようです。
さてここで、この名前を、セントラル側で読む例ですが、サンプルソフトではそれらしいところが無く、あるのは、ペアリングが成立した後、以下のようにMACアドレスを報告する部分です。
static api_result_t device_manager_event_handler(const dm_handle_t * p_handle,
const dm_event_t * p_event,
const api_result_t event_result)
{
bool lcd_write_status;
uint32_t err_code;
switch(p_event->event_id)
{
case DM_EVT_CONNECTION:
{
APPL_LOG("[APPL]: >> DM_EVT_CONNECTION\r\n");
#ifdef ENABLE_DEBUG_LOG_SUPPORT
ble_gap_addr_t * peer_addr;
peer_addr = &p_event->event_param.p_gap_param->params.connected.peer_addr;
#endif // ENABLE_DEBUG_LOG_SUPPORT
APPL_LOG("[APPL]:[%02X %02X %02X %02X %02X %02X]: Connection Established\r\n",
peer_addr->addr[0], peer_addr->addr[1], peer_addr->addr[2],
peer_addr->addr[3], peer_addr->addr[4], peer_addr->addr[5]);
ペアリング後の報告では、スマートフォンみたいに特定のペリフェラルを選択することができません。MACアドレスと名前は、アドバタイジングの時にペリフェラルから送られているので、どこで読みだせるのか、いろいろ調べていると、以下の部分でできることが判りました。 BLE_GAP_EVT_ADV_REPORT とある部分です。名前から想像できるように、ペリフェラルからのアドバタイジングパケットを受信した時の報告です。
static void on_ble_evt(ble_evt_t * p_ble_evt)
{
uint32_t err_code;
const ble_gap_evt_t * p_gap_evt = &p_ble_evt->evt.gap_evt;
switch (p_ble_evt->header.evt_id)
{
case BLE_GAP_EVT_ADV_REPORT:
{
data_t adv_data;
data_t type_data;
APPL_LOG("\t[APPL]: Catched an advertising packet, check for UUID match\r\n");
// Initialize advertisement report for parsing.
adv_data.p_data = (uint8_t *)p_gap_evt->params.adv_report.data;
adv_data.data_len = p_gap_evt->params.adv_report.dlen;
err_code = adv_report_parse(BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_MORE_AVAILABLE,
&adv_data,
&type_data);
BLE_GAP_EVT_ADV_REPORT で、いきなりUUIDをチェックしていますが、UUID以外に、MACアドレスや、デバイス名も送られてきていることが、params.adv_report.data をヘキサダンプすると確認できます。
params.adv_report.data には、UUIDか、又はデバイス名。.data[1] に、UUIDか、デバイス名かを区別するコードが書かれています。
data[0] には、(コード+有効データ)のバイト数、data[1] が 0x09 の場合、デバイス名になります。
data[1] が 0x07 の場合、UUIDになります。
params.adv_report.peer_addr には、MACアドレス。peer_addr[0] --- peer_addr[5] の6バイト。
MACアドレスは、アドバタイジングパケットに常に含まれますが、UUIDと、デバイス名は、1度には送られず、別のパケットに分けて、最低2回送られるようです。
このことを確認する、セントラルのソースで、ヘキサダンプする部分が以下です。
static void on_ble_evt(ble_evt_t * p_ble_evt)
{
uint32_t err_code;
int i,j;
const ble_gap_evt_t * p_gap_evt = &p_ble_evt->evt.gap_evt;
switch (p_ble_evt->header.evt_id)
{
case BLE_GAP_EVT_ADV_REPORT:
{
data_t adv_data;
data_t type_data;
adv_data.p_data = (uint8_t *)p_gap_evt->params.adv_report.data;
adv_data.data_len = p_gap_evt->params.adv_report.dlen;
APPL_LOG("adv_report.data = ");
for(i=0;iparams.adv_report.data[i]);
}
APPL_LOG("len = %d\r\n",adv_data.data_len);
APPL_LOG("\t[APPL]: Catched an advertising packet, check for UUID match\r\n");
上記のセントラルソース一式 BVMCN5103_uartx_cent_s120.lzh
上記のセントラルソース一式 こちらが、MAC表示付 BVMCN5103_uartx_cent_s120_v2.lzh
実行させて、ペアリングが確立するまでターミナルに表示させたものです。この例では、MACは表示されていません。
この図から、3行目に、
adv_report.data = 0C,09,4E,6F,72,64,69,63,5F,55,41,52,31,02,01,05,len = 16 とありますが、これは、"Nordic_UAR1" であるのが確認できます。
さらに、5行目に、
adv_report.data = 11,07,9E,CA,DC,24,0E,E5,A9,E0,93,F3,A3,B5,01,00,40,6E,len = 18 とありますが、
これは、ペリフェラルのソース、ble_nus.c に、以下のように設定されている、UUIDです。(一部異なっています。0xB5 の次が 0x01)
uint32_t ble_nus_init(ble_nus_t * p_nus, const ble_nus_init_t * p_nus_init)
{
uint32_t err_code;
ble_uuid_t ble_uuid;
ble_uuid128_t nus_base_uuid = {0x9E, 0xCA, 0xDC, 0x24, 0x0E, 0xE5, 0xA9, 0xE0,
0x93, 0xF3, 0xA3, 0xB5, 0x00, 0x00, 0x40, 0x6E};
if ((p_nus == NULL) || (p_nus_init == NULL))
{
return NRF_ERROR_NULL;
}
// Initialize service structure.
p_nus->conn_handle = BLE_CONN_HANDLE_INVALID;
p_nus->data_handler = p_nus_init->data_handler;
p_nus->is_notification_enabled = false;
/**@snippet [Adding proprietary Service to S110 SoftDevice] */
// Add custom base UUID.
err_code = sd_ble_uuid_vs_add(&nus_base_uuid, &p_nus->uuid_type);
if (err_code != NRF_SUCCESS)
{
return err_code;
}
nRF51822 を使って相互にUART通信。 通信インターバル変更
今までの通信では、サンプルソフトのデフォルト30ms設定で、通信インターバルが設定されていましたが、このインターバルは、1.25ms単位で変更できます。設定しているところは、セントラル、ペリフェラル共に、main.c の冒頭近くにあります。
セントラルの main.c の設定
#define MIN_CONNECTION_INTERVAL MSEC_TO_UNITS(7.5, UNIT_1_25_MS) /**< Determines maximum connection interval in millisecond. */
#define MAX_CONNECTION_INTERVAL MSEC_TO_UNITS(30, UNIT_1_25_MS) /**< Determines maximum connection interval in millisecond. */
ペリフェラルの main.c の設定
#define MIN_CONN_INTERVAL 16 /**< Minimum acceptable connection interval (20 ms), Connection interval uses 1.25 ms units. */
#define MAX_CONN_INTERVAL 600 /**< Maximum acceptable connection interval (75 ms), Connection interval uses 1.25 ms units. */
これらは、デフォルトで設定されていて、意味するところは、最短は、両方とも条件を満たす長い方、最長は、両方とも条件を満たす短い方で、最短は20ms、最長は750ms(コメントの75msはコメントの間違い)になります。そして、実際に通信インターバルが落ち着くところは、BLEのコンセプトである、できるだけ消費電力を少なくするように、両方の条件を満たす30msになります。
通信インターバルは、セントラル側が、ペリフェラルからのアドバタイジングにOKを出すときに行われ、それを行っているところが、main.c の以下の部分です。
関数、static void on_ble_evt(ble_evt_t * p_ble_evt) の中ほど、sd_ble_gap_connect() の引数、&m_connection_param で、で通信インターバルを設定しています。
if (err_code == NRF_SUCCESS)
{
if(!memcmp( nus_service_uuid,type_data.p_data,16))
{
APPL_LOG("\t[APPL]: UUID matched\r\n");
// Stop scanning.
err_code = sd_ble_gap_scan_stop();
if (err_code != NRF_SUCCESS)
{
APPL_LOG("[APPL]: Scan stop failed, reason %d\r\n", err_code);
}
nrf_gpio_pin_clear(SCAN_LED_PIN_NO);
m_scan_param.selective = 0;
// Initiate connection.
err_code = sd_ble_gap_connect(&p_gap_evt->params.adv_report.\
peer_addr,
&m_scan_param,
&m_connection_param);
ペアリングを始める前に、上記のように、インターバルを設定していますが、関数、static void on_ble_evt(ble_evt_t * p_ble_evt) の最後に、
case BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST:
の場合の処理に、sd_ble_gap_conn_param_update() というAPIを呼び出しています。ここに来るのは、たぶん、ペリフェラルから通信インターバルの変更要求があった場合に、応答する処理と思われ、ペリフェラル側でその要求を出さない限り、この処理は行われないみたいです。
ともあれ、この sd_ble_gap_conn_param_update() というAPIで、ペアリングした後でも、通信インターバルを変更できそうです。
APIを呼び出しているところは、main.c の、関数、static void on_ble_evt(ble_evt_t * p_ble_evt) の中の以下です。コメントには、 Accepting parameters requested by peer. とありますが、通信インターバルを変更できる権利は、セントラル側だけにあるようです。このAPIの引数を見ると、p_gap_evt->conn_handle が、ペリフェラルを操作するハンドル、そして、&p_gap_evt->params.conn_param_update_request.conn_params が、インターバルの数値を格納したパラメターテーブルですが、どのような数値なのかは、SoftDevice のマニュアルをたよりに探すと、include\ble\120\ble_gap.h で定義されているのが判ります。
case BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST:
// Accepting parameters requested by peer.
err_code = sd_ble_gap_conn_param_update(p_gap_evt->conn_handle,
&p_gap_evt->params.conn_param_update_request.conn_params);
APP_ERROR_CHECK(err_code);
break;
上記の p_gap_evt->params.conn_param_update_request.conn_params は、include\ble\120\ble_gap.h で定義されています。
typedef struct
{
uint16_t min_conn_interval; /**< Minimum Connection Interval in 1.25 ms units, see @ref BLE_GAP_CP_LIMITS.*/
uint16_t max_conn_interval; /**< Maximum Connection Interval in 1.25 ms units, see @ref BLE_GAP_CP_LIMITS.*/
uint16_t slave_latency; /**< Slave Latency in number of connection events, see @ref BLE_GAP_CP_LIMITS.*/
uint16_t conn_sup_timeout; /**< Connection Supervision Timeout in 10 ms units, see @ref BLE_GAP_CP_LIMITS.*/
} ble_gap_conn_params_t;
そこで、p_gap_evt->conn_handle を記憶しておき、ble_gap_conn_params_t で定義テーブルを作成しておき、このAPIを呼び出せば、通信インターバルを変更できるのか、確認してみます。定義テーブルは、main.c では、初期化のときにはROMになっているので、これを変数にして初期化時と、ペアリング後の変更時とで共有します。
こちらが通信インターバルを変更できる、BVMCN5103_uartx_cent_speed_s120.lzh セントラルのソースです。
こちらが通信インターバルを変更できる(MAC表示付)、BVMCN5103_uartx_cent_speed_s120_a.lzh セントラルのソースです。
通信インターバルは、コントロールD1文字で、短くし、コントロールU1文字で長くします。変更するインターバルの値は、ソースにテーブルで定義し、それへのインデックスで指定します。コントロールDでインデックスを下げ、コントロールUでインデックスを上げます。初期値は、30msとし、コントロールコードを1文字、UARTで BVMVN5103 に送ると、コードに従ってインデックスの内容で、通信インターバルを変更します。なお、コントロールコードは、無線で送信しません。
static uint8_t intervalindex = 3;// 30ms
static const uint16_t conn_interval[8] ={
// 5ms, 7.5ms, 15ms, 30ms, 60ms, 120ms,480ms, 960ms
4, 6, 12, 24, 48, 96, 384, 768};
コントロールコードで変更している部分は以下。conn_handle は、コネクションが成立したとき、記録した値。
void UART0_IRQHandler(void)
{
static char data_array[BLE_NUS_MAX_DATA_LEN];
static uint8_t index = 0;
uint16_t interval;
uint32_t err_code;
// uint32_t err_code;
int c;
/**@snippet [Handling the data received over UART] */
nrf_gpio_pin_set(5);// UARTから1文字受信すると、点灯
c = simple_uart_get();
if(c == 3)// デバッグ情報表示のON,OFF
{
logenf ^= 1;
return;
}
else if(c == '\4')// crtl-D
{
if(intervalindex > 0)
{
intervalindex--;
interval = conn_interval[intervalindex];
m_connection_param.max_conn_interval = interval;
err_code = sd_ble_gap_conn_param_update(conn_handle,
&m_connection_param);
APP_ERROR_CHECK(err_code);
if(logenf & 1)
APPL_LOG("[APPL]: interval = %d.%02d ms\r\n",(interval*125)/100,(interval*125)%100);
}
return;
}
else if(c == '\25')// crtl-U
{
if(intervalindex < 7)
{
intervalindex++;
interval = conn_interval[intervalindex];
m_connection_param.max_conn_interval = interval;
err_code = sd_ble_gap_conn_param_update(conn_handle,
&m_connection_param);
APP_ERROR_CHECK(err_code);
if(logenf & 1)
APPL_LOG("[APPL]: interval = %d.%02d ms\r\n",(interval*125)/100,(interval*125)%100);
}
return;
}
data_array[index] = c;
index++;
nrf_gpio_pin_clear(5);// 消灯
if ((data_array[index - 1] == '\r') || (index >= (BLE_NUS_MAX_DATA_LEN)))// BLE_NUS_MAX_DATA_LEN: 20
{
send_char(&data_array[0], index);
index = 0;
}
/**@snippet [Handling the data received over UART] */
}
以下は、このセントラルのプログラムで、通信インターバルを短くしたときのターミナルの表示と、オシロの波形です。
青色が、UARTのコントロールDでトリガ(黄色の菱形)をかけた波形、赤色が、BVMCN5103 の電源電圧のドロップ。10オームを介してで電源と接続しており、無線電波を使っているとき、電流が増加するので、下がります。コントロールDを受けた後、8回ほどペリフェラルと通信している部分は、30msですが、その後15msになっています。しかし、何秒か経つと、ターミナルで2行目に表示しているように、600(750msに相当)になってしまいます。というのは、ペリフェラルでの最短インターバルが、16(20msに相当)なので、範囲外となり、最長のインターバルにするように、ペリフェラルから、通信インターバルの変更要求が来て、それに応答した結果です。
通信インターバルが、750msになった波形
nRF51822 を使って相互にUART通信。2台のペリフェラルとペアリング
SDK( nrf51_sdk_v6_0_0_43681 )には、複数のペリフェラルと接続するサンプルとして、
nrf51_sdk_v6_0_0_43681\nrf51822\Board\nrf6310\s120\experimental\ble_app_multilink_central と
nrf51_sdk_v6_0_0_43681\nrf51822\Board\nrf6310\s120\experimental\ble_app_multilink_peripheral が ありますが、
ble_app_multilink_central のフォルダに、device_manager_cnfg.h があり、このファイルの52行に、
#define DEVICE_MANAGER_MAX_CONNECTIONS 8
と設定されており、最大8台のペリフェラルと接続できることが判ります。
サンプルソフトでは、セントラルにLEDを複数個設置し、対応したペリフェラルの押しボタンの操作で、ON,OFFする機能があります。この例を参考に、UARTでの相互通信で、複数のペリフェラルと通信できるように改造しようとしましたが、UART相互通信と、このサンプルとでは違いが多すぎて、改造を進めることができず、途中であきらめました。
そこで、先ほどの、#define DEVICE_MANAGER_MAX_CONNECTIONS に注目し、1対1のUART相互通信のソースにある、device_manager_cnfg.h で 1 に設定されているところを、2に変更して、どのようになるのか、試してみることにしました。セントラル側のソースは、DEVICE_MANAGER_MAX_CONNECTIONS を2にするだけでコンパイルし、ペリフェラルは( UART相互通信 )を2台用意し、セントラルを先に電源を入れ、1台目のペリフェラルの電源を入れます。そして通信が確立したところで、2台目の電源をいれると、
ペアリングの過程が表示され、エラーらしきものもありません。これはもしかして、2台と通信できるということなのかも!!
以下が、2台のペリフェラルとペアリングする過程のセントラル側の記録です。
[APPL]: << DM_EVT_SECURITY_SETUP_COMPLETE conn_handle = 0 で、1台目のペアリングが確立。
そして最後から2行目の
[APPL]: << DM_EVT_SECURITY_SETUP_COMPLETE conn_handle = 1 で、2台目のペアリングが確立。
[APPL]: Start...
adv_report.data = 0C,09,4E,6F,72,64,69,63,5F,55,41,52,31,02,01,05,len = 16
[APPL]: Catched an advertising packet, check for UUID match
adv_report.data = 11,07,9E,CA,DC,24,0E,E5,A9,E0,93,F3,A3,B5,01,00,40,6E,len = 18
[APPL]: Catched an advertising packet, check for UUID match
[APPL]: UUID matched
adv_report.data = 0C,09,4E,6F,72,64,69,63,5F,55,41,52,31,02,01,05,len = 16
[APPL]: Catched an advertising packet, check for UUID match
adv_report.data = 11,07,9E,CA,DC,24,0E,E5,A9,E0,93,F3,A3,B5,01,00,40,6E,len = 18
[APPL]: Catched an advertising packet, check for UUID match
[APPL]: UUID matched
[APPL]: Scan stop failed, reason 8
[APPL]: Connection Request Failed, reason 17
[APPL]: >> DM_EVT_CONNECTION
[APPL]:[69 E3 B6 EB A1 C3]: Connection Established
[DB]: Starting discovery of service with UUID 0x1 for Connection handle 0
[APPL]: << DM_EVT_CONNECTION conn_handle = 0
[DB]: Discovery of service with UUID 0x1 completed with success for Connectionhandle 0
[uart_C]: Heart Rate Service discovered at peer.
[uart_C]: Configuring CCCD. CCCD Handle = 12, Connection Handle = 0
[uart_C]: SD Read/Write API returns Success..
[uart_C]: Writing to characteristic Handle = 14, Connection Handle = 0
[uart_C]: SD Read/Write API returns error. This message sending will be attempted again..
[uart_C]: SD Read/Write API returns error. This message sending will be attempted again..
[APPL]: >> DM_LINK_SECURED_IND
[APPL]: << DM_LINK_SECURED_IND
[APPL]: >> DM_EVT_SECURITY_SETUP_COMPLETE
[uart_C]: Configuring CCCD. CCCD Handle = 12, Connection Handle = 0
[uart_C]: SD Read/Write API returns Success..
[APPL]: << DM_EVT_SECURITY_SETUP_COMPLETE conn_handle = 0
[uart_C]: SD Read/Write API returns Success..
adv_report.data = 0C,09,4E,6F,72,64,69,63,5F,55,41,52,31,02,01,05,len = 16
[APPL]: Catched an advertising packet, check for UUID match
adv_report.data = 11,07,9E,CA,DC,24,0E,E5,A9,E0,93,F3,A3,B5,01,00,40,6E,len = 18
[APPL]: Catched an advertising packet, check for UUID match
[APPL]: UUID matched
adv_report.data = 0C,09,4E,6F,72,64,69,63,5F,55,41,52,31,02,01,05,len = 16
[APPL]: Catched an advertising packet, check for UUID match
adv_report.data = 11,07,9E,CA,DC,24,0E,E5,A9,E0,93,F3,A3,B5,01,00,40,6E,len = 18
[APPL]: Catched an advertising packet, check for UUID match
[APPL]: UUID matched
[APPL]: Scan stop failed, reason 8
[APPL]: Connection Request Failed, reason 8
adv_report.data = 0C,09,4E,6F,72,64,69,63,5F,55,41,52,31,02,01,05,len = 16
[APPL]: Catched an advertising packet, check for UUID match
[APPL]: >> DM_EVT_CONNECTION
[APPL]:[1A 21 4C 54 9A EE]: Connection Established
[DB]: Starting discovery of service with UUID 0x1 for Connection handle 1
[APPL]: << DM_EVT_CONNECTION conn_handle = 1
[DB]: Discovery of service with UUID 0x1 completed with success for Connectionhandle 1
[uart_C]: Heart Rate Service discovered at peer.
[uart_C]: Configuring CCCD. CCCD Handle = 12, Connection Handle = 1
[uart_C]: SD Read/Write API returns Success..
[uart_C]: Writing to characteristic Handle = 14, Connection Handle = 1
[uart_C]: SD Read/Write API returns error. This message sending will be attempted again..
[uart_C]: SD Read/Write API returns error. This message sending will be attempted again..
[APPL]: >> DM_LINK_SECURED_IND
[APPL]: << DM_LINK_SECURED_IND
[APPL]: >> DM_EVT_SECURITY_SETUP_COMPLETE
[uart_C]: Configuring CCCD. CCCD Handle = 12, Connection Handle = 1
[uart_C]: SD Read/Write API returns Success..
[APPL]: << DM_EVT_SECURITY_SETUP_COMPLETE conn_handle = 1
[uart_C]: SD Read/Write API returns Success..
ためしに、各ペリフェラルに、20バイトの文字をUARTで送ると、それぞれが無線で送信し、セントラル側に表示されます。
nRF51822 を使って相互にUART通信。2台のペリフェラルと相互通信
2台のペリフェラルから送られたデータを表示できたものの、どちらからのデータなのかは、このままではデータの内容で区別するしかありません。また、セントラルからデータを送る場合、2台のペリフェラルのうち、送信先を指定するのはどうするのか、このままのソースでは行えません。
ためしに、2台とペアリングできた時点で、セントラルにUARTで20バイト送ると、先にペアリングした方ではなく、あとからペアリングした方に送信されます。このことは、送り先を指定する方法のヒントになります。
さて、今までは、2台のペリフェラルの起動は同時ではなく、1台目のペアリングが確定してから、2台目を起動していましたが、これを2台同時に起動するとどうなるのかを試すと、途中でエラーで止まってしまいます。サンプルソフトでは、app_error_handler で、永久ループするようになっています。なぜエラーになるのかは、サンプルソフトの、ble_app_uart_c が、元々1台のペリフェラルだけを想定していて、ペアリング確立過程で、相手を区別することなく進んでいくからです。
では、同時に2台のペリフェラルからのアドバタイジングが有る場合はどうするのか、以下の方法を考えてみました。
ペアリング確立過程で、 Softdevice からのメッセージで送られてくる情報の記憶場所を2台分用意する。
具体的には、m_ble_db_discovery をもう一つ、m_ble_db_discovery2
と、m_dm_device_handle をもう一つ、m_dm_device_handle2
アドバタイジングのデータのデバイス名で2台のペリフェラルを区別し(0と1)、それぞれMACアドレスを対応して記録しておく。
DM_EVT_CONNECTION で、MACアドレスを読んで、記憶しておいたMACアドレスのどれと一致しているのか探す。このとき、デバイス名で区別した、0か1かが判明する。
DM_EVT_CONNECTION では、コネクションハンドル情報も通知されているので(0か1になる)、ハンドル番号を、デバイス名(番号)に対応させて記録する。
DM_EVT_CONNECTION での処理は、ble_db_discovery_start で次のペアリング処理に移るが、このときハンドル番号に対応した、m_ble_db_discovery か、m_ble_db_discovery2 を(アドレスで渡す)引数にする
UARTのセントラル側では、コネクションの最終手続きで、uart_c_evt_handler が呼び出され、ここで、write_dummy() 関数を使っていますが、この先は、ble_uart_c.c の中にあり、そこでは、write_char(mp_ble_uart_c->conn_handle, mp_ble_uart_c->TX_handle, (uint8_t *) "dummy", 5); をさらに実行します。ここで、mp_ble_uart_c なるポインタを引数にしているので、そこのハンドル値を要求されている、0か1にする。このハンドル値は、db_discover_evt_handler 関数が、uart_c_evt_handler を呼び出す前に、ハンドル値をその都度書き換えているので、最後に呼び出したハンドル値になり、write_dummy() 関数では0か1か、特定することができない。ここで、特定できる、write_dummy() を用意する。write_dummy0() と、write_dummy1() 。
上記の0か1かの区別のため、ble_uart_c.h で定義している、ble_uart_c_evt_t に、uint8_t handle; を追加し、db_discover_evt_handler 関数が、uart_c_evt_handler を呼び出す前に、このハンドル値を、evt.handle = p_evt->conn_handle; で書く。これで、uart_c_evt_handler 関数内で、ハンドル値を確認して、write_dummy0() と、write_dummy1() を振り分ける。
セントラル側は、どちらにUARTデータを送るかを区別できるように、ホストマイコンとのUARTプロトコルを策定する。
ペリフェラルから無線で送られたデータを区別できるように、セントラル側は、uart_c_evt_handler 内でハンドル値で区別し、プロトコルに反映する。
ペアリング確立過程では、1台単位で処理が進むようにする。この為に、ペアリング進行状態を示す、シーケンス管理フラグを2台分確保して、1台目のペアリング処理が終わるまで、2台目からのアドバタイジングには応答しないようにする。
まあ、ざっとこんな具合ですが、ここまでたどり着くのに、ずいぶんと試行錯誤がありました。
2台のペリフェラルとの接続で、接続の順番が変わると、それぞれのペリフェラルを特定するコネクションハンドルも変わる
下図のように、ペリフェラルとの接続順番の違いで、ハンドル番号も変わる。
複数のペリフェラルとの接続では、ソフトデバイスによってコネクションハンドルが、0と1に割り振られますが、ハンドル番号だけではペリフェラルを特定することができません。この為、2台のペリフェラルを特定して、セントラルからデータを送りたいとき、ハンドル番号と、ペリフェラルとの関係を知っておく必要があります。
しかし、この関連付けは簡単ではありません。事前に、各ペリフェラルのMACアドレスが判っている場合は、比較的簡単で、device_manager_event_handler の、DM_EVT_CONNECTION の時、MACと、ハンドル番号が同時に参照できるので、このとき、指定したいペリフェラルのハンドル番号が特定できます。
通常、MACアドレスを事前に知っておいてペリフェラルを特定することはほとんどなく、このことは、イーサネットのMACアドレスと、IPアドレスの
関係付けと似ています。IPアドレスに相当するのが、BLEではUUIDになりますが、UUIDは、勝手に値を変えて使えるものではないので、デバイス名を使うようにします。無論このデバイス名は、任意に設定できるので、全く関係の無いペリフェラルで同じデバイス名になることも想定されるので、イーサネットのIPアドレスのようには扱えませんが、UUIDの不一致で排除することになります。
こちらがデバイス名を設定できるペリフェラルと、セントラルのソースです。BVMCN5103_setdev_s110_s120.lzh
以下は、セントラル、ぺリフェラル共用の回路図。図の右で、「デバイス番号(P) ショートで2番」とありますが、セントラル側では、ショートでデバッグ情報をUARTに出力します。オープンだとUART通信で受信したデータだけUARTに出力します。
セントラル側では、アドバタイジングパケットで、ペリフェラルのデバイス名を表示する機能を追加しています。
ペリフェラル側では、P0_02 ピンを入力ピンとしてプルアップ設定し、ジャンパーでショートすると、Lowになるので、起動時に、デバイス名の最後の文字を、 '2' に変更しています。ショートしなければ、1となります。デバイス名は、それぞれ、Peripheral_1 と、Peripheral_2 となります。
以下は、最初にペリフェラル2を起動し、あとからペリフェラル1を起動したときの、セントラルのログ情報です。
4行目から、
[APPL]:[1A 21 4C 54 9A EE]: ADV EVT
[APPL]:device name = Peripheral_2
とあり、ペリフェラル2からのアドバタイジングパケットを示し、
36行目から、
[APPL]:[69 E3 B6 EB A1 C3]: ADV EVT
[APPL]:device name = Peripheral_1
ペリフェラル1と報告されています。それぞれのMACアドレスも報告しています。
[APPL]: Start...
[APPL]: Start...
RSSI = -68 ,09
[APPL]:[1A 21 4C 54 9A EE]: ADV EVT
[APPL]:device name = Peripheral_2
[APPL]: Catched an advertising packet, check for UUID match
[APPL]: Catched an advertising packet, check for UUID match
[APPL]: UUID matched
RSSI = -70 ,09
[APPL]:[1A 21 4C 54 9A EE]: ADV EVT
[APPL]:device name = Peripheral_2
[APPL]: Catched an advertising packet, check for UUID match
[APPL]: Catched an advertising packet, check for UUID match
[APPL]: UUID matched
[APPL]: Scan stop failed, reason 8
[APPL]: Connection Request Failed, reason 17
[APPL]: >> DM_EVT_CONNECTION
[APPL]:[1A 21 4C 54 9A EE]: Connection Established
[DB]: Starting discovery of service with UUID 0x1 for Connection handle 0
[APPL]: << DM_EVT_CONNECTION conn_handle = 0
[DB]: Discovery of service with UUID 0x1 completed with success for Connectionhandle 0
[uart_C]: Heart Rate Service discovered at peer.
[uart_C]: Configuring CCCD. CCCD Handle = 12, Connection Handle = 0
[uart_C]: SD Read/Write API returns Success..
[uart_C]: Writing to characteristic Handle = 14, Connection Handle = 0
[uart_C]: SD Read/Write API returns error. This message sending will be attempted again..
[uart_C]: SD Read/Write API returns error. This message sending will be attempted again..
[APPL]: >> DM_LINK_SECURED_IND
[APPL]: << DM_LINK_SECURED_IND
[APPL]: >> DM_EVT_SECURITY_SETUP_COMPLETE
[uart_C]: Configuring CCCD. CCCD Handle = 12, Connection Handle = 0
[uart_C]: SD Read/Write API returns Success..
[APPL]: << DM_EVT_SECURITY_SETUP_COMPLETE conn_handle = 0
[uart_C]: SD Read/Write API returns Success..
RSSI = -51 ,09
[APPL]:[69 E3 B6 EB A1 C3]: ADV EVT
[APPL]:device name = Peripheral_1
[APPL]: Catched an advertising packet, check for UUID match
[APPL]: Catched an advertising packet, check for UUID match
[APPL]: UUID matched
[APPL]: >> DM_EVT_CONNECTION
[APPL]:[69 E3 B6 EB A1 C3]: Connection Established
[DB]: Starting discovery of service with UUID 0x1 for Connection handle 1
[APPL]: << DM_EVT_CONNECTION conn_handle = 1
[DB]: Discovery of service with UUID 0x1 completed with success for Connectionhandle 1
[uart_C]: Heart Rate Service discovered at peer.
[uart_C]: Configuring CCCD. CCCD Handle = 12, Connection Handle = 1
[uart_C]: SD Read/Write API returns Success..
[uart_C]: Writing to characteristic Handle = 14, Connection Handle = 1
[uart_C]: SD Read/Write API returns error. This message sending will be attempted again..
[uart_C]: SD Read/Write API returns error. This message sending will be attempted again..
[APPL]: >> DM_LINK_SECURED_IND
[APPL]: << DM_LINK_SECURED_IND
[APPL]: >> DM_EVT_SECURITY_SETUP_COMPLETE
[uart_C]: Configuring CCCD. CCCD Handle = 12, Connection Handle = 1
[uart_C]: SD Read/Write API returns Success..
[APPL]: << DM_EVT_SECURITY_SETUP_COMPLETE conn_handle = 1
[uart_C]: SD Read/Write API returns Success..
ここで、3行目にRSSI = -68 ,09 とありますが、これは、ペリフェラル2からの、電波強度の報告です。マイナス表記ですが、下がるほど弱いことを示します。09 は、パケットデータ種別の9番ということで、デバイス名が送られてきたということです。35行目では、RSSI = -51 とあり、Peripheral_1 の方からの電波が強いことが判ります。
2台のペリフェラルと相互通信できる、 セントラルとペリフェラルの KEIL プロジェクトソース一式です。
nrf51_sdk_v6_0_0_43681\nrf51822\Board\ フォルダに、解凍したフォルダ、BVMCN5103_2peripheral を配置すると、KEIL のプロジェクトを開けます。
各モジュールのUART通信のフォーマットは以下の説明図を参照。
2台のペリフェラルと通信させるテスト環境
2台のペリフェラルと接続ができることは判りましたが、ペリフェラルを指定してデータを送るには、UARTで指示するか、I/OピンのLow、Highでペリフェラルの区別をする必要があります。また、ペリフェラルから送られてきたデータがどちらからのものか、これもUARTで知らせるか、I/OピンのLow,Highで区別する必要があります。I/Oピンで行う方法では、セントラルとUARTで接続する相手がパソコンなどの場合は、I/Oを制御しなければならず、そう簡単ではないので、UARTで指定することとします。3台のBLEモジュールの3ポートのUARTを一元管理するには、USB−UART変換ケーブルを3本使うことになりますが、これも、ソフトが複雑になりそうなので、一元管理できる方法を考えてみました。
以下が、FPGAを制御マイコンとして、3ポートのUART一元管理する構成と、UARTフォーマットです。
上のフォーマットでは、ペリフェラルのUART受信で受信バッファーのポインタを初期化する機能が無いので、パワーONなどのごみが残ることがあるので、STXで初期化をする機能を追加したものが以下。
テスト環境に使った、FPGA基板。Digilent の、ARTY。コネクタ Pmod JA に接続しているのが、3chのUART。ピン配置は、USB−UART変換ケーブル(秋月電子通商製のピン配置に合わせている)
UART コネクタ、1:セントラル、2:ペリフェラル1、3:ペリフェラル2
FPGAのソースと、.bit .bin ファイル。ソース、bin, bit これには、Custom MicroBlase RTL ソース、プログラムソース、GCC makefile など含みます。開発は、Vivado 15.3。モニタにバグあり
どのようなバグなのか、調査 Vivado 15.3 SDK の使い方でコンパイルオプションにバグあり
FPGAのソースと、.bit .bin ファイル。ソース、bin, bit これには、Custom MicroBlase RTL ソース、プログラムソース、GCC makefile など含みます。開発は、Vivado 15.3。とISE 14.4 の、GCCツールチェーンを使ったもの
カスタムマイクロブレーズのソースの説明はこちら→外部メモリでも動作できる、新たなカスタム設計の MicroBlaze 5クロック/1命令
Custom MicroBlaze は、Xilinx の MicroBlaze のサブセットですが、GCCコンパイラが生成する命令を実行できるものです。(ネイティブな乗除算命令を使わないコードに限る。乗除算命令は、加減算でエミュレートされるので、三角関数など、超越関数も演算可能)
以下の Vivado のSDKでは、一部不具合が見られたので、( どのようなバグなのか、調査 Vivado 15.3 SDK の使い方でコンパイルオプションにバグあり ) ISE 14.4 の、GCCでの開発を後述します。
Vivado は、SDKをインストールすると、MicroBlaze の開発ツールチェーンを使えるようになりますが、そのためのパスの設定を以下のようにします。
マイクロブレーズ用コンパイラのツールチェーンパスを、C:\Xilinx\SDK\2015.3\gnu\microblaze\nt\bin
GCCツールのパスを、C:\Xilinx\SDK\2015.3\gnuwin\bin
とすると、コマンドプロンプトで、
set path=c:\Xilinx\SDK\2015.3\gnu\microblaze\nt\bin;c:\Xilinx\SDK\2015.3\gnuwin\bin;%PATH% を実行します。
上記、src_bit.lzh を解凍すると、src_bit\sources_1\new\artycmb フォルダができますが、このフォルダにコマンドプロンプトを移動し、 make ( enter ) で、Custom MicroBlaze の0番地からの実行ファイル(ヘキサ表現)、cmbiofint.mot が生成されます。このデータを0番地からアクセスできるブロックRAMなどに書いておくと、0番地から実行できます。
ISE 14.4 をインストールすると、( c:\xilinx\14.4\ フォルダとする )
マイクロブレーズ用コンパイラのツールチェーンパスを、C:\xilinx\14.4\ISE_DS\EDK\gnu\microblaze\nt\bin
GCCツールのパスを、C:\xilinx\14.4\ISE_DS\EDK\gnuwin\bin;
とすると、コマンドプロンプトで、
set path=C:\xilinx\14.4\ISE_DS\EDK\gnu\microblaze\nt\bin;C:\xilinx\14.4\ISE_DS\EDK\gnuwin\bin;%PATH%
上記、src_bit_ise14_4gcc.lzh を解凍すると、src_bit_ise14_4gcc\sources_1\new\14artycmb フォルダができますが、このフォルダにコマンドプロンプトを移動し、 make ( enter ) で、Custom MicroBlaze の0番地からの実行ファイル(ヘキサ表現)、cmbiofint.mot が生成されます。このデータを0番地からアクセスできるブロックRAMなどに書いておくと、0番地から実行できます。
では、ブロックRAMに書くのはどうするのか、ですが、RTLソースの中で、defparam で宣言します。しかし、これが一筋縄ではできず、専用の変換ツールを使います。
ヘキサファイル( cmbiofint.mot )から、Custom MicroBlaze のメインメモリ mainmem.v を作成する専用変換ツール、binutils.exe binutils_018.lzh
BLEモジュールのUARTと接続して、PCから制御する MicroBlaze の機能として以下のものがあります。CMB-BUG> は、FPGA内の操作ソフトのコマンドプロンプト。115200 bps のターミナルソフトで、UART経由で操作します。
セントラルから、ペリフェラル1にデータを送信し、返答を待ってから再度送るのを繰り返す。( CMB-BUG>rl2 (enter ) )
セントラルから、ペリフェラル1とペリフェラル2にデータを送り、各ペリフェラルで受信したら、各ペリフェラルからセントラルにデータを送り、それをセントラルで受信するとUARTで受け取る。両方のペリフェラルからの返答データが揃うと、またセントラルからペリフェラル1とペリフェラル2にデータを送る。これを繰り返す。( CMB-BUG>rl3 (enter ) )
指定したインターバルで、上記のような、2個のペリフェラルにデータを送信し、返答を待つが、指定時間まで返答がなければ、やり直す。( CMB-BUG>rl7,300 (enter ) 300ms のインターバルの場合)
BLEモジュール操作のコマンドは以上ですが、メモリの内容を表示したり、変更したりできる簡単なモニタ機能もあります。また、任意のアドレスから実行できる g コマンドがあり、0x8000000 番地から32kBのメモリを実装しているので、コンパイラで生成するMAPを変えたソフトをロードして実行でき、Vivado を走らせなくても操作プログラムのテストができます。同梱した、artycmb フォルダ以下の、makefile には、0x8000000 番地から実行できるヘキサデータを生成するスクリプトを含んでいます。
モニタの操作と機能はこちら→ Custom MicroBlaze モニタの操作
以下は、BLEテストで使ったCPUのレジスタ、メモリマップ
Custom MicroBlaze のレジスタ、メモリマップ
名称
アドレス
アクセスサイズ
機能1
機能2
装備
mainmem
0x0 ---- 0x7FFF
1,2,4バイト
プログラム、データ32kB
5クロックで1命令
標準装備
mainmem2
0x8000000 ---- 0x8007FFF
1,2,4バイト
プログラム、データ32kB
プログラムデバッグ用
BLE test 装備
SIOC
0xFFFF0003
1 バイト
UARTステータス
b2:1で送信中 b1:1で送信バッファー1バイトあり b0:受信バイトあり
標準装備
SIOD
0xFFFF0007
1 バイト
UARTデータレジスタ
ライトで送信データ リードで受信データ
標準装備
TIMER0BASE + 4
0xFFFF0014
フリーカウンタ
100MHz でカウントアップ
CPUクロックでカウント 100 MHz
標準装備
TIMER0BASE + 8
0xFFFF0018
フリーカウンタ
上記カウンタオーバーでカウント
42.949 秒でカウント
標準装備
SIOC1
0xFFF00003
1 バイト
UART1ステータス
b2:1で送信中 b1:1で送信バッファー1バイトあり b0:受信バイトあり
BLE test 装備
SIOD1
0xFFF00007
1 バイト
UART1データレジスタ
ライトで送信データ リードで受信データ
BLE test 装備
SIOC2
0xFFF00013
1 バイト
UART2ステータス
b2:1で送信中 b1:1で送信バッファー1バイトあり b0:受信バイトあり
BLE test 装備
SIOD2
0xFFF00017
1 バイト
UART2データレジスタ
ライトで送信データ リードで受信データ
BLE test 装備
SIOC3
0xFFF00023
1 バイト
UART3ステータス
b2:1で送信中 b1:1で送信バッファー1バイトあり b0:受信バイトあり
BLE test 装備
SIOD3
0xFFF00027
1 バイト
UART3データレジスタ
ライトで送信データ リードで受信データ
BLE test 装備
LED1
0xFFF00043
1 バイト
BLEテストでセントラル送信開始で点灯
BLEテストでペリフェラル受信で消灯
BLE test 装備
MICROTIMER0BASE
0xFFFF0014
32 bit 1μsec カウンタ
何かを書くと0になる
1MHzでカウントアップ
BLE test 装備
以下は、BLE test で使っている Digilent の、ARTY 基板の使用ピン
ARTIX-35T ピン割り当て
名称
ARTY コネクタ
XC7A35TICSG324−1L ピン番号
機能
RXD ( SIO )
USB-UART FT2232H
D10
CPU操作UART受信ポート
TXD ( SIO )
USB-UART FT2232H
A9
CPU操作UART送信ポート
RXD1 ( SIO1 )
Pmod JA
D12
セントラル操作UART受信ポート
TXD1 ( SIO1 )
Pmod JA
K16
セントラル操作UART送信ポート
RXD2 ( SIO2 )
Pmod JA
A11
ペリフェラル1操作UART受信ポート
TXD2 ( SIO2 )
Pmod JA
A18
ペリフェラル1操作UART送信ポート
RXD3 ( SIO3 )
Pmod JA
B11
ペリフェラル2操作UART受信ポート
TXD3 ( SIO3 )
Pmod JA
B18
ペリフェラル2操作UART送信ポート
LED1
ARTY LED4
J5
BLEテストで1巡で点滅
LED
ARTY LED5
H5
100MHzカウンタ D24で点滅
BVMCN5103-CFAC が新しく入手できたので、これでSDK v10.0 を試す
ここまでは、SDK v6.0.0という古いものでしたが、nRF51822 の、RAMが32kBになった、CFACというデバイスで、簡単に、UART相互通信ができることを確認できたので、それの解説をします。解説と言っても、SDKのサンプルをすこし改造するだけで、UART相互通信を行えるので、その備忘録です。
ここでは、以下の動作確認をしたものの説明します。
ペリフェラル側で、アドバタイジング時や、接続中の、出力電波の強さを可変にする。
ペリフェラル側で、アドバタイジング時の、デバイスアドレス(通称MACアドレスと呼ばれる)を任意に設定する。
セントラル側にタイマーを実装し、時間で処理を管理できるようにする。
セントラル側で、アドバタイジングしているペリフェラルのMACアドレス、電波強度、デバイス名などをUARTで出力する。
セントラル側で、特定のペリフェラルを指定して接続し、UARTの相互通信をする。
・・・・
最初にUARTの相互通信を、スマートフォンとできるようにする。
サンプルソースは、SDK v10.0 をインストールしたフォルダを、nRF51_SDK_10.0.0_dc26b5e とすると、
nRF51_SDK_10.0.0_dc26b5e\examples\ble_central\ble_app_uart_c フォルダが、セントラル、
nRF51_SDK_10.0.0_dc26b5e\examples\ble_peripheral\ble_app_uart フォルダが、ペリフェラルです。このフォルダの main.c をまず変更します。
修正するのは、UARTのポートと、LEDのポート設定です。ボーレートは、デフォルトの38400のままとします。また、BVMCN5103では、RTC(32768Hzの水晶)が実装されていないので、その変更も必要です。
UARTポートは、main.c で直接設定変更可能ですが、LEDのポートに関しては、define で設定されたポートの設定値が main.c にはないので 、ポートの数値を直接使う関数を使います。
LEDのポートは、各種ノルディックの評価ボードに対応するため、どのボードかを設定すれば、LEDポートの番号が特定できるような設定ファイル(xxx.h )に基づいて設定されるようになっていて、main.c を見る限り、そのポートが物理的に何番なのか、判りづらくなっています。無論、この方法は、同じmain.c のソースを使っても、ボードの種別に左右されることなく、LEDの点灯、消灯を行えるので、評価ボードのメーカーにとっては手間が省けるのですが、いざ、カスタム設計のボードでLEDを操作するには、手間のかかるソースの体系です。
評価ボードでテストするにはいいですが、カスタムなボードでは、LEDを点灯するポートがどこなのか、ソースを見てすぐに判るようになっている方が、ボードの回路を含めたデバッグの手間がかかりません。そこで、main.c から直接ポート指定できるように変更します。
また、ble_app_uart フォルダ以下を、別のPCへコピーする場合、変更した部分は、すべて ble_app_uart フォルダ以下に含まれていないと、コピー先のPCでコンパイルしたとき、同じ hex ファイルが作成されなくなるので、このような変更方法とします。
UARTポートの変更は、main.c の、
static void uart_init(void) 関数内を、 以下のように変更します。
const app_uart_comm_params_t comm_params =
{
12,//RX_PIN_NUMBER,
13,//TX_PIN_NUMBER,
RTS_PIN_NUMBER,// このピンは、使われない
CTS_PIN_NUMBER,// このピンは、使われない
// APP_UART_FLOW_CONTROL_ENABLED,
APP_UART_FLOW_CONTROL_DISABLED,// RTS,CTS ピンを使わない設定。
false,
UART_BAUDRATE_BAUDRATE_Baud38400
};
LEDポートを設定、点灯、消灯の関数は以下です。これらは、SDKの中で宣言され、使えるものです。LEDは、p0.19 ポートから抵抗を通してアノードに接続、LEDのカソードはGNDへ接続するとして、
nrf_gpio_cfg_output(19); ポートを出力に設定。
nrf_gpio_pin_clear(19); ポートをLowで消灯。
nrf_gpio_pin_set(19); ポートをHighで点灯。
上記のポート設定は、以下の回路図では、「接続」のLEDが点灯となります。
そして、RTCが実装されていないので、main.c の、
static void ble_stack_init(void) という関数内で、
SOFTDEVICE_HANDLER_INIT(NRF_CLOCK_LFCLKSRC_XTAL_20_PPM, NULL); をコメントアウトし、代わりに
SOFTDEVICE_HANDLER_INIT(NRF_CLOCK_LFCLKSRC_RC_250_PPM_250MS_CALIBRATION , false); に置き換えます。
以上で、オリジナルの、ble_app_uart.c の main.c の変更で、上図の回路で、スマートフォンと、PCのUART間でUART相互通信ができるようになります。
さて、ソースを変更しても、サンプルソフトのプロジェクトファイル( ble_app_uart_s110_pca10028.uvproj )を開いただけでは、コンパイルエラーが出てしまいます。
KEILで、プロジェクトファイル、ble_app_uart_s110_pca10028.uvproj を開き、Project --> Options for Target 'nRF51422_xxac_s110' を選択して、
C/C++ タブを開くと、
Include Paths 欄の右のボタンを押し、図のように、..\..\..\..\..\..\components\toolchain への Path を追加し、
さらに ..\..\..\..\..\..\components\toolchain\gcc への Path を追加する。
パスの設定を追加したあと、メモリマップの確認をしておきます。Options for Target 'nRF51422_xxac_s110' ウィンドウの、Target タブを選択すると、以下のようなメモリのアドレス設定の表などが出てきますが、
IROM1: Start 0x18000 Size 0x28000
IRAM1: Start 0x20002000 Size 0x6000
を確認します。
コンパイルして実行形式 nrf51422_xxac_s110.hex を生成する
実行形式は( nrf51422_xxac_s110.hex )、以下のフォルダに作成されます。
nRF51_SDK_10.0.0_dc26b5e\examples\ble_peripheral\ble_app_uart\pca10028\s110\arm4\_build\nrf51422_xxac_s110.hex
ここで、ファイル名が、nRF51422 となっています。最近のノルディックの評価基板には、nRF51822 が実装されたものが見当たらないですが、nRF51822 でも実行できます。nRF51422 と nRF51822 の違いは、ANTというプロトコルを実行できる機能を入れるか否かです。nRF51822 にはその機能が実装されていないだけで、他の機能は同じです。以下は、ノルディックの、NORDIC DEVELOPER ZONE にある、Q&Aに説明があります。
What is the difference between nRF51822 and nRF51422?
だから、サンプルソフトは、nRF51422 用に作成されていても、ANTプロトコルを使わないサンプルであれば、nRF51822 でも実行可能みたいです。
コンパイルして、実行形式を作成したプロジェクトファイル一式
解凍して、ble_app_uart フォルダ以下を、nRF51_SDK_10.0.0_dc26b5e\examples\ble_peripheral\ フォルダの、ble_app_uart に上書きすると使えるようになります。この例では、上記回路図 で、スマートフォンと接続できると、LEDが点灯します。
注意:ここで、BVMCN5103 にはあらかじめソフトデバイスが書かれていますが、バージョンが一致していても、一度全部消して、nRF51_SDK_10.0.0_dc26b5e\components\softdevice\s110\hex\s110_nrf51_8.0.0_softdevice.hex を書いたほうが無難です。
通信の電波強度を変更する
無線の電波強度を下げると消費電力を下げられますが、ソフトデバイスには、そのためのシステムコールが用意されていて、アドバタイジング中や、ペアリング確立の後でも、いつでも変更できるようです。システムコールの名称は、sd_ble_gap_tx_power_set(n) です。n に数値を設定しますが、ble_advertising_start 関数ではデフォルトの0になります。変更するには、アドバタイジングを開始した後、sd_ble_gap_tx_power_set(n)で変更します。n は、
-40, -30, -20, -16, -12, -8, -4, 0, 4 のみです。これ以外の数値は設定できません。マイナスが大きい方が、電波が弱くなります。
アドバタイジング時のMACアドレスを変更する
BLEのMACアドレスは、イーサネットのMACアドレスとは異なり、メーカーに割り振られている3バイトの値を使っていないものもあります。ノルディックがその例で、ノルディックで重複しないような値を、チップに書き込んでいて、アドバタイジング時にその値を使うようになっています。http://standards.ieee.org/regauth/oui/oui.txt(このサイトはダウンロード時間がかかる) でMACアドレスを調べてみると、 ノルディックには割付が無いみたいです。ちなみに、BLEモジュールのメーカーである、Silicon Laboratories の、Blue Gecko BGM113 Bluetooth Smart Module のBLEのMACには、Silicon Laboratories に割り当てられた、0x00, 0x0B, 0x57 で始まるMACの値で、アドバタイジングをしています。
アドバタイジング時のMACアドレスを変更するソフトデバイスのシステムコールは、sd_ble_gap_address_set(0,&caddr); で、caddr は、ble_gap_addr_t で定義される構造体です。第一引数の0は、BLE_GAP_ADDR_CYCLE_MODE_NONE で ここに解説があります。 この sd_ble_gap_address_set を、ble_advertising_start を実行する前に呼び出します。
ここで、ble_gap_addr_t addr 構造体ですが、以下のように初期化すると、MACが、0x00, 0x11, 0x22, 0x33, 0x44, 0x55 になります。
// アドバタイジング時のMACアドレスを、001122334455 に設定する。
uint32_t set_mac_and_advertize()
{
static ble_gap_addr_t caddr;
uint32_t err_code;
caddr.addr_type = 0;
caddr.addr[0] = 0;
caddr.addr[1] = 0x11;
caddr.addr[2] = 0x22;
caddr.addr[3] = 0x33;
caddr.addr[4] = 0x44;
caddr.addr[5] = 0x55;
err_code = sd_ble_gap_address_set(BLE_GAP_ADDR_CYCLE_MODE_NONE,&caddr);
printf("ret = %08X\r\n",err_code);
if(err_code == NRF_SUCCESS)
err_code = ble_advertising_start(BLE_ADV_MODE_FAST);
return err_code;
}
ところで、チップに書かれている工場出荷時のMACの値ですが、
static ble_gap_addr_t caddr;
uint32_t macaddr[2];
uint8_t *mcp;
uint8_t i;
macaddr[0] = *((uint32_t *)0x100000A4);// nRF51822 CFAC device address LSB 4 bytes
macaddr[1] = *((uint32_t *)0x100000A8);// nRF51822 CFAC device address MSB 2 bytes( D15--D0)
mcp = (uint8_t *)(&macaddr[0]);
for(i=0;i<6;i++)
caddr.addr[i] = *mcp++;
で、caddr 構造体に読むことができます。
セントラルのサンプルを改造してUARTで相互通信できるようにする
nRF51_SDK_10.0.0_dc26b5e\examples\ble_central\ble_app_uart_c フォルダが、セントラルのサンプルになっているので、これを改造します。
改造箇所は、UARTのポートと、32768の水晶が無いので、その設定です。main.c には、APP_LOG というマクロで、デバッグ情報を出すようになっていますが、APP_LOG は、app_trace_log に置き換えられており、これを有効にするには、KEIL のIDEの、Options for Target 'nRF51422_xxac_s120' のダイアログで、Define 欄に、ENABLE_DEBUG_LOG_SUPPORT を追加する必要があります。これを行わずに、
#define APP_LOG app_trace_log を、
#define APP_LOG printf に書き換えます。
こうすると、デバッグ情報がUARTに出力されます。ただ、このままでは、いつも出力されてしまうので、コントロール 'L' で、有効、無効を切り替える機能を入れるため、endgbout 変数を用意して、UARTからの、コントロール 'L ' で、endbgout を操作します。そして、APP_LOG を使ってる行に、if(endbgout) を追加します。
static void uart_init(void) 関数内を、 ペリフェラルと同じように、以下のように変更します。
const app_uart_comm_params_t comm_params =
{
.rx_pin_no = 12,//RX_PIN_NUMBER,
.tx_pin_no = 13,//TX_PIN_NUMBER,
// .rts_pin_no = RTS_PIN_NUMBER,
// .cts_pin_no = CTS_PIN_NUMBER,
// .flow_control = APP_UART_FLOW_CONTROL_ENABLED,
.flow_control = APP_UART_FLOW_CONTROL_DISABLED,
.use_parity = false,
.baud_rate = UART_BAUDRATE_BAUDRATE_Baud38400
};
};
そして、RTCが実装されていないので、main.c の、
static void ble_stack_init(void) という関数内で、
SOFTDEVICE_HANDLER_INIT(NRF_CLOCK_LFCLKSRC_XTAL_20_PPM, NULL); をコメントアウトし、代わりに
SOFTDEVICE_HANDLER_INIT(NRF_CLOCK_LFCLKSRC_RC_250_PPM_250MS_CALIBRATION , false); に置き換えます。
改造した、プロジェクトファイル一式 ble_app_uart_c.lzh
解凍して、トップフォルダ ble_app_uart_c を、nRF51_SDK_10.0.0_dc26b5e\examples\ble_central\ に置くとコンパイルできます。
ペリフェラルの電源を入れ、セントラルにUARTを接続してターミナルで表示したもの。
Conecting to target ed24482927ea のメッセージは、ペリフェラルのMACアドレスを報告しています。
The device has the device RX characteristic は、UARTサービスの、受信キャラクタリスティックを確認したメッセージ、
The device has the device TX characteristic は、UARTサービスの、送信キャラクタリスティックを確認したメッセージ で、以後、UARTで送受信できます。
ペリフェラル、セントラル共に、接続できると、UARTから20文字、または、文字列+LFコードで、無線送信を開始します。サンプルソフトでの無線送信開始が、このようになっているからであり、いわゆる、インタラクティブなUARTのように、「1文字でもUARTで受信すると送信する」という仕様ではありません。
セントラル側にタイマーを実装し、時間で処理を管理できるようにする
タイマー機能を入れるには、app_simple_timer.c 内の関数を呼び出すのですが、所在は、nRF51_SDK_10.0.0_dc26b5e\components\libraries\simple_timer フォルダにあるので、KEIL のIDEで、そのパスの設定を行います。2箇所設定します。
また、 Project 表示ウインドウで、以下のように、app_simple_timer.c と、nrf_drv_timer.c のソースファイルを追加します。
さらに、IDEの main.c は、図のソース表示のように、simple_timer_init(), NRF_TIMER1->PRESCALER = 6, app_simple_timer_start(APP_SIMPLE_TIMER_MODE_REPEATED,timer_handler,50000,NULL) をソースに追加します。ここで、timer_handler は、タイマー割込みで実行される関数で、これも追加します。この関数の中で、定期的な割り込みで、目的の処理を記述します。
timer_handler() では、以下のように、例として、LEDを点滅させて、タイマーが動作していることを確認します。
uint8_t ledoutd;
static char apptimercount[4];// 200ms tick count, 1seccount(0--59)
void timer_handler(void * p_context)
{
ledoutd ^= 1;
if(ledoutd)
nrf_gpio_pin_set(5);// timer work
else
nrf_gpio_pin_clear(5);// timer work
apptimercount[0]++;
if(apptimercount[0] >= 5)// 1 sec event
{
apptimercount[0] = 0;
apptimercount[1]++;
if(apptimercount[1] >= 60)
{
apptimercount[1] = 0;
}
//
}
}
上記のタイマー追加で、図の P0_05 に接続したテスト用のLEDが点滅します。
ペリフェラルのMACアドレス、電波強度、デバイス名などをUARTで出力する。
MACとデバイス名、そしてRSSIを報告する、プロジェクトファイル一式 ble_app_uart_c_ckADV.lzh
ノルディックのスマートフォンのBLEアプリ、ノルディックのスマートフォンのBLEアプリ のUARTをタップして、connect を押すと、アドバタイジングしているペリフェラルの デバイス名とMACがリストアップされ、電波強度は、アイコンで表示されます。
この機能を、セントラル側に実装し、UARTで報告できるようにします。
アドバタイジングで送られてくる情報には多くのものがあり、それらは、
nRF51_SDK_10.0.0_dc26b5e\components\softdevice\s120\headers\ble_gap.h
で定義されています。この、ble_gap.h の、171行目から抜き出したのが以下です。
/**@defgroup BLE_GAP_AD_TYPE_DEFINITIONS GAP Advertising and Scan Response Data format
* @note Found at https://www.bluetooth.org/Technical/AssignedNumbers/generic_access_profile.htm
* @{ */
#define BLE_GAP_AD_TYPE_FLAGS 0x01 /**< Flags for discoverability. */
#define BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_MORE_AVAILABLE 0x02 /**< Partial list of 16 bit service UUIDs. */
#define BLE_GAP_AD_TYPE_16BIT_SERVICE_UUID_COMPLETE 0x03 /**< Complete list of 16 bit service UUIDs. */
#define BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_MORE_AVAILABLE 0x04 /**< Partial list of 32 bit service UUIDs. */
#define BLE_GAP_AD_TYPE_32BIT_SERVICE_UUID_COMPLETE 0x05 /**< Complete list of 32 bit service UUIDs. */
#define BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_MORE_AVAILABLE 0x06 /**< Partial list of 128 bit service UUIDs. */
#define BLE_GAP_AD_TYPE_128BIT_SERVICE_UUID_COMPLETE 0x07 /**< Complete list of 128 bit service UUIDs. */
#define BLE_GAP_AD_TYPE_SHORT_LOCAL_NAME 0x08 /**< Short local device name. */
#define BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME 0x09 /**< Complete local device name. */
#define BLE_GAP_AD_TYPE_TX_POWER_LEVEL 0x0A /**< Transmit power level. */
#define BLE_GAP_AD_TYPE_CLASS_OF_DEVICE 0x0D /**< Class of device. */
#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C 0x0E /**< Simple Pairing Hash C. */
#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R 0x0F /**< Simple Pairing Randomizer R. */
#define BLE_GAP_AD_TYPE_SECURITY_MANAGER_TK_VALUE 0x10 /**< Security Manager TK Value. */
#define BLE_GAP_AD_TYPE_SECURITY_MANAGER_OOB_FLAGS 0x11 /**< Security Manager Out Of Band Flags. */
#define BLE_GAP_AD_TYPE_SLAVE_CONNECTION_INTERVAL_RANGE 0x12 /**< Slave Connection Interval Range. */
#define BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_16BIT 0x14 /**< List of 16-bit Service Solicitation UUIDs. */
#define BLE_GAP_AD_TYPE_SOLICITED_SERVICE_UUIDS_128BIT 0x15 /**< List of 128-bit Service Solicitation UUIDs. */
#define BLE_GAP_AD_TYPE_SERVICE_DATA 0x16 /**< Service Data - 16-bit UUID. */
#define BLE_GAP_AD_TYPE_PUBLIC_TARGET_ADDRESS 0x17 /**< Public Target Address. */
#define BLE_GAP_AD_TYPE_RANDOM_TARGET_ADDRESS 0x18 /**< Random Target Address. */
#define BLE_GAP_AD_TYPE_APPEARANCE 0x19 /**< Appearance. */
#define BLE_GAP_AD_TYPE_ADVERTISING_INTERVAL 0x1A /**< Advertising Interval. */
#define BLE_GAP_AD_TYPE_LE_BLUETOOTH_DEVICE_ADDRESS 0x1B /**< LE Bluetooth Device Address. */
#define BLE_GAP_AD_TYPE_LE_ROLE 0x1C /**< LE Role. */
#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_HASH_C256 0x1D /**< Simple Pairing Hash C-256. */
#define BLE_GAP_AD_TYPE_SIMPLE_PAIRING_RANDOMIZER_R256 0x1E /**< Simple Pairing Randomizer R-256. */
#define BLE_GAP_AD_TYPE_SERVICE_DATA_32BIT_UUID 0x20 /**< Service Data - 32-bit UUID. */
#define BLE_GAP_AD_TYPE_SERVICE_DATA_128BIT_UUID 0x21 /**< Service Data - 128-bit UUID. */
#define BLE_GAP_AD_TYPE_3D_INFORMATION_DATA 0x3D /**< 3D Information Data. */
#define BLE_GAP_AD_TYPE_MANUFACTURER_SPECIFIC_DATA 0xFF /**< Manufacturer Specific Data. */
ここで、デバイス名は、Complete local device name と説明されている、0x09, なので、これをアドバタイジングで受信したデータからさがします。
static void on_ble_evt(ble_evt_t * p_ble_evt) 関数でソフトデバイスからイベントとして呼び出されますが、アドバタイジングのデータは、p_ble_evt というポインタで渡されているので、このポインタから参照します。このポインタから、ble_gap_evt_t で定義される構造体へのポインタ、gap_evt を ble_gap_evt_t *p_gap_evt の、p_gap_evt にコピーしておきます。
p_ble_evt には、どういうデータかを示すIDが、p_ble_evt->header.evt_id にあるので、これを調べて BLE_GAP_EVT_ADV_REPORT なのかを判定し、一致すると、MACとか、デバイス名とか、サービスのUUIDとかを読み出せます。ここのところが、on_ble_evt 関数で、switch() されていて、case BLE_GAP_EVT_ADV_REPORT: の場合に、読み出します。読み出すとき、先にコピーしたポインタ、p_gap_evt を使って辿っていきます。
サンプルソフトでは、デバイス名とか、RSSIを読み出すようにはなっておらず、これらを追加します。
以下が、以上の機能を入れて、MAC、デバイス名、UUIDをUARTで報告する、static void on_ble_evt(ble_evt_t * p_ble_evt) 関数の最初の部分です。
/**@brief Function for handling the Application's BLE Stack events.
*
* @param[in] p_ble_evt Bluetooth stack event.
*/
static void on_ble_evt(ble_evt_t * p_ble_evt)
{
uint32_t err_code;
uint32_t advlen,i,j,n;
uint32_t index = 0;
uint8_t field_length;
uint8_t field_type;
const ble_gap_evt_t * p_gap_evt = &p_ble_evt->evt.gap_evt;
switch (p_ble_evt->header.evt_id)
{
case BLE_GAP_EVT_ADV_REPORT:
{
const ble_gap_evt_adv_report_t *p_adv_report = &p_gap_evt->params.adv_report;
uint8_t *p_data = (uint8_t *)p_adv_report->data;
advlen = p_adv_report->dlen;
if(endbgout)
APPL_LOG("ADVDATA =");
for(i=0;i < advlen;i++)
{
if(endbgout)
APPL_LOG("%02X ",p_data[i]);
}
if(endbgout)
APPL_LOG("\r\n");
for(index=0;index < advlen;)
{
field_length = p_data[index];
field_type = p_data[index+1];
if(field_type == BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME)// long devicename. max 16
{
j = field_length + index + 1;
for(i=index + 2,n=0;i= 15)
{
n = 15;
break;
}
}
devicename[n] = 0;
if(endbgout)
{
APPL_LOG("Device Name = [%s] ",devicename);
APPL_LOG("MAC = %02X%02X%02X%02X%02X%02X",
p_adv_report->peer_addr.addr[0],
p_adv_report->peer_addr.addr[1],
p_adv_report->peer_addr.addr[2],
p_adv_report->peer_addr.addr[3],
p_adv_report->peer_addr.addr[4],
p_adv_report->peer_addr.addr[5]
);
APPL_LOG(" RSSI = %3d\r\n",p_adv_report->rssi);
}
break;
}
index += field_length + 1;
}
if (is_uuid_present(&m_nus_uuid, p_adv_report))// nordic NUS servis
{
err_code = sd_ble_gap_connect(&p_adv_report->peer_addr,
&m_scan_params,
&m_connection_param);
if (err_code == NRF_SUCCESS)
{
// scan is automatically stopped by the connect
err_code = bsp_indication_set(BSP_INDICATE_IDLE);
APP_ERROR_CHECK(err_code);
if(endbgout)
APPL_LOG("Connecting to target %02x%02x%02x%02x%02x%02x\r\n",
p_adv_report->peer_addr.addr[0],
p_adv_report->peer_addr.addr[1],
p_adv_report->peer_addr.addr[2],
p_adv_report->peer_addr.addr[3],
p_adv_report->peer_addr.addr[4],
p_adv_report->peer_addr.addr[5]
);
}
}
break;
}
以上の機能を入れて、ターミナルで、接続までの状態を、表示したものが以下です。
上の記録で、ADVDATA =02 01 05 0C 09 4E 6F 72 64 69 63 5F 55 41 52 54 ですが、アドバタイジングで送られてきた、データです。
データの意味は、02 は、続く2バイトのデータ区切りを示し、01 は、BLE_GAP_AD_TYPE_FLAGS で、内容は 05 。意味は判りません。その次の、0C が、12バイトのデータで、09 が、BLE_GAP_AD_TYPE_COMPLETE_LOCAL_NAME で、デバイス名のデータという意味。これに続いて、デバイス名が、4E 6F 72 64 69 ・・・と続き、"Nordic_UART" です。このデータはこれで全部ですが、別のパケットで、ADVDATA =11 07 9E CA DC ・・・・と送られており、このデータは、Complete list of 128 bit service UUIDs. であることが、11 の次の 07 で判ります。このデータは16バイトあり、ノルディックの、UARTサービスのユニークなUUIDです。セントラル側では、このUUIDをチェックして、相手が、ノルディックのUARTサービスのUUIDなら、接続手続きに移行します。
次の行の、Connecting to target ed24482927ea は、相手のMACを示して、接続シーケンスに移ることを報告したものです。
さてここで、MAC アドレスはどう読み出すかですが、MACは、アドバタイジングを受信したデータには、常に含まれていて、別の場所にあります。p_gap_evt->params.adv_report->data が、UUIDや、デバイス名で、MACは、p_gap_evt->params.adv_report.peer_addr.addr に記録されています。
デバイス名が送られてくるアドバタイジングパケットにも、UUIDが送られてくるアドバタイジングパケットにも、常に、MAC情報がバンドルされています。RSSIも同じく、どのアドバタイジングパケットにもバンドルされています。RSSIは、p_gap_evt->params.adv_report.rssi の1バイトの情報です。
p_gap_evt->params.adv_report.data が、アドバタイジングデータですが、構造は以下のようになっています。
アドバタイジングデータの構造 2個の場合
バイト数 (データ種別1バイト+データのバイト数)
データ種別
データ
バイト数 (データ種別1バイト+データのバイト数)
データ種別
データ
02
01
05
0C
09
0C 09 4E 6F 72 64 69 63 5F 55 41 52 54 (Nordic_UART 11 バイト)
MACとデバイス名、そしてRSSIを報告する、プロジェクトファイル一式 ble_app_uart_c_ckADV.lzh
解凍して、トップフォルダ ble_app_uart_c_ckADV を、nRF51_SDK_10.0.0_dc26b5e\examples\ble_central\ に置くと変更、再コンパイルできます。
セントラル側で、特定のペリフェラルを指定して接続し、UARTの相互通信をする。
この機能を実現するには、ペリフェラルの、デバイス名か、MACアドレスを指定するとできますが、そのデータをどこに置くかという違いでセントラルのUARTの扱い方が異なってきます。nRF51822 のチップにROMとして書くと、常に通信相手は固定され、変更するには、ROMを書き換えないといけませんが、UARTの操作はそのままです。
もう一つの方法は、nRF51822 の外部、つまり、セントラルとUARTで接続するホストに設定しておき、UARTで通知するという方法です。
まず、ペリフェラルのデバイス名とか、MACをROMに書いておいて、特定の相手だにけ接続できるかを確認します。
アドバタイジングのデータで得られたデバイス名などが、所定のものならば、接続に移行するのは、比較的簡単に行えます。
on_ble_evt 関数の、case BLE_GAP_EVT_ADV_REPORT: のところで UUIDの判定をしているところがありますが、
if (is_uuid_present(&m_nus_uuid, p_adv_report))
この判定条件に、デバイス名などの一致を条件として追加すればいいわけです。
デバイス名が一致したか、否か を区別するため、ペリフェラル側で、デバイス名を一部変更できる機能を先に実装します。デフォルトでは、"Nordic_UART" になっているので、中央付近の、'_' のところを、電源ON後に変更できるようにしてみます。変更する操作が済むまで、アドバタイジングに移行しないようにフラグで操作します。
以上のような機能を入れた、ペリフェラルのプロジェクトファイル一式です。ble_app_uart3.lzh
ble_app_uart3.lzh では、電源ONで、接続したときに点灯するようにしていたLEDを点滅させ、UARTから何か1文字受信すると、LEDが消え、アドバタイジングを開始します。1文字として、' ' スペースなら、"Nordic_UART" とデフォルトのデバイス名、スペース以外なら、その文字、たとえば、'1' なら、"Nordic1UART" というデバイス名になります。
セントラル側で、ペリフェラルのデバイス名として、"Nordic_UART" なら、接続を開始するように改造したものが以下です。
ペリフェラルのデバイス名が"Nordic_UART"のみと接続できる。ble_app_uart_c_ckADV_dev.lzh
準備中
ホームに戻る
SEO