GAP广播
1.1概述
支持4.0~5.1规范定义的所有BLE广播类型:
- 传统广播
- 扩展广播
- 周期广播
传统广播的有效载荷最长为31B,而扩展广播(包括周期广播)每个广播包的有效载荷最长接近255B,每个扩展广播又可包含多个广播包(广播链条),总有效载荷最长达1650B。
1.1.1类型
广播有4种不同的属性:
- 可连接:接受对方发来的连接建立请求
- 可扫描:接受对方发来的扫描请求,并响应
- 定向:只用于可连接广播,只接受特定方发来的连接请求
- 高占空比:以更高的频率重复发送广播数据,常用语需要快速连接的场景
对于传统的扫描响应包,有效载荷最长也为31B;扩展的扫描响应包,有效载荷最长为1650B。
传统广播类型:
|----------------|-----------------|------|
| 类型 | PDU类型 | 广播数据 |
| 非定向可连接可扫描广播 | ADV_IND | 支持 |
| 定向可连接广播(非高占空比) | ADC_DIRECT_IND | 不支持 |
| 定向可连接广播(高占空比) | ADV_DIRECT_IND | 不支持 |
| 非定向可扫描广播 | ADV_SCAN_IND | 支持 |
| 非定向不可连接不可扫描广播 | ADV-NONCONN_IND | 支持 |
1.1.2过滤策略
概念:对于可连接或可扫描广播,可以只接受某些设备的连接建立请求或扫描请求。BLE 定义了 4 种策略:
typedef enum adv_filter_policy
{
// 接受所有的连接建立请求或扫描请求
ADV_FILTER_ALLOW_ALL = 0x00,
// 只接受白名单内的扫描请求,接收所有的连接建立请求
ADV_FILTER_ALLOW_SCAN_WLST_CON_ALL,
// 只接受白名单内的连接建立请求,接收所有的扫描请求
ADV_FILTER_ALLOW_SCAN_ALL_CON_WLST,
// 只接受白名单内的连接建立请求和扫描请求
ADV_FILTER_ALLOW_SCAN_WLST_CON_WLST
} adv_filter_policy_t;
1.1.3PHY
对于扩展广播,计需要再煮广播通信(37、38、39)上发送少量信息,也需要再其他信道(即辅广播信道)上发送。所以需要分别设置主、辅广播信道只能使用1M、Coded等两种PHY,而辅广播信道3种PHY皆可。
1.1.4广播集
从5.0开始,BLE支持并发,发送多个广播,每个广播称为一个广播集,由广播句柄指示。每个广播使用 各自独立的参数,包括地址、广播类型、PHY、数据等。开发者可以为广播指定一个4比特长的SID。
1.1.5相关事件
- HCI_SUBEVENT_LE_ADVERTISING_SET_TERMINATED:一个广播集停止广播时,HCI回调会收到HCI_SUBEVENT_LE_ADVERTISING_SET_TERMINATED事件。这个事件的粗发条件如下:
- 连接建立:此时status为0,可读取广播句柄和连接句柄的对应关系;
- 已达到预定的广播时长、次数,自动停止:此时的status为0x43(达到指定次数时),或0x3c(达到指定的广播时长时);
- 极端情况:Controller无法完成任务处理,此时status为0xFE
- HCI_SUBEVENT_LE_SCAN_REQUEST_RECEIVED:开发者使能扫描请求指示后,HCI回调会收到
HCI_SUBEVENT_LE_SCAN_REQUEST_RECEIVED事件。 - HCI_SUBEVENT_PRD_ADV_SUBEVT_DATA_REQ:发送PAwR时,通过该事件向上层应用请求设置子事件数据。该事件的内容为:le_mete_event_prd_adv_subevent_data_req_t。应用收到该事件后,可通过gap_set_periodic_adv_subevent_data为子事件设置数据。
- HCI_SUBEVENT_PRD_ADV_RSP_REPORT:收到PAwR的响应时,HCI回调收到HCI_SUBEVENT_PRD_ADV_RSP_REPORT事件,内容为:le_mete_event_prd_adv_rsp_report_t
2.使用说明
2.1配置广播
主要用到4个函数,gap_set_adv_set_random_addr、gap_set_ext_adv_para、gap_set_ext_adv_data 和 gap_set_ext_scan_response_data,分别配置随机地址、参数、广播数据和扫描响应数据。参数最复杂的函数是gap_set_ext_adv_para,其原型为:
uint8_t gap_set_ext_adv_para(
// 广播集句柄
const uint8_t adv_handle,
// 属性比特组合
const adv_event_properties_t properties,
// 广播间隔
const uint32_t interval_min,
const uint32_t interval_max,
// 使用的主广播信道比特组合(0x7 表示使用全部 3 个主广播信道)
const adv_channel_bits_t primary_adv_channel_map,
// 使用的地址类型(随机地址来自 gap_set_adv_set_random_addr)
const bd_addr_type_t own_addr_type,
// 设置定向广播的对端地址
const bd_addr_type_t peer_addr_type,
const uint8_t *peer_addr,
// 过滤策略
const adv_filter_policy_t adv_filter_policy,
// 发射功率,单位为 dBm
const int8_t tx_power,
// 主信道 PHY
const phy_type_t primary_adv_phy,
// 是否允许跳过部分辅信道的发送(填 0 表示总是发送)
const uint8_t secondary_adv_max_skip,
// 辅信道 PHY
const phy_type_t secondary_adv_phy,
// 广播集 SID
const uint8_t sid,
// 使能扫描请求上报
const uint8_t scan_req_notification_enable);
其中,properties为以下比特的组合:
// 可连接广播
#define CONNECTABLE_ADV_BIT ...
// 可扫描广播
#define SCANNABLE_ADV_BIT ...
// 定向广播
#define DIRECT_ADV_BIT ...
// 高频广播
#define HIGH_DUTY_CIR_DIR_ADV_BIT ...
// 传统广播
#define LEGACY_PDU_BIT ...
// 匿名广播
#define ANONY_ADV_BIT ...
// 包含发射功率
#define INC_TX_ADV_BIT ...
对于传统广播,必须受定义的约束。扩展广播不能既可连接又可扫描;不支持高占空比广播 。匿名广播中不包含广播者的地址,所以称为"匿名"广播。附加INC_TX_ADV_BIT比特后,广播内自动包含发射功率,比在载荷内通过AD项"0x0A - <<Tx Power Level>>"发送开销更小。
注意!Controller执行gap_set_ext_adv_para()命令时,会重新初始化对应的广播集,数据需要重新设置。广播数据以空口PDU的格式可能改变。由于一个扩展广播集可能包含多个广播PDU,Controller难以逐个调整PDU的数据格式,所以采用了这种重新初始化、由上层应用重新配置数据的方案。
对于带广播数据的广播,使用gap_set_ext_adv_data设置广播数据:
uint8_t gap_set_ext_adv_data(
// 广播集句柄
const uint8_t adv_handle,
// 数据总长度(对于扩展广播,最长可达 1650)
uint16_t length,
// 数据
const uint8_t *data);
对于可扫描广播,使用gap_set_ext_scan_response_data设置扫描响应数据:
uint8_t gap_set_ext_scan_response_data(
const uint8_t adv_handle,
const uint16_t length,
const uint8_t *data);
2.2广播数据
使用Wizard里的广播数据编辑器可以方便地编辑数据。广播数据编辑器同时可以生成一些常数,方便开发者编程修改广播数据。下面例子把蓝牙地址的最末两个字节填充到设备名称的最后4个字符里。
-
用广播数据编辑器生成初始数据
-
导入广播数据及常数:
static uint8_t adv_data[] = { #include "../data/advertising.adv" }; // 这个文件里是编辑器生成的常数 #include "../data/advertising.const" -
修改广播名称
void assign_name(const uint8_t *id_bytes) { char temp[5]; sprintf(temp, "%02X%02X", id_bytes[0], id_bytes[1]); // ADVERTISING_ITEM_OFFSET_COMPLETE_LOCAL_NAME 是编译器自动生成的常数, // 表示 "name_xxxx" 在整个数据里的偏移位置 memcpy(adv_data + ADVERTISING_ITEM_OFFSET_COMPLETE_LOCAL_NAME + 5, temp, sizeof(temp) - 1); } // 假设地址存放于 rand_addr assign_name(&rand_addr[4]);2.3配置周期广播
周期广播总是一个不可连接、不可扫描的扩展广播绑定。使用gap_set_ext_adv_para 设置了扩展广播参数后就可以通过gap_set_periodic_adv_para 创建相关联的周期广播:
uint8_t gap_set_periodic_adv_para(
// 使用同一个广播集句柄
const uint8_t adv_handle,
// 广播周期
const uint16_t interval_min,
const uint16_t interval_max,
// 属性(仅支持 0 或 PERIODIC_ADV_BIT_INC_TX)
const periodic_adv_properties_t properties);
周期广播的数据通过gap_set_periodic_adv_data设置,而不是gap_set_ext_adv_para。
2.4起停广播
通过gap_set_ext_adv_enable控制多个广播集的使能、停止状态。
uint8_t gap_set_ext_adv_enable(
// 使能还是停止?
const uint8_t enable,
// 广播集数目
const uint8_t set_number,
// 每个广播集的使能参数
const ext_adv_set_en_t *adv_sets);
这个函数支持一种快速停止所有广播的用法:gap_set_ext_adv_enable(0, 0, NULL)。除此之外,都需要用adv_sets数组表明每个广播集的句柄。
对于使能广播的情况,adv_sets使用另外两个参数来控制广播次数
typedef struct ext_adv_set_en
{
uint8_t handle;
// 广播持续时间,单位为 10ms。0ms 表示一直广播
uint16_t duration;
// 最大广播次数。0 表示一直广播
uint8_t max_events;
} ext_adv_set_en_t;
当duraction或max_event条件满足时,广播就会自动停止。