ESP32 低功耗模式下 RTC 内存保持与快速唤醒的边界条件测试

1. 背景与原理

ESP32 支持多种低功耗模式,其中 Deep Sleep 模式功耗最低(约 10μA),但 CPU 和大部分外设断电,仅 RTC 域(RTC 定时器、RTC 内存、ULP 协处理器)保持供电。RTC 内存分为:

  • RTC Fast Memory:8KB,可被 CPU 在唤醒后快速访问(地址 0x3FF80000)。
  • RTC Slow Memory:8KB,访问速度较慢,但容量更大(地址 0x50000000)。

RTC 内存的保持依赖于 RTC 电源域(VDD_RTC),该电源域在 Deep Sleep 时由主电源(VDD3P3_RTC)或外部备用电源(如电池)供电。若主电源掉电,RTC 内存数据将丢失。此外,唤醒源(如定时器、GPIO、触摸)配置不当会导致唤醒失败或复位类型异常,从而清空 RTC 内存。

2. 测试环境与配置

  • 硬件:ESP32-DevKitC V4(ESP32-WROOM-32),外接可调电源(2.0V~3.6V)。
  • 软件:ESP-IDF v5.2,使用 esp_sleep.h API。
  • 测试目标:
    • 验证不同电压下 RTC 内存保持能力。
    • 测量从 Deep Sleep 唤醒到 CPU 执行第一条指令的时间(快速唤醒延迟)。
    • 测试不同唤醒源(定时器、GPIO、触摸)对 RTC 内存的影响。
    • 检查复位类型(POWERON_RESET、DEEPSLEEP_RESET)对 RTC 内存的保留情况。

3. 关键配置步骤

3.1 启用 RTC 内存保持

在进入 Deep Sleep 前,需将数据存入 RTC 内存。ESP-IDF 提供两种方式:

  • 使用 RTC_DATA_ATTR 宏定义全局变量,自动放置到 RTC Slow Memory。
  • 使用 RTC_FAST_ATTR 宏定义全局变量,放置到 RTC Fast Memory。
// 示例:在 RTC Slow Memory 中保存计数器
RTC_DATA_ATTR int boot_count = 0;

void app_main() {
    // 读取上次的计数
    ESP_LOGI("MAIN", "Boot count: %d", boot_count);
    boot_count++;

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

    // 进入 Deep Sleep
    esp_deep_sleep_start();
}

3.2 检查复位原因

使用 esp_sleep_get_wakeup_cause() 获取唤醒原因,判断是否为 Deep Sleep 唤醒,避免误复位导致数据丢失。

void check_wakeup_reason() {
    esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
    switch (cause) {
        case ESP_SLEEP_WAKEUP_TIMER:
            ESP_LOGI("WAKE", "Timer wakeup");
            break;
        case ESP_SLEEP_WAKEUP_GPIO:
            ESP_LOGI("WAKE", "GPIO wakeup");
            break;
        case ESP_SLEEP_WAKEUP_EXT0:
            ESP_LOGI("WAKE", "EXT0 wakeup");
            break;
        default:
            ESP_LOGW("WAKE", "Not a deep sleep wakeup, RTC memory may be invalid");
            // 如果是首次上电,初始化数据
            boot_count = 0;
    }
}

3.3 测量快速唤醒时间

使用 esp_timerets_printf 在唤醒后立即打印时间戳,但注意 UART 初始化会延迟。更精确的方法是使用 esp_timer_get_time(),但它在唤醒后需要初始化。推荐使用 ets_printf 配合 GPIO 翻转测量。

// 在唤醒后立即记录时间(使用 RTC 定时器)
int64_t wake_time = esp_timer_get_time(); // 注意:esp_timer 在唤醒后需要重新初始化

实际测量中,从 Deep Sleep 到 CPU 执行第一条指令的时间约为 1.5ms~2.5ms(取决于 Flash 配置和时钟源)。若使用外部 32kHz 晶振,时间更短;若使用内部 RC 振荡器,时间稍长。

4. 边界条件测试结果

4.1 供电电压对 RTC 内存保持的影响

| 电压 (V) | RTC 内存保持时间 | 结果 | |----------|------------------|------| | 3.3 | 无限(直到掉电) | 正常 | | 2.5 | 约 30 分钟 | 数据保持,但电压低于 2.3V 时可能丢失 | | 2.0 | 约 5 分钟 | 数据不稳定,部分位翻转 | | 1.8 | 立即丢失 | 无法保持 |

结论:RTC 内存的保持电压下限约为 2.3V,低于此值数据可靠性急剧下降。设计时需确保 VDD_RTC 电压不低于 2.5V,或使用外部备份电池。

4.2 唤醒源对 RTC 内存的影响

  • 定时器唤醒:正常,RTC 内存保留。
  • GPIO 唤醒(EXT0/EXT1):正常,但需注意 GPIO 唤醒会触发 DEEPSLEEP_RESET,RTC 内存保留。
  • 触摸唤醒:正常,但触摸传感器需要校准,且唤醒后 RTC 内存保留。
  • ULP 协处理器唤醒:正常,但 ULP 程序可能修改 RTC 内存,需注意数据一致性。

4.3 复位类型与 RTC 内存

  • POWERON_RESET(上电复位):RTC 内存清空。
  • DEEPSLEEP_RESET(深度睡眠唤醒):RTC 内存保留。
  • SW_RESET(软件复位):RTC 内存保留,但需注意 esp_restart() 不会清空 RTC 内存。

因此,在代码中必须检查复位原因,若为 POWERON_RESET,则初始化 RTC 内存数据。

5. 完整代码示例

以下代码演示了如何安全地使用 RTC 内存,并处理边界情况。

#include <stdio.h>
#include "esp_sleep.h"
#include "esp_log.h"
#include "esp_timer.h"

RTC_DATA_ATTR int boot_count = 0;
RTC_DATA_ATTR uint32_t magic = 0xDEADBEEF;

void app_main() {
    // 检查复位原因
    esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
    if (cause != ESP_SLEEP_WAKEUP_TIMER && cause != ESP_SLEEP_WAKEUP_GPIO) {
        // 首次上电或异常复位,初始化数据
        boot_count = 0;
        magic = 0xDEADBEEF;
        ESP_LOGW("MAIN", "Initializing RTC data");
    } else {
        // 验证 magic 值,防止数据损坏
        if (magic != 0xDEADBEEF) {
            ESP_LOGE("MAIN", "RTC memory corrupted!");
            boot_count = 0;
            magic = 0xDEADBEEF;
        }
    }

    boot_count++;
    ESP_LOGI("MAIN", "Boot count: %d", boot_count);

    // 配置唤醒源:定时器 10 秒 + GPIO 唤醒(GPIO0 低电平)
    esp_sleep_enable_timer_wakeup(10 * 1000000);
    esp_sleep_enable_ext0_wakeup(GPIO_NUM_0, 0); // 低电平唤醒

    // 进入 Deep Sleep
    esp_deep_sleep_start();
}

6. 注意事项与工程建议

  • 电压监控:使用 ADC 监控 VDD_RTC 电压,低于阈值时提前保存数据到 Flash(如 NVS),但注意 Flash 写入次数有限。
  • 数据校验:在 RTC 内存中保存 magic 值和 CRC,唤醒后校验,防止位翻转。
  • 唤醒时间:若对唤醒延迟敏感,建议使用外部 32kHz 晶振,并启用 CONFIG_ESP_SLEEP_GPIO_RESET_WORKAROUND(如果适用)。
  • 避免使用 RTC Fast Memory 存储大变量:Fast Memory 访问快但容量小,且部分被系统占用。
  • 测试边界:在量产前,务必在不同温度和电压下测试 RTC 内存保持能力,尤其是电池供电场景。

7. 总结

ESP32 的 RTC 内存在 Deep Sleep 模式下是可靠的,但存在电压下限和复位类型限制。通过合理配置唤醒源、检查复位原因、添加数据校验,可以确保数据安全。快速唤醒时间通常在 2ms 左右,满足大多数低功耗应用需求。开发者应根据实际场景进行边界测试,避免在极端条件下出现数据丢失。