# 引言 ESP32 的低功耗模式(尤其是 Deep Sleep)是物联网设备省电的关键,但很多开发者在使用 RTC 内存保持数据和 UART 唤醒时,会遇到数据丢失或无法唤醒的问题。本文基于 ESP-IDF v4.4 实测,梳理 RTC 内存的可用区域、UART 唤醒的硬件限制,以及唤醒后的系统状态,并提供完整的代码示例。 # 1. RTC 内存保持的边界条件 ## 1.1 RTC 内存分区 ESP32 的 RTC 内存分为两个区域: - **RTC Fast Memory**(8KB,地址 0x3FF80000):可被 CPU 和 DMA 访问,适合存放关键变量。 - **RTC Slow Memory**(8KB,地址 0x50000000):仅可被 RTC 外设访问,通常用于备份。 在 Deep Sleep 模式下,RTC 内存由 RTC 电源域供电,数据可保持,但 **普通 RAM(DRAM/IRAM)会掉电**。 ## 1.2 数据保持的实测条件 - 使用 `esp_sleep_pd_config()` 可配置电源域,但默认情况下 RTC 电源域是开启的。 - 若调用 `esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF)`,则 RTC Slow Memory 会掉电,数据丢失。 - 实测中,**RTC Fast Memory 始终可用**,但需注意其容量有限(8KB),且与 WiFi/BT 蓝牙栈共享(若启用则可用空间减少)。 ## 1.3 数据保持的代码示例 ```c #include "esp_sleep.h" #include "esp_attr.h" // 放在 RTC Fast Memory 中的变量 RTC_DATA_ATTR int boot_count = 0; void app_main() { // 每次唤醒后递增 boot_count++; printf("Boot count: %d\n", boot_count); // 配置 RTC 内存电源域保持开启(默认) esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_ON); esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_ON); // 进入深度睡眠 10 秒 esp_sleep_enable_timer_wakeup(10 * 1000000); esp_deep_sleep_start(); } ``` 注意:`RTC_DATA_ATTR` 宏将变量放入 RTC Fast Memory,但若同时启用 WiFi,则可用空间会减少,需检查链接错误。 # 2. UART 唤醒的边界条件 ## 2.1 硬件限制 ESP32 的 UART 唤醒并非所有引脚都支持。根据芯片手册,**仅 RTC GPIO(即 GPIO 0-15,但 GPIO 6-11 用于 SPI Flash,不可用)可触发 UART 唤醒**。具体来说,UART 唤醒需要将 UART 的 RX 引脚映射到 RTC GPIO 上,且该引脚必须支持外部中断。 实测发现: - 支持唤醒的引脚:GPIO 0, 2, 4, 12, 13, 14, 15(但 GPIO 12 有上拉,需注意电平)。 - 不支持:GPIO 16-39(非 RTC GPIO)。 ## 2.2 唤醒逻辑 UART 唤醒的触发条件是 **RX 引脚上的下降沿**(即从高电平到低电平)。因此,在进入睡眠前,必须将 RX 引脚配置为输入模式,并启用 UART 的唤醒功能。 ## 2.3 配置步骤 1. 初始化 UART,并设置 RX 引脚为 RTC GPIO。 2. 调用 `uart_set_wakeup_threshold()` 设置唤醒阈值(通常为 1,表示一个下降沿)。 3. 调用 `esp_sleep_enable_uart_wakeup()` 使能 UART 唤醒。 4. 进入 Deep Sleep。 ## 2.4 完整代码示例 ```c #include "driver/uart.h" #include "esp_sleep.h" #define UART_NUM UART_NUM_1 #define RX_PIN 4 // 必须是 RTC GPIO void app_main() { // 配置 UART1,波特率 115200,RX 引脚 GPIO4 uart_config_t uart_config = { .baud_rate = 115200, .data_bits = UART_DATA_8_BITS, .parity = UART_PARITY_DISABLE, .stop_bits = UART_STOP_BITS_1, .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, }; uart_param_config(UART_NUM, &uart_config); uart_set_pin(UART_NUM, UART_PIN_NO_CHANGE, RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); // 启用 UART 唤醒,阈值 1(下降沿) uart_set_wakeup_threshold(UART_NUM, 1); esp_sleep_enable_uart_wakeup(UART_NUM); // 进入深度睡眠 esp_deep_sleep_start(); } // 唤醒后,需要重新初始化 UART,因为外设状态已丢失 void wakeup_handler() { // 重新初始化 UART,并配置中断等 } ``` # 3. 唤醒后的系统状态 ## 3.1 时钟与电源域 - Deep Sleep 唤醒后,系统从复位向量启动,**主时钟(PLL)和 XTAL 均关闭**,默认使用内部 RC 振荡器(约 8MHz),直到调用 `esp_clk_init()` 或 `esp_pm_configure()` 重新配置。 - RTC 外设(如 RTC 定时器)保持,但 UART、SPI 等外设需重新初始化。 ## 3.2 内存状态 - RTC 内存数据保持,但普通变量(非 RTC_DATA_ATTR)会丢失。 - 堆栈指针在启动时指向 RTC 内存,因此 `app_main` 中的局部变量在唤醒后是未定义的,需谨慎使用。 ## 3.3 实测注意事项 - 唤醒后,UART 的 RX 引脚可能仍处于输入状态,需重新配置为 UART 功能。 - 若使用 WiFi,唤醒后需重新初始化 WiFi 栈,且 RTC Fast Memory 中用于 WiFi 的部分会被覆盖。 # 4. 常见陷阱与解决方案 - **陷阱1:使用非 RTC GPIO 作为 UART RX** → 唤醒失败。解决:改用 GPIO 0-15 中的可用引脚。 - **陷阱2:RTC 内存电源域被关闭** → 数据丢失。解决:确保 `esp_sleep_pd_config` 未关闭 RTC 内存域。 - **陷阱3:唤醒后未重新初始化 UART** → 无法通信。解决:在唤醒后调用 `uart_driver_install` 等初始化函数。 - **陷阱4:唤醒阈值设置不当** → 误唤醒或漏唤醒。解决:根据信号电平设置阈值,通常为 1 或 2。 # 5. 总结 ESP32 的 RTC 内存保持和 UART 唤醒是可行的,但边界条件明确:RTC 内存仅限 RTC 电源域,UART 唤醒仅限 RTC GPIO。开发者需仔细阅读芯片手册,并在代码中显式配置电源域和唤醒源。本文的实测代码可作为基础模板,但实际项目中需根据具体硬件调整引脚和阈值。 最后,建议在进入睡眠前,将所有外设置于低功耗状态,并检查电源域配置,以最大化电池寿命。