ESP32 BLE 扩展广播 1 KiB 大载荷示例深度解析:基于 esp-iot-solution 的 ble_ext_adv 广播端实现
【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution
导读
本文围绕 esp-iot-solution 仓库中的 ble_ext_adv 示例 展开,它是配套 ble_ext_scan 扫描端 的广播端(Advertiser),演示如何在 ESP32 系列芯片上基于 BLE 5.0 扩展广播(Extended Advertising)一次发送1000 字节(1 KiB)的非连接、不可扫描广播 PDU,并在 primary 1M / secondary 2M PHY 上以 Advertising SID = 2 对外广播。读完本文,你将掌握:扩展广播与传统广播的差异、1 KiB 广播载荷的合法 AD 结构构造方法、ble_conn_mgr 组件广播参数的完整配置、FNV-1a 校验链路,以及如何配合扫描端完成端到端验证。
背景:为什么需要扩展广播
传统 BLE 广播(Legacy Advertising)的广播数据(AdvData)加上扫描响应(ScanRsp)合计最多31 字节,只能承载设备名、少量服务 UUID 等短数据。BLE 5.0 引入的扩展广播将单次广播 PDU 的载荷上限提升到1650 字节(可被控制器分片为多个 AUX 扩展广播事件发送),并支持:
- 在 primary 1M / Coded PHY 上发送指针(AdvA + AUX 指针),实际数据走 secondary 2M / Coded PHY,吞吐更高、更省电;
- 通过 Advertising SID 区分同一设备上的多个广播集(Advertising Set);
- 非连接、不可扫描、匿名等多种事件属性组合。
本示例正是利用这一能力,将 1000 字节载荷一次性装进扩展广播。README 中明确指出:这不是周期性广播(Periodic Advertising),若需要周期广播/同步演示,应使用仓库内的 ble_periodic_adv 与 ble_periodic_sync 示例。
示例概览:广播端 + 扫描端配对
ble_ext_adv与ble_ext_scan构成一对演示:
| 角色 | 示例目录 | 职责 |
|---|---|---|
| 广播端 | ble_ext_adv | 构造 1000 字节广播载荷,以 SID=2、primary 1M / secondary 2M PHY 广播 |
| 扫描端 | ble_ext_scan | 在 1M / 2M / Coded PHY 上被动扫描,按 SID=2 重组分片并校验 1000 字节载荷与 FNV-1a |
两个示例共享同一份载荷构造与哈希函数(ext_adv.h),因此扫描端可以用预先计算的期望值校验收到的载荷,广播端打印的 FNV-1a 与扫描端打印的 FNV-1a 必须一致,这是验证链路的唯一判据。
支持芯片与构建运行
README 给出了本示例的支持目标:
| Supported Targets | ESP32-C3 | ESP32-C2 | ESP32-S3 | ESP32-C6 | ESP32-H2 |
|---|
这些芯片均支持 BLE 5.0 扩展广播(对应 ble_conn_mgr 中SOC_BLE_50_SUPPORTED能力)。构建、烧录、监视的步骤与仓库内其他 ESP-IDF 示例一致:
idf.py set-target <chip_name> idf.py build flash monitor示例通过 idf_component.yml 以本地 override 方式依赖ble_conn_mgr组件(版本~1.*,路径指向components/bluetooth/ble_conn_mgr),无需额外联网拉取:
dependencies: idf: ">=4.3" ble_conn_mgr: version: "~1.*" override_path: "../../../../../components/bluetooth/ble_conn_mgr"广播端实现深入解析
1. 1 KiB 广播载荷的合法 AD 结构
传统广播载荷按Length | Type | Data的 AD Structure 组织,扩展广播同样遵循该规则。核心难点在于:要构造出恰好 1000 字节且结构合法的载荷。ext_adv.h 中的ext_adv_1k_demo_fill()给出了可复用的构造方法,其布局为:
| 段 | Length | Type | Data | 说明 |
|---|---|---|---|---|
| Flags | 2 | 0x01 | 0x06 | LE General Discoverable + BR/EDR Not Supported |
| 完整设备名 | 1 + strlen(name) | 0x09 | ESP_EXT_ADV_1K | 与 GAP 设备名一致(12 字节) |
| 制造商数据 | ≤255 | 0xFF | 公司 ID0xE5 0x02(即 0x02E5,Espressif)+ 填充模式 | 循环填充直至剩余空间不足 |
填充逻辑的关键细节:
- 每条制造商数据 TLV 的长度字段
L取min(rem-1, 255),确保不超过 255 字节的 AD 结构上限; - 当剩余空间不足 3 字节时,直接以
0x00补齐到 1000 字节(空 Type 段在接收端会被安全忽略); - 数据区以
(pos + j) ^ j的确定性模式填充,使扫描端可以做逐字节比对,而非仅比对哈希。
#define EXT_ADV_1K_TOTAL 1000 #define EXT_ADV_DEMO_NAME "ESP_EXT_ADV_1K"设备名常量同时被用作广播端的 GAP 设备名(见 app_main.c 中的esp_ble_conn_config_t配置),实现“载荷内嵌名称”与“广播设备名”统一。
2. FNV-1a 校验值
ext_adv.h 同时提供 FNV-1a 哈希实现,用于端到端完整性校验:
static inline uint32_t ext_adv_1k_demo_fnv1a(const uint8_t *p, size_t n) { uint32_t h = 2166136261u; /* FNV-1a 偏移基值 */ for (size_t i = 0; i < n; i++) { h ^= p[i]; h *= 16777619u; /* FNV 素数 */ } return h; }广播端在启动时先填充缓冲区、计算 FNV-1a 并打印:
Extended ADV payload: 1000 bytes, primary 1M / secondary 2M, non-conn non-scan AUX, FNV-1a=0x...扫描端同样基于同一填充函数计算期望 FNV-1a,重组完毕后打印Target ESP_EXT_ADV_1K found: ... FNV-1a=0x... (compare with advertiser log),两行日志的哈希值一致即表示整条链路(构造 → 广播 → 分片 → 重组)正确无误。
3. 广播配置与启动流程
app_main.c 的完整流程为:
- 构造载荷并打印 FNV-1a:调用
ext_adv_1k_demo_fill()+ext_adv_1k_demo_fnv1a(); - 初始化配置:
esp_ble_conn_config_t中设置device_name、broadcast_data = "NA"、extended_adv_data与extended_adv_len = 1000; - NVS 初始化:处理
ESP_ERR_NVS_NO_FREE_PAGES/ESP_ERR_NVS_NEW_VERSION_FOUND后重试; - 组件初始化:
esp_ble_conn_init(&config); - 设置扩展广播参数:
esp_ble_conn_adv_params_set(&adv); - 启动广播:
esp_ble_conn_start(),失败则依次esp_ble_conn_stop()、esp_ble_conn_deinit()兜底。
其中扩展广播参数是本节的核心,逐字段解析如下(字段定义与注释来自 esp_ble_conn_mgr.h):
esp_ble_conn_adv_params_t adv = {0}; adv.adv_handle = 0; /* 广播集句柄,0x00-0xEF */ adv.own_addr_type = ESP_BLE_CONN_ADDR_RANDOM; /* 使用随机地址 */ adv.primary_phy = ESP_BLE_CONN_PHY_1M; /* 主信道 PHY:1M */ adv.secondary_phy = ESP_BLE_CONN_PHY_2M; /* 次信道 PHY:2M,承载实际数据 */ adv.sid = 2; /* Advertising SID,扫描端按此过滤 */ adv.itvl_min = 0x140; /* 主广播间隔下限,0.625ms 为单位 = 200ms */ adv.itvl_max = 0x140; /* 上限,0 = 使用栈默认值 */ adv.adv_event_properties = 0; /* 非连接、不可扫描 */ adv.tx_power = 127; /* 127 = 使用协议栈默认发射功率 */ ESP_ERROR_CHECK(esp_ble_conn_adv_params_set(&adv));关键参数含义:
adv_event_properties:与 HCILE Set Extended Advertising Parameters的Advertising_Event_Properties对齐的 16 位位图:BIT0=connectable、BIT1=scannable、BIT2=directed、BIT3=high-duty directed、BIT4=legacy_pdu、BIT5=anonymous、BIT6=include_tx_power、BIT7=scan_req_notif。本例置 0,表示纯广播、不可连接、不可扫描;itvl_min/itvl_max:单位 0.625ms,合法范围 0x0020-0x4000。0x140 × 0.625ms = 200ms;tx_power:127 表示由协议栈选择默认发射功率;sid:广播集标识(0-15),扫描端必须匹配此值才接收。
代码注释同时揭示了 NimBLE 的一个约束(app_main.c):
NimBLE rejects connectable+scannable when legacy_pdu=0 (see ble_gap_ext_adv_params_validate).
即:非 legacy PDU 的扩展广播不允许同时设置 connectable + scannable,否则 NimBLE 返回BLE_HS_EINVAL。这是将adv_event_properties置 0 的根本原因。
4. 底层参数映射
在 esp_nimble.c 中可以看到扩展广播数据的落地逻辑:初始化时若配置了extended_adv_data,组件会将其拷贝进内部缓冲区,长度取MIN(config->extended_adv_len, CONFIG_BT_NIMBLE_EXT_ADV_MAX_SIZE)——即最终广播载荷受 NimBLE 编译期最大扩展广播尺寸约束(默认 1650 字节),1000 字节完全在其能力范围内。
而adv_event_properties在启动广播时被映射为扩展广播能力位图(esp_nimble.c):ev_props非零时直接使用,否则回退到ext_adv_cap字段(兼容旧配置),因此应用层应优先使用adv_event_properties表达事件属性。
sdkconfig 关键配置
ble_ext_adv/sdkconfig.defaults 是本示例正确运行的开关组合:
CONFIG_BT_ENABLED=y CONFIG_BT_NIMBLE_ENABLED=y CONFIG_BT_NIMBLE_EXT_ADV=y CONFIG_BLE_CONN_MGR_ROLE_PERIPHERAL=y CONFIG_BLE_CONN_MGR_EXTENDED_ADV=y CONFIG_BLE_CONN_MGR_EXTENDED_ADV_CAP=0 CONFIG_BLE_CONN_MGR_PERIODIC_ADV=n对照 ble_conn_mgr 的 Kconfig,这些选项的语义如下:
| 配置项 | 说明 |
|---|---|
CONFIG_BLE_CONN_MGR_EXTENDED_ADV | 使能扩展广播;依赖SOC_BLE_50_SUPPORTED,并自动开启BT_NIMBLE_EXT_ADV |
CONFIG_BLE_CONN_MGR_EXTENDED_ADV_CAP | 扩展广播能力位图(0-255):BIT0=connectable、BIT1=scannable、BIT2=directed、BIT3=high-duty directed、BIT4=legacy、BIT5=anonymous、BIT6=include TX power、BIT7=scan req notif。置 0 表示纯非连接广播,与代码中adv_event_properties = 0一致 |
CONFIG_BLE_CONN_MGR_PERIODIC_ADV | 周期性广播开关,本示例专注扩展广播、明确关闭 |
值得一提的是,组件还提供BLE_CONN_MGR_SCAN_RESULT_ADV_MAX_LEN(范围 31-1650,默认 31),决定每个扩展扫描报告分片拷贝到esp_ble_conn_scan_result_t.adv_data的最大字节数,直接影响扫描端 RAM 占用(见 Kconfig)。
扫描端的分片重组原理
扩展广播数据可能跨多个 HCI 事件分片到达,扫描端 ble_ext_scan/main/app_main.c 展示了标准的分片重组范式:
- 按广播身份过滤:仅接收
sid == 2且ext_data_status != LEGACY的报告; - 按身份键分组:以
(addr, addr_type, sid)作为重组上下文(reasm_same()判断是否同一条流,不同则reasm_begin()重新开始); - 顺序拼接分片:把每个
adv_data_len字节追加到重组缓冲区,溢出则复位; - 状态机判定:
BLE_GAP_EXT_ADV_DATA_STATUS_TRUNCATED(被控制器截断)或COMPLETE但长度 ≠ 1000 字节时均判定失败并复位;只有COMPLETE且恰好 1000 字节才进入比对; - 载荷校验:先与期望模式
memcmp逐字节比对,再打印 FNV-1a,并额外输出载荷首 64 字节与末 64 字节供人工核对。
扫描参数方面(ble_ext_scan/main/app_main.c):passive = true(被动扫描,不发送扫描请求)、filter_duplicates = false(关闭重复过滤,避免丢掉分片)、scan_phys覆盖 1M 与 Coded 两个主扫描 PHY。注意 HCI 扩展扫描不支持 2M 作为扫描 PHY,scan_phys的 2M 位会被协议栈忽略(见 esp_ble_conn_mgr.h 注释)。每个分片到达时打印EXT scan fragment: prim_phy=... sec_phy=... sid=... status=... len=... off=... rssi=...,可借此观察实际分片数量与 PHY 使用情况。
端到端验证方法
按 README 的操作指引完成验证:
- 在一块支持板上烧录
ble_ext_adv(广播端); - 在另一块板上烧录
ble_ext_scan(扫描端),确保两者处于无线通信距离内; - 对比两台设备的串口日志中FNV-1a行的哈希值,必须完全一致;
- 广播端应打印
Extended ADV payload: 1000 bytes ... FNV-1a=0x...,扫描端应打印Target ESP_EXT_ADV_1K found: 1000 bytes ... FNV-1a=0x... (compare with advertiser log)。
若哈希不一致,可通过扫描端打印的首/末 64 字节十六进制与广播端载荷模式人工比对定位问题,也可检查是否存在Truncated report/Complete status but assembled N bytes警告,后者通常意味着信号干扰或距离过远导致分片丢失。
与周期广播的区别
README 特别强调本示例不是周期性广播。两者的本质区别在于:扩展广播(本示例)由扫描端通过被动扫描发现,广播数据跟随广播事件发送;而周期性广播需要先建立周期同步(Periodic Sync),数据按固定周期独立于广播事件发送,吞吐和时序可控性更强,但接收端必须持有同步关系。若需在扩展广播基础上叠加周期广播/同步,请参考仓库内的 ble_periodic_adv 与 ble_periodic_sync 示例,以及 ble_conn_mgr 组件的ESP_BLE_CONN_EVENT_PERIODIC_REPORT/ESP_BLE_CONN_EVENT_PERIODIC_SYNC事件(见 esp_ble_conn_mgr.h)。
延伸阅读
- 组件总览与更多示例:components/bluetooth/ble_conn_mgr/README.md、ble_conn_mgr Kconfig
- 扩展广播/扫描/周期广播完整 API:esp_ble_conn_mgr.h
- 底层 NimBLE 适配实现:esp_nimble.c
- 本示例周边配对:ble_ext_scan、ble_periodic_adv、ble_periodic_sync
【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考