ESP32 低功耗实战:RTC 内存保持与 ULP 协处理器协同唤醒的深度配置
👁 2 阅读 · 2026-08-27 · 嵌入式
在电池供电的物联网设备中,功耗是核心指标。ESP32 的深度睡眠(Deep Sleep)模式可将电流降至微安级,但如何在此模式下保持关键数据并实现智能唤醒?本文深入解析 RTC 内存(RTC Memory)的保持机制与 ULP 协处理器的协同工作,通过一个完整的实战案例,展示如何配置 ULP 定期采样传感器、在 RTC 内存中累积数据,并在满足条件时唤醒主 CPU。你将掌握从原理到代码的完整链路,为低功耗产品开发打下坚实基础。
# ESP32 低功耗实战:RTC 内存保持与 ULP 协处理器协同唤醒的深度配置
在物联网边缘节点、可穿戴设备等场景中,功耗直接决定产品续航。ESP32 的深度睡眠(Deep Sleep)模式能将功耗降至 ~10µA,但唤醒后重新初始化外设和变量会消耗时间和能量。幸运的是,ESP32 提供了 **RTC 内存** 和 **ULP 协处理器**,两者结合可实现“数据保持 + 智能唤醒”的黄金组合。
## 一、核心原理:RTC 内存与 ULP 协处理器
### 1. RTC 内存(RTC Fast Memory)
- 位于 RTC 域,深度睡眠期间由 RTC 电源供电,数据不会丢失。
- 容量有限:ESP32 有 8KB 慢速 RTC 内存和 8KB 快速 RTC 内存,通常使用快速内存(地址 0x3FF80000 起)。
- 通过 `RTC_DATA_ATTR` 属性将全局变量放入 RTC 内存,或使用 `esp_sleep_get_wakeup_cause()` 判断唤醒源。
### 2. ULP 协处理器(Ultra Low Power Processor)
- 一个极低功耗的 RISC-V 核心,可在主 CPU 睡眠时独立运行。
- 可访问 RTC 内存、GPIO、I2C 等外设,支持 ADC 采样、脉冲计数等。
- 通过 `ulp_set_wakeup_period()` 设置周期唤醒,或通过 `ulp_wakeup()` 主动唤醒主 CPU。
### 3. 协同工作流程
- 主 CPU 进入 Deep Sleep 前,将 ULP 程序加载到 RTC 内存,并配置唤醒源。
- ULP 按周期运行,读取传感器数据,写入 RTC 内存中的变量。
- 当 ULP 检测到特定条件(如阈值超限),调用 `wake_up` 指令唤醒主 CPU。
- 主 CPU 从 RTC 内存读取累积数据,处理后再进入睡眠。
## 二、实战配置:温度监测 + 阈值唤醒
假设需求:使用内置温度传感器,每 10 秒采样一次,若温度超过 40°C 则唤醒主 CPU 并打印数据,否则继续睡眠。
### 1. 硬件准备
- ESP32 DevKit 开发板(内置温度传感器,无需外接)。
- 可选:外接 LED 指示唤醒状态。
### 2. 软件环境
- ESP-IDF v4.4 或 v5.x(本文以 v5.1 为例)。
- 启用 ULP 支持:在 `menuconfig` 中设置 `Component config → ESP32-specific → Ultra Low Power (ULP) Coprocessor`,选择 `ULP RISC-V`。
### 3. 编写 ULP 程序(RISC-V 汇编)
ULP 程序需用汇编编写,但 ESP-IDF 提供了 `ulp_riscv` 宏和工具链。为简化,我们使用 `ulp_riscv` 的 C 语言支持(需在 `CMakeLists.txt` 中配置)。
**ULP 程序文件 `ulp_temperature.S`**(汇编)或 `ulp_temperature.c`(需额外配置)。这里采用官方推荐的汇编方式,但为可读性,使用 C 语言接口(ESP-IDF v5.1 支持)。
```c
// ulp_temperature.c
#include "ulp_riscv.h"
#include "ulp_temperature.h"
volatile uint32_t temperature_raw; // 存储 ADC 原始值
volatile uint32_t wake_flag; // 唤醒标志
int main(void) {
// 初始化 ADC 通道(内部温度传感器对应 ADC1_CH0)
ulp_riscv_adc_init();
ulp_riscv_adc_enable(ADC1_CH0);
// 采样并转换(简化:直接读取原始值)
temperature_raw = ulp_riscv_adc_read(ADC1_CH0);
// 简单阈值判断:假设原始值 > 3000 对应 40°C(需根据实际校准)
if (temperature_raw > 3000) {
wake_flag = 1;
ulp_riscv_wakeup_main_processor();
} else {
wake_flag = 0;
}
return 0;
}
```
**注意**:ULP 程序中的变量需在头文件 `ulp_temperature.h` 中声明,并指定 RTC 内存地址。
```c
// ulp_temperature.h
#pragma once
#include "ulp_riscv.h"
extern volatile uint32_t temperature_raw;
extern volatile uint32_t wake_flag;
```
### 4. 主程序配置
```c
// main.c
#include
#include "esp_sleep.h"
#include "ulp_riscv.h"
#include "ulp_temperature.h"
extern const uint8_t ulp_bin_start[] asm("_binary_ulp_temperature_bin_start");
extern const uint8_t ulp_bin_end[] asm("_binary_ulp_temperature_bin_end");
void app_main(void) {
// 1. 加载 ULP 程序到 RTC 内存
esp_err_t err = ulp_riscv_load_binary(ulp_bin_start, ulp_bin_end - ulp_bin_start);
ESP_ERROR_CHECK(err);
// 2. 设置 ULP 唤醒周期(10 秒)
ulp_set_wakeup_period(0, 10 * 1000000); // 单位:微秒
// 3. 配置唤醒源:ULP 唤醒
ESP_ERROR_CHECK(esp_sleep_enable_ulp_wakeup());
// 4. 进入深度睡眠
printf("Entering deep sleep...\n");
esp_deep_sleep_start();
}
// 唤醒后的回调(通过 RTC 内存变量判断)
void wakeup_handler(void) {
// 读取 RTC 内存中的变量
printf("Wake up! Temperature raw: %lu, flag: %lu\n",
(unsigned long)temperature_raw, (unsigned long)wake_flag);
// 处理数据...
}
```
**注意**:`app_main` 中调用 `esp_deep_sleep_start()` 后,代码不会继续执行。唤醒后,系统从 `app_main` 重新开始,因此需在开头判断唤醒原因。
### 5. 完整唤醒流程修正
```c
void app_main(void) {
// 获取唤醒原因
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
if (cause == ESP_SLEEP_WAKEUP_ULP) {
// ULP 唤醒:处理数据
printf("ULP wakeup! Temperature raw: %lu\n", (unsigned long)temperature_raw);
// 可在此处执行网络上传、日志等
} else {
// 首次启动或其它唤醒源
printf("Normal boot or other wakeup\n");
// 初始化 ULP 并进入睡眠
load_and_start_ulp();
}
// 重新进入睡眠(或根据业务逻辑决定)
esp_deep_sleep_start();
}
void load_and_start_ulp(void) {
// 加载 ULP 程序
esp_err_t err = ulp_riscv_load_binary(ulp_bin_start, ulp_bin_end - ulp_bin_start);
ESP_ERROR_CHECK(err);
// 设置周期
ulp_set_wakeup_period(0, 10 * 1000000);
// 使能 ULP 唤醒
ESP_ERROR_CHECK(esp_sleep_enable_ulp_wakeup());
// 启动 ULP
ESP_ERROR_CHECK(ulp_riscv_run());
}
```
### 6. CMakeLists.txt 配置
在组件目录下,需添加 ULP 支持:
```cmake
# CMakeLists.txt
idf_component_register(SRCS "main.c"
INCLUDE_DIRS ".")
# 添加 ULP 支持
set(ULP_SOURCES "ulp_temperature.c")
ulp_riscv_embed_binary(ulp_temperature)
```
## 三、关键注意事项
- **RTC 内存变量访问**:在 ULP 程序和主程序间共享变量时,必须使用 `volatile` 且确保地址对齐。
- **ADC 校准**:内置温度传感器的 ADC 值需查表或线性校准,示例中的阈值 3000 仅为演示。
- **ULP 程序大小**:RTC 快速内存有限,ULP 程序需精简,避免大数组。
- **唤醒周期精度**:`ulp_set_wakeup_period` 的周期基于 RTC 定时器,精度受晶振影响,通常 ±10%。
- **调试技巧**:在 ULP 程序中可设置 GPIO 翻转,用示波器观察唤醒周期。
- **功耗优化**:ULP 运行期间功耗约 20µA,加上 RTC 内存保持,整体可控制在 30µA 以下。
## 四、总结
通过 RTC 内存与 ULP 协处理器的协同,ESP32 实现了“低功耗待机 + 智能事件唤醒”的完美方案。ULP 负责持续监测,主 CPU 仅在必要时唤醒,极大延长了电池寿命。本文的实战配置可直接用于温度、湿度、运动传感器等场景,只需修改 ULP 程序中的采样逻辑和阈值判断。掌握这一技术,你的嵌入式产品将更具竞争力。