Adafruit Bluefruit nRF52 で複数の BLE コネクションを張るには

Adafruit Bluefruit nRF52 向けの Arduino 開発環境で、デバイス側を Peripheral にしたとき複数の Bluetooth LE 接続を受け入れる方法を説明します。

# Adafruit Bluefruit nRF52 とは

Adafruit が販売している、Nordic nRF52 系チップを搭載した開発ボードです。
Arduino 開発環境がしっかりしていて、気軽に BLE が使えて大変便利です。
https://github.com/adafruit/Adafruit_nRF52_Arduino

具体的な製品は、以下の2つになります。

- [Adafruit Feather nRF52832](https://www.adafruit.com/product/3406)
- [Adafruit Feather nRF52840 Express](https://www.adafruit.com/product/4062)

# Peripheral で複数の BLE コネクションを張る

[Adafruit_nRF52_Arduino](https://github.com/adafruit/Adafruit_nRF52_Arduino/) では、バージョン 0.10.0 から複数の BLE コネクションを同時に処理することができるようになっています。

ここでは、ボード側が Peripheral で複数の Central からの接続を受け入れるようなものを想定しています。
Central になったときの複数接続も、ライブラリは多分対応していると思う。(使ったことがない。

## サンプルコード

UART サービスを用いたサンプルコードは、ライブラリの examples に含まれています。
https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/master/libraries/Bluefruit52Lib/examples/Peripheral/bleuart_multi/bleuart_multi.ino

## 具体的な使い方

### 最大接続数の指定

Bluefruit ライブラリを `begin()` するときに、最大接続数を指定します。
通常では、1 になっていて単一のコネクションしか受け付けません。
1 以上に値にすると、複数のコネクションを受け付けることができるようになります。

```cpp
  // Initialize Bluefruit with max concurrent connections as Peripheral = 2
  Bluefruit.begin(2);
```

### コネクションの特定: conn_hdl

複数コネクションが確立されたとき、どのコネクションからの要求なのかは `conn_hdl` というパラメーターにより特定することができます。

例えば、ある Characteristic への write callback handler は、`conn_hdl` をパラメーターに含みます。
https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/master/libraries/Bluefruit52Lib/src/BLECharacteristic.h#L79

```cpp
typedef void (*write_cb_t)           (uint16_t conn_hdl, BLECharacteristic* chr, uint8_t* data, uint16_t len);
```

殆どの callback handler のパラメーターに `conn_hdl` が含まれていて、どのデバイスからのオペレーションなのかが特定できます。(connect, disconnect, write など)
詳しいコネクションの情報を取得するには、`conn_hdl` を利用して以下のように取得します。

```cpp
BLEConnection* connection = Bluefruit.Connection(conn_hdl);
```

Notify や Indicate をするときに、特定のデバイスのみに Notify を行うために `conn_hdl` をパラメーターに与えてあげます。
単一コネクション時は、このパラメーター無しで notify できますが、複数コネクションを扱う場合には `conn_hdl` の指定が必要です。

```cpp
bool notify   (uint16_t conn_hdl, const void* data, uint16_t len);
```

### Advertising の制御

また、connect されたときのハンドラで、少し Advertising の制御を行う必要があることも忘れないでください。

単一コネクションのときは、接続されたら Advertising が止まり、接続を解除されると再度 Advertising が始まります。
そして、その間に他の Central は接続することができません。

しかし、複数コネクションのときは、接続されても他の Central を接続可能にするために、最大接続数に到達していなければ、Advertising を出し続ける必要があります。
Bluefruit ライブラリはこのような制御を自動で行ってくれないので、自分で connection handler で行う必要があります。

```cpp
uint8_t connection_count = 0;

void connect_callback(uint16_t conn_handle) {
  connection_count++;
  
  // Keep advertising if not reaching max
  if (connection_count < MAX_PRPH_CONNECTION) {
    Bluefruit.Advertising.start(0);
  }
}
```

## 最大接続数を上げすぎると...

サンプルコードでは最大接続数が2になっていますが、3までは問題なく動作するようです。

Bluefruit ライブラリを読み込んでいくと、Nordic の SoftDevice は最大 20 接続まで対応しているようなことが書いてあります。
実際、ライブラリの実装もこの数字で制限をかけています。
https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/8f35e83b4f39916ed996bd4ab9466107b0768804/libraries/Bluefruit52Lib/src/bluefruit.h#L49

しかし、最大接続数を 3 以上に指定すると、以下のようなエラーが出て起動しません。
(確か、このログは Debug レベルを Level 2: Full debug にして取得した)
つまり、SoftDevice に与えるメモリが足りないようです。

```
[CFG   ] SoftDevice config require more SRAM than provided by linker.
App Ram Start must be at least 0x20006528 (provided 0x20003400)
Please update linker file or re-config SoftDevice
[CFG   ] SoftDevice's RAM requires: 0x20006528
bool AdafruitBluefruit::begin(uint8_t, uint8_t): 447: verify failed, error = NRF_ERROR_NO_MEM
```

### NRF_ERROR_NO_MEM で起動しない場合の解決方法

ライブラリを読み込んでいくと興味深いコメントを発見しました。

```cpp
   *  Attr Table Size, HVN queue size, Write Command queue size is
   *  determined later in begin() depending on number of peripherals
   *  and central connections for optimum SRAM usage.
```

このあたりから推測するに、最大接続数を上げるには、attr table とか queue とかで SoftDevice に沢山メモリを与える必要があるが、それが足りていないようです。
エラーメッセージにはリンカスクリプトを変えろと出ていますので、それを見てみましょう。

https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/master/cores/nRF5/linker/nrf52832_s132_v6.ld

```
  /* SRAM required by S132 depend on
   * - Attribute Table Size
   * - Vendor UUID count
   * - Max ATT MTU
   * - Concurrent connection peripheral + central + secure links
   * - Event Len, HVN queue, Write CMD queue
   */ 
  RAM (rwx) :  ORIGIN = 0x20003400, LENGTH = 0x20010000 - 0x20003400
```

確かに、エラーメッセージに含まれている `provided 0x20003400` と同じアドレスがここで指定されているので、この値は固定値のようです。
流石に、SoftDevice の最大値までコネクションを張るやつはいないと思われているんでしょうか... (まあ SRAM は貴重ですからね)

ここでは、`[CFG   ] SoftDevice's RAM requires: 0x20006528` と言われているので、`0x20003400` をこの値を超える適当な数字に書き換えればよいでしょう。

あとは、ライブラリをリンクしなおせば、無事解決するはず...
それにしても、結構 SoftDevice はメモリ持ってくようですね。
最大接続 20 は実運用では厳しいかも。