ESP32 低功耗优化实战:Power Management + Light Sleep 让实测电流降 67%
ESP32 低功耗优化实战:Power Management + Light Sleep 让实测电流降 67%
关键词:ESP32、低功耗、Light Sleep、Power Management、FreeRTOS、ESP-IDF
Github 源码:https://github.com/WuQinghui-00/ESP32-Light-Sensor-Monitor
一、背景
我在做一款基于 ESP32 的智能光照监测系统:FreeRTOS 多任务架构(Sensor / Control / Display / Monitor),光敏传感器每 3 秒采集一次,Wi-Fi 连接 + MQTT 每 3 秒上报,LCD 实时显示,暗环境自动开灯。
系统跑通后,下一步就是低功耗。作为可能电池供电的物联网节点,两次采样之间 CPU 完全空闲,这部分的电不能白白浪费。本文记录我基于 ESP-IDF Power Management(DFS + Light Sleep)做的真实优化:实测空闲电流从 21mA 降到 7mA,约降 67%。
说明:网上很多文章写的"80mA → 2mA、续航提升 40 倍"是估算值,本文所有数据均为万用表实测。
二、方案选型
ESP32 的几种低功耗状态:
| 模式 | 典型电流 | 特点 |
|---|---|---|
| Modem Sleep | ~20mA+ | 保留 Wi-Fi 连接 |
| Light Sleep | ~1mA 级(芯片) | CPU 暂停、外设保留,可定时器/GPIO 唤醒,唤醒后继续执行 |
| Deep Sleep | ~10µA | 深度睡眠,唤醒后程序从头执行 |
我的系统是周期性采集(3 秒一次),唤醒后要继续原来的任务流,而不是重新跑一遍启动流程,所以选 Light Sleep。
实现上我没有手动调用 esp_light_sleep_start(),而是用 ESP-IDF 的 Power Management:配置好之后,FreeRTOS 的 Tickless Idle 会在系统空闲时自动进入 Light Sleep,任务代码完全无感知,这是更工程化的做法。
三、代码实现
3.1 开启电源管理(sdkconfig.defaults)
在 sdkconfig.defaults 中启用以下配置:
CONFIG_PM_ENABLE=y
CONFIG_PM_DFS_INIT_AUTO=y
CONFIG_FREERTOS_USE_TRACE_FACILITY=y
CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
注意:修改 defaults 后必须删除旧的 sdkconfig 再重新编译,否则配置不会生效。
3.2 应用层配置(light_sensor_main.c)
esp_pm_config_esp32_t pm_config = {
.light_sleep_enable = true,
.max_freq_mhz = 80,
.min_freq_mhz = 40,
};
esp_err_t pm_err = esp_pm_configure(&pm_config);
if (pm_err == ESP_OK) {
ESP_LOGI(TAG, "Power management configured");
}
启动日志确认:
I (652) pm: Frequency switching config: CPU_MAX: 80, APB_MAX: 80, APB_MIN: 40, Light sleep: ENABLED
I (652) MAIN: Power management configured
3.3 关键修复:控制任务轮询 20ms → 200ms
配置好之后实测电流纹丝不动,排查发现是控制任务的问题:
// 修改前:每 20ms 轮询一次队列
vTaskDelay(pdMS_TO_TICKS(20));
// 修改后:每 200ms 轮询一次
vTaskDelay(pdMS_TO_TICKS(200));
原因:FreeRTOS Tickless Idle 要求系统空闲窗口大于 CONFIG_FREERTOS_IDLE_TIME_BEFORE_SLEEP(默认 3 ticks = 30ms)才会进入 Light Sleep。控制任务 20ms 就醒一次,系统永远凑不出 30ms 的空闲窗口,Light Sleep 一次都进不去。改成 200ms 后,LED 控制和命令响应依然足够快,但系统有了充足的睡眠窗口。
四、效果验证
4.1 测试方法
- 外部 5V 供电(VIN 引脚),USB 断开
- DMM6500 万用表串联在电源与 VIN 之间(红表笔 AMPS 孔、黑表笔 COM 孔),直流电流档
- 为排除外设影响,数据统一在拔掉 LCD 的条件下采集
- 四组对照:PM 开/关 × WiFi 开/关
4.2 实测数据
| PM | WiFi | 谷底电流 | 峰值电流 | 说明 |
|---|---|---|---|---|
| 关 | 开 | 24mA | 101mA | WiFi 发射瞬间 |
| 关 | 关 | 21mA | ~23mA | 无 PM 基线 |
| 开 | 开 | 8mA | 101mA | WiFi 保活拉高功耗 |
| 开 | 关 | 7mA | ~23mA | Light Sleep 生效 |
4.3 结论
- Light Sleep + DFS 使谷底电流降低约 67%:WiFi 关 21mA → 7mA,WiFi 开 24mA → 8mA。
- 101mA 峰值是 WiFi 发射瞬间的射频代价,PM 无法消除,属于射频本身的功耗。
- WiFi 连接时系统被保活机制占用,Light Sleep 收益有限;WiFi 关场景收益最大。
五、踩坑记录
坑 1:ESP_ERR_NOT_SUPPORTED,Light Sleep 配置失败
启动日志一直报 Power management configuration failed: ESP_ERR_NOT_SUPPORTED。查了 IDF 源码 esp_pm/pm_impl.c,发现只要 CONFIG_FREERTOS_USE_TICKLESS_IDLE 没开,请求 Light Sleep 就必然返回该错误。补齐配置并删除旧 sdkconfig 重建后解决。
坑 2:配置成功但电流不降
Power management configured 已经打印,但 PM 开/关实测都是 21mA。通过任务时序分析定位到控制任务 20ms 轮询堵死了 Light Sleep(见 3.3),改 200ms 后立刻见效。
坑 3:"关热点"不等于"WiFi 关"
测 WiFi 关时只把手机热点关了,结果电流反而升到 111mA。原因是固件立即重连(无退避),射频疯狂扫描/重试。正确做法是用测试固件变体注释 wifi_init(),真正关闭射频再测。
坑 4:LCD 背光掩盖芯片功耗
接 LCD 33mA、拔 LCD 21mA——背光约占 12mA。评估芯片功耗必须先排除外设,否则 PM 开关的差异会被外设电流淹没。
坑 5:万用表接法
测电流红表笔必须插 AMPS 孔并与电源、板子串联;插 INPUT HI 是测电压的并联接法,测不出电流。
坑 6:外部电源冷启动 WiFi 连不上
外部 5V 上电瞬间 3.3V 还没稳,WiFi 发射大电流把电压拉低,首次连接失败;按 EN 复位(电源已稳定)后正常。测试统一"上电等 1~2 秒按 EN";产品化方向是加大 VIN 电容 + 重连退避。
六、总结
通过这次优化,我掌握了:
- ESP-IDF Power Management(DFS + Light Sleep)的正确打开方式:配置开关 +
esp_pm_configure+ Tickless Idle 自动睡眠 - 读框架源码定位问题的能力:
ESP_ERR_NOT_SUPPORTED直接翻pm_impl.c找到根因 - 任务时序对低功耗的影响:轮询频率必须给系统留出足够的空闲窗口
- 低功耗测量的方法学:串联电流表、四组对照、排除外设、区分整板功耗与芯片功耗
最终效果:实测空闲电流 21mA → 7mA,约降 67%。下一步计划:WiFi 重连退避、VIN 电容优化、Deep Sleep 电池供电方案。
七、项目链接
GitHub:https://github.com/WuQinghui-00/ESP32-Light-Sensor-Monitor
低功耗实测报告(含完整数据):https://github.com/WuQinghui-00/ESP32-Light-Sensor-Monitor/blob/fae-improvement/docs/low-power-test-report.md
八、参考资源
更多推荐
所有评论(0)