Nordic社の BLE通信モジュール nRF54l15 の開発ボードを用いて、nRF connect SDK で BLEのアドバタイジングを設定する方法について解説します。
今回の記事の内容は、Nordic Developer Academy の以下のレクチャーを参照しました。
技適について
今回の記事で使用している開発ボードは技適を取得していないのですが、技適未取得機器を用いた実験等の特例制度に登録しているので、BLE通信を使用しても問題ないようになっています。
私の知る範囲では、大企業でもフリーランスでも開発ボードを特例制度に申請せず使っているところが大半で、内々で使用している限りは事実上黙認状態になっているかもしれませんが、外部に公開する記事や展示会、試作品で開発ボードを使用する際には、様々なリスクがあるので違法にならないようにご注意ください。
申請方法は簡単で、許可もすぐに降ります。
唯一分かりづらいのは「技術基準に適合する事実の確認方法」への記入ですが、これは外国の法令のCEと回答してください。
半年後に機器の使用を停止しないといけないこと、停止時に「廃止届出」も提出しないといけないことだけが、この特例制度の面倒な点です。
以下のサイトが申請方法を解説しているのでご参照ください。
アドバタイジングの種類
アドバタイジングはBLEのPeripheral側が、Central側とBLE接続していない時に発信するデータです。
アドバタイジングの用途は2つあります。
1つは、Central側と接続するために自身の存在をアドバタイズ(宣伝)することです。
もう1つは、Central側とのBLE接続はせずに、ひたすらに周囲にデータを送信し続けるビーコンとして使用することです。
また、アドバタイジングパケットにデータが収まりきらない場合、スキャンレスポンスというデータをアドバタイジングに付随させて送信できます。
アドバタイジングを検知したCentral側からのスキャンリクエストを受けて、スキャンレスポンスを返します。
また通常のアドバタイジングパケットは31バイトという制限がありますが、Bluetooth Low Energy 5.0以降に導入された拡張アドバタイジングを用いれば、255バイトまでデータ送信できます。
特定のデバイスに向けてアドバタイジングパケットを送信するダイレクトアドバタイジングを設定することもできます。
特定のデバイスと素早く接続できるという利点があります。
なお、ダイレクトアドバタイジングには、高デューティサイクルと低デューティサイクルの2種類があります。
よってアドバタイジングは、以下の組み合わせで様々なパターンがあり得ます。
- BLE接続するかどうか
- スキャンレスポンスをつけるかどうか
- 拡張アドバタイジングにするかどうか
- ダイレクトアドバタイジングにするかどうか
アドバタイジングパラメータの設定
bt_le_adv_param の設定
アドバタイジングには様々なパラメータがあります。
パラメータの構造体 bt_le_adv_param
を用いてパラメータの設定を行うことができます。
アドバタイジングのパラメータも拡張アドバタイジングのパラメータも bt_le_adv_param
で設定できます
bt_le_adv_param
のメンバは以下になります
- id
- Bluetooth IDを指定するパラメータ
- 通常はBluetooth IDであるBT_ID_DEFAULTを指定する
- sid
- Advertising Set Identifierの略で、値の有効範囲は0x00から0x0F
- 複数のアドバタイジングセットを区別するために使用する
- 単一のアドバタイジングセットのみを使用する場合、0で固定
- secondary_max_skip
- 拡張アドバタイジングでのみ設定できる
- セカンダリチャネルでのアドバタイジングイベントのスキップ回数
- スキップ回数を増やすとアドバタイジング回数が減るので、消費電力が下がる。ただし、代わりに検出率も下がる
- options
- 様々なアドバタイジングの性能を決める。詳細は後述
- interval_min
- アドバタイジング周期の最小値を定める値で、値の有効範囲は0x0020から0x4000
- 設定した値× 0.625msが最小周期になる
- interval_max
- アドバタイジング周期の最大値を定める値で、値の有効範囲は0x0020から0x4000
- 設定した値× 0.625msが最大周期になる
- 必ずinterval_minより大きい値を設定しないといけない
- peer
- ダイレクトアドバタイジングが有効な場合のみ用いる
- ダイレクトアドバタイジングは、特定のデバイスに向けてアドバタイジングパケットを送信する。そのデバイスのアドレスを指定する
- アンダイレクトアドバタイジングの場合はNULLに設定する
パラメータbt_le_adv_param
は以下の BT_LE_ADV_PARAM_INIT を用いて設定するのが一番簡単です。
#define BT_LE_ADV_PARAM_INIT(_options, _int_min, _int_max, _peer) \
{ \
.id = BT_ID_DEFAULT, \
.sid = 0, \
.secondary_max_skip = 0, \
.options = (_options), \
.interval_min = (_int_min), \
.interval_max = (_int_max), \
.peer = (_peer), \
}
BT_LE_ADV_PARAM_INIT
ではidがBluetooth IDであるBT_ID_DEFAULTに固定され、sidが0に固定されています。
単一のアドバタイジングセットのみを使用する場合、sidを0に固定しても問題ありません。
struct bt_le_adv_param param
= BT_LE_ADV_PARAM_INIT(BT_LE_ADV_OPT_CONNECTABLE |
BT_LE_ADV_OPT_USE_NAME,
800,
801,
NULL);
上記の設定ではBLE接続可能なアドバタイジングのオプションとアドバタイジングパケットにデバイス名を含めるオプションが有効化されています。
オプションについての詳細は後述します。
また、アドバタイジング周期は最小で800×0.625m、最大で801×0.625msです。
ダイレクトアドバタイジングを使用しないのでpeerアドレスはNULLが指定されています。
ほとんど同じ機能を持つ関数として BT_LE_ADV_PARAM
もあります。
こちらは、後述するアドバタイジング開始関数のパラメータの引数へ直接に代入できます。
err = bt_le_adv_start(BT_LE_ADV_PARAM(options, int_min, int_max, peer), ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
もちろんBT_LE_ADV_PARAM_INIT
もBT_LE_ADV_PARAM
も使用せずに、手動で構造体の値を設定しても問題ありません。
struct bt_le_adv_param param = {
.id = BT_ID_DEFAULT,
.sid = 0,
.options = BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_EXT_ADV,
.interval_min = 800,
.interval_max = 801,
.peer = NULL,
};
idやsidの値も設定する必要がある場合は、手動で設定しないといけません。
拡張アドバタイジングで複数のアドバタイジング設定を作成した時にsidに番号をつけて識別する必要があります。
options
オプションの設定はorで連結してoptionsに代入します。
オプションには以下の種類があります。
拡張アドバタイジングでのみ有効なオプション、ダイレクトアドバタイジングを使用している場合のみ有効なオプション、ホワイトリスト(正確にはFilter Accept List)を使用している場合にのみ有効なオプションがあるのでご注意ください。
- アドバタイジング種類の設定
- BT_LE_ADV_OPT_CONN
- BLE接続可能なアドバタイジング
- BT_LE_ADV_OPT_SCANNABLE
- Central側からのスキャンリクエストに対してスキャンレスポンスも送信するアドバタイジング
- BT_LE_ADV_OPT_EXT_ADV
- 拡張アドバタイジング
- BT_LE_ADV_OPT_NOTIFY_SCAN_REQ
- 拡張アドバタイジング使用時のみ有効なオプション
- Central側からのスキャンリクエストを受信した時、Peripheral側がスキャンレスポンスを送信すると同時に、Peripheral側へそれを報せる通知を出す
- 具体的には
bt_le_ext_adv_cb.scanned
に登録した「コールバック関数が呼び出される
- BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY
- ダイレクトアドバタイジング使用時のみ有効なオプション
- Low Dutyのダイレクトアドバタイジング
- ダイレクトアドバタイジングの初期設定はHigh Dutyなので、High Dutyを使用する場合はオプションの指定は必要ない
- BT_LE_ADV_OPT_DIR_ADDR_RPA
- ダイレクトアドバタイジング使用時のみ有効なオプション
- 「Directed advertising to privacy-enabled peer」(プライバシー対応ピアへのダイレクトアドバタイジング)を意味する
- ダイレクトアドバタイジングのターゲットアドレスとして、Resolvable Private Address (RPA)を使用できるようにする
- BT_LE_ADV_OPT_USE_NRPA
- アドバタイジングアドレスとしてNon-Resolvable Private Address (NRPA)を使用する
- アドバタイジングパラメータを更新するたびに新しいアドレスが設定される
- このオプションの代わりにCONFIG_BT_EXT_ADVを有効にすることが推奨されているので、なるべく使用しない方が良いオプション
- BT_LE_ADV_OPT_USE_IDENTITYと組み合わせて使用することはできない
- BT_LE_ADV_OPT_CONN
- Filter Accept List(旧ホワイトリスト)に関する設定
- BT_LE_ADV_OPT_FILTER_SCAN_REQ
- Filter Accept Listに登録されているデバイスからのスキャンリクエストのみを受け付ける
- BT_LE_ADV_OPT_FILTER_CONN
- Filter Accept Listに登録されているデバイスからの接続リクエストのみを受け付ける
- BT_LE_ADV_OPT_FILTER_SCAN_REQ
- アドバタイジングパケットのデータに関する設定
- BT_LE_ADV_OPT_USE_IDENTITY
- ローカルIDを使用(静的アドレス)
- 通常はランダムに生成されたアドレスが使用されるが、このオプションを有効にするとアドレスが固定される
- 複数のアドバタイジングセットを使用している場合、アドレスが共通になる
- デバイスを識別しやすくなり、BLE接続の処理時間が1.6msから0.6msに短縮される利点がある
- デバイスのプライバシーが低下する欠点がある
- BT_LE_ADV_OPT_ANONYMOUS
- 拡張アドバタイジング使用時のみ有効なオプション
- アドバタイジングPDU(Protocol Data Unit)からアドバタイザーのアドレスが省略する。これにより、デバイスの匿名性が高まり、追跡が困難になる
- BLE接続可能時には使用できないので、BT_LE_ADV_OPT_CONNECTABLEと組み合わせて使用することは不可
- BT_LE_ADV_OPT_USE_TX_POWER
- 拡張アドバタイジング使用時のみ有効なオプション
- TX Powerの情報をアドバタイジングパケットに含める
- BT_LE_ADV_OPT_USE_NAME
- 将来的に廃止される予定のオプションなので、なるべく使用しないこと
- デバイス名をアドバタイジングパケットに含める
- 現在は明示的にデバイス名をアドバタイジングパケットに含める方法が推奨される
- BT_LE_ADV_OPT_FORCE_NAME_IN_AD
- 将来的に廃止される予定のオプションなので、なるべく使用しないこと
- 通常、デバイス名はスキャンレスポンスに含まれるが、このオプションを有効にするとアドバタイジングパケットに含まれるようになる
- 現在は明示的にデバイス名をアドバタイジングパケットに含める方法が推奨される
- BT_LE_ADV_OPT_USE_IDENTITY
- 通信性能に関する設定
- BT_LE_ADV_OPT_NO_2M
- 拡張アドバタイジング使用時のみ有効なオプション
- セカンダリアドバタイジングチャネルでの2M PHYを無効化して1M PHYを使用する
- セカンダリアドバタイジングチャネルは、初期設定では2M PHYになるので、2M PHYを使用するならばオプションで指定する必要はない
- BT_LE_ADV_OPT_CODED
- 拡張アドバタイジング使用時のみ有効なオプション
- プライマリアドバタイジングチャネルでLE Coded PHY(長距離通信用)を使用する
- プライマリアドバタイジングチャネルは、初期設定では1M PHYになるので、1M PHYを使用するならばオプションで指定する必要はない
- BT_LE_ADV_OPT_REQUIRE_S2_CODING
- LE Coded PHYを使用する際に、S=2コーディングスキームを使用する
- S=8の約2倍のデータレートを持つ
- エラー訂正能力が低いため通信範囲はS=8より短い
- 使用条件が満たされない場合は自動でS=8になる
- BT_LE_ADV_OPT_REQUIRE_S8_CODING
- LE Coded PHYを使用する際に、S=8コーディングスキームを使用する
- データレートは低いが、通信範囲が広くなる
- BT_LE_ADV_OPT_DISABLE_CHAN_37
- アドバタイジング用の3つのチャネルの1つであるチャネル37 (2402 MHz)を無効にする
- BT_LE_ADV_OPT_DISABLE_CHAN_38
- アドバタイジング用の3つのチャネルの1つであるチャネル38 (2426 MHz)を無効にする
- BT_LE_ADV_OPT_DISABLE_CHAN_39
- アドバタイジング用の3つのチャネルの1つであるチャネル39 (2480 MHz)を無効にする
- BT_LE_ADV_OPT_NO_2M
bt_le_ext_adv_start_param
拡張アドバタイジングの場合、もう1つのパラメータbt_le_ext_adv_start_paramも設定しないといけません。
これはアドバタイジングを一定時間後に停止させるDurationの設定です。
これは以下のパラメータから成ります。
- timeout
- 設定した値×10msの時間が経過するとアドバタイジングを停止する
- 0に設定した場合、タイムアウト時間は無限とみなす
- num_events
- 設定した回数のアドバタイジング(イベント)を発信するとアドバタイジングを停止する
- 0に設定した場合、イベント数は無限とみなす
このパラメータは後述する拡張アドバタイジングの開始関数の第2引数に代入します。
bt_le_ext_adv_start(adv, &start_param);
もし両パラメータの値が初期値の0のままでよいならば、bt_le_ext_adv_start_param
の設定をする必要はなく、以下のようにマクロを代入します。
bt_le_ext_adv_start(adv, BT_LE_EXT_ADV_START_DEFAULT);
アドバタイジングのDuraton
なお、通常のアドバタイジングで一定時間経過後にアドバタイジング停止させたい場合は、タイマーを使用する方法が推奨されています。
// タイマーコールバック
static void adv_timer_handler(struct k_timer *timer)
{
// アドバタイジングを停止
bt_le_adv_stop();
}
int some_function(void)
{
// タイマーの定義
struct k_timer adv_timer;
// アドバタイジング開始時にタイマーをセット
k_timer_init(&adv_timer, adv_timer_handler, NULL);
k_timer_start(&adv_timer, K_MSEC(5000), K_NO_WAIT);
// アドバタイジング開始処理
...
}
Kconfigファイルの設定とインクルードファイル
BLE機能を使用する際には様々なConfig設定やインクルードの指定が必要になります。
その中でも最低限必要になる設定は以下になります。
nRF Connect SDKのBluetooth LE stackを使用する為に、prj.confに以下を設定します。
CONFIG_BT=y
この設定を有効化した時点で、アドバタイジングのブロードキャストは有効化され、BT_LL_SOFTDEVICEのコントローラが使用可能になり、TX Powerは0dBmになります。
また、インクルードファイルとして以下を指定します
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/gap.h>
BLE接続可能なアドバタイジングを使用する場合
オプションで BT_LE_ADV_OPT_CONN を設定し、PeripheralとしてBLE接続可能なアドバタイジングを使用する場合には、prj.confに以下を設定します。
CONFIG_BT_PERIPHERAL=y
拡張アドバタイジングを使用する場合
オプションで BT_LE_ADV_OPT_EXT_ADV を設定し、拡張アドバタイジングを使用する場合は、拡張アドバタイジングとそのコントローラを有効にした上で、拡張アドバタイジングの最大セット数と255バイト以下の拡張アドバタイジングの最大長を設定します。
CONFIG_BT_EXT_ADV=y
CONFIG_BT_CTLR_ADV_EXT=y
CONFIG_BT_EXT_ADV_MAX_ADV_SET=2
CONFIG_BT_CTLR_ADV_DATA_LEN_MAX=251
アドバタイジングパケットのデータ設定
アドバタイジングパケットには外部に通知する様々なデータを設定します。
Central側は、それらのデータを解析してBLE接続します。
もしくはBeaconのように単にブロードキャストされているデータを受信して、アプリや人がそれらのデータを分析します。
注意しないといけないのは、実際の性能や設定値と異なる値をアドバタイジングパケットに設定できてしまうことです。
例えば本当のデバイス名がAAAでも、アドバタイジングデータのデバイス名をBBBにできますし、BLE接続不可能なのにアドバタイジングデータではBLE接続可能と偽ることができます。
意図的に偽る人はほとんどいないでしょうが、誤って実際と異なる設定にしないようにご注意ください
アドバタイジングパケットのデータはbt_data
で設定できます。
アドバタイジングデータの各要素は、typeとデータ長と実際のデータから成ります。
この3つをBT_DATA_BYTES
やBT_DATA
を用いて簡単に設定できます。
例えば、以下のように各要素を設定してアドバタイジングパケットのデータを作成します。
static const struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_NO_BREDR),
BT_DATA_BYTES(BT_DATA_UUID16_ALL, 0xaa, 0xfe),
BT_DATA_BYTES(BT_DATA_SVC_DATA16,
0xaa, 0xfe, /* Eddystone UUID */
0x10, /* Eddystone-URL frame type */
0x00, /* Calibrated Tx power at 0m */
0x00, /* URL Scheme Prefix http://www. */
'z', 'e', 'p', 'h', 'y', 'r',
'p', 'r', 'o', 'j', 'e', 'c', 't',
0x08) /* .org */
};
以下のように手動で構造体を設定することもできます。
typeとデータ長と実際のデータを各要素ごとに設定します。
struct bt_data ad[1]; // 必要な要素数に応じて調整
void init_adv_data(ADV_DATA_T *adv_data) {
// 手動で各フィールドを設定
ad[0].type = BT_DATA_FLAGS;
ad[0].data_len = sizeof(adv_data->adv_flags);
ad[0].data = adv_data->adv_flags;
//その他の設定やアドバタイジング開始処理
}
また、同じ方法でスキャンレスポンス用のデータも設定できます
static const struct bt_data sd[] = {
BT_DATA(BT_DATA_URI, url_data, sizeof(url_data)),
BT_DATA_BYTES(BT_DATA_UUID16_ALL, BT_UUID_16_ENCODE(BT_UUID_DIS_VAL))
};
以下でアドバタイジングデータのtypeについて代表的な幾つかを紹介します。
BT_DATA_FLAGS
AD flagsのtypeです。
AD flagsは、Peripheralの発信しているアドバタイジングパケットがCentralとBLE接続可能かどうか、classic Bluetoothにも対応しているかどうかを通知します。
BT_DATA_BYTES
によりtypeとデータを以下のように指定します。データ長はマクロ内部で計算してくれるので省略できます。
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR))
第2引数のデータについては、以下の定数の組み合わせで設定できます
- BT_LE_AD_GENERAL
- 値: 0x02
- 時間制限なくCentralとのBLE接続可能
- BT_LE_AD_LIMITED
- 値: 0x01
- 一定の時間制限内ならばCentralとのBLE接続可能
- BT_LE_AD_NO_BREDR
- 値: 0x04
- Bluetooth 4.0以前のclassic Bluetoothには未対応
Device Name
BT_DATA_NAME_x はデバイス名をアドバタイジングパケットに含めるためのtypeです。
このtypeには以下の2種類があります。
- BT_DATA_NAME_SHORTENED
- 短縮したデバイス名
- BT_DATA_NAME_COMPLETE
- 完全なデバイス名
初期設定でデバイス名を固定する場合は、以下のように名称を設定できます。
CONFIG_BT_DEVICE_NAME="MY_DEVICE"
この名称を用いて、以下の要素を ad もしくは sd に追加し、デバイス名をアドバタイジングパケットもしくはスキャンレスポンスパケットに含めることができます。
BT_DATA
にはtype、データ、データ長の3つを入力しないといけません。
BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1)
動作中にデバイス名を変更する場合は、変更を許可し、デバイス名の最大サイズを設定します。
最大サイズについては、拡張アドバタイジングを使用すれば251バイトにもできるでしょうが、通常のアドバタイジングを超えるサイズにしない方がいいでしょう。
CONFIG_BT_DEVICE_NAME_DYNAMIC=y
CONFIG_BT_DEVICE_NAME_MAX=31
UUID
UUIDをアドバタイジングパケットに含めることができます。
多くの場合、CentralはPeripheralとBLE接続してから、Peripheralがどのようなサービス(UUID)を持つかを取得します。
ですが、BLE接続前にPeripheralがどのようなサービスを持っているか分かった方が便利な場合もあります。
例えば、特定のサービスを持つPeripheralとのみBLE接続したい場合です。
このUUIDのtypeには以下の種類があります。
- BT_DATA_UUID16_SOME
- 16 bitのUUIDをアドバタイジングパケットに含める
- アドバタイジングパケットにはサポートしているUUIDの一部だけを含む
- BT_DATA_UUID16_ALL
- 16 bitのUUIDをアドバタイジングパケットに含める
- アドバタイジングパケットには全てのサポートしているUUIDを列挙する
- BT_DATA_UUID32_SOME
- 32 bitのUUIDをアドバタイジングパケットに含める
- アドバタイジングパケットにはサポートしているUUIDの一部だけを含む
- BT_DATA_UUID32_ALL
- 32 bitのUUIDをアドバタイジングパケットに含める
- アドバタイジングパケットには全てのサポートしているUUIDを列挙する
- BT_DATA_UUID128_SOME
- 128 bitのUUIDをアドバタイジングパケットに含める
- アドバタイジングパケットにはサポートしているUUIDの一部だけを含む
- BT_DATA_UUID128_ALL
- 128 bitのUUIDをアドバタイジングパケットに含める
- アドバタイジングパケットには全てのサポートしているUUIDを列挙する
例えば、以下のように設定します
BT_DATA_BYTES(BT_DATA_UUID128_ALL, uuid_service_val_1,
uuid_service_val_2,
uuid_service_val_3)
TX Power
BT_DATA_TX_POWER は、TX Powerをアドバタイジングパケットに含めるためのtypeです。
int8_t tx_power = 0; //0dBm
BT_DATA(BT_DATA_TX_POWER, &tx_power, sizeof(tx_power))
このアドバタイジングパケットを受信した側は、TX Powerの設定と実際に計測された送信出力を比較して、Peripheralの位置を推定することができます。
なお、後述する拡張アドバタイジングの場合は、パラメータのoptionsに BT_LE_ADV_OPT_USE_TX_POWER を設定すれば、自動でTX Powerがアドバタイジングパケットに含まれるので、本設定は不要になります。
アドバタイジングの開始と停止
アドバタイジングを開始するにはbt_le_adv_start
関数を使用します。
第1引数にパラメータへのポインタ、第2引数にアドバタイジングのデータへのポインタ、第3引数にアドバタイジングのデータ長、第4引数にスキャンレスポンスのデータへのポインタ、第5引数にキャンレスポンスのデータ長を代入します
int err = bt_le_adv_start(¶m, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
if (err) {
// エラー処理
}
スキャンレスポンスを使用しない場合は第4引数にNULL、第5引数に0を代入します。
int err = bt_le_adv_start(¶m, ad, ARRAY_SIZE(ad), NULL, 0);
if (err) {
// エラー処理
}
アドバタイジングを停止する際には以下を実行します
int err = bt_le_adv_stop();
if (err) {
// エラー処理
}
拡張アドバタイジングパケットのデータ設定
ここでは先述したstruct bt_le_adv_param param
の設定は終わっているものとして話を進めます。
通常のアドバタイジングと異なり、パラメータを設定するのはアドバタイジング開始関数ではありません。
アドバタイジングのインスタンスを作成する必要があります。
// 拡張アドバタイジングインスタンスの宣言
static struct bt_le_ext_adv *ext_adv;
このインスタンスの中身はbt_le_ext_adv_create
関数で作成します。
第1引数にパラメータへのポインタ、第3引数にインスタンスへのポインタを代入します。
int err = bt_le_ext_adv_create(¶m, NULL, &ext_adv);
if (err) {
// エラー処理
}
第2引数はコールバック関数です。スキャンリクエスト通知を受ける場合に使用します。
先述したオプションのBT_LE_ADV_OPT_NOTIFY_SCAN_REQを設定した場合のみ使用できます。
上記の例ではコールバック関数を使用しないのでNULLを代入しました。
もしスキャンリクエスト通知を受けるのでしたら、以下のように設定します。
static void on_scanned(struct bt_le_ext_adv *ext_adv,
struct bt_le_ext_adv_scanned_info *info)
{
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(info->addr, addr, sizeof(addr));
printk("Scanned by %s with rssi %d\n", addr, info->rssi);
// ここでスキャンリクエストに対する処理を行う
}
static const struct bt_le_ext_adv_cb adv_cb = {
.scanned = on_scanned,
// 他のコールバック関数
};
int err = bt_le_ext_adv_create(¶m, &adv_cb, &ext_adv);
if (err) {
// エラー処理
}
拡張アドバタイジングパケットの開始と停止
ここでは先述したstruct bt_le_ext_adv_start_param start_param
の設定は終わっているものとして話を進めます。
拡張アドバタイジングの開始時には、先ほど作成したインスタンスとを使用します
int err = bt_le_ext_adv_start(ext_adv, &start_param);
if (err) {
// エラー処理
}
拡張アドバタイジングの停止時にもインスタンスを使用します。
int err = bt_le_ext_adv_stop(ext_adv);
if (err) {
// エラー処理
}
Filter Accept List
ホワイトリストはnRF connect SDkではFilter Accept Listという名称に変更されていました。
ホワイトリストで情報を検索しても情報が見つかりにくいのでご注意ください。
Filter Accept ListについてはBLE接続の設定の記事で解説したいと思います。
動作未確認のアドバタイジング設定
これ以降の解説は資料を調べた結果を整理しただけで、まだ実際での動作は未確認になります。
誤っている可能性もあるのでご注意ください
複数のアドバタイジングセット
以下は資料やサンプルを調べた結果を整理したもので、幾つかの設定については、まだ実動作で確認していません。
よって正しく動作しないかもしれないので、ご注意ください。
拡張アドバタイジングを用いれば複数のアドバタイジングセットを使用できます。
以下はサンプルプロジェクトの multiple_adv_sets を参照しました。
複数のアドバタイジングセットを使用する場合、拡張アドバタイジングのインスタンスを複数作ります。
KconfigのCONFIG_BT_EXT_ADV_MAX_ADV_SET
で設定した数以上のアドバタイジングセットは使用できません。
ここではCONFIG_BT_EXT_ADV_MAX_ADV_SET=2
に設定したと仮定します。
static struct bt_le_ext_adv *ext_adv[CONFIG_BT_EXT_ADV_MAX_ADV_SET];
そしてパラメータを2つ用意してidに異なる番号を振ります。
idを変更しているので、異なるMACアドレスになります。
同じMACアドレスにしたいのならば、sidに異なる番号を振ってください。
static const struct bt_le_adv_param adv_param_0 = {
.id = 0,
.options = BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_USE_NAME | BT_LE_ADV_OPT_USE_IDENTITY,
.interval_min = BT_GAP_ADV_FAST_INT_MIN_2, /* 100 ms */
.interval_max = BT_GAP_ADV_FAST_INT_MAX_2, /* 150 ms */
.peer = NULL,
};
static const struct bt_le_adv_param adv_param_1 = {
.id = 1,
.options = BT_LE_ADV_OPT_USE_NAME | BT_LE_ADV_OPT_USE_IDENTITY,
.interval_min = 0x140, /* 200 ms */
.interval_max = 0x190, /* 250 ms */
.peer = NULL,
};
異なるMACアドレスは以下のように設定します
bt_addr_le_t addr_id0;
bt_addr_le_t addr_id1;
err = bt_addr_le_from_str("FF:FF:FF:FF:FF:FF", "random", &addr_id0);
err = bt_addr_le_from_str("FF:AA:AA:AA:AA:FF", "random", &addr_id1);
bt_id_create(&addr_id0, NULL);
bt_id_create(&addr_id1, NULL);
アドバタイジングパケットのデータも複数用意します
// 1つ目のアドバタイジングセット用のデータ(例:通常の接続可能広告)
static const struct bt_data connectable_ad_data[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
BT_DATA_BYTES(BT_DATA_UUID128_ALL, MY_SERVICE_UUID)
};
// 2つ目のアドバタイジングセット用のデータ(例:iBeacon)
static const struct bt_data beacon_ad_data[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_MANUFACTURER_DATA,
0x4C, 0x00, /* Apple */
0x02, 0x15, /* iBeacon */
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, /* UUID前半 */
0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, /* UUID後半 */
0x00, 0x01, /* Major */
0x00, 0x02, /* Minor */
0xC5) /* 送信出力 */
};
そして、それぞれに対してアドバタイジングを作成して開始します。
// アドバタイジングセットの作成と開始
static int advertising_set_create(struct bt_le_ext_adv **adv,
const struct bt_le_adv_param *param,
const struct bt_data *ad, size_t ad_len,
const struct bt_data *sd, size_t sd_len)
{
int err;
struct bt_le_ext_adv *adv_set;
err = bt_le_ext_adv_create(param, NULL, adv);
if (err) {
return err;
}
adv_set = *adv;
printk("Created adv: %p\n", adv_set);
// アドバタイジングデータとスキャンレスポンスデータを設定
err = bt_le_ext_adv_set_data(adv_set, ad, ad_len, sd, sd_len);
if (err) {
printk("Failed to set advertising data (err %d)\n", err);
return err;
}
return bt_le_ext_adv_start(adv_set, BT_LE_EXT_ADV_START_DEFAULT);
}
void main(void)
{
// ...
// BLE接続可能なアドバタイジングを開始
err = advertising_set_create(&ext_adv[0], &adv_param_0,
connectable_ad_data, ARRAY_SIZE(connectable_ad_data),
connectable_sd_data, ARRAY_SIZE(connectable_sd_data));
if (err) {
printk("Failed to create connectable advertising set (err %d)\n", err);
}
// BLE接続しないビーコンのアドバタイジングを開始
err = advertising_set_create(&ext_adv[1], &adv_param_1,
beacon_ad_data, ARRAY_SIZE(beacon_ad_data),
NULL, 0);
if (err) {
printk("Failed to create beacon advertising set (err %d)\n", err);
}
// ...
}
なお、bt_le_ext_adv_get_index
関数により各アドバタイジングセットのインデックスを取得できます。
アドバタイジングパケットのデータ更新
動的にアドバタイジングパケットのデータやパラメータを変更することもできます。
アドバタイジングパケットのデータを設定する時に、データに変数へのポインタを用いれば、その変数を変更すれば自動でアドバタイジングパケットのデータも変わります。
最初に紹介したDevAcademyの演習では上記のことをしています。
アドバタイジングを停止させる必要はなく、アドバタイジングを発信しながら、そのデータを変更することができます。
ただし、この場合、初期化時に設定したアドバタイジングパケットのデータサイズを変えたり、アドバタイジングパケットに新規のtypeを追加することはできません。
通常のアドバタイジングパケットのデータ更新の更新
アドバタイジングの場合はbt_le_adv_update_data
関数を使用して新しいデータに更新できます。
bt_le_adv_update_data(ad, ARRAY_SIZE(ad), NULL, 0);
パラメータ設定やスキャンレスポンスのデータは更新前の設定が維持されます。
もしスキャンレスポンスのデータだけを更新したい場合は、第3引数と第4引数だけを設定します。
bt_le_adv_update_data(NULL, 0, sd, ARRAY_SIZE(sd));
もちろんアドバタイジングとスキャンレスポンスの両方のデータを更新することもできます
bt_le_adv_update_data(ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
拡張アドバタイジングパケットのデータ更新の更新
拡張アドバタイジングの場合はbt_le_ext_adv_set_data
関数でデータ更新できます。
// 特定のアドバタイジングセットのデータを更新
err = bt_le_ext_adv_set_data(ext_adv[1], connectable_ad_data,
ARRAY_SIZE(connectable_ad_data),
connectable_sd_data, ARRAY_SIZE(connectable_sd_data));
if (err) {
// エラー処理
}
第1引数は拡張アドバタイジングのインスタンスです。
アドバタイジングの場合と同様に、アドバタイジングだけを更新したい場合は、第4引数をNILL、第5引数を0にします。スキャンレスポンスだけを更新したい場合は、第2引数をNILL、第3引数を0にします。
データのサイズやtypeの構成を変更したい場合は、アドバタイズを停止し、最初からアドバタイジングパケットを作り直します。
パラメータの設定もやり直しになりますが、次の項目で説明するパラメータ更新もできるとも言えます。
アドバタイジングのパラメータ更新
パラメータを更新したい場合は、最初からアドバタイジングパケットを作り直すのが簡単です。
ですが、拡張アドバタイジングの場合はbt_le_ext_adv_update_param
関数を使用して、パラメータだけを更新できます。
パラメータの更新は、アドバタイジングを停止してから行い、上記の関数を実行後にアドバタイジングを開始してください。
err = bt_le_ext_adv_stop(adv);
if (err) {
// エラー処理
}
// 新しいパラメータを設定
struct bt_le_adv_param new_param = BT_LE_ADV_PARAM_INIT(BT_LE_ADV_OPT_EXT_ADV | BT_LE_ADV_OPT_CODED,
900,
901,
NULL);
// パラメータの更新
err = bt_le_ext_adv_update_param(adv, &new_param);
if (err) {
// エラー処理
}
// アドバタイジングを再開
err = bt_le_ext_adv_start(adv, BT_LE_EXT_ADV_START_DEFAULT);
if (err) {
// エラー処理
}
動作中にPHYを変更する場合
パラメータの更新でPHYを変更する場合は、以下の設定を有効にしてください
CONFIG_BT_USER_PHY_UPDATE=y
ダイレクトアドバタイジング
以下の解説はネットでの情報を整理したものになります。
ダイレクトアドバタイジングについては、実際に試したことがないので、以下の解説通りにして動作するかどうかは未検証になります。
ダイレクトアドバタイジングは、通常のアドバタイジングでも、拡張アドバタイジングでもパラメータのpeerにアドレスを指定することで有効になります。
初期状態では High Duty Cycle で、オプションの BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY を設定していれば Low Duty Cycle になります。
また、以下のサイトによれば、拡張アドバタイジングの場合、Low Duty Cycleのみが使用可能になります。よって必ず BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY を設定しないといけません。
High Duty Cycleは以下の特徴があります。
- 3.75ms周期でアドバタイジングを行う
- 電力消費が大きいが、BLE接続が早い
- 時間制限内にBLE接続できない場合、connectedコールバックがステータス BT_HCI_ERR_ADV_TIMEOUT で呼ばれる
Low Duty Cycleは以下の特徴があります。
- パラメータで設定した周期でアドバタイジングを行う
High Duty Cycleの場合、以下のマクロを使用してパラメータの設定を簡略化できます。
#define BT_LE_ADV_CONN_DIR(_peer) BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONN, 0, 0, _peer)
// ピアデバイスのアドレスを指定
bt_addr_le_t peer_addr;
// ピアアドレスを設定(実際のコードでは保存されたアドレスを使用)
bt_addr_le_copy(&peer_addr, &saved_peer_addr);
// 高デューティサイクルのダイレクトアドバタイジングを開始
int err = bt_le_adv_start(BT_LE_ADV_CONN_DIR(&peer_addr), NULL, 0, NULL, 0);
if (err) {
// エラー処理
}
Low Duty Cycleの場合、以下のマクロを使用してパラメータの設定を簡略化できます。
#define BT_LE_ADV_CONN_DIR_LOW_DUTY(_peer) \
BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONN | BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY, \
BT_GAP_ADV_FAST_INT_MIN_2, BT_GAP_ADV_FAST_INT_MAX_2, _peer)
// ピアデバイスのアドレスを指定
bt_addr_le_t peer_addr;
// ピアアドレスを設定(実際のコードでは保存されたアドレスを使用)
bt_addr_le_copy(&peer_addr, &saved_peer_addr);
// 低デューティサイクルのダイレクトアドバタイジングを開始
int err = bt_le_adv_start(BT_LE_ADV_CONN_DIR_LOW_DUTY(&peer_addr), NULL, 0, NULL, 0);
if (err) {
// エラー処理
}
また、ボンディング情報を利用してダイレクトアドバタイジングを行う場合は、以下のようにしてボンディング済みデバイスの情報を取得します
int err;
bt_addr_le_t bond_addr;
struct bt_le_adv_param adv_param;
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_copy(&bond_addr, BT_ADDR_LE_NONE);
bt_foreach_bond(BT_ID_DEFAULT, copy_last_bonded_addr, NULL);
if (bt_addr_le_cmp(&bond_addr, BT_ADDR_LE_NONE) != 0) {
// ボンディング済みデバイスが存在する場合、ダイレクトアドバタイジングを行う
bt_addr_le_to_str(&bond_addr, addr, sizeof(addr));
adv_param = *BT_LE_ADV_CONN_DIR_LOW_DUTY(&bond_addr);
adv_param.options |= (BT_LE_ADV_OPT_DIR_ADDR_RPA | BT_LE_ADV_OPT_CONN);
err = bt_le_adv_start(&adv_param, NULL, 0, NULL, 0);
if (err) {
// エラー処理
}
拡張アドバタイジングの場合、必ずLow Duty Cycleに設定した上で、上記のアドバタイジング開始関数を拡張アドバタイジングの作成と開始の関数に置き換えます。
TX Powerの変更
先述したように、初期設定での TX Power は 0dbm です。
アドバタイジングのTX Powerを初期値から変更したい場合は以下のようにします。
アドバタイジング用に設定したTX Powerは、そのままBLE接続時のTX Powerに引き継がれます。
40dBmから+8dBmに設定できますが、電波法に違反しないように注意してください。
TX Power を固定値に設定するのでしたら、prj.confで設定できます。
例えば+8dbmに設定する場合は、以下を追加します。
CONFIG_BT_CTLR_TX_PWR_PLUS_8=y
動的にTX Powerを変更するには、以前のSDKではsd_ble_gap_tx_power_set
関数で設定できましたが、nRF connect SDKではHCIコマンドを送信する必要があります。
以下はNordicが公開しているサンプルプロジェクト hci_pwr_ctrlのTX Powerを設定する関数です。
static void set_tx_power(uint8_t handle_type, uint16_t handle, int8_t tx_pwr_lvl)
{
struct bt_hci_cp_vs_write_tx_power_level *cp;
struct bt_hci_rp_vs_write_tx_power_level *rp;
struct net_buf *buf, *rsp = NULL;
int err;
buf = bt_hci_cmd_create(BT_HCI_OP_VS_WRITE_TX_POWER_LEVEL,
sizeof(*cp));
if (!buf) {
printk("Unable to allocate command buffer\n");
return;
}
cp = net_buf_add(buf, sizeof(*cp));
cp->handle = sys_cpu_to_le16(handle);
cp->handle_type = handle_type;
cp->tx_power_level = tx_pwr_lvl;
err = bt_hci_cmd_send_sync(BT_HCI_OP_VS_WRITE_TX_POWER_LEVEL,
buf, &rsp);
if (err) {
printk("Set Tx power err: %d\n", err);
return;
}
rp = (void *)rsp->data;
printk("Actual Tx Power: %d\n", rp->selected_tx_power);
net_buf_unref(rsp);
}
なお、HCI (Host Controller Interface)など上記のサンプルを使用するためには以下の設定が必要になります。
CONFIG_BT_CTLR=y
CONFIG_BT_HCI=y
CONFIG_BT_HCI_VS=y
CONFIG_BT_CTLR_TX_PWR_DYNAMIC_CONTROL=y
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/hci_vs.h>
#include <zephyr/sys/byteorder.h>
現時点では、実際に上記の関数を試していないので、本当にTX Powerが変更できるかどうかは分かりません。今後試す機会があれば、ここに結果を追記します。