ESP-IoT-Solution 的 BLE HCI 组件:绕过协议栈、通过 VHCI 直接驱动 BLE Controller 的轻量广播与扫描方案

发布时间:2026/9/18 23:23:09
ESP-IoT-Solution 的 BLE HCI 组件:绕过协议栈、通过 VHCI 直接驱动 BLE Controller 的轻量广播与扫描方案 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导读本篇文章聚焦 esp-iot-solution 仓库中的ble_hci组件位于 components/bluetooth/ble_hci讲解如何通过 ESP-IDF 的 VHCIVirtual Host Controller Interface接口直接操作 BLE Controller实现广播Advertising、扫描Scanning、白名单Accept List与本地地址设置等能力。读完本文你将掌握该组件的完整 API、底层 HCI 命令构造原理、初始化/去初始化流程以及如何基于测试用例写出可运行的广播与扫描应用——适用于对内存占用、固件体积和启动时间敏感的嵌入式 BLE 场景。为什么需要直接操作 BLE Controller在 ESP-IDF 生态中绝大多数 BLE 应用通过NimBLE或Bluedroid完整协议栈发起广播与扫描。协议栈提供了 GATT、SM 等上层能力但代价是引入了可观的内存与 Flash 开销且初始化链路较长。ble_hci组件则选择了另一条路径跳过 Host 协议栈通过 VHCI 接口把标准 HCI 命令直接下发给 BLE Controller。根据 README_CN.md 的说明相比 Nimble/Bluedroid 方案它带来三项直接收益更少的内存占用不加载协议栈堆与 BSS 段占用显著下降更小的固件尺寸省去 Host 协议栈代码Flash 占用更低更快的初始化流程Controller 使能后即可收发 HCI 命令无需等待 Host 协议栈启动。适用前提是该场景只需要 Controller 层能力广播、扫描、白名单过滤不需要 GATT 等 Host 层服务。组件支持的功能与整体架构组件支持的核心指令集见 README_CN.md发送广播包扫描广播包白名单Accept List管理设置本地地址组件共由 4 个核心源文件组成文件作用include/ble_hci.h对外 API、数据结构与常量定义ble_hci.cVHCI 注册、HCI 命令收发、事件解析与任务调度priv_include/bt_hci_common.hHCI 命令 Opcode、参数长度、H4 封装宏定义bt_hci_common.c各类 HCI 命令的 H4 报文打包实现数据流向为应用调用 API → 命令按 H4 协议打包进cmd_buf→esp_vhci_host_send_packet()发送给 Controller → Controller 事件如 LE Advertising Report经esp_vhci_host_register_callback()注册的回调进入队列 → 后台任务解析后回调应用层。快速开始将组件加入工程依赖声明从源码结构看该组件通过REQUIRES bt依赖 ESP-IDF 的 BT 组件见 CMakeLists.txt并且其 idf_component.yml 声明了idf: 5.0因此最低需要 ESP-IDF v5.0。向项目添加依赖的标准方式是使用idf.py add-dependency命令CMake 阶段会自动下载组件idf.py add-dependency espressif/ble_hci*关键 sdkconfig 配置由于不经过 Host 协议栈组件要求固件以Controller-only模式运行。参考 test_apps/sdkconfig.defaults核心配置为CONFIG_BT_ENABLEDy CONFIG_BTDM_CTRL_MODE_BLE_ONLYy CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLYn CONFIG_BTDM_CTRL_MODE_BTDMn CONFIG_BT_BLUEDROID_ENABLEDn CONFIG_BT_CONTROLLER_ONLYy其中CONFIG_BT_CONTROLLER_ONLYy关闭 Host 协议栈、仅使能 ControllerCONFIG_BT_BLUEDROID_ENABLEDn关闭 Bluedroid。测试工程还设置了CONFIG_FREERTOS_HZ1000并关闭了任务看门狗CONFIG_ESP_TASK_WDT_ENn以配合长时间阻塞测试。API 全览与关键数据结构以下 API 全部声明于 include/ble_hci.h。生命周期管理函数说明esp_err_t ble_hci_init(void)初始化 Controller、注册 VHCI 回调并创建事件处理任务esp_err_t ble_hci_deinit(void)停任务、禁用并去初始化 Controller释放队列与内存esp_err_t ble_hci_reset(void)向 Controller 发送 HCI Reset 命令esp_err_t ble_hci_enable_meta_event(void)设置事件掩码使能 LE Meta Event 上报广播相关函数说明ble_hci_set_adv_param(ble_hci_adv_param_t *param)设置广播参数ble_hci_set_adv_data(uint8_t len, uint8_t *data)设置广播数据最长 31 字节ble_hci_set_adv_enable(bool enable)开启/关闭广播扫描相关函数说明ble_hci_set_scan_param(ble_hci_scan_param_t *param)设置扫描参数ble_hci_set_scan_enable(bool enable, bool filter_duplicates)开启/关闭扫描可选去重过滤ble_hci_set_register_scan_callback(ble_hci_scan_cb_t cb)注册扫描结果回调白名单与地址函数说明ble_hci_add_to_accept_list(ble_hci_addr_t addr, ble_hci_addr_type_t addr_type)向 Accept List白名单添加设备ble_hci_clear_accept_list(void)清空 Accept Listble_hci_set_random_address(ble_hci_addr_t addr)设置本地随机地址常量与数据结构要点地址类型ble_hci_addr_type_tBLE_ADDR_TYPE_PUBLIC(0x00)、BLE_ADDR_TYPE_RANDOM(0x01)、BLE_ADDR_TYPE_RPA_PUBLIC(0x02)、BLE_ADDR_TYPE_RPA_RANDOM(0x03)。广播参数结构体ble_hci_adv_param_t关键字段adv_int_min/adv_int_max广播间隔单位为 N × 0.625 ms合法范围 0x0020 ~ 0x4000adv_type广播类型如ADV_TYPE_IND(0x00)、ADV_TYPE_NONCONN_IND(0x03)own_addr_type/peer_addr_type本地/对端地址类型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) 允许任何人发起扫描与连接请求。扫描参数结构体ble_hci_scan_param_t关键字段scan_typeBLE_SCAN_TYPE_PASSIVE(0x0) 被动扫描 /BLE_SCAN_TYPE_ACTIVE(0x1) 主动扫描scan_interval/scan_window单位 N × 0.625 ms合法范围 0x0004 ~ 0x4000filter_policy复用ble_hci_adv_filter_t的过滤策略枚举。扫描结果结构体ble_hci_scan_result_t包含搜索事件类型search_evt、设备类型、地址bda6 字节、地址类型、原始广播数据ble_adv最大ESP_BLE_ADV_DATA_LEN_MAX ESP_BLE_SCAN_RSP_DATA_LEN_MAX即 62 字节、广播数据长度、扫描应答长度与 RSSIrssi。另外ESP_BLE_ADV_DATA_LEN_MAX与ESP_BLE_SCAN_RSP_DATA_LEN_MAX均为 31 字节BLE_HCI_ADDR_LEN为 6这与 BLE 4.x/5.x 的广播报文格式保持一致。广播与扫描实战从测试用例到应用test_apps/main/ble_hci_test.c 提供了两段可直接复用的完整用例下面分别剖析。用例一发起不可连接广播测试[ble hci adv]展示了“初始化 → 设置随机地址 → 配置广播参数 → 填充广播数据 → 使能广播 → 去初始化”的完整链路ble_hci_init(); /* 1. 设置本地随机地址 */ uint8_t own_addr[6] {0xff, 0x22, 0x33, 0x44, 0x55, 0x66}; ble_hci_set_random_address(own_addr); /* 2. 配置广播参数不可连接广播全信道 */ ble_hci_adv_param_t adv_param { .adv_int_min 0x20, /* 0x20 × 0.625 ms 20 ms */ .adv_int_max 0x40, /* 0x40 × 0.625 ms 40 ms */ .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); /* 3. 组装广播数据Flags 完整本地名 ESP-BLE-1 */ 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 }; /* AD: Flags(0x01)0x06, 待填的 Complete Local Name */ adv_data[3] name_len 1; /* Name AD 的长度字段 */ memcpy(adv_data 5, adv_name, name_len); ble_hci_set_adv_data(5 name_len, adv_data); /* 4. 开启广播并保持 5 秒 */ ble_hci_set_adv_enable(true); vTaskDelay(5000 / portTICK_PERIOD_MS); /* 5. 关闭广播并释放资源 */ ble_hci_set_adv_enable(false); ble_hci_deinit();广播数据采用标准 ADAdvertising Data结构0x02 0x01 0x06表示长度为 2 的 Flags AD类型 0x01值 0x06 表示支持 LE General Discoverable随后以0x09Complete Local Name类型携带设备名长度字段由代码动态计算。用例二扫描并回调打印测试[ble hci scan]展示了扫描链路初始化后必须先ble_hci_reset()复位 Controller再通过ble_hci_enable_meta_event()打开 LE Meta Event 掩码否则 Controller 不会上报 LE Advertising Report 事件。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, /* 0x50 × 0.625 ms 50 ms */ .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); 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_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); /* 开启扫描不去重 */ vTaskDelay(5000 / portTICK_PERIOD_MS); ble_hci_set_scan_enable(false, false); ble_hci_deinit();测试工程使用 Unity 框架组织用例app_main中调用unity_run_menu()进入交互式测试菜单setUp/tearDown还会基于heap_caps_get_free_size对比测试前后MALLOC_CAP_8BIT与MALLOC_CAP_32BIT空闲内存验证组件无内存泄漏。底层实现原理VHCI、H4 封装与事件解析初始化Controller-only 与 VHCI 回调注册ble_hci_init()ble_hci.c的核心步骤以calloc分配组件上下文ble_hci_t创建两个 FreeRTOS 队列hci_data_queue深度 15承载 Controller 上报数据与hci_cmd_evt_queue深度 5承载命令完成事件用BT_CONTROLLER_INIT_CONFIG_DEFAULT()初始化 Controller并以ESP_BT_MODE_BLE模式使能——这与 sdkconfig 中CONFIG_BT_CONTROLLER_ONLYy呼应通过esp_vhci_host_register_callback(vhci_host_cb)注册两个回调controller_rcv_pkt_readyController 可接收包通知与host_rcv_pktHost 收到数据包创建优先级 6、固定运行在 Core 0 的hci_evt_process任务负责消费数据队列。host_rcv_pkt将收到的原始数据malloc拷贝后投递到hci_data_queue队列满时打印告警并释放内存避免内存泄漏。H4 报文构造make_cmd 系列函数每条 HCI 命令在 bt_hci_common.c 中对应一个make_cmd_*打包函数均遵循 H4 协议格式类型字节 2 字节 Opcode 1 字节参数长度 参数载荷。例如make_cmd_ble_set_adv_param依次写入首字节H4_TYPE_COMMAND(0x01)2 字节 OpcodeHCI_BLE_WRITE_ADV_PARAMS由 priv_include/bt_hci_common.h 中的HCI_OCF_WRITE_ADV_PARAMS | HCI_GRP_BLE_CMDS拼接而成即 0x08 10 的 LE 命令组参数长度HCIC_PARAM_SIZE_BLE_WRITE_ADV_PARAMS(15)依次是广播间隔最小值、最大值各 2 字节小端、广播类型、本地地址类型、对端地址类型、对端地址BDADDR_TO_STREAM按蓝牙小端序倒序写入 6 字节、信道掩码与过滤策略。make_cmd_ble_set_adv_data中有一个值得注意的实现细节参数长度固定写为HCIC_PARAM_SIZE_BLE_WRITE_ADV_DATA 132并先将缓冲清零再写入实际数据长度当data_len超过 31 时会被截断——从源码结构可以推断这是为了满足控制器对广播数据命令固定载荷长度的要求同时也为上层 31 字节上限提供了硬性保护。事件解析hci_evt_process 任务事件解析任务hci_evt_processble_hci.c按 HCI 事件格式逐字节解析第 2 字节为事件码LE_META_EVENTS(0x3E) 表示 LE 元事件LE_CMD_COMPLETE_EVENT(0x0E) 表示命令完成对于 LE 元事件偏移到子事件字段HCI_LE_ADV_REPORT(0x02) 即广播上报随后解析报告数量、每个报告的事件类型、地址类型、6 字节地址逆序还原、广播数据长度、广播数据与 RSSI-(0xFF - raw)换算为负值 dBm最后统一调用用户回调s_ble_hci-cb对于命令完成事件取第 5 字节命令 Opcode 与第 7 字节返回状态0 表示成功封装为hci_cmd_event_t投递到hci_cmd_evt_queue供各 API 同步等待。所有 API如ble_hci_set_adv_param都采用同步模式发送命令后以CMD_WAIT_TIME100 ticks等待hci_cmd_evt_queue校验返回的 Opcode 与状态码后返回esp_err_t。去初始化ble_hci_deinit()先等待并删除事件任务依次调用esp_bt_controller_disable()/esp_bt_controller_deinit()删除两个队列并释放上下文最后将全局指针置空重复调用时会打印告警并安全返回ESP_OK。设计要点与注意事项必须先复位并开启元事件再做扫描从扫描测试可见ble_hci_reset()与ble_hci_enable_meta_event()是扫描的前置步骤其中事件掩码将 8 字节掩码第 7 字节置为 0x20对应 bit 61以打开 LE Meta Event。地址字节序写入地址使用BDADDR_TO_STREAM倒序蓝牙规范的小端存储解析扫描结果时用bda[5 - j]还原——与 priv_include/bt_hci_common.h 中BDADDR_TO_STREAM宏对称。内存与资源ble_hci_init全程只分配一个上下文结构体与两个队列广播/扫描结果缓冲SCAN_RESULT_LEN_MAX为 25 条内嵌在上下文中无协议栈大块内存消耗这正是“更少内存占用”的源码级佐证。适用边界本组件面向 Controller 直控场景不提供 GATT/ATT/SMP 能力若需要连接、配对或服务发现应回归 NimBLE/Bluedroid 方案。组件要求idf 5.0并以 BLE-only Controller 模式运行CONFIG_BT_CONTROLLER_ONLYy。组件版本仓库内组件当前为 v1.0.0见 CHANGELOG.md首版即支持广播、扫描、白名单与随机地址设置等能力。总结ble_hci是 esp-iot-solution 为 ESP32 系列提供的最小 BLE 数据通路以约 300 行核心 C 代码通过 VHCI 直连 Controller完整覆盖广播、扫描、白名单与地址管理。配合 test_apps/main/ble_hci_test.c 中的两段用例开发者可以快速搭建 Beacon、扫描器等轻量应用深入阅读 ble_hci.c 与 bt_hci_common.c 则可完整理解 H4 报文打包与 LE 事件解析的底层细节为在资源受限设备上定制 BLE 数据收发方案提供坚实起点。【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考