Chapter 09

低功耗与电池优化

用深度睡眠将 ESP32 平均电流降至微安级,实现电池供电数月乃至数年

ESP32 五种电源模式

IoT 设备往往依赖电池供电,低功耗设计直接决定设备寿命。ESP32 提供五种由高功耗到超低功耗的工作模式,理解每种模式的特性是优化电池寿命的基础。

电源模式典型电流Wi-FiCPU唤醒延迟
Active(活跃)240mA(峰值)/ 80mA(平均)发送/接收满速运行N/A
Modem-sleep3~20mA定时休眠(DTIM 间隔)正常运行毫秒级
Light-sleep0.8mA暂停,可保持连接暂停约 3ms
Deep-sleep10~150μA关闭关闭(仅 RTC 运行)约 300ms(含启动)
Hibernation2.5μA关闭关闭,RTC 外设也关约 300ms + 完整启动
电流数字背后的含义

一块常见的 18650 锂电池容量约 2500mAh。在纯 Active 模式下(80mA),只能用约 31 小时。而用深度睡眠(每 10 分钟唤醒一次,每次活跃 3 秒),平均电流约 30μA,理论寿命超过 9 年(当然实际还受自放电影响)。

深度睡眠(Deep Sleep)详解

深度睡眠是 IoT 设备最重要的省电手段。进入深度睡眠后,主 CPU、大部分 RAM 和外设全部断电,只有 RTC 时钟域(RTC 定时器、RTC 内存、RTC GPIO)保持供电。

深度睡眠时序: ESP32 活跃 唤醒 ───────┐ ┌───────────────────── │ │ 完整启动流程: │ 深度睡眠 │ bootloader (300ms) │ 仅 RTC 运行 │ → app_main() ───────┘────────────────────────┘ │←────── sleep time ───────────→│←── active time ──→│ 能耗占比(每 10 分钟唤醒一次,活跃 3s): 睡眠 597s × 10μA = 5970 μAh 活跃 3s × 80mA = 66.7 μAh(= 240000 μAh × 3/3600) 总计 ≈ 73 μAh/次 → 平均电流 ≈ 30 μA

唤醒源类型

RTC 定时器
最常用的唤醒方式,设置睡眠时长后到时自动唤醒。精度约 ±1%(RTC 时钟为低精度 RC 振荡器,150kHz)。如需精准时间戳,唤醒后需同步 NTP。
外部 GPIO(ext0/ext1)
ext0:单个 RTC GPIO 电平触发(高或低);ext1:多个 RTC GPIO 的逻辑 AND 或 OR 触发。可用于按键唤醒、PIR 运动传感器触发等。
触摸唤醒
触摸传感器的电容变化作为唤醒源,适合无物理按键的设计。RTC 子系统持续监测触摸值,无需 CPU 参与。
ULP 协处理器
超低功耗协处理器(Ultra Low Power Coprocessor),可在深度睡眠中独立运行简单程序,读取 ADC/I2C,满足条件时唤醒主 CPU,功耗仅 100μA 级别。
RTC SRAM(8KB)
深度睡眠期间保持数据的唯一 RAM(前 4KB 由 ULP 使用,后 4KB 可由主 CPU 存储跨睡眠数据)。通过 RTC_DATA_ATTR 宏声明的变量存放在此。

深度睡眠完整示例

#include "esp_sleep.h"
#include "esp_log.h"
#include "driver/rtc_io.h"

static const char *TAG = "SLEEP";

/* RTC_DATA_ATTR:变量存储在 RTC SRAM,深度睡眠后保留 */
static RTC_DATA_ATTR int boot_count = 0;
static RTC_DATA_ATTR float last_temperature = 0.0f;

#define SLEEP_DURATION_S  600    // 10 分钟
#define WAKEUP_PIN        GPIO_NUM_33

void app_main(void)
{
    boot_count++;
    ESP_LOGI(TAG, "第 %d 次启动", boot_count);

    /* 判断唤醒原因 */
    esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
    switch (cause) {
        case ESP_SLEEP_WAKEUP_TIMER:
            ESP_LOGI(TAG, "定时器唤醒,上次温度: %.1f°C", last_temperature);
            break;
        case ESP_SLEEP_WAKEUP_EXT0:
            ESP_LOGI(TAG, "GPIO 唤醒(按键)");
            break;
        default:
            ESP_LOGI(TAG, "上电启动");
            break;
    }

    /* ── 执行业务逻辑(读传感器、上报数据)── */
    last_temperature = 25.6f;  // 模拟读取传感器
    ESP_LOGI(TAG, "数据已处理,准备进入深度睡眠");

    /* ── 配置唤醒源 ── */

    /* 1. 定时器唤醒 */
    esp_sleep_enable_timer_wakeup((uint64_t)SLEEP_DURATION_S * 1000000ULL);

    /* 2. GPIO 唤醒(GPIO33 低电平触发,需使用 RTC GPIO)*/
    rtc_gpio_pullup_en(WAKEUP_PIN);    // 上拉,按键接地
    esp_sleep_enable_ext0_wakeup(WAKEUP_PIN, 0);  // 0 = 低电平唤醒

    /* 进入深度睡眠 */
    ESP_LOGI(TAG, "进入深度睡眠 %d 秒...", SLEEP_DURATION_S);
    esp_deep_sleep_start();    // 不返回!
}

Light Sleep(浅睡眠)

Light Sleep 比 Deep Sleep 功耗稍高(约 0.8mA),但唤醒后无需完整启动流程(仅约 3ms),程序从调用 esp_light_sleep_start() 的下一行继续执行,RAM 和状态完全保留。适合需要频繁唤醒的场景(如每秒唤醒一次)。

void light_sleep_demo(void)
{
    while (1) {
        ESP_LOGI(TAG, "开始 5 秒浅睡眠");
        esp_sleep_enable_timer_wakeup(5000000);  // 5秒
        esp_light_sleep_start();    // ← 此处暂停 5 秒

        /* 唤醒后从这里继续,变量状态保留 */
        ESP_LOGI(TAG, "唤醒!继续执行...");
        /* 处理数据... */
    }
}

ULP(超低功耗)协处理器

ULP(Ultra Low Power Coprocessor)是 ESP32 内置的独立处理器,在深度睡眠中以极低功耗(100μA 以下)独立运行,可以读取 ADC、I2C 传感器,在满足条件时唤醒主 CPU。

#include "esp32/ulp.h"
#include "driver/rtc_io.h"
#include "ulp_main.h"    // 由 ULP 汇编程序生成

/* ULP 读取 ADC 并在超阈值时唤醒主 CPU */
/* ULP 程序用汇编或 C(ESP32-S2/S3 支持 C)编写 */
extern const uint8_t ulp_main_bin_start[] asm("_binary_ulp_main_bin_start");
extern const uint8_t ulp_main_bin_end[]   asm("_binary_ulp_main_bin_end");

void start_ulp_program(void)
{
    /* 加载 ULP 程序到 RTC 内存 */
    ulp_load_binary(0, ulp_main_bin_start,
        (ulp_main_bin_end - ulp_main_bin_start) / sizeof(uint32_t));

    /* 设置 ULP 唤醒间隔(每 100ms 运行一次) */
    ulp_set_wakeup_period(0, 100000);   // 100000 μs = 100ms

    /* 启动 ULP */
    ulp_run(&ulp_main - RTC_SLOW_MEM);

    /* 允许 ULP 唤醒主 CPU */
    esp_sleep_enable_ulp_wakeup();
    esp_deep_sleep_start();
}
ESP32-S2/S3 的 ULP-RISC-V 核

ESP32 原版 ULP 只能用汇编编写,学习曲线陡峭。ESP32-S2 和 S3 引入了 ULP-RISC-V 核,支持用 C 语言编写 ULP 程序,大大降低了开发门槛。新项目建议优先考虑 ESP32-S3。

电流实测与分析

电流实测工具推荐: Nordic PPK2(Power Profiler Kit 2) └─ 专业 IoT 电流分析仪,0.2μA 分辨率 └─ 可实时显示电流波形,与代码执行对应 └─ 约 ¥400,强烈推荐 平替方案: INA219 电流采样芯片(精度 0.1mA) └─ 连接到 Arduino/ESP32 采集 └─ 成本约 ¥5,适合粗略测量 典型优化目标(每 10 分钟上报一次): ┌─────────────────────────────────────────┐ │ 未优化(Always Active): ~80mA │ │ Modem Sleep(TCP 保活): ~15mA │ │ Deep Sleep + 快速上报 : ~500μA │ │ Deep Sleep + Wi-Fi 关 : ~30μA │ │ Deep Sleep + BLE Adv : ~20μA │ │ Hibernation(仅 RTC) : ~5μA │ └─────────────────────────────────────────┘
深度睡眠功耗异常的常见原因

1. GPIO 有外接上拉/下拉电阻:睡眠时 GPIO 浮空,电阻持续漏电。解决:睡眠前将相关 GPIO 配置为 RTC GPIO 并设置保持(hold)状态。
2. 外部传感器/模块仍在供电:用 MOSFET 或 Load Switch 在睡眠前切断外设供电。
3. I2C 总线上拉电阻:ESP32 I2C 引脚低电平时,上拉电阻到 3.3V 持续产生漏电流。睡眠前释放 I2C 总线并切断电源。

GPIO Hold 与外设断电

深度睡眠期间,普通 GPIO 状态不确定,需要在睡眠前"锁定"(Hold)引脚状态:

#include "driver/gpio.h"
#include "driver/rtc_io.h"
#include "esp_sleep.h"

#define SENSOR_PWR_PIN   GPIO_NUM_26    /* 传感器供电 MOSFET 控制引脚 */
#define I2C_SDA_PIN      GPIO_NUM_21
#define I2C_SCL_PIN      GPIO_NUM_22

void prepare_for_deep_sleep(void)
{
    /* 1. 关闭外设供电(MOSFET 控制:低电平关断) */
    gpio_set_level(SENSOR_PWR_PIN, 0);

    /* 2. 释放 I2C 总线(先设为输入,避免驱动总线产生漏电) */
    gpio_set_direction(I2C_SDA_PIN, GPIO_MODE_INPUT);
    gpio_set_direction(I2C_SCL_PIN, GPIO_MODE_INPUT);
    gpio_set_pull_mode(I2C_SDA_PIN, GPIO_FLOATING);  /* 关闭内部上拉 */
    gpio_set_pull_mode(I2C_SCL_PIN, GPIO_FLOATING);

    /* 3. 对 RTC GPIO 设置 Hold:睡眠期间保持当前电平 */
    /* 注意:只有 RTC GPIO(GPIO0,2,4,12-15,25-27,32-39)支持 hold */
    rtc_gpio_hold_en(SENSOR_PWR_PIN);   /* 锁定低电平,传感器持续断电 */

    /* 4. 整个芯片级别的 Hold(所有 RTC GPIO 都保持)*/
    /* esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); */

    esp_deep_sleep_start();
}

void release_hold_after_wakeup(void)
{
    /* 唤醒后必须释放 hold,否则引脚无法再驱动 */
    rtc_gpio_hold_dis(SENSOR_PWR_PIN);
    gpio_set_level(SENSOR_PWR_PIN, 1);   /* 重新开启传感器电源 */
    vTaskDelay(pdMS_TO_TICKS(50));       /* 等待传感器上电稳定 */
}

Modem Sleep 与 Light Sleep 的应用场景

不是所有场景都适合深度睡眠。当设备需要保持 Wi-Fi 连接(如做 WebSocket 服务器、接收实时推送),可以使用功耗更低的 Modem Sleep:

#include "esp_wifi.h"
#include "esp_pm.h"

void enable_modem_sleep(void)
{
    /* Modem Sleep:Wi-Fi 在 DTIM beacon 间隔内休眠,其余时间活跃
     * 路由器通常 DTIM = 1(100ms),设为 3 则每 300ms 唤醒一次
     * 电流从 80mA 降至 15-20mA,响应延迟增加约 DTIM × 100ms */
    esp_wifi_set_ps(WIFI_PS_MIN_MODEM);   /* 最小省电 */
    /* WIFI_PS_MAX_MODEM:最大省电,延迟更高但更省电 */
}

/* 自动 Light Sleep(ESP-IDF v5.0+):CPU 空闲时自动进入浅睡眠 */
void enable_auto_light_sleep(void)
{
    /* 配置动态频率调节(DFS):CPU 空闲时降频 */
    esp_pm_config_t pm_config = {
        .max_freq_mhz = 240,    /* 活跃时最高 240MHz */
        .min_freq_mhz = 40,     /* 空闲时降至 40MHz */
        .light_sleep_enable = true,  /* CPU 真正空闲时进入 Light Sleep */
    };
    esp_pm_configure(&pm_config);

    /* 同时配置 Wi-Fi Modem Sleep,使 RF 也可以休眠 */
    esp_wifi_set_ps(WIFI_PS_MIN_MODEM);
    ESP_LOGI("PM", "自动 Light Sleep 已启用");
}
/* 效果:Wi-Fi 保持连接,整机平均电流约 1~5mA(取决于流量) */

太阳能与超级电容供电设计

在户外 IoT 场景(农业传感器、气象站),太阳能 + 超级电容是无线供电的理想方案:

MPPT(最大功率点跟踪)
Maximum Power Point Tracking,太阳能电池板在不同光照条件下有不同的最优工作电压,MPPT 控制器动态调整负载电阻使面板始终在最优点工作,比简单二极管充电效率提高 15%~30%。小型系统常用 CN3791、BQ24650 等集成芯片。
超级电容 vs 锂电池
锂电池:能量密度高(100~250Wh/kg),适合需要持续供电数天的设备,但低温性能差(-20°C 容量下降50%)。超级电容:功率密度高、充放电寿命几乎无限(>100万次),但能量密度低,适合短暂活跃、长期休眠(功耗 μA 级)的设备。
电量监测
通过 ADC 读取电池电压来估算剩余电量。锂电池:3.3V=空,4.2V=满;超级电容:电量与电压平方成正比(E=0.5CV²),3V 时只剩 5V 时 36% 的能量。建议在上报数据中包含电量信息,实现远程监控。
本章小结

ESP32 提供五种电源模式,从 Active(240mA)到 Hibernation(2.5μA)。深度睡眠是电池设备的核心省电手段,唤醒源有定时器、外部 GPIO(ext0/ext1)、触摸和 ULP 协处理器。RTC_DATA_ATTR 声明的变量在深度睡眠间保持数据。睡眠前必须用 rtc_gpio_hold_en 锁定 GPIO 状态、断开外设供电,否则功耗异常。Modem Sleep 适合需保持 Wi-Fi 连接的场景;自动 Light Sleep(esp_pm_configure)适合低流量但需快速响应的设备。一块 2500mAh 电池在深度睡眠模式下理论续航可超过 9 年。