跳转至

低功耗模式

ESP32 提供多种睡眠模式,从轻度省电到深度休眠,可大幅降低功耗。掌握低功耗技术是电池供电 IoT 项目的核心。


一、功耗模式总览

模式 功耗 保留内容 唤醒速度 典型应用
Active 80~260 mA 全部 正常工作
Modem Sleep 20~30 mA CPU + RAM 即时 WiFi 间歇通信
Light Sleep 0.8~2 mA CPU + RAM(暂停) ~1 ms 快速响应唤醒
Deep Sleep 10~150 μA RTC 控制器 + RTC 内存 ~ms 级 定时上报数据
Hibernation ~5 μA RTC 定时器 较慢 极低功耗待机
graph LR
    A[Active<br>80~260mA] -->|关闭射频| B[Modem Sleep<br>20~30mA]
    B -->|CPU 暂停| C[Light Sleep<br>0.8mA]
    C -->|关闭主CPU/RAM| D[Deep Sleep<br>10~150μA]
    D -->|仅 RTC 定时器| E[Hibernation<br>5μA]

    style A fill:#ff6b6b
    style B fill:#ffa94d
    style C fill:#ffd43b
    style D fill:#69db7c
    style E fill:#4dabf7

Deep Sleep 的本质

Deep Sleep 唤醒后,ESP32 会==重新从 setup() 执行==,相当于重启。普通变量会丢失。 如果需要跨睡眠保留数据,必须存储在 RTC 内存 中。


二、Deep Sleep(深度睡眠)

Deep Sleep 是最常用的低功耗模式,功耗可降至 10 μA 级别。

定时器唤醒

#define uS_TO_S_FACTOR 1000000ULL  // 微秒转秒
#define TIME_TO_SLEEP  30          // 睡眠 30 秒

// RTC 内存中的变量(Deep Sleep 后保留)
RTC_DATA_ATTR int bootCount = 0;

void setup() {
    Serial.begin(115200);
    delay(100);  // 等待串口稳定

    ++bootCount;
    Serial.printf("第 %d 次启动\n", bootCount);

    // 做一些工作:读取传感器、上传数据...
    doWork();

    // 设置唤醒源:定时器
    esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
    Serial.printf("设置 %d 秒后唤醒\n", TIME_TO_SLEEP);

    Serial.println("进入 Deep Sleep...");
    Serial.flush();  // 等待串口发送完毕

    esp_deep_sleep_start();  // 进入深度睡眠
    // ⚠ 此行以下代码永远不会执行
}

void loop() {
    // Deep Sleep 模式下 loop 不会执行
}

void doWork() {
    // 模拟传感器读取和数据上传
    float temp = 25.0 + random(-50, 50) / 10.0;
    Serial.printf("温度: %.1f°C\n", temp);
    delay(2000);  // 模拟上传
}

GPIO 唤醒(ext0 — 单引脚)

#define WAKEUP_PIN GPIO_NUM_33  // 使用 RTC GPIO

RTC_DATA_ATTR int bootCount = 0;

void setup() {
    Serial.begin(115200);
    ++bootCount;

    // 检查唤醒原因
    esp_sleep_wakeup_cause_t reason = esp_sleep_get_wakeup_cause();
    switch (reason) {
        case ESP_SLEEP_WAKEUP_TIMER:
            Serial.println("定时器唤醒");
            break;
        case ESP_SLEEP_WAKEUP_EXT0:
            Serial.println("GPIO 唤醒 (ext0)");
            break;
        case ESP_SLEEP_WAKEUP_EXT1:
            Serial.println("GPIO 唤醒 (ext1)");
            break;
        case ESP_SLEEP_WAKEUP_TOUCHPAD:
            Serial.println("触摸唤醒");
            break;
        default:
            Serial.printf("正常启动 / 其他原因: %d\n", reason);
            break;
    }

    // ext0: 单个 RTC GPIO 唤醒,指定高/低电平触发
    esp_sleep_enable_ext0_wakeup(WAKEUP_PIN, 0);  // 0 = 低电平唤醒

    Serial.println("进入 Deep Sleep,等待按键唤醒...");
    Serial.flush();
    esp_deep_sleep_start();
}

void loop() {}

多 GPIO 唤醒(ext1 — 多引脚)

// ext1: 多个 RTC GPIO 组合唤醒
#define BUTTON_1 GPIO_NUM_32
#define BUTTON_2 GPIO_NUM_33
#define BUTTON_3 GPIO_NUM_27

void setup() {
    Serial.begin(115200);

    // 使用位掩码指定多个引脚
    uint64_t bitmask = (1ULL << BUTTON_1) | (1ULL << BUTTON_2) | (1ULL << BUTTON_3);

    // ESP_EXT1_WAKEUP_ANY_HIGH: 任一引脚为高电平唤醒
    // ESP_EXT1_WAKEUP_ALL_LOW: 所有引脚都为低电平才唤醒
    esp_sleep_enable_ext1_wakeup(bitmask, ESP_EXT1_WAKEUP_ANY_HIGH);

    // 检查哪个引脚触发唤醒
    if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_EXT1) {
        uint64_t status = esp_sleep_get_ext1_wakeup_status();
        int pin = __builtin_ffsll(status) - 1;
        Serial.printf("触发引脚: GPIO %d\n", pin);
    }

    Serial.println("进入 Deep Sleep...");
    Serial.flush();
    esp_deep_sleep_start();
}

void loop() {}

触摸唤醒

RTC_DATA_ATTR int bootCount = 0;

void callback() {
    // 触摸唤醒回调(可以为空)
}

void setup() {
    Serial.begin(115200);
    ++bootCount;
    Serial.printf("第 %d 次启动\n", bootCount);

    // 查看唤醒原因
    if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_TOUCHPAD) {
        Serial.printf("触摸引脚 T%d 唤醒\n", esp_sleep_get_touchpad_wakeup_status());
    }

    // 设置触摸唤醒
    touchAttachInterrupt(T3, callback, 40);  // GPIO 15, 阈值 40
    esp_sleep_enable_touchpad_wakeup();

    Serial.println("进入 Deep Sleep,触摸唤醒...");
    Serial.flush();
    esp_deep_sleep_start();
}

void loop() {}

RTC GPIO 对照表

ext0/ext1 唤醒只能使用 RTC GPIO:

RTC GPIO 实际引脚 RTC GPIO 实际引脚
RTC_GPIO0 GPIO 36 RTC_GPIO9 GPIO 32
RTC_GPIO3 GPIO 39 RTC_GPIO10 GPIO 33
RTC_GPIO4 GPIO 34 RTC_GPIO11 GPIO 25
RTC_GPIO5 GPIO 35 RTC_GPIO12 GPIO 26
RTC_GPIO6 GPIO 25 RTC_GPIO13 GPIO 27
RTC_GPIO7 GPIO 26 RTC_GPIO14 GPIO 14
RTC_GPIO8 GPIO 33 RTC_GPIO15 GPIO 12

三、Light Sleep(浅度睡眠)

Light Sleep 保留 CPU 和 RAM 状态,唤醒后从==睡眠处继续执行==(不重启):

void setup() {
    Serial.begin(115200);

    // 配置唤醒源
    esp_sleep_enable_timer_wakeup(5000000);  // 5 秒后唤醒

    Serial.println("进入 Light Sleep...");
    Serial.flush();

    // 进入 Light Sleep
    esp_err_t ret = esp_light_sleep_start();

    // ✅ 唤醒后从这里继续执行
    if (ret == ESP_OK) {
        Serial.println("Light Sleep 唤醒!");
        esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
        Serial.printf("唤醒原因: %d\n", cause);
    }
}

void loop() {
    Serial.println("正常运行...");
    delay(1000);

    // 可以在 loop 中反复进入 Light Sleep
    esp_sleep_enable_timer_wakeup(3000000);
    Serial.println("再次进入 Light Sleep...");
    Serial.flush();
    esp_light_sleep_start();
    Serial.println("再次唤醒!");
}

Light Sleep vs Deep Sleep

特性 Light Sleep Deep Sleep
功耗 ~0.8 mA ~10 μA
RAM 保留 ✅ 全部保留 ❌ 仅 RTC 内存
唤醒行为 从睡眠处继续 重新进入 setup()
WiFi 连接 可保持(Modem Sleep) 断开
适用场景 需要快速响应 长时间待机

四、Modem Sleep(调制解调器睡眠)

WiFi 连接状态下自动省电,无需手动配置:

#include <WiFi.h>

void setup() {
    Serial.begin(115200);
    WiFi.begin("SSID", "password");
    while (WiFi.status() != WL_CONNECTED) delay(500);

    // 设置 WiFi 省电模式
    // WIFI_PS_NONE     — 不省电(最低延迟)
    // WIFI_PS_MIN_MODEM — 最小省电(DTIM 1)
    // WIFI_PS_MAX_MODEM — 最大省电(DTIM 由 AP 决定)
    WiFi.setSleep(WIFI_PS_MAX_MODEM);

    Serial.println("WiFi 已连接,Modem Sleep 已启用");
    Serial.printf("IP: %s\n", WiFi.localIP().toString().c_str());
}

void loop() {
    // 正常工作,WiFi 空闲时自动进入 Modem Sleep
    delay(1000);
}

五、实际应用:电池供电传感器

低功耗环境监测站

#include <WiFi.h>
#include <HTTPClient.h>

#define uS_TO_S_FACTOR 1000000ULL
#define SLEEP_MINUTES  5
#define BATTERY_PIN    35  // ADC 检测电池电压

// RTC 内存保留数据
RTC_DATA_ATTR int bootCount = 0;
RTC_DATA_ATTR float lastTemp = 0;

const char* ssid = "SSID";
const char* password = "password";

void setup() {
    Serial.begin(115200);
    ++bootCount;

    unsigned long startTime = millis();
    Serial.printf("=== 第 %d 次唤醒 ===\n", bootCount);

    // 1. 读取传感器(WiFi 未开启,ADC2 可用)
    float temp = readTemperature();
    float battery = readBattery();

    Serial.printf("温度: %.1f°C, 电池: %.2fV\n", temp, battery);

    // 2. 仅在数据变化较大时上传(节省功耗)
    bool shouldUpload = abs(temp - lastTemp) > 0.5 || bootCount % 12 == 1;

    if (shouldUpload) {
        // 3. 连接 WiFi
        WiFi.mode(WIFI_STA);
        WiFi.begin(ssid, password);

        int timeout = 20;  // 最多等 10 秒
        while (WiFi.status() != WL_CONNECTED && timeout > 0) {
            delay(500);
            timeout--;
        }

        if (WiFi.status() == WL_CONNECTED) {
            // 4. 上传数据
            uploadData(temp, battery, bootCount);
            lastTemp = temp;
        }

        // 5. 立即关闭 WiFi
        WiFi.disconnect(true);
        WiFi.mode(WIFI_OFF);
    }

    unsigned long elapsed = millis() - startTime;
    Serial.printf("工作耗时: %lu ms\n", elapsed);

    // 6. 电池低电量加长睡眠
    int sleepMin = SLEEP_MINUTES;
    if (battery < 3.3) {
        sleepMin = SLEEP_MINUTES * 4;
        Serial.println("低电量,延长睡眠");
    }

    // 7. 进入 Deep Sleep
    esp_sleep_enable_timer_wakeup(sleepMin * 60 * uS_TO_S_FACTOR);
    Serial.printf("睡眠 %d 分钟...\n", sleepMin);
    Serial.flush();
    esp_deep_sleep_start();
}

void loop() {}

float readTemperature() {
    // 示例:模拟传感器读取
    return 25.0 + random(-30, 30) / 10.0;
}

float readBattery() {
    // 电池通过分压电阻接 ADC
    // 例:100K + 100K 分压,满量程对应 4.2V * 2 = 8.4V 范围
    int raw = analogRead(BATTERY_PIN);
    return (raw / 4095.0) * 3.3 * 2;  // 分压系数 × 参考电压
}

void uploadData(float temp, float bat, int boot) {
    HTTPClient http;
    http.begin("http://api.example.com/sensor");
    http.addHeader("Content-Type", "application/json");

    char json[128];
    snprintf(json, sizeof(json),
        "{\"temp\":%.1f,\"bat\":%.2f,\"boot\":%d}", temp, bat, boot);

    http.POST(json);
    http.end();
}
graph TB
    A[唤醒] --> B[读取传感器<br>~10ms]
    B --> C{数据变化?}
    C -->|是| D[连接 WiFi<br>2~5s]
    D --> E[上传数据<br>0.5~2s]
    E --> F[关闭 WiFi]
    C -->|否| G[跳过上传]
    F --> H[Deep Sleep<br>5~20 min]
    G --> H
    H -->|定时器唤醒| A

    style H fill:#69db7c
    style D fill:#ff6b6b

低功耗设计要点

  1. 尽快完成工作:WiFi 连接是最耗电的,尽量缩短连接时间
  2. 按需上传:数据无变化时跳过 WiFi,可节省 90% 以上功耗
  3. 先读传感器再开 WiFi:ADC2 和 WiFi 冲突
  4. 用 RTC_DATA_ATTR 保留状态:避免每次唤醒都初始化
  5. 主动关闭 WiFiWiFi.disconnect(true) + WiFi.mode(WIFI_OFF)
  6. 去掉不必要的外设:LED、电压调节器的静态电流可能远大于 ESP32 睡眠电流

六、功耗优化技巧

关闭未使用的外设

#include "driver/adc.h"

void setup() {
    // 关闭蓝牙(如果不用)
    btStop();
    // 或编译时禁用:menuconfig -> Component config -> Bluetooth

    // 关闭 WiFi
    WiFi.mode(WIFI_OFF);

    // 关闭 ADC
    adc_power_off();

    // 降低 CPU 频率(默认 240MHz)
    setCpuFrequencyMhz(80);  // 80 / 160 / 240
    Serial.printf("CPU 频率: %d MHz\n", getCpuFrequencyMhz());
}

降低 CPU 频率

CPU 频率 Active 电流 适用场景
240 MHz ~68 mA WiFi、复杂计算
160 MHz ~45 mA 一般处理
80 MHz ~27 mA 轻量任务

七、常见问题

Deep Sleep 功耗实测比官方数据大很多?

常见原因:

  • USB-UART 芯片(CP2102/CH340)在供电:开发板自带的串口芯片会消耗 5~20 mA
  • LDO 线性稳压器的静态电流:AMS1117 静态电流 ~5 mA
  • LED 指示灯:板载 LED 可能消耗 1~5 mA

解决方案:生产用 PCB 去掉 USB 芯片和 LED,使用低静态电流 LDO(如 HT7333、ME6211)。

Deep Sleep 唤醒后 WiFi 连接很慢?

Deep Sleep 唤醒等于重启,WiFi 需要重新关联。加速方法:

  • 使用 WiFi.persistent(true) 保存连接参数到 Flash
  • 用 RTC 内存保存信道和 BSSID,通过 WiFi.begin(ssid, pass, channel, bssid) 快速重连
  • 典型效果:连接时间从 3~5 秒缩短到 0.5~1 秒

RTC 内存有多大?

ESP32 有 8 KB RTC 快速内存(RTC_FAST)和 8 KB RTC 慢速内存(RTC_SLOW)。

  • RTC_DATA_ATTR 存在 RTC_FAST,Deep Sleep 保留
  • ULP 协处理器程序存在 RTC_SLOW
  • 不要存太多数据,8 KB 很容易用满

能在 Deep Sleep 中保持蓝牙/WiFi 连接吗?

不能。Deep Sleep 会关闭射频和主 CPU。如果需要保持连接:

  • 使用 Light Sleep + WiFi Modem Sleep(~1 mA)
  • 或者接受断线重连的方案(Deep Sleep 唤醒后快速重连)