跳转到内容

深度睡眠模式配置

深度睡眠模式配置

本节介绍电池供电的电子纸显示应用中 ESP32 深度睡眠模式的配置。深度睡眠通过关闭主 CPU 和大多数外设,仅保持 RTC(实时时钟)用于定时唤醒,从而大幅降低功耗。完成本节后,你将能够:

  • 配置带定时器唤醒的 ESP32 深度睡眠
  • 计算活动模式和睡眠模式之间的功耗节省
  • 为电子纸显示屏实现定期唤醒-刷新-睡眠循环
  • 处理深度睡眠后的 WiFi 重连

开始本节前,请确保:

  • 基本的 ESP32 编程知识
  • 显示屏已接线并与 GxEPD2 配合工作(参见 02-03
  • 功耗测量工具(万用表或功耗监测仪)

ESP32 有几种电源模式:

模式CPUWiFi/BTRTCRAM典型电流
活动运行中全部80-260 mA
调制解调器睡眠运行中全部30-50 mA
轻度睡眠暂停全部5-10 mA
深度睡眠关闭仅 RTC5-150 µA
休眠关闭关闭0.5-5 µA

深度睡眠是电池供电运行的关键:功耗从约 100 mA(活动)降至约 10 µA(深度睡眠)——减少了 10000 倍。

ESP32 支持从深度睡眠使用以下唤醒源:

描述典型用途
定时器RTC 定时器在指定时间后唤醒定期数据刷新
外部(EXT0)GPIO 电平变化按钮按下
外部(EXT1)多个 GPIO 或逻辑运动传感器
触摸触摸传感器用户交互
ULP 协处理器超低功耗传感器读取持续监控

对于电子纸显示项目,定时器唤醒是主要机制。

活动周期(约 10 秒)
┌──────────────────────────────────────────────────────┐
│ 1. 从深度睡眠唤醒 │
│ 2. 重新连接 WiFi(2-5 秒) │
│ 3. 通过 MQTT 获取数据(0.5 秒) │
│ 4. 更新显示屏(3-5 秒) │
│ 5. 进入深度睡眠 │
└──────────────────────────────────────────────────────┘
│ 定时器唤醒(1 小时)
深度睡眠(约 10 µA)
#include <WiFi.h>
// 配置
const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASSWORD";
const int SLEEP_HOURS = 1; // 每小时唤醒一次
void setup() {
Serial.begin(115200);
Serial.println("设备正在唤醒...");
// 连接 WiFi
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nWiFi 已连接");
Serial.print("IP:");
Serial.println(WiFi.localIP());
// TODO:在此处获取数据并更新显示屏
delay(1000);
}
Serial.println("进入深度睡眠...");
// 配置深度睡眠 1 小时
esp_sleep_enable_timer_wakeup(SLEEP_HOURS * 3600 * 1000000ULL);
// 进入深度睡眠
esp_deep_sleep_start();
}
void loop() {
// 永远不会到达这里——设备在 setup() 中进入睡眠
}

重要提示:在深度睡眠模式下,loop() 永远不会运行。所有逻辑必须在 setup() 中。

步骤 2:带深度睡眠的完整显示实现

Section titled “步骤 2:带深度睡眠的完整显示实现”

结合显示屏更新和深度睡眠的完整实现:

#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <GxEPD2_BW.h>
// 配置
const char* ssid = "YOUR_SSID";
const char* password = "YOUR_PASSWORD";
const char* mqtt_server = "192.168.1.100";
const char* mqtt_topic = "factory/weather/data";
// 深度睡眠设置
const unsigned long SLEEP_INTERVAL_US = 3600 * 1000000ULL; // 1 小时
// 电子纸引脚
#define EPD_CS 5
#define EPD_DC 17
#define EPD_RST 16
#define EPD_BUSY 4
// 显示构造函数
GxEPD2_BW<GxEPD2_213_B72, GxEPD2_213_B72::HEIGHT> display(
GxEPD2_213_B72(EPD_CS, EPD_DC, EPD_RST, EPD_BUSY)
);
WiFiClient espClient;
PubSubClient client(espClient);
// 数据变量
char temperature[8] = "--.-";
int humidity = 0;
char description[32] = "加载中...";
void callback(char* topic, byte* payload, unsigned int length) {
char buffer[256];
if (length >= 256) return;
memcpy(buffer, payload, length);
buffer[length] = '\0';
JsonDocument doc;
DeserializationError error = deserializeJson(doc, buffer);
if (!error) {
strlcpy(temperature, doc["temperature"] | "--.-", sizeof(temperature));
humidity = doc["humidity"] | 0;
strlcpy(description, doc["description"] | "Unknown", sizeof(description));
}
}
void updateDisplay() {
display.init(115200);
display.setRotation(1);
display.fillScreen(GxEPD_WHITE);
// 显示温度(大号)
display.setFont(&FreeMonoBold18pt7b);
display.setCursor(10, 50);
display.print(temperature);
display.print(" C");
// 显示湿度
display.setFont(&FreeMonoBold12pt7b);
display.setCursor(10, 80);
display.print("湿度:");
display.print(humidity);
display.print("%");
// 显示描述
display.setFont(&FreeMono9pt7b);
display.setCursor(10, 105);
display.print(description);
display.display();
display.powerOff(); // 更新后关闭显示屏电源
}
void setup() {
Serial.begin(115200);
Serial.println("[唤醒] 开始...");
// 连接 WiFi
WiFi.begin(ssid, password);
int retries = 0;
while (WiFi.status() != WL_CONNECTED && retries < 30) {
delay(500);
Serial.print(".");
retries++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\n[唤醒] WiFi 已连接");
// 连接 MQTT 并获取数据
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
if (client.connect("ESP32_Display_DeepSleep")) {
client.subscribe(mqtt_topic);
// 通过发布到触发主题来请求新数据
client.publish("factory/weather/request", "update");
// 等待数据(带超时)
unsigned long timeout = millis() + 5000;
while (millis() < timeout) {
client.loop();
delay(10);
}
}
// 更新显示屏
updateDisplay();
// 睡眠前断开 WiFi
client.disconnect();
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
} else {
Serial.println("\n[唤醒] WiFi 连接失败,使用缓存数据");
updateDisplay(); // 显示缓存/空白显示
}
Serial.println("[睡眠] 进入深度睡眠...");
// 配置唤醒定时器
esp_sleep_enable_timer_wakeup(SLEEP_INTERVAL_US);
// 进入深度睡眠
esp_deep_sleep_start();
}
void loop() {
// 永远不会执行
}

步骤 3:RTC 内存实现数据持久化

Section titled “步骤 3:RTC 内存实现数据持久化”

使用 RTC 内存在睡眠周期之间保留数据:

// RTC 内存——深度睡眠期间保留
RTC_DATA_ATTR int bootCount = 0;
RTC_DATA_ATTR char cachedTemperature[8] = "--.-";
RTC_DATA_ATTR int cachedHumidity = 0;
void setup() {
Serial.begin(115200);
// 增加启动计数器
bootCount++;
Serial.print("启动次数:");
Serial.println(bootCount);
// 首次启动时,使用缓存值
if (WiFi.status() != WL_CONNECTED) {
// 使用 RTC 内存中的缓存数据
Serial.print("使用缓存的温度:");
Serial.println(cachedTemperature);
}
// ... 更新显示屏并缓存新数据
strlcpy(cachedTemperature, temperature, sizeof(cachedTemperature));
cachedHumidity = humidity;
}

使睡眠间隔可配置:

// 睡眠间隔(微秒)
#define ONE_MINUTE_US 60 * 1000000ULL
#define ONE_HOUR_US 3600 * 1000000ULL
#define SIX_HOURS_US 6 * 3600 * 1000000ULL
#define ONE_DAY_US 24 * 3600 * 1000000ULL
// 根据用例选择间隔
const unsigned long SLEEP_INTERVAL = ONE_HOUR_US;
// 动态间隔——根据条件更改
void configureSleep(bool dataReceived) {
if (dataReceived) {
// 正常间隔
esp_sleep_enable_timer_wakeup(ONE_HOUR_US);
} else {
// 如果数据获取失败,更快重试
esp_sleep_enable_timer_wakeup(5 * ONE_MINUTE_US);
}
}

要测量实际功耗:

Terminal window
# 使用万用表测量电流
# 1. 将万用表设置为 DC mA 档
# 2. 串联到 ESP32 电源线中
# 3. 在活动和睡眠阶段监测
预期测量值:
活动(WiFi + 显示):100-200 mA (约 10 秒)
深度睡眠: 10-50 µA (约 1 小时)
  • ESP32 进入深度睡眠并在配置的间隔时间唤醒
  • 每次唤醒周期”启动次数”递增
  • 每次唤醒后显示屏正确更新
  • 深度睡眠后 WiFi 成功重连
  • 睡眠模式下功耗降至 µA 级别

问题 1:ESP32 无法从深度睡眠唤醒

Section titled “问题 1:ESP32 无法从深度睡眠唤醒”

症状

  • 设备从不唤醒
  • 显示屏不更新

解决方案

  • 验证定时器配置:esp_sleep_enable_timer_wakeup()
  • 检查 GPIO 唤醒是否干扰
  • 尝试较短的间隔(10 秒)进行测试
  • 确保 EN 引脚未被拉低

症状

  • 唤醒后 WiFi 连接失败
  • 需要重新上电才能恢复 WiFi

解决方案

// 强制 WiFi 正确重新初始化
WiFi.disconnect(true); // 删除存储的凭据
WiFi.mode(WIFI_OFF); // 关闭 WiFi 硬件
delay(100);
WiFi.mode(WIFI_STA); // 设置为站点模式
WiFi.begin(ssid, password);

症状

  • 唤醒后,显示屏短暂显示旧数据

解决方案

  • 在绘制新内容前调用 display.fillScreen(GxEPD_WHITE)
  • 电子纸的双稳态特性意味着它会保留先前图像直到被覆盖
  • 最小化活动时间——一切放在 setup() 中,loop() 中无延时
  • 睡眠前断开 WiFi——节省电量并避免唤醒时的连接问题
  • 使用 RTC 内存存储计数器和跨睡眠周期的缓存数据
  • 开发时使用短间隔(10 秒)进行测试
  • 不要过度使用 delay()——在活动阶段,每毫秒都很重要
  • 避免在外设通电时深度睡眠——断开传感器电源
  • 在连接前不要使用 WiFi 扫描——会增加 3-5 秒活动时间
  1. 深度睡眠将功耗从约 100 mA 降至约 10 µA——支持电池供电运行
  2. 定时器唤醒是定期显示更新的主要机制
  3. 所有逻辑必须在 setup() 中——深度睡眠后 loop() 永远不会执行
  4. RTC 内存保留数据(但不是变量)跨睡眠周期
  5. 最小化活动时间——将唤醒周期设计得尽可能短
  6. 对于 1 小时刷新周期,ESP32 约 99.7% 的时间处于深度睡眠状态