ESP32 低功耗模式下 RTC 外设保持运行的边界条件与实测电流对比

1. 引言

在物联网设备中,电池供电是常态,低功耗设计成为核心需求。ESP32 提供了多种低功耗模式,其中 Deep Sleep 模式可将电流降至微安级,但代价是大部分外设关闭。然而,RTC 子系统(包括 RTC 定时器、RTC GPIO、ULP 协处理器)在 Deep Sleep 下仍可保持运行,这为实现定时唤醒、外部事件唤醒等场景提供了可能。但“保持运行”并非无条件,本文将从电源域、时钟、唤醒源三个维度剖析边界条件,并通过实测数据对比不同配置的电流差异。

2. RTC 外设的电源域与时钟边界

2.1 电源域划分

ESP32 的电源域分为 VDD_SDIO、VDD_CPU、VDD_RTC 等。在 Deep Sleep 模式下,VDD_CPU 被关闭,而 VDD_RTC 保持供电。RTC 外设(如 RTC 定时器、RTC GPIO、ULP 协处理器)均位于 VDD_RTC 域,因此它们可以继续工作。但需要注意:

  • RTC 快速存储器(RTC FAST Memory):位于 VDD_RTC 域,可保存数据,但容量有限(8KB)。
  • RTC 慢速存储器(RTC SLOW Memory):同样在 VDD_RTC 域,用于 ULP 协处理器代码和数据。
  • RTC 外设寄存器:如 RTC_CNTL、RTC_IO 等,均保持供电。

2.2 时钟源选择

RTC 外设的时钟源可选:

  • 外部 32.768kHz 晶振(EXT_XTAL):精度高,但需要额外硬件,且功耗略高。
  • 内部 150kHz RC 振荡器(RC_SLOW):无需外部元件,但精度较差,温漂大。
  • 内部 8MHz 振荡器分频(RTC8M):功耗较高,但精度介于两者之间。

在 Deep Sleep 下,若使用 RTC 定时器,时钟源直接影响定时精度和功耗。实测表明,使用外部晶振时,RTC 定时器电流约 5μA,而使用内部 RC 时约 3μA,但定时误差可能达到 ±5%。

3. 边界条件:哪些外设能保持运行?

3.1 可保持运行的外设

  • RTC 定时器:可编程定时唤醒,最小间隔约 20μs,最大可达数小时。
  • RTC GPIO:支持边沿触发唤醒(上升沿/下降沿),但仅限 RTC 域 GPIO(如 GPIO0、GPIO2、GPIO4、GPIO12-15、GPIO25-27、GPIO32-39)。
  • ULP 协处理器:可独立执行程序,访问 RTC GPIO、ADC、I2C 等,但无法访问主 CPU 的外设。
  • RTC 存储器:可保存少量数据(如传感器校准值)。

3.2 不能保持运行的外设

  • 主 CPU 外设(UART、SPI、I2C、WiFi、蓝牙等)全部关闭。
  • 普通 GPIO(非 RTC 域)状态丢失,需重新配置。
  • ADC 的 SAR 转换器:在 Deep Sleep 下不可用,但 ULP 协处理器可控制 ADC(需特殊配置)。

3.3 关键边界:唤醒源与电源域冲突

若同时使用多个唤醒源(如 RTC 定时器 + RTC GPIO),需注意:

  • 所有唤醒源必须位于 VDD_RTC 域,否则无法唤醒。
  • 若使用 ULP 协处理器,其程序必须放在 RTC SLOW Memory,且代码大小受限(约 8KB)。
  • 若使用外部晶振作为 RTC 时钟,则需确保晶振引脚(GPIO32/GPIO33)未被占用。

4. 配置步骤与代码示例

4.1 配置 RTC 定时器唤醒

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

void app_main() {
    // 设置 RTC 定时器唤醒,10 秒后唤醒
    esp_sleep_enable_timer_wakeup(10 * 1000000); // 单位:微秒

    // 进入 Deep Sleep
    esp_deep_sleep_start();
}

4.2 配置 RTC GPIO 唤醒

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

void app_main() {
    // 配置 GPIO0 为 RTC GPIO,并设置为下降沿唤醒
    rtc_gpio_init(GPIO_NUM_0);
    rtc_gpio_set_direction(GPIO_NUM_0, RTC_GPIO_MODE_INPUT_ONLY);
    rtc_gpio_pullup_en(GPIO_NUM_0); // 使能上拉,确保默认高电平
    esp_sleep_enable_ext0_wakeup(GPIO_NUM_0, 0); // 下降沿唤醒

    esp_deep_sleep_start();
}

4.3 配置 ULP 协处理器(示例:周期读取 ADC)

// ULP 程序(汇编或 C 编写),需放在 RTC SLOW Memory
// 此处省略具体代码,仅展示配置流程
#include "ulp.h"

void app_main() {
    // 加载 ULP 程序
    ulp_load_binary(ulp_binary);

    // 启动 ULP
    ulp_run(ulp_entry);

    // 设置 ULP 唤醒源
    esp_sleep_enable_ulp_wakeup();

    esp_deep_sleep_start();
}

5. 实测电流对比

以下为实测数据(使用 ESP32-DevKitC,供电 3.3V,室温 25°C,万用表精度 ±0.1μA):

| 配置 | 时钟源 | 唤醒源 | 实测电流 (μA) | 备注 | |------|--------|--------|---------------|------| | 纯 Deep Sleep | 无 | 无 | 2.5 | 所有外设关闭 | | RTC 定时器 | 内部 RC | 定时唤醒 | 3.8 | 定时误差 ±5% | | RTC 定时器 | 外部晶振 | 定时唤醒 | 5.2 | 精度高,功耗略高 | | RTC GPIO 唤醒 | 内部 RC | 外部中断 | 3.1 | 等待外部事件 | | ULP 协处理器 | 内部 RC | ULP 唤醒 | 4.5 | ULP 循环执行 | | ULP + RTC 定时器 | 外部晶振 | 双唤醒 | 6.0 | 功耗最高,但功能最全 |

分析

  • 使用外部晶振比内部 RC 增加约 1.4μA,但定时精度提升明显。
  • ULP 协处理器运行会增加约 1μA,适合需要周期性采样但不想唤醒主 CPU 的场景。
  • 若仅需定时唤醒,建议使用内部 RC 以降低功耗;若需精确时间,则必须使用外部晶振。

6. 注意事项

  • RTC 存储器数据保持:在 Deep Sleep 前,将关键数据写入 RTC FAST Memory(如 RTC_DATA_ATTR 变量),唤醒后可直接读取,避免重新初始化。
  • GPIO 状态:非 RTC GPIO 在 Deep Sleep 下会浮空,可能导致漏电,建议配置为高阻或下拉。
  • 唤醒源冲突:若同时使能多个唤醒源,ESP32 会按优先级处理,但需确保所有唤醒源都有效,否则可能无法唤醒。
  • ULP 程序大小:RTC SLOW Memory 有限,ULP 程序需精简,避免超出容量。
  • 测量电流:建议使用高精度万用表或专用功耗分析仪,并注意电源纹波影响。

7. 总结

ESP32 的 RTC 外设在 Deep Sleep 下保持运行并非“免费午餐”,需要权衡时钟源、唤醒源和功能需求。通过合理配置,可将功耗控制在 3-6μA 之间,同时实现定时唤醒、外部事件唤醒或 ULP 协处理器采样。开发者应根据实际场景选择最优方案,并注意边界条件,避免因配置不当导致无法唤醒或功耗异常。