深度睡眠模式配置
深度睡眠模式配置
本节介绍电池供电的电子纸显示应用中 ESP32 深度睡眠模式的配置。深度睡眠通过关闭主 CPU 和大多数外设,仅保持 RTC(实时时钟)用于定时唤醒,从而大幅降低功耗。完成本节后,你将能够:
- 配置带定时器唤醒的 ESP32 深度睡眠
- 计算活动模式和睡眠模式之间的功耗节省
- 为电子纸显示屏实现定期唤醒-刷新-睡眠循环
- 处理深度睡眠后的 WiFi 重连
开始本节前,请确保:
- 基本的 ESP32 编程知识
- 显示屏已接线并与 GxEPD2 配合工作(参见 02-03)
- 功耗测量工具(万用表或功耗监测仪)
ESP32 电源模式
Section titled “ESP32 电源模式”ESP32 有几种电源模式:
| 模式 | CPU | WiFi/BT | RTC | RAM | 典型电流 |
|---|---|---|---|---|---|
| 活动 | 运行中 | 开 | 开 | 全部 | 80-260 mA |
| 调制解调器睡眠 | 运行中 | 关 | 开 | 全部 | 30-50 mA |
| 轻度睡眠 | 暂停 | 关 | 开 | 全部 | 5-10 mA |
| 深度睡眠 | 关闭 | 关 | 开 | 仅 RTC | 5-150 µA |
| 休眠 | 关闭 | 关 | 关 | 关闭 | 0.5-5 µA |
深度睡眠是电池供电运行的关键:功耗从约 100 mA(活动)降至约 10 µA(深度睡眠)——减少了 10000 倍。
ESP32 支持从深度睡眠使用以下唤醒源:
| 源 | 描述 | 典型用途 |
|---|---|---|
| 定时器 | RTC 定时器在指定时间后唤醒 | 定期数据刷新 |
| 外部(EXT0) | GPIO 电平变化 | 按钮按下 |
| 外部(EXT1) | 多个 GPIO 或逻辑 | 运动传感器 |
| 触摸 | 触摸传感器 | 用户交互 |
| ULP 协处理器 | 超低功耗传感器读取 | 持续监控 |
对于电子纸显示项目,定时器唤醒是主要机制。
电子纸的深度睡眠循环
Section titled “电子纸的深度睡眠循环” 活动周期(约 10 秒)┌──────────────────────────────────────────────────────┐│ 1. 从深度睡眠唤醒 ││ 2. 重新连接 WiFi(2-5 秒) ││ 3. 通过 MQTT 获取数据(0.5 秒) ││ 4. 更新显示屏(3-5 秒) ││ 5. 进入深度睡眠 │└──────────────────────────────────────────────────────┘ │ │ 定时器唤醒(1 小时) ▼ 深度睡眠(约 10 µA)步骤 1:基础深度睡眠草图
Section titled “步骤 1:基础深度睡眠草图”#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;}步骤 4:可调节的睡眠间隔
Section titled “步骤 4:可调节的睡眠间隔”使睡眠间隔可配置:
// 睡眠间隔(微秒)#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); }}要测量实际功耗:
# 使用万用表测量电流# 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 引脚未被拉低
问题 2:深度睡眠后 WiFi 失败
Section titled “问题 2:深度睡眠后 WiFi 失败”症状:
- 唤醒后 WiFi 连接失败
- 需要重新上电才能恢复 WiFi
解决方案:
// 强制 WiFi 正确重新初始化WiFi.disconnect(true); // 删除存储的凭据WiFi.mode(WIFI_OFF); // 关闭 WiFi 硬件delay(100);WiFi.mode(WIFI_STA); // 设置为站点模式WiFi.begin(ssid, password);问题 3:显示屏显示先前内容
Section titled “问题 3:显示屏显示先前内容”症状:
- 唤醒后,显示屏短暂显示旧数据
解决方案:
- 在绘制新内容前调用
display.fillScreen(GxEPD_WHITE) - 电子纸的双稳态特性意味着它会保留先前图像直到被覆盖
- ✅ 最小化活动时间——一切放在 setup() 中,loop() 中无延时
- ✅ 睡眠前断开 WiFi——节省电量并避免唤醒时的连接问题
- ✅ 使用 RTC 内存存储计数器和跨睡眠周期的缓存数据
- ✅ 开发时使用短间隔(10 秒)进行测试
- ❌ 不要过度使用 delay()——在活动阶段,每毫秒都很重要
- ❌ 避免在外设通电时深度睡眠——断开传感器电源
- ❌ 在连接前不要使用 WiFi 扫描——会增加 3-5 秒活动时间
- 深度睡眠将功耗从约 100 mA 降至约 10 µA——支持电池供电运行
- 定时器唤醒是定期显示更新的主要机制
- 所有逻辑必须在 setup() 中——深度睡眠后 loop() 永远不会执行
- RTC 内存保留数据(但不是变量)跨睡眠周期
- 最小化活动时间——将唤醒周期设计得尽可能短
- 对于 1 小时刷新周期,ESP32 约 99.7% 的时间处于深度睡眠状态