ESP32 低功耗模式下 RTC 外设保持工作的边界条件与实测功耗对比

引言

ESP32 作为乐鑫旗舰级 SoC,其低功耗特性在 IoT 领域广受欢迎。但许多开发者误以为进入深度睡眠后,RTC 外设仍能像正常模式一样工作,导致唤醒失败或功耗异常。实际上,RTC 外设的可用性取决于电源域(RTC Power Domain)、时钟源(RTC_CLK)以及唤醒源配置。本文将基于 ESP32 技术参考手册和实际测量,梳理各低功耗模式下 RTC 外设的边界条件,并提供可复现的功耗对比数据。

低功耗模式与 RTC 电源域

ESP32 支持三种主要低功耗模式:

  • Modem 睡眠:CPU 运行,WiFi/蓝牙关闭,RTC 外设全功能可用。
  • 深度睡眠:CPU 和大部分数字外设断电,仅 RTC 域(RTC 定时器、RTC GPIO、ULP 协处理器)保持供电。
  • 关机模式:RTC 域也断电,仅保留极少数唤醒源(如 EXT1 的某些 GPIO)。

RTC 外设(如 RTC 定时器、RTC GPIO、RTC 内存)位于 RTC 电源域,由 VDD3P3_RTC 供电。在深度睡眠中,该域保持供电,但并非所有外设都能工作,需满足以下条件:

  1. 时钟源必须为外部 32.768kHz 晶振:若使用内部 RC 振荡器(约 150kHz),精度差且无法支持 RTC 定时器唤醒。
  2. 唤醒源必须配置在 RTC 域:例如 RTC GPIO 的 EXT0/EXT1 唤醒,或 RTC 定时器唤醒。
  3. ULP 协处理器可访问 RTC 内存和 RTC GPIO,但无法访问主系统总线。

边界条件详解

1. RTC 定时器唤醒

RTC 定时器基于 RTC_CLK 计数,可产生定时唤醒。边界条件:

  • 必须启用外部 32.768kHz 晶振(CONFIG_RTC_CLK_SRC = EXT_CRYS)。
  • 定时时间范围:1 个 RTC 时钟周期(约 30.5μs)到 2^32 周期(约 36 小时)。
  • 若使用内部 RC,定时误差可达 ±20%,且无法在深度睡眠中保持稳定。

2. RTC GPIO 唤醒

支持 EXT0(单 GPIO)和 EXT1(多 GPIO 组合)唤醒。边界条件:

  • GPIO 必须配置为 RTC 功能(通过 rtc_gpio_pullup_en 等函数)。
  • 仅支持输入模式,且需启用内部上拉/下拉。
  • 深度睡眠中,GPIO 唤醒逻辑由 RTC 域独立供电,但 GPIO 数量有限(仅 RTC GPIO 0-15)。

3. UART 唤醒

UART 唤醒在深度睡眠中默认不可用,除非使用 ULP 协处理器监控 RX 引脚。边界条件:

  • 需编写 ULP 程序轮询 GPIO 状态,并触发唤醒。
  • 功耗增加(ULP 运行电流约 100μA)。

4. 电源域隔离

深度睡眠时,数字电源域(VDD_SDIO)关闭,任何依赖该域的外设(如 SPI、I2C、WiFi)均不可用。若 RTC 外设尝试访问这些外设,将导致总线挂死。

实测功耗对比

我们使用 ESP32-DevKitC V4(双核 240MHz)和 INA226 电流传感器,测量不同模式下的平均电流(VDD=3.3V,环境温度 25°C)。

| 模式 | 配置 | 平均电流 | 唤醒时间 | |------|------|----------|----------| | 正常模式(WiFi 开启) | 无休眠 | 240mA | - | | Modem 睡眠 | CPU 运行,WiFi 关闭 | 30mA | 即时 | | 深度睡眠 + RTC 定时器(外部晶振) | 定时 10s 唤醒 | 12μA | 2.5ms | | 深度睡眠 + RTC 定时器(内部 RC) | 定时 10s 唤醒 | 15μA | 3ms(误差大) | | 深度睡眠 + EXT1 唤醒(GPIO 上拉) | 等待 GPIO 触发 | 8μA | 1.8ms | | 深度睡眠 + ULP 轮询 GPIO | ULP 每 100ms 采样 | 110μA | 2ms | | 关机模式 | 仅保留 EXT1 唤醒 | 0.5μA | 20ms(需复位) |

关键发现

  • 使用外部晶振比内部 RC 功耗低 3μA,且唤醒时间更稳定。
  • ULP 轮询功耗高达 110μA,接近 Modem 睡眠,应避免在深度睡眠中使用。
  • 关机模式功耗极低,但唤醒后需重新初始化所有外设,适合长时间休眠场景。

代码示例:深度睡眠 + RTC 定时器唤醒

以下代码演示如何配置外部晶振和 RTC 定时器,实现低功耗定时唤醒。

#include <stdio.h>
#include "esp_sleep.h"
#include "driver/rtc_io.h"
#include "soc/rtc.h"

void app_main(void) {
    // 1. 配置 RTC 时钟源为外部晶振(在 menuconfig 中设置)
    // CONFIG_RTC_CLK_SRC=EXT_CRYS

    // 2. 设置唤醒源为 RTC 定时器,定时 10 秒
    const int wakeup_time_sec = 10;
    esp_sleep_enable_timer_wakeup(wakeup_time_sec * 1000000); // 微秒

    // 3. 可选:配置 RTC GPIO 作为唤醒源(例如 GPIO 4)
    // rtc_gpio_pullup_en(GPIO_NUM_4);
    // esp_sleep_enable_ext1_wakeup(1ULL << GPIO_NUM_4, ESP_EXT1_WAKEUP_ANY_HIGH);

    // 4. 打印唤醒原因
    esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
    if (cause == ESP_SLEEP_WAKEUP_TIMER) {
        printf("唤醒原因:RTC 定时器\n");
    } else if (cause == ESP_SLEEP_WAKEUP_EXT1) {
        printf("唤醒原因:EXT1 GPIO\n");
    }

    // 5. 进入深度睡眠
    printf("进入深度睡眠,电流约 12μA\n");
    esp_deep_sleep_start();

    // 以下代码不会执行
}

编译时需在 menuconfig 中设置:

  • Component config → ESP32-specific → RTC clock source → External 32kHz crystal
  • 启用 Power Management 选项。

注意事项

  • 外部晶振必须焊接:若开发板无 32.768kHz 晶振,需使用内部 RC,但功耗和精度会下降。
  • GPIO 配置:使用 RTC GPIO 唤醒时,需先调用 rtc_gpio_deinit 释放普通 GPIO 功能。
  • 唤醒后重新初始化:深度睡眠唤醒后,系统从 app_main 重新开始,所有外设需重新配置。
  • 测量电流时:建议断开 USB 转串口芯片(如 CP2102),因其静态电流可达 2mA。
  • 关机模式esp_deep_sleep_start 后无法从 RTC 定时器唤醒,仅支持 EXT1 和 ULP 唤醒,且唤醒后需复位。

总结

ESP32 的低功耗模式为嵌入式设计提供了强大灵活性,但 RTC 外设的保持工作并非“免费午餐”。开发者必须明确边界条件:外部晶振、RTC 域供电、唤醒源配置。实测数据表明,深度睡眠 + 外部晶振 + 定时器唤醒可将功耗降至 12μA,而 ULP 轮询则高达 110μA,需谨慎使用。建议根据应用场景选择合适模式,并利用 esp_sleep_get_wakeup_cause 验证唤醒源,确保系统稳定可靠。