ESP32 低功耗模式下 RTC 内存保持与 UART 唤醒的边界条件实测
👁 2 阅读 · 2026-08-27 · 嵌入式
ESP32 在深度睡眠(Deep Sleep)下,RTC 内存可保持数据,但 UART 唤醒并非所有 GPIO 都支持,且唤醒后外设状态需重新初始化。本文通过实测,详细分析 RTC 内存的保持范围、UART 唤醒的引脚限制、以及唤醒后系统时钟与电源域的变化,给出可复用的配置代码和边界条件,帮助开发者规避常见陷阱。
# 引言
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。开发者需仔细阅读芯片手册,并在代码中显式配置电源域和唤醒源。本文的实测代码可作为基础模板,但实际项目中需根据具体硬件调整引脚和阈值。
最后,建议在进入睡眠前,将所有外设置于低功耗状态,并检查电源域配置,以最大化电池寿命。