nRF52でBLEデバイスを開発する（４）サービスを追加する

この記事は、[nRF52でBLEデバイスを開発する（３）nRF52840のRTTでログ出力する](https://qiita.com/jiro-aqua/items/80e29b32702c7ebe3981)の続きです。

# はじめに
　今回から、前回作った[OTAサンプルプロジェクト](https://github.com/jiro-aqua/nrf52-ota-example)にBLEサービスを追加していきます。

# サービス仕様
　スマホからコマンドを投げて、デバイスを制御したり、センサーを読んだりするようなサービスをふんわりと想定して、ざっくりと以下のような仕様にしましょう。

UUID : 8154ef7d-469b-471e-b071-2cfe62d43f3a

|Characteristics|方向|用途|種別|
|:-:|:-:|:-:|:-:|
|ef7d||サービス識別||
|ef70|下り|コマンド|Write without Response|
|ef71|上り|コマンド応答|Notification|
|ef72|上り|データ|Notification|

　UUIDは[Online GUID Generator](https://www.guidgenerator.com/online-guid-generator.aspx)で128bitのGUIDを生成します。
　3-4byte目をサービス毎に使い分けます。

　下りは数バイトのコマンド列を想定。Write With ResponseのResponseに値を乗せることは出来ないので、without Responseでコマンドを投げます。
　上りはNotificationを使用します。コマンド応答(Ack/Nack)とは別に、センサーデータの垂れ流し用のチャネルを作っておくことにします。

### MTU(Maximum Transfer Unit)
　MTUによって、一パケットで送信可能なデータサイズの上限が決まります。Androidがホストの場合512bytes、iOSがホストの場合185Bytesとなります。今時、スマホ対応はAndroid/iOSの両プラットフォームが対応の場合がほとんどなので、MTU=185Bytesとして設計を進めることとします。

　参照）【Bluetooth Low Energy: BLE】iOS & Androidの最大データ転送量 https://qiita.com/hituziando/items/50c7ba7fb9f8917e1ebf

### CI(Connection Interval)
　AndroidのCI=11ms、iOSのCI=30msなので、高速通信を想定するとmin=11ms,max=30msに設定するのがベストプラクティスです。


# DK依存部分の削除
　まず、前回のサンプルはnRF52840-DKで動作させる前提のサンプルコードのため、DKのLED/ボタンを直接アクセスしています。Featherではピン配置が異なるので、依存部分をコメントアウトします。

```
//#include "bsp_btn_ble.h"
```
　コンパイルエラーとなる部分もコメントアウトします。

　Bootloaderも同様に処理します。

```
//#include "boards.h"
```

# サービスの追加
### UUIDの追加
　まず、sdk_config.hでUUID数を設定します。以下の部分を１→２に変更します。

```sdk_config.h
// <o> NRF_SDH_BLE_VS_UUID_COUNT - The number of vendor-specific UUIDs. 
#ifndef NRF_SDH_BLE_VS_UUID_COUNT
#define NRF_SDH_BLE_VS_UUID_COUNT 2
#endif
```
　次に以下の定義を行います。

```
#define AB_BLE_UUID_BASE        {0x3a, 0x3f, 0xd4, 0x62, 0xfe, 0x2c, 0x71, 0xb0, \
                                 0x1e, 0x47, 0x96, 0x46, 0x00, 0x00, 0x54, 0x81}
```

　そして、UUIDを追加します。

```
static uint8_t uuid_type;

static void services_init(void)
{
    uint32_t                  err_code;

～～～～
    // Add service.
    ble_uuid128_t base_uuid = {AB_BLE_UUID_BASE};
    err_code = sd_ble_uuid_vs_add(&base_uuid, &uuid_type);
    APP_ERROR_CHECK(err_code);
```
　これを実行すると、以下のようなエラーがログに出力されます。

```
<warning> nrf_sdh_ble: Insufficient RAM allocated for the SoftDevice.
<warning> nrf_sdh_ble: Change the RAM start location from 0x20002228 to 0x20002230.
<warning> nrf_sdh_ble: Maximum RAM size for application is 0xDDD0.
<error> nrf_sdh_ble: sd_ble_enable() returned NRF_ERROR_NO_MEM.
<error> app: ERROR 4 [NRF_ERROR_NO_MEM] at C:\Tech-Pitch\nordic\002DF1F
```
　他にもsdk_config.hを編集すると同様のエラーが出る場合があります。ソフトデバイスに割り当てられたメモリが不足しているという趣旨のメッセージです。このエラーが出た場合は、メモリのアサイン領域を変更する必要があります。今回の場合、RAM_STARTを0x20002230に、RAM_SIZEを0xDDD0に変更しろ、という意味になります。nRF52832とnRF52840で利用可能なメモリサイズが異なるため、異なる値になります。

### メモリアサインの変更
　まず、Project ExplorerでProject xxx...を選び、Optionsを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89529/3e152b0c-5da0-4c54-5674-189a72c3213e.png)

　次に、ConfigurationのCommonを選びます。
　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89529/ee3c5e14-a66f-eda2-2b48-1ebe39c06607.png)
　Code>Linker>Section Placement Macrosを選択し、[...]を押します。
　![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89529/7607eb40-8726-4619-837d-78777712bafc.png)
　ここのRAM_STARTをエラーメッセージに従い修正します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89529/8a99e60e-ba90-8a6d-840d-cff89f0755ce.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89529/1871b62d-5762-e4c4-7622-30002e20492d.png)

　変更したら、リビルドします。

### アドバタイズ
　サービスのUUIDを定義し、

```
#define AB_BLE_UUID_SERVICE       0xef7d
```
　m_adv_uuidsを書き換えて、

```
static ble_uuid_t m_adv_uuids[] = {{AB_BLE_UUID_SERVICE, 0}};
```
　advertising_init()内で、UUID TYPEを代入しておきます。UUID TYPEはsd_ble_uuid_vs_add()呼び出し時に動的に決まるので、動的に設定します。(実際には0,1,2...と発行されるので、固定値でも実質問題はありません)

```
static void advertising_init(void)
{
    uint32_t               err_code;
    ble_advertising_init_t init;

    m_adv_uuids[0].type = uuid_type;
```
　main()でservices_init()がadvertising_init()の前に呼ばれるように修正します。

```
    services_init();
    advertising_init();
```
 最後に、デバイス名を書き換えておきましょう。

```
#define DEVICE_NAME                     "AquaBlue"                                  /**< Name of device. Will be included in the advertising data. */
#define MANUFACTURER_NAME               "Aquamarine Networks."                      /**< Manufacturer. Will be passed to Device Information Service. */
```
　今日から君の名前はAquaBlueだっ！

### 実行してみましょう

　Nordic純正のnRF Connectアプリ(Play StoreかApp Storeからダウンロードしましょう)でスキャンかけると、定義したUUIDでアドバタイズできていることが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89529/26f756fd-6f5f-b9cc-7074-675a4d0f1d2a.png)

　あれ？　名前が短い。「AquaBlue」で定義したのに、「Aqua」しか出てきません。これは、アドバタイズヘッダにUUIDが入るせいで、名前の部分が圧迫されたためです。アドバタイズを名無しにして、スキャンレスポンスでフルネームが返るように設定することで解決します。

```
    init.advdata.name_type               = BLE_ADVDATA_NO_NAME;
    init.srdata.name_type                = BLE_ADVDATA_FULL_NAME;
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89529/90b43ce9-6e19-4433-398b-a593f6b6dc83.png)
　名前が出ました！

　以上です。

　ソース一式はgithub(https://github.com/jiro-aqua/nrf52-ota-example )においてありますので、動かす方はそちらを参照してください。