arduino-esp32 Deep Sleep 完全指南:ExternalWakeUp 外部唤醒与 TimerWakeUp 定时器唤醒实战
【免费下载链接】arduino-esp32Arduino core for the ESP32 family of SoCs项目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32
导读
本文基于 arduino-esp32 官方文档 docs/en/api/deepsleep.rst 的 Deep Sleep 章节,系统讲解 ESP32 系列芯片在 Arduino 环境下的深度睡眠(Deep Sleep)编程。内容覆盖两种最基础的唤醒方式——外部 GPIO 唤醒(External Wake Up)与定时器唤醒(Timer Wake Up),并延伸至触摸唤醒、ULP 协处理器唤醒、唤醒原因(Wakeup Reason)解析与 RTC 内存数据持久化等进阶主题。读完本文,你将能够编写低功耗 IoT 固件:让设备按需休眠、按外部事件或定时周期唤醒,并在多次唤醒之间保留关键数据。文中所有示例均来自本仓库libraries/ESP32/examples/DeepSleep/目录下可编译、可运行的官方示例。
一、Deep Sleep 模式概述:为什么要省电
对于电池供电的 IoT 应用,功耗是首要设计约束。ESP32 的 Deep Sleep 模式正是为此设计:进入该模式后,CPU、大部分 RAM 以及所有由 APB_CLK 提供时钟的数字外设都会被断电,芯片中仅保留以下电源域(power domain)继续工作:
- RTC 控制器(RTC controller)
- RTC 外设(RTC peripherals)
- RTC 内存(RTC memories)
正如官方示例 TimerWakeUp.ino 头部注释所描述的,这套"选择性断电"机制让芯片在休眠期间维持极低功耗,同时仍能通过 RTC 域内的计时器、GPIO、触摸传感器或 ULP 协处理器侦测唤醒事件。
进入 Deep Sleep 的 API 调用链非常简洁:Arduino 层封装了ESP.deepSleep(time_us)(定义于 cores/esp32/Esp.h),其实现直接调用 ESP-IDF 的esp_deep_sleep()(见 cores/esp32/Esp.cpp)。而所有示例代码中使用的是更底层的组合式调用:
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR); // 先配置唤醒源 esp_deep_sleep_start(); // 再进入深度睡眠需要注意:setup()末尾调用esp_deep_sleep_start()之后,程序流程即被冻结,loop()不会执行——这也是所有 Deep Sleep 示例中loop()为空或永不触达的原因。
二、定时器唤醒(Timer Wake Up):周期任务的最简实现
定时器唤醒适合需要周期性采集数据、周期上报状态的场景,是 Deep Sleep 中最常用的唤醒源。完整代码见 TimerWakeUp.ino,核心步骤如下。
2.1 定义休眠时长
#define uS_TO_S_FACTOR 1000000ULL /* Conversion factor for micro seconds to seconds */ #define TIME_TO_SLEEP 5 /* Time ESP32 will go to sleep (in seconds) */esp_sleep_enable_timer_wakeup()的参数单位是微秒(µs),因此先用uS_TO_S_FACTOR把秒换算成微秒,避免魔法数字。上例表示每 5 秒唤醒一次。
2.2 在 setup() 中配置唤醒源并进入休眠
void setup() { Serial.begin(115200); delay(1000); // 等待串口监视器打开 ++bootCount; Serial.println("Boot number: " + String(bootCount)); print_wakeup_reason(); esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR); Serial.println("Setup ESP32 to sleep for every " + String(TIME_TO_SLEEP) + " Seconds"); // 可选:关闭 RTC 外设电源域以进一步省电 //esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF); Serial.println("Going to sleep now"); Serial.flush(); esp_deep_sleep_start(); Serial.println("This will never be printed"); // 永远不会执行到 } void loop() { // 不会被执行 }2.3 电源域精细控制(可选进阶)
示例中注释掉了一行关键配置:
//esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);默认情况下,ESP32 会自动关闭唤醒源不需要的电源域;但作为"省电高手",你可以用esp_deep_sleep_pd_config(domain, option)手动指定某电源域在休眠期间的开/关状态。例如关闭ESP_PD_DOMAIN_RTC_PERIPH可进一步降低休眠电流,代价是 RTC 外设(如 RTC GPIO、触摸)相关唤醒能力受限。选择电源域策略时,需要与你的唤醒源类型匹配。
2.4 唤醒原因(Wakeup Reason)解析
无论是定时唤醒还是外部唤醒,醒来后第一件事往往是判断"为什么醒"。示例中的print_wakeup_reason()通过esp_sleep_get_wakeup_cause()返回的枚举值区分唤醒来源:
switch (wakeup_reason) { case ESP_SLEEP_WAKEUP_EXT0: Serial.println("Wakeup caused by external signal using RTC_IO"); break; case ESP_SLEEP_WAKEUP_EXT1: Serial.println("Wakeup caused by external signal using RTC_CNTL"); break; case ESP_SLEEP_WAKEUP_TIMER: Serial.println("Wakeup caused by timer"); break; case ESP_SLEEP_WAKEUP_TOUCHPAD: Serial.println("Wakeup caused by touchpad"); break; case ESP_SLEEP_WAKEUP_ULP: Serial.println("Wakeup caused by ULP program"); break; default: Serial.printf("Wakeup was not caused by deep sleep: %d\n", wakeup_reason); break; }其中ESP_SLEEP_WAKEUP_EXT0表示由 RTC_IO 驱动的外部信号唤醒,ESP_SLEEP_WAKEUP_EXT1表示由 RTC 控制器(RTC_CNTL)驱动的外部信号唤醒,其余依次对应定时器、触摸板与 ULP 程序唤醒。设备首次上电(非深睡唤醒)时该函数会落入default分支。
三、外部唤醒(External Wake Up):EXT0 与 EXT1 两种机制
外部唤醒适合按钮、门磁、运动检测等"事件驱动"场景。完整代码见 ExternalWakeUp.ino,其硬件连接为:按键一端接 GPIO 33,另一端经 10KΩ 下拉电阻接地。
3.1 重要前提:只能用 RTC IO 引脚
示例注释明确给出了硬件约束:
Only RTC IO can be used as a source for external wake source. They are pins: 0,2,4,12-15,25-27,32-39.
即 ESP32 上只有 RTC 域引脚(0、2、4、12–15、25–27、32–39)可作为外部唤醒源。设计电路时务必避开非 RTC 引脚。
3.2 EXT0:单引脚、电平触发
EXT0 使用RTC_IO实现唤醒,需要 RTC 外设(RTC_PERIPH)电源域保持开启:
#define WAKEUP_GPIO GPIO_NUM_33 // 必须是 RTC IO esp_sleep_enable_ext0_wakeup(WAKEUP_GPIO, 1); // 1 = High 唤醒, 0 = Low 唤醒 // 通过 RTCIO 配置内部上/下拉,将唤醒引脚在休眠期间钳位到非激活电平 rtc_gpio_pullup_dis(WAKEUP_GPIO); rtc_gpio_pulldown_en(WAKEUP_GPIO);esp_sleep_enable_ext0_wakeup(gpio_num, level)的第二个参数指定唤醒电平:1表示引脚出现高电平即唤醒,0表示低电平唤醒。示例中按下按键使 GPIO33 拉高从而唤醒。由于 EXT0 与 RTC IO 的上下拉同属RTC_PERIPH电源域,示例注释特别说明:EXT0 无需像 EXT1 那样显式保持该电源域。
3.3 EXT1:多引脚、掩码触发
EXT1 使用RTC 控制器(RTC_CNTL)实现唤醒,不依赖 RTC 外设供电,因此功耗更低,且可同时监测多个引脚:
#define BUTTON_PIN_BITMASK(GPIO) (1ULL << GPIO) // 将 GPIO 编号转为位掩码 esp_sleep_enable_ext1_wakeup_io(BUTTON_PIN_BITMASK(WAKEUP_GPIO), ESP_EXT1_WAKEUP_ANY_HIGH);BUTTON_PIN_BITMASK(GPIO)展开为1ULL << GPIO,即把引脚号映射到位掩码,例如 GPIO 33 对应1ULL << 33。唤醒模式参数ESP_EXT1_WAKEUP_ANY_HIGH表示"任一被掩码的引脚为高电平即唤醒",对应地还有ESP_EXT1_WAKEUP_ALL_LOW(全部为低才唤醒)。
示例同时给出了 EXT1 模式下的上下拉建议:若外部没有上/下拉电阻,可在深睡期间用 RTC IO 的内部上/下拉把唤醒引脚钳位到非激活电平。但要注意——RTC IO 依赖 RTC_PERIPH 电源域,保持该域开启会增加功耗;若关闭RTC_PERIPH域、或某些芯片本身缺少该电源域,则需要使用 HOLD 特性在睡眠期间维持引脚上/下拉状态。
3.4 在两种模式间切换
示例用编译期宏快速切换两种外部唤醒机制:
#define USE_EXT0_WAKEUP 1 // 1 = EXT0 wakeup, 0 = EXT1 wakeup #if USE_EXT0_WAKEUP esp_sleep_enable_ext0_wakeup(WAKEUP_GPIO, 1); rtc_gpio_pullup_dis(WAKEUP_GPIO); rtc_gpio_pulldown_en(WAKEUP_GPIO); #else // EXT1 WAKEUP esp_sleep_enable_ext1_wakeup_io(BUTTON_PIN_BITMASK(WAKEUP_GPIO), ESP_EXT1_WAKEUP_ANY_HIGH); rtc_gpio_pulldown_en(WAKEUP_GPIO); // GPIO33 被下拉到 GND,因此高电平唤醒 rtc_gpio_pullup_dis(WAKEUP_GPIO); // 禁用上拉以允许高电平唤醒 #endif实践中的选型建议:单引脚唤醒优先用 EXT0(配置直观);多引脚监测或追求更低休眠功耗时用 EXT1(无需保持 RTC_PERIPH 域)。
四、用 RTC_DATA_ATTR 在休眠期间持久化数据
Deep Sleep 会清空常规 RAM,因此跨重启保留计数器、状态标志等数据必须借助RTC 内存。所有 Deep Sleep 示例都使用了同一行声明:
RTC_DATA_ATTR int bootCount = 0;RTC_DATA_ATTR由 ESP-IDF 提供(esp_attr.h),它把变量放置到 RTC 慢速内存(RTC_SLOW_MEM)中,在 Deep Sleep 期间不被断电,重启后值依然保留。典型用法:
void setup() { ++bootCount; // 每次唤醒自增 Serial.println("Boot number: " + String(bootCount)); ... }配合esp_sleep_get_wakeup_cause(),你可以在首次上电(bootCount == 1)时执行初始化,在后续唤醒时直接恢复现场。这是实现"断电不丢状态"的最简方案。
五、进阶:触摸唤醒与 ULP 协处理器唤醒
仓库的libraries/ESP32/examples/DeepSleep/目录还提供了另外两种官方示例,用于更复杂的低功耗场景。
5.1 触摸唤醒(Touch Wake Up)
TouchWakeUp.ino 演示以电容触摸作为唤醒源。示例代码使用esp_sleep_get_touchpad_wakeup_status()反查具体触发触摸的引脚,并强调了一个跨芯片差异:
- ESP32:支持多个触摸板同时作为唤醒源(示例启用 Touch Pad 3 和 7,对应 GPIO 15 与 GPIO 27);
- ESP32-S2 / ESP32-S3:仅支持 1 个触摸板作为唤醒源;
- 不同芯片的触摸阈值语义也不同:ESP32 阈值
40(越大越灵敏),S2/S3 阈值5000(越小越灵敏),其他芯片默认500。
#if CONFIG_IDF_TARGET_ESP32 touchSleepWakeUpEnable(T3, THRESHOLD); touchSleepWakeUpEnable(T7, THRESHOLD); #else //ESP32-S2 + ESP32-S3 + ESP32-P4 touchSleepWakeUpEnable(T3, THRESHOLD); #endif5.2 ULP 协处理器唤醒(SmoothBlink_ULP_Code)
SmoothBlink_ULP_Code.ino 展示了 Deep Sleep 的最高级玩法:主 CPU 休眠期间,由ULP(Ultra Low Power)协处理器独立运行汇编程序,在 GPIO 2 上输出约 10KHz 的渐变 PWM 呼吸灯,并跨休眠周期保存占空比状态。
其实现要点包括:
- 用
RTC_SLOW_MEM[]数组索引与主程序共享变量(如fadeCycleDelay),通过uint32_t *fadeCycleDelay = &RTC_SLOW_MEM[RTC_fadeDelay];传递参数; - 以
ulp_insn_t ulp_prog[]数组定义 ULP 汇编指令序列(I_MOVI、I_ST、I_LD、M_BL、I_WR_REG等),再用ulp_process_macros_and_load()加载、ulp_run()启动; - 通过
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON)显式保持 RTC 外设电源域,供 ULP 操作 GPIO; - 配合
esp_sleep_enable_timer_wakeup(),让主 CPU 每 5 秒醒来一次调整亮度参数,其余时间交给 ULP 全权处理。
这是"主 CPU 休眠、协处理器值守"的典型架构,适合需要持续输出信号(如呼吸灯、传感器激励)同时又要求极低功耗的应用。
六、实战要点速查
| 场景 | 唤醒源 API | 关键参数/约束 |
|---|---|---|
| 周期性任务 | esp_sleep_enable_timer_wakeup(time_us) | 单位微秒,TIME_TO_SLEEP * 1000000ULL |
| 单引脚外部事件 | esp_sleep_enable_ext0_wakeup(gpio, level) | 仅限 RTC IO 引脚:0,2,4,12–15,25–27,32–39;level1=高唤醒/0=低唤醒 |
| 多引脚外部事件 | esp_sleep_enable_ext1_wakeup_io(mask, mode) | 掩码1ULL << GPIO;ESP_EXT1_WAKEUP_ANY_HIGH/ESP_EXT1_WAKEUP_ALL_LOW |
| 触摸唤醒 | touchSleepWakeUpEnable(pad, threshold) | ESP32 可多 pad;S2/S3 仅 1 个 pad |
| ULP 值守 | ulp_run(offset)+ 定时器唤醒 | 需保持ESP_PD_DOMAIN_RTC_PERIPH开启 |
| 跨休眠数据 | RTC_DATA_ATTR变量 | 存放于 RTC_SLOW_MEM,Deep Sleep 不丢失 |
| 电源域微调 | esp_deep_sleep_pd_config(domain, option) | 例如ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF |
所有示例的通用骨架可归纳为三步:① 声明RTC_DATA_ATTR持久变量 → ② 在setup()中解析唤醒原因并配置唤醒源 → ③ 调用esp_deep_sleep_start()进入休眠(loop()不会执行)。需要特别留意两点:
esp_deep_sleep_start()前的Serial.flush():确保休眠前的串口输出完整发送(见 TimerWakeUp 示例),否则可能丢失最后的日志;- 无唤醒源时的行为:如示例注释所述,如果未配置任何唤醒源就调用
esp_deep_sleep_start(),芯片将永久休眠,只能通过硬件复位(EN 引脚)唤醒。
七、进一步阅读
- Deep Sleep 章节原始文档:docs/en/api/deepsleep.rst(该章节标记为 WIP,正文以示例为核心)
- 四个可编译运行的官方示例均位于 libraries/ESP32/examples/DeepSleep/:ExternalWakeUp、TimerWakeUp、TouchWakeUp、SmoothBlink_ULP_Code
- 唤醒原因解析的配套示例可参考 libraries/ESP32/examples/ResetReason/(ResetReason.ino 同样使用
esp_sleep_get_wakeup_cause()体系) - Arduino 层封装入口:
ESP.deepSleep(time_us)声明于 cores/esp32/Esp.h,实现在 cores/esp32/Esp.cpp - 低功耗触发的底层实现可继续阅读 ESP-IDF 的
esp_sleep系列驱动(本仓库cores/esp32/与variants/中对应芯片的头文件定义了CONFIG_IDF_TARGET_*芯片差异宏)
掌握上述示例与原理后,即可根据实际硬件选择定时、外部、触摸或 ULP 唤醒组合,构建出符合产品功耗指标的 Deep Sleep 应用。
【免费下载链接】arduino-esp32Arduino core for the ESP32 family of SoCs项目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考