arduino-esp32 Deep Sleep 完全指南:ExternalWakeUp 外部唤醒与 TimerWakeUp 定时器唤醒实战
2026/9/13 20:18:30 网站建设 项目流程

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); #endif

5.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_MOVII_STI_LDM_BLI_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 << GPIOESP_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()不会执行)。需要特别留意两点:

  1. esp_deep_sleep_start()前的Serial.flush():确保休眠前的串口输出完整发送(见 TimerWakeUp 示例),否则可能丢失最后的日志;
  2. 无唤醒源时的行为:如示例注释所述,如果未配置任何唤醒源就调用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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询