跳转到内容

深度睡眠定时唤醒

深度睡眠定时唤醒

本节介绍如何配置 ESP32 的定时器深度睡眠模式,实现每小时唤醒一次的功耗优化方案。学习完成后,您将能够:

  • 配置 ESP32 的定时器唤醒深度睡眠
  • 理解深度睡眠对代码执行的影响
  • 设计电池供电场景的功耗优化方案
  • 在 Node-RED 中验证和监控唤醒记录
┌──────────────────────────────────────────────────────────────┐
│ 定时唤醒周期流程 │
├──────────────────────────────────────────────────────────────┤
│ │
│ [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 秒后重新开始循环 │
│ │
└──────────────────────────────────────────────────────────────┘

深度睡眠后 ESP32 的行为与上电复位相同:

深度睡眠恢复流程:
深度睡眠 (Deep Sleep)
│ 定时器触发
CPU 重新上电
├── Bootloader 执行
├── 应用程序从 setup() 开始
│ ├── 所有变量重新初始化
│ ├── RTC_DATA_ATTR 变量保留值
│ └── RTC 内存中的数据可用
├── 执行完整的工作流程
└── 完成后 esp_deep_sleep_start()
重要:
- loop() 中的代码不会继续执行
- 所有非 RTC 变量都会丢失
- Wi-Fi/MQTT 需要重新连接
- 摄像头/传感器需要重新初始化
// ===== 自动投料系统 - 深度睡眠版本 =====
// 此版本完全在 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 回调 - 深度睡眠版本
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");
}
}
}
阶段功耗持续时间每小时占比日能耗
深度睡眠~10μA3594 秒99.83%0.24 mAh
Wi-Fi 连接~200mA5 秒0.14%0.28 mAh
数据发送/接收~180mA3 秒0.08%0.15 mAh
投料执行~220mA3 秒0.08% (按需)0.02 mAh
总计~1 小时~0.7 mAh/小时

电池续航估算:

电池容量续航 (每小时唤醒)
2×AA 碱性2000mAh~120 天
18650 Li-ion3000mAh~180 天
太阳能 + 18650持续充电>365 天
// 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;
Terminal window
# 1. 烧录后观察首次唤醒
# 串口输出:
Boot count: 1 (Total dosing: 0)
WiFi connected
MQTT connected
Data published, waiting for command...
...
# 2. 检查 MQTT 唤醒记录
mosquitto_sub -t "esp32/dosing/sleep_record" -v
# 每小时应收到一条记录
# 3. 检查 Node-RED Dashboard
# 查看 "设备运行状态" 面板:
# - 上次唤醒时间
# - 今日唤醒次数
# - 总投料次数
# - 唤醒间隔偏差

Q1: 深度睡眠后 Wi-Fi 需要重新连接会不会很耗电?

Section titled “Q1: 深度睡眠后 Wi-Fi 需要重新连接会不会很耗电?”

连接 Wi-Fi 约 3-8 秒,约 200mA。每小时一次,每天仅约 4.8 mAh(不到 18650 电池的 0.2%)。相比持续运行的 ~80mA,深度睡眠节省 99.9%+ 的功耗。

// 修改定时器值
// 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 连接占比太高)
  1. 定时器深度睡眠: esp_sleep_enable_timer_wakeup(3600s)
  2. 完整重启: 深度睡眠后从 setup() 开始执行
  3. RTC_DATA_ATTR: 保留跨睡眠周期的计数数据
  4. 功耗: 每小时唤醒一次,日均 ~16.8 mAh
  5. 电池续航: 18650 电池可达 180 天