带响应的周期性广播(PAWR) 是蓝牙 5.4的一项新功能。它扩展了蓝牙 5.0 中的周期性广播协议。周期性广播是指设备以确定的时间发送广播数据,现在可以进行双向通信。接收器可将响应有效载荷传送回advertiser。有了 PAwR,现在就可以建立一个超大型的一对多双向网络,并以极低的功耗运行。在许多应用中,人们必须通过双向通信管理成百上千个能源受限的设备,例如电子货架标签(ESL)、传感器网络、仓库管理等。让我们深入了解这项非常有趣的新技术。
1. PAwR 如何工作
1.1 广播类型
蓝牙 5.4 在逻辑传输层上定义了三种主要的广告类型: 广播(Advertising Broadcast, ADVB), 周期性广播(Periodic Advertising Broadcast, PADVB), 和带响应的周期性广播 (Periodic Advertising with Responses, PAwR)。这些广播协议允许advertiser向周围任意数量的scanner广播数据,仅受advertiser射频范围的限制。
蓝牙 LE 传统广播和蓝牙 LE 扩展广播都属于 ADVB。在这些模式中,广播是不规则的,数据只向一个方向发送。在大多数情况下,scanner需要有充足的电源,因为scanner和broadcaster之间没有时间同步。此外,为避免advertiser之间的定时碰撞,广播间隔会有 0-10 毫秒的随机延迟。这就要求scanner具有较大的 RX 窗口,因此需要消耗更多的电流。
与 ADVB 不同,周期性广播(PADVB)进行定期且精确的定时广播。scanner和advertiser之间有时间同步。PADVB 不使用 ADVB 的 0-10 毫秒随机延迟。因此,scanner只需监听一段非常短且可预测的时间。利用周期性广播建立一个大型的一对多低功耗网络是可能的,例如音频广播、测向、物联网网络等。然而,缺少的是低功耗scanner只能接收数据,而不能将数据发送回advertiser。
PAwR 的推出就是为了解决这个问题。它允许advertiser发送精确的常规广播数据,同时允许scanner向advertiser发送响应有效载荷。此外,在需要更高的吞吐量时,它还为advertiser提供了与scanner建立蓝牙(Bluetooth LE)连接的机制。
1.2 时间
与其他类型的广播类似,PAwR 的运行间隔称为周期性广播间隔(Periodic Advertising Interval)。它在准确的时间间隔内发生,没有随机延迟。每个 PAwR 事件都会有多个子事件。这与 PADVB 不同,在 PADVB 中,每个事件只有一个周期性广播数据包。重要的是,同步到 PAwR 的scanner不会监听所有子事件,只会选择它订阅/同步到的子事件。多个scanner可以订阅同一个子事件。这意味着一台scanner可以订阅多个子事件。每个广播事件最多可以有 128 个子事件。
放大其中一个子事件,在事件开始时,有一个来自advertiser的传输包。它可以是:
AUX_SYNC_SUBEVENT_IND
- 周期性广播包.
AUX_CONNECT_REQ
- advertiser的连接请求.
与该子事件同步的所有scanner都会扫描该数据包并处理有效载荷。数据包发出后,会有一列响应槽供scanner将其响应发送回advertiser。响应槽最多可达 256 个。在哪个响应槽上发送哪个scanner由应用程序/配置文件决定。为避免碰撞,一个响应槽中只能有一个scanner响应。
这是 PAwR 的 R 部分,是与普通周期性广播的主要区别。这样就能以极低的功耗形成超大型双向一对多网络。可以发现,scanner/observer只需在每个周期性广播间隔唤醒一次,以扫描子事件传输包。如果它有数据提供给advertiser,就可以在其中一个响应时隙进行传输,然后再次休眠。由于并非所有scanner都需要同时发送数据,因此一个advertiser可以为大量scanners提供服务。延迟时间很短;可低至周期性广播间隔,根据advertiser的配置,间隔时间在 7.5ms 至 81.91875s 之间。
这里的限制是吞吐量。如果scanner需要传输或接收大量数据,可能会受到广播时间间隔的限制。如上所述,广播时间间隔最长可达 81.91875 秒,将广播时间间隔改为更短的时间间隔需要更新所有scanner以同步到新的时间间隔。在大多数情况下,这是不现实的。
解决这个问题的办法是在scanner和advertiser之间建立蓝牙 LE 连接。PAwR 支持在不使用传统 ADVB 的情况下建立连接。为了启动连接,advertiser在子事件传输包中发送 AUX_CONNECT_REQ 包,而不是 AUX_SYNC_SUBEVENT_IND 包。AUX_CONNECT_REQ 包含scanner的地址和scanner进入蓝牙 LE 连接时应使用的连接参数。待连接的scanner将以 AUX_CONNECT_RSP 作为回应。该响应应在 AUX_CONNECT_REQ 数据包之后立即发送,无需等待响应时隙。随后将立即建立蓝牙 LE 连接。advertiser将成为中心,scanner将成为外围。通过这种分离的蓝牙 LE 连接,它们可以以更高的吞吐量进行通信。
1.3 同步
要跟踪周期性广播并订阅子事件,scanner需要从advertiser处获得同步和 PAwR 信息。获取同步信息的程序与普通周期性广播PADVB 类似。scanner可以通过扫描扩展广播数据包 ADV_EXT_IND 来获取信息:
或者通过建立连接并执行周期性广播同步传输 (PAST) 程序。
这两种方法各有利弊。扫描扩展广播数据包 ADV_EXT_IND 不需要连接,但它要求scanner能够进行连续扫描以捕捉广播数据包。相比之下,使用 PAST 进行连接并接收定时信息可适用于电源有限的scanner仪,因为它只要求scanner进行正常的蓝牙 LE 广播。不过,这对advertiser的无线电调度要求更高,因为advertiser需要同时进行周期性广播和扫描以建立蓝牙 LE 连接。advertiser可以决定不做 ADV_EXT_IND,只做周期性广播 AUX_SYNC_SUBEVENT_IND。这是为了减少advertiser的工作量。在这种情况下,PAST 是同步scanner的唯一选择。
与 PADVB 相比,除了 SyncInfo(周期性广播间隔、同步Packet Window Offset)外,还有一些专门用于 PAwR 的额外信息需要交换:
- ACAD- Additional Controller Advertising Data:
- 响应访问地址
- 子事件数量
- 子事件间隔
- 响应时隙延迟
- 响应槽间隔
您可能会发现,这些程序中没有定义scanner应订阅哪些子事件或响应槽的信息。这就需要应用程序/上层来决定scanner应如何获取有关子事件和响应槽的信息。例如,在执行 PAST 时配置响应槽,或对scanner进行硬编码,使其根据自身地址自动选择响应槽。
2. PAwR 优势和局限
2.1 优势
- 一对多拓扑结构中的双向通信. PAwR 为蓝牙产品增加了一个重要的缺失部分:大规模双向一对多网络。在蓝牙 5.4 之前,常见的蓝牙网络拓扑结构如下:
- 一对一。一个外设连接一个中心。
- 一对多,星形网络。一个中心可以连接多个外围设备,但链接数量有限,通常每个中心的连接数少于 20 个。
- 一对多广播。一个advertiser可以向许多观observers广播数据。observers的数量可以非常多,但数据只能单向传播,即从advertiser到observer。
- 多对多,mesh。mesh网络可提供大规模双向网络。不过,它需要由backbone节点组成的基础设施来中继信息。这些backbone节点通常需要主电源。连接的mesh网络可以作为一种解决方案,但它可能比较复杂和僵化。
使用 PAwR,只需一个简单的同步程序,即可建立一个拥有数百或数千个节点的一对多网络。一个广告商就可以管理与数千台设备之间的数据收发任务。这里的亮点在于,这种双向通信是以同步方式进行的,没有泛洪或潜在的碰撞。
- 广播数据可以频繁更改。PAwR 允许频繁更改广播数据。特别是,主机可以精确控制哪个数据包将进入哪个广播子事件。这是 ADVB 和 PADVB 所不具备的。使用 ADVB 和 PADVB 时,您可以随时间更新广播数据,但数据是重复发送的,而且您对广播进行时如何更新数据的控制也很有限。使用 PAwR 时,控制器会请求主机(应用程序)在每次广播事件中更新数据。应用程序可以更新每个广播事件中的每个子事件广播数据包。
- advertiser可与scanner建立蓝牙 LE 连接。当对等设备需要传输大量数据,而 PAwR 有限的吞吐量无法满足要求时,例如向 ESL 标签传输显示图像时,就会使用这种连接。它与普通的蓝牙 LE 连接略有不同,不是由scanner,而是由周期性advertiser决定与scanner建立连接。它选择应该连接哪个scanner。这样做是有道理的,因为这样可以避免大量节点同时发布连接广播,造成广播信道拥塞。此外,它还允许advertiser安排新的连接,以免与正在进行的 PAwR 活动相冲突。
- 低功耗运行,但仍有相对较低的延迟。scanner的功耗与正常蓝牙 LE 连接时的功耗相似。这意味着超低功耗。设备可以使用纽扣电池运行数年,延迟时间等于网络的周期性广播间隔。
2.2 局限
- 该网络依靠一个advertiser作为网关/接入点。这意味着网络中的所有节点都必须在advertiser的覆盖范围内。可以通过多个advertiser来扩展网络,但规范并没有定义如何建立这样的网络。
- 吞吐量有限。advertiser需要为大量节点提供服务,与普通蓝牙 LE 连接相比,数据吞吐量相对较低,具体取决于广播间隔和响应槽的大小。
- 每个scanner可能没有足够的响应槽。多个scanner可以同步到同一个子事件,而子事件中的每个节点往往没有足够的响应槽。应用程序需要定义scanner应如何轮流向接入点发送数据。
- PAwR 的设计将大部分复杂性放在了advertiser接入点一方。advertiser必须足够强大,才能同时处理 PAwR 广告、连接处理和节点管理任务。
- PAwR 只处理堆栈的低端。它没有规定网络的高级功能,如流量控制、数据包确认、加密、身份验证等。上层有责任为应用定制这些功能。
3. 在 nRF Connect SDK 中实现
本节主要介绍如何在 nRF Connect SDK 中实现 PAwR 应用程序。nRF Connect SDK 在 v2.4.0 中添加了两对示例来演示 PAwR。我们将深入代码,了解 PAwR 在这些示例中是如何实现的。
- 带响应的周期性广播(PAwR) advertiser和同步 (periodic_adv_rsp& periodic_sync_rsp):
- advertiser会定期发布广播并扫描 PAST advertiser。建立蓝牙 LE 连接后,它将发送 PAST 信息,然后发送子事件信息。
- 开始时,observer/scanner会做广播以获取 PAST 信息。同步后,它将收到子事件信息,从而知道应该监听哪个子事件。
- 周期性广播连接程序 - 发起方和响应方 (periodic_adv_conn& periodic_sync_conn):
- advertiser只做周期性广播,不做 PAST。
- observer/scanner扫描扩展广播数据包并同步。同步后,它会在 PAwR 响应中向advertiser发送地址,以便advertiser通过 AUX_CONNECT_REQ 建立连接。有关子事件的信息是硬编码。
3.1 广播
让我们仔细看看 periodic_adv_rsp 示例中是如何设置周期性广播的:
pei:
static const struct bt_le_per_adv_param per_adv_params = {
.interval_min = 0xFF,
.interval_max = 0xFF,
.options = 0,
.num_subevents = NUM_SUBEVENTS,
.subevent_interval = 0x30,
.response_slot_delay = 0x5,
.response_slot_spacing = 0x50,
.num_response_slots = NUM_RSP_SLOTS,
};
struct bt_le_ext_adv *pawr_adv;
static const struct bt_le_ext_adv_cb adv_cb = {
.pawr_data_request = request_cb,
.pawr_response = response_cb,
};
配置子事件和响应槽时间参数时需要格外注意。
- 广播间隔应≥ subevent_interval * NUM_SUBEVENTS。
- 子事件间隔应≥ response_slot_delay + response_slot_spacing*NUM_RSP_SLOT
否则将收到错误 12 BT_HCI_ERR_INVALID_PARAM。
设置并开始广播:
/* Create a non-connectable non-scannable advertising set */
bt_le_ext_adv_create(BT_LE_EXT_ADV_NCONN, &adv_cb, &pawr_adv);
/* Set periodic advertising parameters */
bt_le_per_adv_set_param(pawr_adv, &per_adv_params);
/* Enable Periodic Advertising */
bt_le_per_adv_start(pawr_adv);
/* Start Periodic Advertising */
bt_le_ext_adv_start(pawr_adv, BT_LE_EXT_ADV_START_DEFAULT);
在 bt_le_ext_adv_start() 之后,advertiser将进行扩展广播和 PAwR。这包括图 4 所示的数据包:ADV_EXT_IND、AUX_ADV_IND 和 AUX_SYNC_SUBEVENT_IND。
如上一节所述。advertiser可以选择不做扩展广播(ADV_EXT_IND 和 AUX_ADV_IND),只做周期性广播。这将有助于减少advertiser的额外广播活动,从而为更多连接/响应提供服务。要停止扩展广播,可在周期性广播开始后调用 bt_le_ext_adv_stop()。这不会停止周期性广播。要停止周期性广播,您需要调用 bt_le_per_adv_stop()。
注意:大多数嗅探器依靠扩展广播的 Syncinfo 来跟踪周期性广播train。如果关闭扩展广播,嗅探器就无法找到周期性广播数据包。
周期性advertiser有 2 个回调:
pawr_data_request
: 当有缓冲区可供advertiser更新子事件数据包内容时调用。通常,每个要更新的子事件插槽都会收到一个回调。在本例中,代码只需在每个子事件中将计数器增加一个即可。
pawr_response
: 当某个scanner对某个子事件做出响应时调用。在示例中,scanner只需回传它在子事件数据包中收到的内容。
这些是与普通广播或周期性广播的主要区别。您现在可以控制精确广播数据包(子事件数据包)的有效载荷。此外,您还可以从scanner中获取响应数据,这在其他类型的广播中是无法通过扫描请求获得的。
3.2 同步
要实现同步,需要扩展广播或 PAST。如果要使用 PAST,则需要扫描普通广播以建立蓝牙 LE 连接,然后再使用 PAST。建立连接后,需要调用 bt_le_per_adv_set_info_transfer()。这将向scanner发送同步信息和 PAwR 信息。
在 periodic_adv_rsp 示例中,您可以找到一条写入命令,用于发送一些有关scanner子事件编号和响应槽编号的专有数据(sync_config)。至于如何发送,则由应用程序自行决定。
以下代码来自 periorid_adv_rsp。达到同步设备的最大数量后,advertiser会停止扫描。
while (num_synced < MAX_SYNCS) {
err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, device_found);
if (err) {
printk("Scanning failed to start (err %d)\n", err);
return 0;
}
printk("Scanning successfully started\n");
k_sem_take(&sem_connected, K_FOREVER);
err = bt_le_per_adv_set_info_transfer(pawr_adv, default_conn, 0);
if (err) {
printk("Failed to send PAST (err %d)\n", err);
goto disconnect;
}
printk("PAST sent\n");
discover_params.uuid = &pawr_char_uuid.uuid;
discover_params.func = discover_func;
discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
err = bt_gatt_discover(default_conn, &discover_params);
if (err) {
printk("Discovery failed (err %d)\n", err);
goto disconnect;
}
printk("Discovery started\n");
err = k_sem_take(&sem_discovered, K_SECONDS(10));
if (err) {
printk("Timed out during GATT discovery\n");
goto disconnect;
}
sync_config.subevent = num_synced % NUM_SUBEVENTS;
sync_config.response_slot = num_synced / NUM_RSP_SLOTS;
num_synced++;
write_params.func = write_func;
write_params.handle = pawr_attr_handle;
write_params.offset = 0;
write_params.data = &sync_config;
write_params.length = sizeof(sync_config);
err = bt_gatt_write(default_conn, &write_params);
if (err) {
printk("Write failed (err %d)\n", err);
num_synced--;
goto disconnect;
}
printk("Write started\n");
err = k_sem_take(&sem_written, K_SECONDS(10));
if (err) {
printk("Timed out during GATT write\n");
num_synced--;
goto disconnect;
}
printk("PAwR config written to sync %d, disconnecting\n", num_synced - 1);
disconnect:
err = bt_conn_disconnect(default_conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN);
if (err) {
return 0;
}
k_sem_take(&sem_disconnected, K_FOREVER);
}
发送传输信息后,advertiser 会直接断开连接。它不知道scanner是否与 PAwR 列车同步。可以通过在断开连接前添加延迟或最好在断开连接前收到scanner的响应来改善这种情况。您可以在本指南末尾提供的代码中找到添加的延迟。
在scanner方面,如果查看 perioridc_sync_conn 样本,就会发现它是如何注册 PAwR 并扫描包含同步和 PAwr 信息的扩展广播数据包的。
bt_le_scan_cb_register(&scan_callbacks);
bt_le_per_adv_sync_cb_register(&sync_callbacks);
err = bt_le_scan_start(BT_LE_SCAN_ACTIVE, NULL);
if (err) {
printk("failed (err %d)\n", err);
return 0;
}
请注意,在捕获扩展广播数据包后,您需要创建同步对象,以便scanner可以开始跟踪周期性广播数据包。要创建同步对象,需要调用 bt_le_per_adv_sync_create()。这将允许您在scanner同步到周期性广播(而非 PAwR)时接收 bt_le_per_adv_sync_cb.synced 回调:
{
struct bt_le_per_adv_sync_subevent_params params;
uint8_t subevents[1];
char le_addr[BT_ADDR_LE_STR_LEN];
int err;
bt_addr_le_to_str(info->addr, le_addr, sizeof(le_addr));
printk("Synced to %s with %d subevents\n", le_addr, info->num_subevents);
params.properties = 0;
params.num_subevents = 1;
params.subevents = subevents;
subevents[0] = 0;
err = bt_le_per_adv_sync_subevent(sync, ¶ms);
if (err) {
printk("Failed to set subevents to sync to (err %d)\n", err);
}
k_sem_give(&sem_per_sync);
}
正如您在代码中发现的,同步后,如果您想接收 PAwR 子事件数据包,您需要调用 bt_le_per_adv_sync_subevent() 来订阅一个或多个子事件。在本示例中,要订阅的子事件被硬编码为子事件 0,该子事件中的响应槽也被硬编码为 0(参见 recv_cb 函数)。
样本将设备地址发送给advertiser,然后等待advertiser的连接(通过 AUX_CONNECT_REQ 连接 v2)。这是本应用程序特有的功能。调用 bt_le_per_adv_set_response_data(),其中包含要发送数据的子事件和响应槽。
static void recv_cb(struct bt_le_per_adv_sync *sync,
const struct bt_le_per_adv_sync_recv_info *info, struct net_buf_simple *buf)
{
int err;
struct bt_le_oob oob;
char addr_str[BT_ADDR_LE_STR_LEN];
if (default_conn) {
/* Only respond with address if not already connected */
return;
}
if (buf && buf->len) {
/* Respond with own address for the advertiser to connect to */
net_buf_simple_reset(&rsp_buf);
rsp_params.request_event = info->periodic_event_counter;
rsp_params.request_subevent = info->subevent;
rsp_params.response_subevent = info->subevent;
rsp_params.response_slot = 0;
err = bt_le_oob_get_local(BT_ID_DEFAULT, &oob);
if (err) {
printk("Failed to get OOB data (err %d)\n", err);
return;
}
bt_addr_le_to_str(&oob.addr, addr_str, sizeof(addr_str));
printk("Responding with own addr: %s\n", addr_str);
net_buf_simple_add_u8(&rsp_buf, sizeof(bt_addr_le_t));
net_buf_simple_add_u8(&rsp_buf, BT_DATA_LE_BT_DEVICE_ADDRESS);
net_buf_simple_add_mem(&rsp_buf, &oob.addr.a, sizeof(oob.addr.a));
net_buf_simple_add_u8(&rsp_buf, oob.addr.type);
err = bt_le_per_adv_set_response_data(sync, &rsp_params, &rsp_buf);
if (err) {
printk("Failed to send response (err %d)\n", err);
}
} else if (buf) {
printk("Received empty indication: subevent %d\n", info->subevent);
} else {
printk("Failed to receive indication: subevent %d\n", info->subevent);
}
}
3.3 连接 v2
当 PAwR advertiser 需要以更高的吞吐量或更低的延迟与scanner通信时,可以决定连接到同步scanner。为此,advertiser 需要获得scanner设备的地址和scanner监听的子事件。要建立连接,需要调用 bt_conn_le_create_synced():
bt_addr_le_to_str(&peer, addr_str, sizeof(addr_str));
printk("Connecting to %s in subevent %d\n", addr_str, info->subevent);
synced_param.peer = &peer;
synced_param.subevent = info->subevent;
/* Choose same interval as PAwR advertiser to avoid scheduling conflicts */
conn_param.interval_min = SUBEVENT_INTERVAL;
conn_param.interval_max = SUBEVENT_INTERVAL;
/* Default values */
conn_param.latency = 0;
conn_param.timeout = 400;
err = bt_conn_le_create_synced(adv, &synced_param, &conn_param, &default_conn);
if (err) {
printk("Failed to initiate connection (err %d)", err);
}
在进行连接 v2 时,没有扫描超时的概念,bt_conn_le_create_synced() 命令的结果会在下一个周期性广播间隔中返回回调。
注意:建议选择连接时间间隔等于或大于子事件时间间隔。这将减少调度配置,从而提高吞吐量。
4. 低功耗示例
附件是稍作修改的 periodic_adv_rsp 和 periodic_sync_rsp 示例。advertiser 将扫描并建立蓝牙 LE 连接,以发送 PAST 信息和子事件信息。scanner与advertiser 同步后,将在每个广播间隔发送数据。
主要修改是关闭scanner上的 UART 日志记录,并在响应中添加更多有意义的数据。scanner会在每个响应槽发送其内部温度。advertiser 略有改动,使其在更长的广播间隔时间内更加稳健。代码的主要目的是展示scanner(传感器)在不同周期性广播间隔下的低功耗。
您可以在此处下载演示源代码(在 nRF Connect SDK v2.5.0 上进行了测试): 8562.PAwR_Demo.zip
运行示例时,您将在advertiser 一侧看到每个scanner的温度报告:
与 periodic_adv_rsp 相同,advertiser 只支持每个子事件一个scanner。但可以修改代码,使每个子事件支持更多scanner或增加子事件的数量。您需要决定同一子事件中的scanner应如何发送响应的逻辑;通常情况下,每个子事件的响应槽数量小于scanner数量。
以下是scanner在不同周期性广播间隔(从 100 毫秒到 1 秒再到 10 秒)下的功率测量结果。这就改变了scanner的延迟时间。延迟时间越长,功耗越低。
每隔 100 毫秒输出 80uA 电流,每个子事件有 4 个字节的响应。
每个子事件以 1 秒间隔 15uA 响应 4 个字节。
7.3uA 间隔 10 秒,每个子事件响应 4 个字节。
在相对较低的 1 秒延迟时间内,可以实现 15uA 的超低功耗。我们可以使用容量为 250mAh 的 CR2032 电池进行粗略计算。如果我们能将 70% 的容量用于蓝牙 LE,那么它的寿命为 250mAh * 70% / 15uA = 12500 小时 = 520 天。
5. 总结
PAwR 是蓝牙 LE 的一项令人兴奋的新功能。它增强了周期性广播协议,可能会为蓝牙 LE 技术在新的商业垂直领域打开大门。蓝牙 LE 和 PAwR 使许多曾经无法实现或过于复杂的应用变得可行。我们很高兴看到它将如何实施,并随时准备帮助您将这项技术推向市场。
如果您对 PAwR 的主要应用之一 ESL 感兴趣,请继续阅读以下指南: