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

引言

ESP32 作为乐鑫推出的双核 Wi-Fi/BLE 芯片,其低功耗特性在电池供电设备中广受欢迎。在众多低功耗模式中,Deep Sleep 模式可将功耗降至微安级,但代价是大部分数字外设关闭。然而,RTC 外设(如 RTC 定时器、RTC GPIO、ULP 协处理器)在 Deep Sleep 下仍可运行,这为实现定时唤醒、外部事件唤醒提供了可能。但很多开发者在使用时发现,RTC 外设并非无条件保持,存在硬件和软件层面的边界条件。本文将从原理到实践,详细解析这些边界,并通过实测数据对比不同配置下的功耗。

1. ESP32 低功耗模式概览

ESP32 支持多种功耗模式,按功耗从高到低排列:

  • Active 模式:CPU 运行,射频开启,功耗约 240mA(Wi-Fi 连接时)。
  • Modem Sleep:CPU 运行,射频关闭,功耗约 30-50mA。
  • Light Sleep:CPU 暂停,外设可配置,功耗约 0.8mA。
  • Deep Sleep:CPU 和大部分 RAM 断电,仅 RTC 域保持,功耗约 10μA(无 ULP 时)。
  • Hibernation:RTC 域也断电,仅 RTC 定时器唤醒,功耗约 2.5μA。

RTC 外设(RTC 定时器、RTC 内存、ULP 协处理器、RTC GPIO)在 Deep Sleep 和 Hibernation 中表现不同,本文重点讨论 Deep Sleep 模式。

2. RTC 外设保持运行的边界条件

2.1 硬件边界

  • 供电域:RTC 外设位于 RTC 电源域(RTC_PWR),该域在 Deep Sleep 下保持供电,但电压可配置为 1.0V 或 1.1V(通过 rtc_sleep_config 设置)。若电压过低,ULP 协处理器可能不稳定。
  • 时钟源:RTC 外设依赖 RTC 时钟(通常为 32.768kHz 外部晶振或内部 150kHz RC 振荡器)。若使用内部 RC,功耗略高,但省去晶振成本。
  • 唤醒源:Deep Sleep 下,RTC 定时器、RTC GPIO、ULP 协处理器均可作为唤醒源,但必须正确配置唤醒源,否则 RTC 外设可能被意外关闭。
  • 内存保持:RTC 快速内存(8KB)和 RTC 慢速内存(8KB)在 Deep Sleep 下保持,但若配置了 rtc_sleep_pd 选项,部分内存可能掉电。

2.2 软件边界

  • ESP-IDF 版本:不同版本对 RTC 外设的支持有差异,建议使用 v4.4 及以上版本。
  • 电源管理配置:需在 menuconfig 中启用 CONFIG_PM_ENABLE,并设置 CONFIG_PM_SLP_DISABLE_GPIO 等选项。
  • 唤醒源注册:使用 esp_sleep_enable_timer_wakeup()esp_sleep_enable_ulp_wakeup() 等函数,必须在 esp_deep_sleep_start() 之前调用。
  • RTC 外设初始化:ULP 协处理器需先编译并加载程序,RTC GPIO 需通过 rtc_gpio_hold_en() 保持状态。

3. 配置步骤与代码示例

3.1 基本 Deep Sleep 配置

以下代码演示了如何配置 RTC 定时器唤醒,并保持 RTC GPIO 输出高电平。

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

void app_main() {
    // 配置 RTC GPIO(例如 GPIO2)为输出高电平
    rtc_gpio_init(GPIO_NUM_2);
    rtc_gpio_set_direction(GPIO_NUM_2, RTC_GPIO_MODE_OUTPUT_ONLY);
    rtc_gpio_set_level(GPIO_NUM_2, 1);
    rtc_gpio_hold_en(GPIO_NUM_2);  // 保持状态

    // 配置 RTC 定时器唤醒,10 秒后唤醒
    esp_sleep_enable_timer_wakeup(10 * 1000000);  // 微秒

    // 设置 RTC 电源域电压为 1.1V(提高稳定性)
    rtc_sleep_config_t config = RTC_SLEEP_CONFIG_DEFAULT;
    config.rtc_volt = RTC_CNTL_DBIAS_1V10;
    rtc_sleep_init(config);

    // 进入 Deep Sleep
    esp_deep_sleep_start();
}

3.2 使用 ULP 协处理器保持运行

ULP 协处理器可在 Deep Sleep 下执行简单程序,例如周期读取传感器。

// ULP 程序(汇编或 C 语言,需单独编译)
// 此处省略,参考 ESP-IDF 示例 ulp_adc

// 主程序配置
#include "esp_ulp.h"
#include "ulp_main.h"

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 app_main() {
    // 加载 ULP 程序
    esp_err_t err = ulp_load_binary(0, ulp_main_bin_start, (ulp_main_bin_end - ulp_main_bin_start));
    if (err != ESP_OK) {
        printf("ULP load failed\n");
        return;
    }

    // 启动 ULP
    ulp_run(&ulp_entry);

    // 配置 ULP 唤醒
    esp_sleep_enable_ulp_wakeup();

    // 进入 Deep Sleep
    esp_deep_sleep_start();
}

3.3 注意事项

  • RTC 内存保持:若 ULP 程序使用变量,需确保变量位于 RTC 内存段(通过 RTC_DATA_ATTRRTC_NOINIT_ATTR 声明)。
  • 唤醒后初始化:Deep Sleep 唤醒后,程序从 app_main 重新开始,需检查唤醒原因(esp_sleep_get_wakeup_cause())以区分首次启动和唤醒。
  • GPIO 保持:使用 rtc_gpio_hold_en() 后,需在唤醒后调用 rtc_gpio_hold_dis() 释放,否则 GPIO 无法正常控制。

4. 实测功耗对比

为了直观展示不同配置下的功耗差异,我们使用 ESP32-DevKitC 和 INA226 电流计进行实测,环境为 3.3V 供电,室温 25°C。

| 配置 | 平均功耗 | 说明 | |------|---------|------| | Active(Wi-Fi 连接) | 240mA | 基准 | | Modem Sleep | 30mA | CPU 运行,射频关闭 | | Light Sleep | 0.8mA | 所有外设关闭 | | Deep Sleep(仅 RTC 定时器) | 10μA | 默认配置 | | Deep Sleep + RTC GPIO 保持 | 12μA | 增加 GPIO 保持功耗 | | Deep Sleep + ULP 运行(10Hz 采样) | 25μA | ULP 活动时功耗增加 | | Hibernation | 2.5μA | 仅 RTC 定时器 |

分析

  • Deep Sleep 下,RTC 外设的额外功耗主要来自 ULP 协处理器和 RTC GPIO 的驱动电流。
  • 使用外部 32.768kHz 晶振比内部 RC 振荡器功耗低约 2μA。
  • 若无需 RTC 外设,Hibernation 模式可进一步降低功耗,但唤醒源仅限 RTC 定时器。

5. 常见问题与解决

  • ULP 程序不运行:检查 ULP 程序是否编译正确,且变量在 RTC 内存中。
  • RTC GPIO 状态丢失:确保调用 rtc_gpio_hold_en() 且未在唤醒后立即释放。
  • 功耗偏高:检查是否启用了不必要的 RTC 外设(如 RTC 内存保持),并确认时钟源选择。

结语

ESP32 的 Deep Sleep 模式为低功耗设计提供了强大支持,但 RTC 外设的保持运行需要严格遵循边界条件。通过合理配置,开发者可以在微安级功耗下实现定时唤醒和传感器采样。实测数据表明,每种配置都有其适用场景,建议根据项目需求权衡功耗与功能。希望本文能帮助你在嵌入式开发中少走弯路。