深度睡眠定时唤醒
深度睡眠定时唤醒
本节介绍如何配置 ESP32 的定时器深度睡眠模式,实现每小时唤醒一次的功耗优化方案。学习完成后,您将能够:
- 配置 ESP32 的定时器唤醒深度睡眠
- 理解深度睡眠对代码执行的影响
- 设计电池供电场景的功耗优化方案
- 在 Node-RED 中验证和监控唤醒记录
Timer Wake-Up Architecture
Section titled “Timer Wake-Up Architecture”┌──────────────────────────────────────────────────────────────┐│ 定时唤醒周期流程 │├──────────────────────────────────────────────────────────────┤│ ││ [ESP32 睡眠功耗:~10μA] ││ │ ││ │ 定时器唤醒: esp_sleep_enable_timer_wakeup(3600s) ││ │ ││ ▼ ││ [ESP32 启动] ←── 完全重启,从 setup() 开始执行 ││ │ ││ ├── 1. 初始化硬件 ││ ├── 2. 连接 Wi-Fi (3-8 秒) ││ ├── 3. 连接 MQTT (0.5-2 秒) ││ ├── 4. 读取传感器数据 ││ ├── 5. MQTT 发布/订阅 (1-3 秒) ││ ├── 6. 接收 Node-RED 指令 ││ ├── 7. 执行投料 (3 秒, 如需) ││ ├── 8. 发布深度睡眠记录 ││ │ ││ ▼ ││ [ESP32 深度睡眠] ←── esp_deep_sleep_start() ││ │ ││ └── 定时 3600 秒后重新开始循环 ││ │└──────────────────────────────────────────────────────────────┘Key Concept: ESP32 Deep Sleep Restart
Section titled “Key Concept: ESP32 Deep Sleep Restart”深度睡眠后 ESP32 的行为与上电复位相同:
深度睡眠恢复流程:
深度睡眠 (Deep Sleep) │ │ 定时器触发 ▼ CPU 重新上电 │ ├── Bootloader 执行 ├── 应用程序从 setup() 开始 │ ├── 所有变量重新初始化 │ ├── RTC_DATA_ATTR 变量保留值 │ └── RTC 内存中的数据可用 │ ├── 执行完整的工作流程 │ └── 完成后 esp_deep_sleep_start()
重要: - loop() 中的代码不会继续执行 - 所有非 RTC 变量都会丢失 - Wi-Fi/MQTT 需要重新连接 - 摄像头/传感器需要重新初始化ESP32 Implementation
Section titled “ESP32 Implementation”// ===== 自动投料系统 - 深度睡眠版本 =====// 此版本完全在 setup() 中执行,loop() 不会运行
#include <WiFi.h>#include <PubSubClient.h>#include "driver/rtc_io.h"
// RTC 保留数据 (深度睡眠后不丢失)RTC_DATA_ATTR int bootCount = 0;RTC_DATA_ATTR int totalDosingCount = 0;
void setup() { Serial.begin(115200); delay(100);
// 唤醒计数 bootCount++; Serial.printf("Boot count: %d (Total dosing: %d)\n", bootCount, totalDosingCount);
// === 1. 硬件初始化 === pinMode(RELAY_PUMP_PIN, OUTPUT); digitalWrite(RELAY_PUMP_PIN, LOW);
// === 2. 连接 Wi-Fi === WiFi.begin(ssid, password); unsigned long wifiTimeout = millis() + 20000; // 20 秒超时 while (WiFi.status() != WL_CONNECTED && millis() < wifiTimeout) { delay(100); }
if (WiFi.status() != WL_CONNECTED) { Serial.println("WiFi timeout, going back to sleep"); goToSleep(); return; // 不会执行到这里 } Serial.println("WiFi connected");
// === 3. 连接 MQTT === client.setServer(mqtt_server, mqtt_port); client.setCallback(callback);
if (!client.connect(client_id, mqtt_user, mqtt_pass)) { Serial.println("MQTT failed, going back to sleep"); goToSleep(); return; } Serial.println("MQTT connected");
// === 4. 订阅控制 Topic === client.subscribe("esp32/dosing/control"); client.subscribe("esp32/dosing/command");
// === 5. 读取传感器数据 === long distance = measureDistanceAverage(TRIG_PIN, ECHO_PIN); int levelPercent = distanceToLevelPercent(distance, CONTAINER_HEIGHT);
// === 6. 发布数据给 Node-RED === String payload = "{\"distance\":" + String(distance) + ",\"level\":" + String(levelPercent) + ",\"boot\":" + String(bootCount) + ",\"total_dosing\":" + String(totalDosingCount) + "}"; client.publish("esp32/dosing/info", payload.c_str()); Serial.println("Data published, waiting for command...");
// === 7. 等待 Node-RED 指令 === // 在等待期间调用 client.loop() 接收 MQTT 消息 unsigned long waitStart = millis(); while (millis() - waitStart < 15000) { // 最长等待 15 秒 client.loop();
if (dosingCommandReceived) { break; // 收到指令,提前退出等待 } }
// === 8. 准备睡眠 === goToSleep();}
// 深度睡眠准备void goToSleep() { // 发布深度睡眠记录 String sleepRecord = "{\"boot\":" + String(bootCount) + ",\"total_dosing\":" + String(totalDosingCount) + "}"; client.publish("esp32/dosing/sleep_record", sleepRecord.c_str());
delay(500); // 等待 MQTT 发送完成 client.disconnect(); WiFi.disconnect(true); WiFi.mode(WIFI_OFF);
// 配置定时唤醒 (1 小时 = 3,600,000,000 微秒) esp_sleep_enable_timer_wakeup(3600 * 1000000ULL);
Serial.printf("Deep sleep for 1 hour (boot #%d)...\n", bootCount); Serial.flush();
esp_deep_sleep_start();}MQTT Callback for Deep Sleep Version
Section titled “MQTT Callback for Deep Sleep Version”// MQTT 回调 - 深度睡眠版本bool dosingCommandReceived = false;bool shouldPump = false;
void callback(char* topic, byte* payload, unsigned int length) { String message; for (int i = 0; i < length; i++) { message += (char)payload[i]; }
Serial.printf("MQTT: %s → %s\n", topic, message.c_str());
if (strcmp(topic, "esp32/dosing/control") == 0) { shouldPump = (message == "1"); dosingCommandReceived = true;
if (shouldPump) { Serial.println("Starting dosing..."); digitalWrite(RELAY_PUMP_PIN, HIGH);
// 修改为持续时间投料 delay(PUMP_DURATION); // 深度睡眠版本可用 delay // 因为完成后立即睡眠,无需非阻塞
digitalWrite(RELAY_PUMP_PIN, LOW); totalDosingCount++; Serial.println("Dosing complete"); } else { Serial.println("No dosing needed this cycle"); } }}Power Consumption Analysis
Section titled “Power Consumption Analysis”| 阶段 | 功耗 | 持续时间 | 每小时占比 | 日能耗 |
|---|---|---|---|---|
| 深度睡眠 | ~10μA | 3594 秒 | 99.83% | 0.24 mAh |
| Wi-Fi 连接 | ~200mA | 5 秒 | 0.14% | 0.28 mAh |
| 数据发送/接收 | ~180mA | 3 秒 | 0.08% | 0.15 mAh |
| 投料执行 | ~220mA | 3 秒 | 0.08% (按需) | 0.02 mAh |
| 总计 | ~1 小时 | ~0.7 mAh/小时 |
电池续航估算:
| 电池 | 容量 | 续航 (每小时唤醒) |
|---|---|---|
| 2×AA 碱性 | 2000mAh | ~120 天 |
| 18650 Li-ion | 3000mAh | ~180 天 |
| 太阳能 + 18650 | 持续充电 | >365 天 |
Node-RED Deep Sleep Monitoring
Section titled “Node-RED Deep Sleep Monitoring”// Node-RED Function: 监控 ESP32 唤醒记录// 从 esp32/dosing/sleep_record Topic 接收
var record = msg.payload;
// 检查唤醒间隔var lastBoot = flow.get("lastBootTime") || Date.now();var interval = Date.now() - lastBoot;var expectedInterval = 3600 * 1000; // 1 小时
// 计算偏差百分比var deviation = Math.abs(interval - expectedInterval) / expectedInterval * 100;
var status = "normal";if (deviation > 20) { status = "warning"; // 唤醒间隔偏差过大}if (interval > 7200 * 1000) { status = "critical"; // 超过 2 小时未唤醒}
flow.set("lastBootTime", Date.now());
msg.payload = { bootCount: record.boot, totalDosing: record.total_dosing, lastInterval: Math.round(interval / 1000) + "s", status: status, timestamp: Date.now()};
return msg;# 1. 烧录后观察首次唤醒# 串口输出:Boot count: 1 (Total dosing: 0)WiFi connectedMQTT connectedData published, waiting for command......
# 2. 检查 MQTT 唤醒记录mosquitto_sub -t "esp32/dosing/sleep_record" -v# 每小时应收到一条记录
# 3. 检查 Node-RED Dashboard# 查看 "设备运行状态" 面板:# - 上次唤醒时间# - 今日唤醒次数# - 总投料次数# - 唤醒间隔偏差Common Customer Questions
Section titled “Common Customer Questions”Q1: 深度睡眠后 Wi-Fi 需要重新连接会不会很耗电?
Section titled “Q1: 深度睡眠后 Wi-Fi 需要重新连接会不会很耗电?”连接 Wi-Fi 约 3-8 秒,约 200mA。每小时一次,每天仅约 4.8 mAh(不到 18650 电池的 0.2%)。相比持续运行的 ~80mA,深度睡眠节省 99.9%+ 的功耗。
Q2: 如何改变唤醒间隔?
Section titled “Q2: 如何改变唤醒间隔?”// 修改定时器值// 30 分钟唤醒esp_sleep_enable_timer_wakeup(30 * 60 * 1000000ULL);// 2 小时唤醒esp_sleep_enable_timer_wakeup(2 * 3600 * 1000000ULL);// 6 小时唤醒 (推荐用于液位变化慢的场景)esp_sleep_enable_timer_wakeup(6 * 3600 * 1000000ULL);Q3: 深度睡眠时错过投料时间怎么办?
Section titled “Q3: 深度睡眠时错过投料时间怎么办?”Node-RED 设计为”查询-响应”模式:ESP32 唤醒后查询”是否需要投料”,而非 Node-RED 主动推送。因此只要 ESP32 唤醒,就能获取正确的投料指令,不会错过。
✅ 推荐做法:
- 使用
RTC_DATA_ATTR保留投料计数等状态 - 唤醒后设置 Wi-Fi 连接超时 (20 秒)
- 等待 Node-RED 指令时设置最大等待时间 (15 秒)
- 睡眠前断开 Wi-Fi 彻底断电
- 在 Node-RED 中监控唤醒间隔偏差
❌ 避免做法:
- 在深度睡眠版本中使用 loop() 的延迟逻辑
- 睡眠前未关闭继电器(持续消耗泵电)
- 唤醒超时后不进入睡眠(卡死耗电)
- 定时唤醒间隔过短(<15 分钟,Wi-Fi 连接占比太高)
Summary
Section titled “Summary”- 定时器深度睡眠:
esp_sleep_enable_timer_wakeup(3600s) - 完整重启: 深度睡眠后从 setup() 开始执行
- RTC_DATA_ATTR: 保留跨睡眠周期的计数数据
- 功耗: 每小时唤醒一次,日均 ~16.8 mAh
- 电池续航: 18650 电池可达 180 天