4、浅度睡眠(Light-sleep)与Modem-sleep:WiFi/蓝牙连接保持下的低功耗策略,UART唤醒,GPIO唤醒

好,咱们接着聊。前面几章我们把睡眠模式的基本概念讲清楚了,这一章要动真格的了——如何在保持WiFi或蓝牙连接的前提下,把功耗压到最低。

你想想看,很多物联网设备不能断网。智能插座要随时响应云端指令,门锁要能远程开锁,传感器要定时上报数据。如果每次休眠都断开WiFi,那体验就太差了。所以,Modem-sleep和Light-sleep就是专门解决这个问题的。

4.1 Modem-sleep:WiFi连接下的省电模式

Modem-sleep,说白了就是让WiFi的Modem(调制解调器)间歇性休息。ESP32的WiFi模块在工作时功耗很高,但并不是每时每刻都需要全速运转。

工作原理是这样的:

  • ESP32与路由器协商好Beacon间隔(通常是100ms或102.4ms)
  • 在Beacon间隔之间,Modem可以关闭射频电路
  • 只在Beacon到来前醒来,接收信标帧,保持同步
  • 如果有数据要收发,再临时唤醒

关键数据:

模式典型电流说明
Active(全速)~80-120mAWiFi持续收发
Modem-sleep(DTIM1)~5-15mA每100ms唤醒一次
Modem-sleep(DTIM3)~2-5mA每300ms唤醒一次

我在项目中遇到过一个问题:有些廉价的WiFi路由器Beacon间隔不稳定,导致ESP32频繁误唤醒。后来我加了个超时判断,连续收到3个Beacon才确认同步,这才稳定下来。

4.1.1 DTIM参数的影响

DTIM(Delivery Traffic Indication Message)是路由器广播的周期参数。DTIM=1表示每个Beacon都包含广播数据,DTIM=3表示每3个Beacon才包含一次。

我的建议:

  • 对延迟敏感的应用(如智能灯控):用DTIM1,响应快但功耗高
  • 对功耗敏感的应用(如传感器):用DTIM3,省电但延迟大
  • 极端省电场景:可以协商到DTIM10,但要注意丢包风险

小技巧:在ESP-IDF中,可以通过 esp_wifi_set_ps(WIFI_PS_MIN_MODEM) 启用Modem-sleep。默认是WIFI_PS_NONE,记得改过来。

4.2 Light-sleep:更激进的省电方案

Modem-sleep虽然省电,但CPU还在跑,外设还在供电。Light-sleep则更进一步——把CPU也停了,只保留RTC域和WiFi/蓝牙的唤醒逻辑。

嗯,这里要注意:Light-sleep下,WiFi连接是保持的,但CPU不执行代码。这听起来有点矛盾,对吧?

实际上,ESP32的WiFi MAC层有硬件自动管理功能。在Light-sleep期间,WiFi模块自己会按Beacon间隔醒来,接收数据,然后继续睡。CPU只在有数据需要处理时才被唤醒。

功耗对比(实测数据):

模式WiFi保持CPU状态典型电流
Modem-sleep运行5-15mA
Light-sleep(WiFi保持)暂停0.8-2mA
Deep-sleep断电~10μA

你看,Light-sleep比Modem-sleep省了一个数量级。代价是什么?唤醒延迟。从Light-sleep唤醒需要约1-3ms,而Modem-sleep几乎是即时响应。

4.2.1 如何配置Light-sleep保持WiFi

在ESP-IDF中,配置Light-sleep保持WiFi连接其实很简单:

// 启用WiFi的Light-sleep模式
esp_wifi_set_ps(WIFI_PS_MAX_MODEM);

// 进入Light-sleep
esp_light_sleep_start();

// 或者用定时自动进入
esp_sleep_enable_timer_wakeup(10 * 1000000); // 10秒后唤醒
esp_light_sleep_start();

我曾经踩过一个坑:在Light-sleep期间,如果路由器突然重启,ESP32不会自动重连。因为CPU是停的,WiFi模块只负责接收Beacon,不处理连接状态。解决方案是在唤醒后检查WiFi连接状态,必要时手动重连。

注意:Light-sleep保持WiFi时,不能使用 esp_deep_sleep_start(),那会断开所有连接。一定要用 esp_light_sleep_start()

4.3 UART唤醒:串口数据唤醒系统

很多设备需要通过串口接收指令。比如工业控制器、智能网关。如果系统在Light-sleep中,串口来了数据怎么办?

ESP32支持UART唤醒功能。说白了,就是串口的RX引脚可以配置为唤醒源。当有数据到达时,系统从睡眠中醒来,处理数据。

4.3.1 配置UART唤醒

// 配置UART唤醒
uart_config_t uart_config = {
    .baud_rate = 115200,
    .data_bits = UART_DATA_8_BITS,
    .parity = UART_PARITY_DISABLE,
    .stop_bits = UART_STOP_BITS_1,
    .flow_ctrl = UART_HW_FLOWCTRL_DISABLE
};
uart_param_config(UART_NUM_1, &uart_config);
uart_set_pin(UART_NUM_1, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);

// 启用UART唤醒
esp_sleep_enable_uart_wakeup(UART_NUM_1);

// 进入Light-sleep
esp_light_sleep_start();

嗯,这里有个细节:UART唤醒是基于边沿检测的。也就是说,只要RX引脚有电平变化,系统就会醒来。但如果你只收到一个字节,系统醒来后可能发现缓冲区里只有1个字节,然后又睡过去了。这会导致频繁唤醒。

我的经验:在UART唤醒后,不要立即再次睡眠。先等一段时间(比如50ms),收集完整的数据帧,再决定是否继续睡。这样可以避免「假唤醒」问题。

小技巧:如果UART数据是固定长度的,可以在唤醒后检查接收到的字节数。不够就继续等,够了再处理。这样能减少不必要的唤醒次数。

4.4 GPIO唤醒:按键与传感器触发

GPIO唤醒是最常用的唤醒方式之一。按键、门磁、PIR传感器、干簧管……这些都可以通过GPIO唤醒系统。

ESP32支持两种GPIO唤醒模式:

  • 电平触发:引脚保持高电平或低电平唤醒
  • 边沿触发:引脚从高到低或从低到高变化时唤醒

4.4.1 配置GPIO唤醒

// 配置GPIO唤醒(边沿触发)
esp_sleep_enable_gpio_wakeup();

// 设置唤醒引脚
gpio_config_t io_conf = {
    .pin_bit_mask = (1ULL << WAKEUP_PIN),
    .mode = GPIO_MODE_INPUT,
    .pull_up_en = GPIO_PULLUP_ENABLE,
    .pull_down_en = GPIO_PULLDOWN_DISABLE,
    .intr_type = GPIO_INTR_NEGEDGE  // 下降沿唤醒
};
gpio_config(&io_conf);

// 进入Light-sleep
esp_light_sleep_start();

我曾经犯过一个低级错误:把GPIO唤醒引脚配置为内部上拉,但外部电路也是上拉的。结果引脚一直处于高电平,系统永远无法唤醒。后来我改成下拉,或者用外部电路明确控制电平状态,问题才解决。

避坑指南:GPIO唤醒时,RTC域的GPIO(如GPIO0-17)功耗更低。非RTC域的GPIO虽然也能唤醒,但需要额外的电源管理,功耗会高一些。我建议优先使用RTC GPIO。

4.4.2 多GPIO唤醒

有时候需要多个唤醒源。比如一个按键唤醒,一个传感器唤醒。ESP32支持同时配置多个GPIO作为唤醒源:

// 配置多个GPIO唤醒
gpio_config_t io_conf = {
    .pin_bit_mask = (1ULL << GPIO_NUM_4) | (1ULL << GPIO_NUM_5),
    .mode = GPIO_MODE_INPUT,
    .pull_up_en = GPIO_PULLUP_ENABLE,
    .intr_type = GPIO_INTR_ANYEDGE  // 任意边沿
};
gpio_config(&io_conf);

esp_sleep_enable_gpio_wakeup();

// 唤醒后判断是哪个引脚触发的
uint64_t wakeup_pin = esp_sleep_get_gpio_wakeup_status();
if (wakeup_pin & (1ULL << GPIO_NUM_4)) {
    // 按键触发
} else if (wakeup_pin & (1ULL << GPIO_NUM_5)) {
    // 传感器触发
}

4.5 综合策略:如何选择

讲了这么多,你可能会问:到底该用哪种模式?

我的建议是这样的:

  • 需要实时响应云端指令:用Modem-sleep,DTIM1。功耗高但延迟低
  • 定时上报数据,偶尔接收指令:用Light-sleep保持WiFi,配合定时唤醒
  • 按键或传感器触发:用Light-sleep + GPIO唤醒,WiFi保持连接
  • 串口设备控制:用Light-sleep + UART唤醒,WiFi保持连接
  • 极端省电,可以断网:用Deep-sleep,但那是下一章的内容了

核心原则:能睡就睡,能浅睡就不深睡。保持连接的情况下,Light-sleep是最佳平衡点。我做过一个项目,用Light-sleep保持WiFi,配合GPIO唤醒,电池续航从3天延长到了30天。效果非常明显。

好了,这一章的内容就到这里。下一章我们会讲Deep-sleep和RTC内存保持,那才是真正的「极限省电」模式。到时候见。