esp-iot-solution 的 BLE HCI 组件:绕过协议栈、通过 VHCI 直控 BLE Controller 实现广播与扫描
【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution
导读
BLE HCI 组件是 esp-iot-solution 提供的一个轻量级蓝牙组件,它绕过 Nimble / Bluedroid 完整协议栈,通过 Espressif 的 VHCI(Virtual Host Controller Interface)接口直接向 BLE Controller 下发 HCI 命令,实现广播(Advertising)与扫描(Scanning)等最常用的 BLE 功能。读完本文,你将掌握该组件的架构原理、广播与扫描两条完整调用链路、全部公开 API 的参数含义与取值范围,并能够基于仓库中的单元测试示例在自有项目里快速集成。
组件定位:为什么需要绕过协议栈
官方文档 BLE HCI 组件说明 明确指出,该组件用于通过 VHCI 接口直接操作 BLE Controller 实现广播、扫描等功能。与通过 Nimble 或 Bluedroid 协议栈发起广播和扫描相比,使用该组件有如下优点:
- 更少的内存占用:不加载完整 Host 协议栈,组件内部仅维护两条 FreeRTOS 队列与一个事件处理任务;
- 更小的固件尺寸:不链接协议栈代码,只编译 ble_hci.c 与 bt_hci_common.c 两个源文件;
- 更快的初始化流程:初始化时只需
esp_bt_controller_init+esp_bt_controller_enable,无需等待 Host 协议栈启动。
从 README_CN.md 的支持指令列表看,组件聚焦于四类能力:发送广播包、扫描广播包、白名单(Filter Accept List)以及设置本地地址。这正是传感器节点、信标(Beacon)、网关等"只要广播和扫描、不需要 GATT 连接"场景的典型诉求。
底层架构:VHCI 回调、命令封装与事件处理任务
VHCI 回调注册
组件初始化时,通过esp_vhci_host_register_callback注册一组回调,见 ble_hci.c:
static esp_vhci_host_callback_t vhci_host_cb = { controller_rcv_pkt_ready, host_rcv_pkt };controller_rcv_pkt_ready:当 Controller 可以接收新数据时被调用;host_rcv_pkt:当 Controller 有事件包(HCI Event)上送时被调用,数据会拷贝后投递到hci_data_queue队列。
HCI 命令的构造与下发
所有命令都不是直接调用某个封装好的 API,而是由 bt_hci_common.c 中的make_cmd_*系列函数按 HCI 规范手工拼装命令包,再通过esp_vhci_host_send_packet发送。命令包遵循 H4 传输层格式:第 1 字节为 H4 类型(命令为0x01),随后是 2 字节 Opcode 与 1 字节参数长度,之后是参数体。以设置广播参数为例:
uint16_t make_cmd_ble_set_adv_param(uint8_t *buf, uint16_t adv_int_min, uint16_t adv_int_max, uint8_t adv_type, uint8_t addr_type_own, uint8_t addr_type_dir, bd_addr_t direct_bda, uint8_t channel_map, uint8_t adv_filter_policy)对应的 OCF/OGF 定义位于 bt_hci_common.h,例如:
HCI_BLE_WRITE_ADV_PARAMS(OGF=0x08, OCF=0x0006,参数 15 字节)HCI_BLE_WRITE_ADV_DATA(OCF=0x0008,参数上限 31 字节)HCI_BLE_WRITE_ADV_ENABLE(OCF=0x000A)HCI_BLE_WRITE_SCAN_PARAM(OCF=0x000B,参数 7 字节)HCI_BLE_WRITE_SCAN_ENABLE(OCF=0x000C,参数 2 字节)HCI_BLE_SET_RANDOM_ADDR(OCF=0x0005,参数 6 字节)HCI_BLE_ADD_TO_ACCEPT_LIST/HCI_BLE_CLEAR_ACCEPT_LIST(OCF=0x0011 / 0x0010)HCI_SET_EVT_MASK(OGF=0x03, OCF=0x0001,8 字节事件掩码)
命令同步等待机制
每条命令下发后,组件会等待对应的 HCI Command Complete 事件。事件处理任务从hci_cmd_evt_queue中取出响应,并校验 Opcode 与返回状态(reason),超时时间固定为CMD_WAIT_TIME (100/portTICK_PERIOD_MS)。状态码0表示成功,非零值会通过ESP_LOGE打印失败原因(ble_hci.c)。
事件处理任务:从裸字节到结构化结果
初始化时会创建一个名为hci_evt_process、优先级 6、栈 2048 字节、固定核 0 的任务(ble_hci.c)。它循环从hci_data_queue取包并解析:
- 若事件 Opcode 为
0x3E(LE Meta Events),继续读 Sub Event;当 Sub Event 为0x02(LE Advertising Report)时,依次解析设备地址类型、6 字节地址、广播数据长度与内容、扫描响应长度与内容,并把 RSSI 由无符号值转换为负数:
s_ble_hci->scan_result[i].rssi = -(0xFF - queue_data[data_ptr++]);- 若事件 Opcode 为
0x0E(Command Complete),则把命令 Opcode 与 reason 投递到hci_cmd_evt_queue,供下发命令的 API 同步等待; - 其他事件一律打印 "Unhandled HCI event code" 告警。
扫描结果缓冲区上限为SCAN_RESULT_LEN_MAX 25条,每条结果保存在ble_hci_scan_result_t中,解析完成后一次性回调给注册的扫描回调函数。
广播应用:五步完成一次 BLE 广播
根据 BLE HCI 组件说明 的广播流程,典型调用顺序为:
- 调用
ble_hci_init()初始化; 2.(可选)调用ble_hci_set_random_address()设置本地随机地址; - 调用
ble_hci_set_adv_param()配置广播参数; - 调用
ble_hci_set_adv_data()设定广播数据; - 调用
ble_hci_set_adv_enable(true)开启广播。
仓库中的单元测试 ble_hci_test.c 给出了完整的可运行示例:
ble_hci_init(); uint8_t own_addr[6] = {0xff, 0x22, 0x33, 0x44, 0x55, 0x66}; ble_hci_set_random_address(own_addr); ble_hci_adv_param_t adv_param = { .adv_int_min = 0x20, .adv_int_max = 0x40, .adv_type = ADV_TYPE_NONCONN_IND, .own_addr_type = BLE_ADDR_TYPE_RANDOM, .peer_addr_type = BLE_ADDR_TYPE_PUBLIC, .channel_map = ADV_CHNL_ALL, .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, }; uint8_t peer_addr[6] = {0x80, 0x81, 0x82, 0x83, 0x84, 0x85}; memcpy(adv_param.peer_addr, peer_addr, BLE_HCI_ADDR_LEN); ble_hci_set_adv_param(&adv_param); char *adv_name = "ESP-BLE-1"; uint8_t name_len = (uint8_t)strlen(adv_name); uint8_t adv_data[31] = { 0x02, 0x01, 0x06, 0x0, 0x09 }; adv_data[3] = name_len + 1; memcpy(adv_data + 5, adv_name, name_len); ble_hci_set_adv_data(5 + name_len, adv_data); ble_hci_set_adv_enable(true); // ... 广播 5 秒 ... ble_hci_set_adv_enable(false); ble_hci_deinit();测试中构造的广播数据含义:0x02 0x01 0x06为 Flags 段(LE General Discoverable + BR/EDR Not Supported),0x09为 Complete Local Name 类型,随后紧跟设备名 "ESP-BLE-1",其数据段组装方式完全遵循 BLE 广播数据 AD Structure 格式。
广播参数详解
ble_hci_adv_param_t各字段定义与取值范围见 ble_hci.h:
| 字段 | 含义 | 取值范围/说明 |
|---|---|---|
adv_int_min/adv_int_max | 广播间隔上下限 | 实际时间 = N × 0.625 ms,范围 0x0020 ~ 0x4000(即 20 ms ~ 10.24 s) |
adv_type | 广播类型 | ADV_TYPE_IND(0x00) 可连接可扫描、ADV_TYPE_DIRECT_IND_HIGH(0x01) 高占空比定向、ADV_TYPE_SCAN_IND(0x02) 可扫描、ADV_TYPE_NONCONN_IND(0x03) 不可连接、ADV_TYPE_DIRECT_IND_LOW(0x04) 低占空比定向 |
own_addr_type | 本机地址类型 | BLE_ADDR_TYPE_PUBLIC(0x00) /BLE_ADDR_TYPE_RANDOM(0x01) / RPA 类型(0x02/0x03) |
peer_addr/peer_addr_type | 对端地址及类型 | 仅定向广播(Direct)时使用,类型只支持 public/random |
channel_map | 广播信道 | ADV_CHNL_37(0x01) /ADV_CHNL_38(0x02) /ADV_CHNL_39(0x04) /ADV_CHNL_ALL(0x07) |
adv_filter_policy | 广播过滤策略 | ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY(0x00) 等四种组合,控制扫描请求与连接请求是否只接受白名单设备 |
广播数据上限
广播数据最大长度为ESP_BLE_ADV_DATA_LEN_MAX 31字节,扫描响应数据同样为 31 字节(ble_hci.h)。ble_hci_set_adv_data(len, data)内部在构造命令时会做截断保护:当传入长度超过 31 字节时按 31 字节处理(见 bt_hci_common.c)。
扫描应用:五步注册回调并开始扫描
根据 BLE HCI 组件说明 的扫描流程,典型调用顺序为:
- 调用
ble_hci_init()初始化; - 调用
ble_hci_set_scan_param()配置扫描参数; - 调用
ble_hci_enable_meta_event()使能 LE Meta 事件; - 调用
ble_hci_set_register_scan_callback()注册扫描回调; - 调用
ble_hci_set_scan_enable(true, filter_duplicates)开始扫描。
仓库测试用例 ble_hci_test.c 的扫描部分同样可以直接套用:
ble_hci_init(); ble_hci_reset(); ble_hci_enable_meta_event(); ble_hci_scan_param_t scan_param = { .scan_type = BLE_SCAN_TYPE_PASSIVE, .scan_interval = 0x50, .scan_window = 0x50, .own_addr_type = BLE_ADDR_TYPE_PUBLIC, .filter_policy = ADV_FILTER_ALLOW_SCAN_WLST_CON_ANY, }; ble_hci_set_scan_param(&scan_param); ble_hci_set_register_scan_callback(&ble_hci_scan_cb); uint8_t peer_addr[6] = {0xff, 0x22, 0x33, 0x44, 0x55, 0x66}; ble_hci_add_to_accept_list(peer_addr, BLE_ADDR_TYPE_RANDOM); ble_hci_set_scan_enable(true, false); // ... 扫描 5 秒,回调中打印结果 ... ble_hci_set_scan_enable(false, false); ble_hci_deinit();回调函数签名如下,result_len表示本次上报的扫描结果条数,最多 25 条:
static void ble_hci_scan_cb(ble_hci_scan_result_t *scan_result, uint16_t result_len) { for (int i = 0; i < result_len; i++) { printf("%2x:%2x:%2x:%2x:%2x:%2x\n", scan_result[i].bda[0], scan_result[i].bda[1], scan_result[i].bda[2], scan_result[i].bda[3], scan_result[i].bda[4], scan_result[i].bda[5]); } }扫描参数详解
ble_hci_scan_param_t字段(ble_hci.h):
| 字段 | 含义 | 取值范围/说明 |
|---|---|---|
scan_type | 扫描类型 | BLE_SCAN_TYPE_PASSIVE(0x0) 被动扫描 /BLE_SCAN_TYPE_ACTIVE(0x1) 主动扫描(会发送扫描请求以获取扫描响应数据) |
scan_interval | 扫描间隔 | 实际时间 = N × 0.625 ms,范围 0x0004 ~ 0x4000(即 2.5 ms ~ 10.24 s) |
scan_window | 扫描窗口 | 同样按 0.625 ms 换算,范围 0x0004 ~ 0x4000,且应不大于 scan_interval |
own_addr_type | 本机地址类型 | 同广播参数 |
filter_policy | 扫描过滤策略 | 复用ble_hci_adv_filter_t枚举,决定是否只接收白名单设备 |
使能 Meta 事件的关键一步
ble_hci_enable_meta_event()的作用是向 Controller 发送HCI_SET_EVT_MASK命令,把 8 字节事件掩码的第 61 位置 1(掩码{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20}),从而打开 LE Meta Events 通道(ble_hci.c)。漏掉这一步,扫描回调将永远收不到广告上报事件,这也是扫描流程与广播流程最大的差异点。
扫描结果结构
每个扫描结果ble_hci_scan_result_t(ble_hci.h)包含:
search_evt:搜索事件类型(查询结果、发现结果、完成等);dev_type:设备类型(BR/EDR、BLE、双模);bda:6 字节对端地址;ble_addr_type:对端地址类型;ble_adv[62]:接收到的完整 EIR(广播数据 + 扫描响应数据,各最多 31 字节);adv_data_len/scan_rsp_len:广播数据与扫描响应长度;rssi:信号强度(负数,单位为 dBm)。
白名单与随机地址:精细化控制手段
ble_hci_add_to_accept_list(addr, addr_type):把单个设备加入 Controller 内部的 Filter Accept List(白名单),地址类型 0x00 为 public、0x01 为 random;对应 HCI 命令参数长度为 7 字节(1 字节类型 + 6 字节地址)。ble_hci_clear_accept_list():清空白名单。ble_hci_set_random_address(addr):设置本机随机地址。使用随机地址作为广播地址时,需要在配置广播参数前调用,并把own_addr_type设置为BLE_ADDR_TYPE_RANDOM。
白名单与广播/扫描的过滤策略(adv_filter_policy)配合使用,即可实现"只对特定设备可见/只扫描特定设备"的定向广播与定向扫描。
完整 API 参考
组件的全部公开 API 定义在 ble_hci.h,文档中的 "API 参考" 一节即由该头文件自动生成。按功能归类如下:
| API | 功能 |
|---|---|
ble_hci_init() | 初始化:创建队列与事件任务、初始化并使能 BLE Controller、注册 VHCI 回调 |
ble_hci_deinit() | 反初始化:删除任务、禁用并反初始化 Controller、释放队列与内存 |
ble_hci_reset() | 复位 Controller |
ble_hci_set_random_address(addr) | 设置本机随机地址 |
ble_hci_set_adv_param(param) | 设置广播参数 |
ble_hci_set_adv_data(len, data) | 设置广播数据(≤ 31 字节) |
ble_hci_set_adv_enable(enable) | 开启/关闭广播 |
ble_hci_enable_meta_event() | 使能 LE Meta 事件(扫描前必调) |
ble_hci_set_scan_param(param) | 设置扫描参数 |
ble_hci_set_scan_enable(enable, filter_duplicates) | 开启/关闭扫描,可同时决定是否过滤重复设备 |
ble_hci_set_register_scan_callback(cb) | 注册扫描结果回调 |
ble_hci_add_to_accept_list(addr, addr_type) | 向白名单添加设备 |
ble_hci_clear_accept_list() | 清空白名单 |
除ble_hci_set_register_scan_callback与ble_hci_init/ble_hci_deinit外,各命令类 API 均返回 HCI Command Complete 事件的 reason 字段(0表示成功),底层统一通过hci_cmd_evt_queue同步等待,因此不要并发调用多条命令,以免阻塞在 100 ms 的超时等待上。
在项目中集成与验证
组件以标准 IDF 组件形式组织(CMakeLists.txt),核心依赖仅为bt(REQUIRES bt)。从 idf_component.yml 可以确认其依赖约束为idf: ">=5.0"并依赖cmake_utilities: "0.*",因此适用于 ESP-IDF 5.0 及以上版本。既可以将 components/bluetooth/ble_hci 目录整体放入自己工程的components/下作为本地组件,也可以通过 ESP Component Registry 按组件方式拉取。
组件自带完整测试工程 test_apps/main/ble_hci_test.c,基于 Unity 框架提供[ble hci adv]与[ble hci scan]两个测试用例(分别覆盖广播与扫描全流程),并在setUp/tearDown中通过heap_caps_get_free_size对比 8-bit/32-bit 堆内存差值(阈值 -460 字节)来校验 init/deinit 循环后无内存泄漏。参考该测试文件即可快速搭建自己的 demo:先在app_main中调用unity_run_menu(),再按上文步骤在测试用例中完成广播或扫描链路。
结语
BLE HCI 组件把"广播 + 扫描"这一最常用的 BLE 能力压缩到一个轻量组件中:相比 Nimble/Bluedroid 全协议栈,它以更少内存、更小固件、更快初始化的方式满足了信标、传感节点、网关等场景需求;相比裸写 VHCI 回调,它又提供了ble_hci_*一套同步、简洁、参数完备的封装,并自带可验证的测试用例。需要进一步了解组件设计与枚举细节时,可直接阅读 ble_hci.h 与 ble_hci.c 的完整实现。
【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考