ESP-IoT-Solution BTHome 蓝牙调光器示例全解析:事件触发广播、加密载荷与 RTC IO 低功耗唤醒
【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution
导读
本文以 esp-iot-solution 仓库中的bthome/dimmer示例为对象,讲解如何基于 ESP-IDF 的bthome组件与ble_hci组件,实现一个符合 BTHome 协议的加密蓝牙调光器:通过按键与旋钮采集输入、仅在事件发生时广播 300 ms 即自动停播,并借助动态调频、tickless idle 与 RTC IO 唤醒实现极低功耗运行。读完本文,你将掌握 BTHome 广播载荷的构建方式、iot_button/iot_knob低功耗驱动的选型逻辑,以及 ESP32-H2/H4 上 light sleep 与 GPIO 引脚约束的落地实践。
BTHome Dimmer 示例概述
该示例位于 examples/bluetooth/ble_adv/bthome/dimmer,是基于仓库内bthome组件(components/bluetooth/ble_adv/bthome)实现的一个通用蓝牙调光器参考实现。它读取按键与旋钮的状态,并通过 BLE 广播发送符合 BTHome 协议的加密广播包,从而被 BTHome 生态(如 Home Assistant 的 BTHome 集成)直接识别。
软件层面实现的效果可概括为三点:
- 事件采集与上报:读取按键(button)与旋钮(knob)的值,并通过 BLE ADV 发送;
- 事件触发式广播:仅在按键按压或旋钮旋转时广播,广播在最后一次事件后300 ms 自动停止,避免持续广播耗电;
- 自动休眠唤醒:支持 light sleep 自动休眠,按键与旋钮通过RTC IO唤醒,保证休眠期间仍能及时响应交互。
工程结构与依赖组件
目录组成
examples/bluetooth/ble_adv/bthome/dimmer/ ├── main/ │ ├── CMakeLists.txt # 组件构建声明 │ ├── Kconfig.projbuild # 例程可调参数(CPU 频率、GPIO 等) │ ├── app_main.c # 全部业务逻辑 │ └── idf_component.yml # 组件依赖清单 ├── CMakeLists.txt ├── README.md / README_CN.md ├── sdkconfig.defaults # 通用默认配置 └── sdkconfig.defaults.esp32h4 # ESP32-H4 低功耗优化配置依赖关系
main/idf_component.yml 声明了示例的全部依赖,均通过override_path指向仓库内的本地组件:
| 依赖 | 指向仓库组件 | 作用 |
|---|---|---|
bthome | components/bluetooth/ble_adv/bthome | BTHome 协议编解码与加密 |
ble_hci | components/bluetooth/ble_hci | 免 NimBLE/Bluedroid 的底层 HCI 广播接口 |
button | components/button | 按键驱动(含 GPIO/RTC 两种实现) |
knob | components/knob | 旋钮(旋转编码器)驱动(含 GPIO/RTC 两种实现) |
idf | — | 要求 ESP-IDF 版本>=5.0 |
构建声明见 main/CMakeLists.txt:PRIV_REQUIRES driver bthome ble_hci nvs_flash,其中nvs_flash用于 BTHome 参数的持久化存储。
值得注意,本示例不依赖完整的 BLE 协议栈(如 NimBLE 或 Bluedroid),而是直接使用ble_hci组件通过 HCI 层下发广播参数与广播数据,配合sdkconfig.defaults中的CONFIG_BT_CONTROLLER_ONLY=y,将系统资源占用与功耗降到最低。
支持的芯片与硬件约束
目标芯片
- ESP32-H2:默认目标,对应 ESP-Dimmer 硬件参考设计;
- ESP32-H4:低功耗优化版本,选择该目标后会自动加载
sdkconfig.defaults.esp32h4。
GPIO 引脚约束
这是最容易踩坑的部分,示例在 main/Kconfig.projbuild 中针对不同芯片给出了不同的引脚范围限制:
- ESP32-H4:按键与旋钮引脚必须为 RTC GPIO 0–5。默认启用 RTC IO 驱动(
CONFIG_EXAMPLE_BUTTON_KNOB_USE_RTC_IO),通过 RTC IO 实现 light sleep 唤醒; - ESP32-H2:当启用 light sleep 外设掉电(
CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP)时,按键与旋钮引脚需使用RTC 唤醒 GPIO 7–14,此时走 GPIO 驱动路径。
Kconfig 中通过range指令直接约束可选引脚范围,例如EXAMPLE_GPIO_KNOB_A在 ESP32-H4 上为range 0 5、在 ESP32-H2 上为range 0 27;若选择了非法引脚,menuconfig 会直接报错,从配置源头规避唤醒引脚失效问题。
编译与烧写
进入示例目录后使用idf.py设置目标芯片并编译烧写:
cd ./esp-iot-solution/examples/bluetooth/ble_adv/bthome/dimmer # ESP32-H2(默认硬件参考设计) idf.py set-target esp32h2 # ESP32-H4(低功耗优化,自动加载 sdkconfig.defaults.esp32h4) idf.py set-target esp32h4 # 编译并下载,PORT 替换为实际串口 idf.py -p PORT build flash设置esp32h4目标时,IDF 会按照命名规则自动加载sdkconfig.defaults.esp32h4,无需手动合并配置。
源码级工作流程解析
初始化链路:app_main()
main/app_main.c 的启动顺序如下:
- NVS 初始化:
nvs_flash_init(),若发生页耗尽或版本更新则先擦除再初始化; - 电源管理配置:在
CONFIG_PM_ENABLE下调用esp_pm_configure(),以CONFIG_EXAMPLE_MAX_CPU_FREQ_MHZ/CONFIG_EXAMPLE_MIN_CPU_FREQ_MHZ配置动态调频(DFS)范围;若启用了CONFIG_FREERTOS_USE_TICKLESS_IDLE,则同时开启自动 light sleep(light_sleep_enable = true); - BTHome 对象创建与参数注入:
bthome_create()创建句柄,注册 NVS 存储/加载回调,设置 16 字节加密密钥(app_main.c#L55)与本地 MAC 地址,最后bthome_load_params()从 NVS 恢复参数; - 创建任务与定时器:创建
dimmer task(栈 4096、优先级 10),并创建一次性 300 ms 定时器(app_main.c#L357),定时器到点即关闭广播; - 初始化外设:
power_ctrl_io_init()将电源控制引脚拉高并gpio_hold_en()保持(app_main.c#L162-L181),随后初始化旋钮与按键并注册回调。
事件采集:ISR 通知 + 任务消费
按键与旋钮的回调均运行在 ISR 上下文,通过xTaskNotifyFromISR向dimmer task发送事件(TASK_EVENT_BTN/TASK_EVENT_KNOB,见 app_main.c#L60-L82)。其中按键回调还会读取esp_sleep_get_wakeup_cause(),判断是否由 light sleep 唤醒而来——这是 RTC IO 唤醒路径的关键一环。
dimmer_task主循环(app_main.c#L89-L160)通过xTaskNotifyWait阻塞等待事件,收到事件后根据类型编码事件载荷:
- 按键事件:
BUTTON_SINGLE_CLICK时btn_evt_id = 1,否则为 0;此时dim_evt = {0, 0}(无旋转); - 旋钮事件:读取
iot_knob_get_count_value()的累计值,正值编码为dim_evt = {1, value}(左旋),负值编码为{2, -value}(右旋)。
广播数据构建:BTHome 载荷与加密
dimmer task中先配置广播参数(app_main.c#L98-L108):
- 广播间隔
0x50(约 50 ms); ADV_TYPE_NONCONN_IND(不可连接广播);- 随机地址类型(
BLE_ADDR_TYPE_RANDOM),使用ble_hci_set_random_address()设置示例中预定义的本地 MAC; - 广播信道全开(
ADV_CHNL_ALL)。
随后通过 BTHome 组件 API 构建载荷(app_main.c#L142-L144):
payload_length = bthome_payload_adv_add_evt_data(payload_data, payload_length, BTHOME_EVENT_ID_BUTTON, &btn_evt_id, 1); payload_length = bthome_payload_adv_add_evt_data(payload_data, payload_length, BTHOME_EVENT_ID_DIMMER, dim_evt, 2); adv_len = bthome_make_adv_data(s_dimmer->bthome, advertisement_data, name, sizeof(name), info, payload_data, payload_length);其中BTHOME_EVENT_ID_BUTTON = 0x3A、BTHOME_EVENT_ID_DIMMER = 0x3C为事件对象 ID(见 components/bluetooth/ble_adv/bthome/include/bthome_v2.h#L110-L113),name为{0x44, 0x49, 0x59},即 ASCII 的"DIY"——这也是手机 APP 中搜索到的设备名。
设备信息字节(bthome_device_info_t,见 bthome_v2.h#L120-L129)通过位域设置:
| 位 | 字段 | 示例取值 | 含义 |
|---|---|---|---|
| bit 0 | encryption_flag | 1 | 载荷已加密 |
| bit 2 | trigger_based_flag | 0 | 非纯触发型设备(携带事件数据) |
| bit 5–7 | bthome_version | 2 | BTHome 协议版本 v2 |
bthome_make_adv_data()在内部完成服务数据段(BTHome Service UUID + 设备信息字节)、设备名与加密载荷的拼装,加密基于示例预置的 16 字节密钥与本地 MAC 地址(bthome_set_encrypt_key()/bthome_set_local_mac_addr())。
广播包构建成功后调用ble_hci_set_adv_data()下发数据、ble_hci_set_adv_enable(true)开启广播,并启动/重置 300 ms 定时器(app_main.c#L151-L157)。定时器到点后执行ble_hci_set_adv_enable(false)停播(app_main.c#L84-L87)。由于每次新事件都会xTimerReset,实际效果是最后一个事件后 300 ms 停播。
参数持久化:NVS 存储回调
BTHome 组件允许注册store/load回调(bthome_callbacks_t,见 bthome_v2.h#L161-L164)。示例在 app_main.c#L268-L308 中基于 NVS 的nvs_set_blob/nvs_get_blob实现,namespace 为storage,用于保存 BTHome 绑定/配对相关参数,实现重启后免重新配对。
低功耗设计:从配置到驱动的完整链路
两套 sdkconfig 的差异
通用默认配置 sdkconfig.defaults:
CONFIG_BT_ENABLED=y CONFIG_BT_CONTROLLER_ONLY=y # 仅启用控制器,不启用完整协议栈 CONFIG_BT_LE_SLEEP_ENABLE=y # BLE 控制器睡眠 CONFIG_PM_ENABLE=y # 电源管理 CONFIG_PM_DFS_INIT_AUTO=y # 动态调频自动初始化 CONFIG_FREERTOS_HZ=1000 CONFIG_FREERTOS_USE_TICKLESS_IDLE=y # tickless idle(自动 light sleep 前提) CONFIG_LOG_DEFAULT_LEVEL_WARN=y # 默认 WARN 级别,压低日志功耗 CONFIG_GPIO_BUTTON_SUPPORT_POWER_SAVE=yESP32-H4 专属配置 sdkconfig.defaults.esp32h4 在通用配置之上追加:
CONFIG_BT_CTRL_SLEEP_ENABLE=y # 控制器睡眠 CONFIG_BT_CTRL_LP_CLK_SRC_DEFAULT=y CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP=y # light sleep 外设掉电 CONFIG_EXAMPLE_BUTTON_KNOB_USE_RTC_IO=y # 按键/旋钮走 RTC IO 驱动 CONFIG_RTC_CLK_SRC_EXT_CRYS=y # RTC 慢时钟使用外置 32 kHz 晶振动态调频与自动 light sleep
在CONFIG_PM_ENABLE下,示例通过esp_pm_configure()设定 CPU 频率区间。默认值按芯片区分(见 Kconfig.projbuild#L3-L7 与 Kconfig.projbuild#L41-L46):
| 芯片 | 最大 CPU 频率 | 最小 CPU 频率 |
|---|---|---|
| ESP32-H4 | 32 MHz | 16 MHz(依赖 32 MHz XTAL) |
| ESP32-H2 | 96 MHz | 32 MHz |
| 其他芯片(如 ESP32/S2/S3/C5) | 80/120/160/240 MHz 可选 | 与 XTAL 对应 |
最小频率必须为 XTAL 频率或其整数分频,这是 ESP-IDF DFS 的硬件约束。配合CONFIG_FREERTOS_USE_TICKLESS_IDLE,系统在空闲时自动进入 light sleep;再叠加CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP,light sleep 期间外设电源被切断,进一步降低漏电流。
RTC IO 驱动 vs GPIO 驱动
示例通过条件编译在两种驱动之间切换(见 app_main.c#L217-L221 与 app_main.c#L236-L262):
CONFIG_EXAMPLE_BUTTON_KNOB_USE_RTC_IO=y时:旋钮走iot_knob_create_rtc()(components/knob/knob_rtc.c),按键走iot_button_new_rtc_device()(components/button/button_rtc.c),二者均通过 RTC IO 在 light sleep 期间保持唤醒能力;- 关闭该选项时:走
iot_knob_create()与iot_button_new_gpio_device()(components/button/button_gpio.c)的 GPIO 驱动路径。
两种路径都注册了按键/旋钮回调,但唤醒机制不同:RTC IO 驱动允许深度 light sleep 后被外设事件唤醒,而普通 GPIO 驱动仅适用于未启用外设掉电的场景。此外,当启用外设掉电且未使用 RTC IO 驱动时,代码会调用validate_wakeup_gpio()校验旋钮两相引脚是否为合法的 light sleep 唤醒引脚(app_main.c#L183-L192),这是 ESP32-H2 上必须选择 RTC 唤醒 GPIO 7–14 的运行时保障。
外置 32 kHz 晶振
CONFIG_RTC_CLK_SRC_EXT_CRYS在 ESP32-H4 上默认启用:RTC 慢时钟使用外置 32 kHz 晶振而非内部 RC 振荡器,休眠电流显著更低。这是 H4 低功耗优化的重要一环。
menuconfig 可调参数速查
全部参数位于 menuconfig 的Example Configuration菜单下(定义见 main/Kconfig.projbuild):
| 配置项 | 默认值 | 说明 |
|---|---|---|
EXAMPLE_MAX_CPU_FREQ_MHZ | H4: 32 / H2: 96 | DFS 最大 CPU 频率 |
EXAMPLE_MIN_CPU_FREQ_MHZ | H4: 16 / H2: 32 | DFS 最小 CPU 频率(须为 XTAL 或其整数分频) |
EXAMPLE_BUTTON_KNOB_USE_RTC_IO | n(H4 配置中开启) | 按键/旋钮使用 RTC IO 驱动实现低功耗唤醒 |
EXAMPLE_GPIO_KNOB_A | 2 | 旋转编码器 A 相引脚(H4 限 0–5,H2 限 0–27) |
EXAMPLE_GPIO_KNOB_B | 3 | 旋转编码器 B 相引脚(范围同上) |
EXAMPLE_POWER_CTRL_IO_NUM | 9 | 电源控制引脚(上电后保持高电平) |
EXAMPLE_BUTTON_IO_NUM | 0 | 按键引脚(范围同旋钮) |
EXAMPLE_BUTTON_ACTIVE_LEVEL | 0 | 按键有效电平(0 低有效,1 高有效) |
注意旋钮两个引脚与按键引脚都必须满足对应芯片的 RTC 唤醒约束,否则编译期 Kconfigrange或运行期validate_wakeup_gpio()会给出明确错误。
运行验证与输出
为优化功耗,示例默认把日志级别设为CONFIG_LOG_DEFAULT_LEVEL_WARN(sdkconfig 中为 WARN,且示例内部关键日志使用ESP_LOG_BUFFER_HEX_LEVEL(..., ESP_LOG_WARN)打印广播包内容),常规启动几乎无日志输出。
验证方式:烧写后按压按键或旋转旋钮,使用任意 BLE 扫描工具(如手机 APP)搜索名为DIY的设备,即可观察到加密的 BTHome 广播包;将设备接入 Home Assistant 等支持 BTHome 协议的网关后,事件即可被识别为调光/按键操作。该示例的 BTHome 协议实现细节可进一步查阅 components/bluetooth/ble_adv/bthome/README.md 与其单元测试 components/bluetooth/ble_adv/bthome/test_apps/main/bthome_test.c。
总结
bthome/dimmer示例完整展示了"事件触发广播 + 加密载荷 + 低功耗休眠"三合一的 BLE 外设实现范式:
- 协议层:复用
bthome组件的载荷构造、加密与设备信息编码能力,仅需少量 API 即可产出符合 BTHome v2 规范的广播包; - 传输层:
ble_hci组件绕过完整 BLE 协议栈,以最精简的方式管理非连接广播,配合 300 ms 定时器实现"有事件才广播"; - 低功耗层:DFS + tickless idle + light sleep 外设掉电 + BLE 控制器睡眠 + RTC IO 唤醒层层叠加,并针对 ESP32-H4 做了外置 32 kHz 晶振的专项优化。
该示例不仅是 BTHome 调光器的参考实现,其"ISR 通知 + 任务编码 + 定时停播 + 唤醒校验"的工程结构,也适合作为其他事件驱动型低功耗 BLE 广播设备(传感器、遥控器、开关面板等)的开发蓝本。
【免费下载链接】esp-iot-solutionEspressif IoT Library. IoT Device Drivers, Documentations and Solutions.项目地址: https://gitcode.com/GitHub_Trending/es/esp-iot-solution
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考