一、为什么 ESP32 低功耗设计如此关键?

ESP32 虽然性能强劲,但其默认运行功耗可达 200mA 以上(Wi-Fi 发射峰值甚至超过 300mA)。对于一节 3.7V/500mAh 的锂电池,如果持续全速运行,续航不足 3 小时。因此,在电池供电场景中,我们必须引入事件驱动架构:平时让系统沉睡,仅在需要时快速唤醒完成任务,再回到睡眠状态。

二、ESP32 的功耗来源与测量原则

ESP32 的功耗主要来自四部分:

  • CPU 与数字逻辑:运行频率越高,功耗越大。降频至 80MHz 可显著省电。
  • 射频前端(Wi-Fi/蓝牙):哪怕只是保持连接,也需要数十 mA 的电流。
  • RTC 域与外设:包括 ULP 协处理器、RTC 存储器、RTC 时钟等。
  • 漏电流:由芯片制造工艺决定,在 deep-sleep 中表现为几 µA 到十几 µA 的静态电流。

测量原则

  • 使用高精度万用表(如 Keysight 34461A)或低功耗电流测量仪(如 Nordic Power Profiler Kit II)。
  • 测量时必须把开发板上的电源指示灯、USB 转串口芯片(如 CP2102)断开,否则会多出 5-10mA 的额外电流。
  • 建议在电源输入端串联一个 10Ω 采样电阻,用示波器观察动态电流波形。

三、四种睡眠模式对比与选择

| 模式 | 电流典型值(3.3V) | 唤醒源 | 内存保持 | 适用场景 | |------|-------------------|--------|---------|---------| | Active | 40-200mA | - | 全部 | 业务处理、射频通信 | | Modem-sleep | 10-30mA | 定时/外部中断 | 全部 | 保持 Wi-Fi 连接,但降低射频活动 | | Light-sleep | 0.5-2mA | 定时/外部中断 | 全部 | 需要快速响应,但接受微秒级唤醒延迟 | | Deep-sleep | 2-20µA | 定时/外部中断/ULP | RTC 域仅 8KB | 低频传感器上报,可接受 1-5ms 唤醒时间 | | Hibernate | 0.1-2µA | 外部中断/复位 | 无 | 极端省电,每次唤醒需重新初始化 |

注意:Hibernate 模式会丢失所有 RAM 中的数据(包括 RTC 慢速内存),仅保留 RTC 备份寄存器,且唤醒后从复位向量重新执行。

四、配置步骤与代码示例

1. 在 ESP-IDF 中配置 Deep-sleep 唤醒源

#include <stdio.h>
#include "esp_sleep.h"
#include "esp_timer.h"
#include "driver/gpio.h"
#include "esp_log.h"

#define GPIO_WAKE_PIN      GPIO_NUM_4  // 外部唤醒引脚
#define TIME_WAKE_US       (10 * 1000000)  // 10 秒定时唤醒

void app_main(void)
{
    // 配置外部唤醒源:GPIO4 下降沿触发
    esp_sleep_enable_ext0_wakeup(GPIO_WAKE_PIN, 0);  // 0 表示低电平唤醒

    // 配置定时唤醒源
    esp_sleep_enable_timer_wakeup(TIME_WAKE_US);

    // 唤醒后自动校准(否则可能从深度睡眠唤醒后 Wi-Fi 数据错误,但会消耗 5-10ms 时间)
    esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF);
    esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);
    esp_sleep_pd_config(ESP_PD_DOMAIN_XTAL, ESP_PD_OPTION_OFF);

    // 进入深度睡眠
    esp_deep_sleep_start();

    // 注意:执行到此代码时,芯片已经开始睡眠。唤醒后不会回到这里,而是重启。
}

2. 在唤醒后判断唤醒源(用于不同业务逻辑)

esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
switch (cause)
{
    case ESP_SLEEP_WAKEUP_EXT0:
        // GPIO 事件唤醒,例如人体感应传感器触发
        printf("Wakeup by GPIO\n");
        break;
    case ESP_SLEEP_WAKEUP_TIMER:
        // 定时周期上报传感器数据
        printf("Wakeup by timer\n");
        break;
    default:
        printf("Wakeup by reset\n");
        break;
}

3. 使用 ULP 协处理器实现低功耗外设采样

ULP(Ultra Low Power)协处理器可以在 CPU 处于 deep-sleep 时继续执行汇编指令,用于在 µA 级功耗下控制 ADC、外部传感器或做简单逻辑判断。

#include "ulp_riscv.h"
#include "ulp_riscv_utils.h"
#include "ulp_riscv_gpio.h"

// 在 ULP RAM 中定义一个全局变量,用于存储 ADC 读数
extern const uint32_t ulp_adc_sample;

// ULP 程序(运行在 RTC 时钟域,仅支持有限指令集)
int main(void)
{
    // 延迟一段时间让传感器稳定
    ulp_riscv_delay_cycles(1000);

    // 读取 ADC 通道(假设已配置 ADC1_CH0)
    uint32_t adc_val = adc_read(ADC_UNIT_1, ADC_CHANNEL_0);

    // 将结果写入共享变量,唤醒 CPU 处理
    ulp_adc_sample = adc_val;

    // 如果确认数据有效,唤醒主 CPU
    ulp_riscv_wakeup_main_processor();

    // 给自己设置定时唤醒,继续下一次采样
    ulp_riscv_timer_start(1000000); // 1 秒后再次运行

    return 0;
}

五、软件深度优化:从 20µA 到 7µA 的实战技巧

  • 关闭 RTC 外设域:若在 deep-sleep 中不需要 RTC 定时器以外的外设,可依次执行 esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF)
  • 使用内部 8MHz 振荡器(RTC8M):而非外部 32kHz 晶振。虽然计时精度稍差,但可减少约 5-8µA 电流。
  • 将 GPIO 都配置为高电平或下拉:睡眠状态下悬浮的 GPIO 可能产生额外的漏电流。在进入 sleep 前,把所有未使用的 GPIO 统一 gpio_set_direction 为输出低电平。
  • 减少 flash 扫描:第一次启动时读取 flash 中的校准数据并缓存到 RTC 内存,deep-sleep 时不访问 flash。
  • 使用 Deep-sleep 的 ULP 定时唤醒:替代定时唤醒,ULP 可以执行复杂的开关序列,避免 CPU 周期性启动。

六、电池供电的硬件设计要点

  • 电源路径:建议使用 3.7V 锂电池直接供电给 ESP32 的 VBAT 引脚(最高支持 3.6V,需串入二极管降压),或者用超低静态功耗的 LDO(如 TPS7A02,静态电流 <1µA)。
  • 避免使用线性稳压器:大部分常规 LDO 的静态电流是几十 µA,会直接拖垮待机功耗。
  • 电量检测:用 ESP32 的内部 ADC 采样电池电压(分压电阻必须使用高阻值,且只在测量时导通)。
  • 外部中断唤醒:尽量用一边沿触发唤醒,避免电平触发导致的持续漏电。

七、常见设计误区与注意事项

  • esp_deep_sleep_start() 之后的代码不会执行,任何保存工作都必须在调用前完成。
  • 从 deep-sleep 唤醒后,Wi-Fi 和蓝牙需要重新初始化。如果每次唤醒都要连接 Wi-Fi,建议先设置 esp_wifi_set_ps(WIFI_PS_MIN_MODEM) 再连接。
  • 不要使用 freeRTOS 的 vTaskDelay() 作为睡眠手段,它不会降低功耗。
  • 定期验证电池电压,防止电池深度放电损坏。可在休眠期间用 ULP 监控电压,电压过低时彻底关机(通过 MOSFET 切断电池通路)。
  • 开发阶段可用 esp_debugger 附加观察,但正式产品必须断开 JTAG,因为 JTAG 也会增加功耗。

八、结语

ESP32 的低功耗设计远不止调用一个 API 那么简单,它需要深刻理解硬件架构、睡眠状态切换流程、GPIO 状态以及外设的功耗特性。通过综合运用 deep-sleep + ULP + 事件唤醒,并配合科学的硬件设计,完全可以将平均功耗控制在 10µA 以内,实现一颗纽扣电池驱动数月甚至数年的物联网设备。希望本文的实战经验能为你的电池供电产品带来实质性启发。