前言
Deep Sleep(深度睡眠)是電池供電 IoT 裝置續航力的核心技術。一個感測器節點若 24/7 全速運行,2000 mAh 電池只能撐一天;但若善用 Deep Sleep 讓裝置在 99% 時間處於休眠狀態,續航力可長達數月甚至數年。ESP32 與 STM32 都提供多級低功耗模式,本文將從硬體架構切入,詳細解析 RTC Timer 定時喚醒、GPIO 外部喚醒、Touch 喚醒的機制與程式碼實作,並提供電池續航力估算方法。
一、為何需要 Deep Sleep?
在 IoT 應用中,裝置通常不需要持續運作:
- 感測器節點:每分鐘讀取一次溫度,其餘時間休眠
- 智慧門鎖:平時待命,只有門磁觸發或藍牙開鎖時喚醒
- 追蹤器:每小時回報一次 GPS 座標
- 氣象站:每 15 分鐘上傳一次資料

圖 6:2000 mAh 電池在不同喚醒週期下的續航力 — 每小時喚醒 1 秒可達 7.9 年
圖 1:Deep Sleep 工作週期 — Active 模式消耗 ~80 mA,Deep Sleep 僅 ~5 μA,RTC Timer 或 GPIO 喚醒
二、ESP32 低功耗模式

圖 5:ESP32 六種功耗模式 — 從 Active 的 260 mA 到斷電的 0 μA
2.1 ESP32 喚醒來源
| 喚醒來源 | 說明 | 典型用途 |
|---|---|---|
| Timer | RTC 定時器到達警報值 | 定期感測器讀取 |
| EXT0 | RTC_GPIO 指定電平 | 按鈕開機、警報觸發 |
| EXT1 | 多個 RTC_GPIO 任意組合 | 門磁、PIR 感測器 |
| Touch | 觸碰感應值低於閾值 | 觸控開關、接近感測 |
| ULP Coprocessor | ULP 協處理器在睡眠中 執行自訂程式後喚醒 |
ADC 監控、I2C 感測器輪詢 |
2.2 ESP32 Timer 定時喚醒
// esp32_deep_sleep_timer.ino — 定時喚醒範例
#include
// 定義 RTC_DATA_ATTR 變數:Deep Sleep 期間保留
RTC_DATA_ATTR int boot_count = 0;
void setup() {
Serial.begin(115200);
delay(1000); // 等待序列埠穩定
boot_count++;
Serial.printf("開機次數: %d\n", boot_count);
// 取得喚醒原因
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
switch (cause) {
case ESP_SLEEP_WAKEUP_TIMER:
Serial.println("喚醒原因: RTC Timer");
break;
case ESP_SLEEP_WAKEUP_EXT0:
Serial.println("喚醒原因: RTC_GPIO (EXT0)");
break;
case ESP_SLEEP_WAKEUP_EXT1:
Serial.println("喚醒原因: RTC_GPIO (EXT1)");
break;
case ESP_SLEEP_WAKEUP_TOUCHPAD:
Serial.println("喚醒原因: Touch");
break;
case ESP_SLEEP_WAKEUP_ULP:
Serial.println("喚醒原因: ULP Coprocessor");
break;
default:
Serial.println("喚醒原因: 電源開機或復位");
}
// === 執行感測器讀取 ===
float temp = read_temperature(); // 自訂感測器函式
float hum = read_humidity();
Serial.printf("溫度: %.1f°C 濕度: %.1f%%\n", temp, hum);
// === 透過 WiFi 上傳資料 ===
WiFi.begin("SSID", "PASSWORD");
if (WiFi.waitForConnectResult() == WL_CONNECTED) {
HTTPClient http;
http.begin("https://api.example.com/sensor");
http.addHeader("Content-Type", "application/json");
char payload[64];
snprintf(payload, sizeof(payload), "{\"temp\":%.1f,\"hum\":%.1f}", temp, hum);
int code = http.POST(payload);
Serial.printf("HTTP %d\n", code);
http.end();
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
}
// === 設定下次喚醒時間(60 秒後) ===
esp_sleep_enable_timer_wakeup(60 * 1000000); // 微秒單位
Serial.println("進入 Deep Sleep...");
// 清除喚醒原因旗標
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_ON);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_ON);
esp_deep_sleep_start(); // 進入睡眠(不會返回此處)
}
void loop() {
// 永遠不會執行到這裡
}
圖 2:RTC 計數器在 Deep Sleep 期間持續計數,與警報值吻合時觸發喚醒
2.3 ESP32 GPIO 外部喚醒(EXT1)
// esp32_deep_sleep_ext1.ino — 多 GPIO 喚醒範例
#include
#define BUTTON_PIN GPIO_NUM_4 // GPIO4 喚醒(外部下拉)
#define DOOR_PIN GPIO_NUM_33 // GPIO33 門磁開關
void setup() {
Serial.begin(115200);
delay(1000);
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
if (cause == ESP_SLEEP_WAKEUP_EXT1) {
uint64_t gpio_mask = esp_sleep_get_ext1_wakeup_status();
if (gpio_mask & (1ULL << BUTTON_PIN)) {
Serial.println("喚醒原因: 按鈕按下");
}
if (gpio_mask & (1ULL << DOOR_PIN)) {
Serial.println("喚醒原因: 門磁觸發");
}
}
// 設定 EXT1 喚醒:GPIO4 和 GPIO33,任意一個高電平喚醒
const uint64_t ext1_mask = (1ULL << BUTTON_PIN) | (1ULL << DOOR_PIN);
esp_sleep_enable_ext1_wakeup(ext1_mask, ESP_EXT1_WAKEUP_ANY_HIGH);
Serial.println("進入 Deep Sleep(等待外部觸發...)");
esp_deep_sleep_start();
}
void loop() {}
圖 3:GPIO 輸入變化被 RTC_IO 鎖存,產生喚醒脈衝使 CPU 重啟
2.4 ESP32 Touch 喚醒
// esp32_deep_sleep_touch.ino — Touch 喚醒範例
#include
// T0 = GPIO4 (Touch pad 0)
#define TOUCH_THRESHOLD 40 // 觸碰閾值(越低越靈敏)
void setup() {
Serial.begin(115200);
delay(1000);
if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_TOUCHPAD) {
int touch_pin = esp_sleep_get_touchpad_wakeup_status();
Serial.printf("Touch pad %d 觸發喚醒\n", touch_pin);
}
// 設定 Touch pad 0 喚醒(需先讀取基準值)
touch_pad_init();
touch_pad_config(TOUCH_PAD_GPIO4_CHANNEL, TOUCH_THRESHOLD);
esp_sleep_enable_touchpad_wakeup();
Serial.println("進入 Deep Sleep(Touch 喚醒模式)...");
esp_deep_sleep_start();
}
void loop() {}
三、ESP32 ULP 協處理器(進階)
ULP(Ultra Low Power)協處理器是 ESP32 獨有的特色:一個可在 Deep Sleep 期間獨立運作的極低功耗處理器,能執行自訂組合語言程式進行 ADC 採樣、I2C 讀取或 GPIO 控制,僅在需要時才喚醒主 CPU。
// esp32_ulp_adc.S — ULP 組合語言範例(ADC 監控)
// ULP 在 Deep Sleep 中定期讀取 ADC,超過閾值時喚醒主 CPU
/* 定義 ULP 變數存放在 RTC_SLOW_MEM */
.bss
.global sample
.global threshold
sample: .long 0
threshold: .long 1800 // 3.0V × 4095 / 3.3V ≈ 1800
/* ULP 主程式入口 */
.text
.global entry
entry:
/* ADC 初始化(channel 0, GPIO36) */
adc_init 0
/* 開始 ADC 轉換 */
adc_start 0
adc_wait 0
adc_read 0, sample, 12 // 讀取 12-bit ADC 值
/* 檢查是否超過閾值 */
move r0, sample
lsh r0, r0, 16 // load high 16 bits
move r1, threshold
lsh r1, r1, 16
sub r0, r0, r1 // sample - threshold
jumpr gt, wakeup // 若 sample > threshold → 喚醒
/* 未超過:繼續睡眠 */
halt
wakeup:
/* 喚醒主 CPU */
wake
halt
四、STM32 低功耗模式
STM32 提供三種低功耗模式,本文以 STM32F1/F4 為例:
圖 4:STM32 三種低功耗模式 — Sleep(最快喚醒)、Stop(平衡)、Standby(最省電)
| 模式 | CPU 狀態 | SRAM | 喚醒源 | 喚醒時間 | 典型電流 |
|---|---|---|---|---|---|
| Sleep | 停止 | 保留 | 任何中斷 | ~5 μs | ~3 mA |
| Stop | 停止 | 保留 | EXTI (GPIO/RTC/USB) | ~10 μs | ~50 μA |
| Standby | 斷電 | 遺失 | WKUP Pin/RTC/NRST | ~500 μs | ~2 μA |
4.1 STM32 RTC 喚醒(Stop 模式)
// stm32_rtc_stop.c — STM32 RTC 定時喚醒(Stop 模式)
#include "stm32f1xx_hal.h"
RTC_HandleTypeDef hrtc;
void HAL_MspInit(void)
{
HAL_PWR_EnableBkUpAccess(); // 啟用備份域存取
}
void enter_stop_mode(void)
{
// 設定 RTC 喚醒:20 秒後
HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 20000, RTC_WAKEUPCLOCK_CK_SPRE_16BITS);
// 進入 Stop 模式
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
// === 從此處繼續(喚醒後) ===
// 重新配置系統時鐘(HSE/PLL)
SystemClock_Config();
}
// RTC 喚醒中斷回呼
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{
// Stop 模式喚醒後,此中斷被觸發
// 通常不需要在此做任何事,直接返回 main()
}
4.2 STM32 Standby 模式(最低功耗)
// stm32_standby.c — STM32 Standby 模式
#include "stm32f1xx_hal.h"
void enter_standby_mode(void)
{
// 清除 Wake-up 旗標
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
// 啟用 RTC 喚醒(40 秒)
HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 40000, RTC_WAKEUPCLOCK_CK_SPRE_16BITS);
// 啟用 WKUP Pin (PA0) 外部喚醒
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
// 進入 Standby 模式
HAL_PWR_EnterSTANDBYMode();
// === 不會到達此處 ===
// Standby 喚醒等同於復位,程式從頭開始執行
}
// 檢查復位原因
void check_reset_cause(void)
{
if (__HAL_RCC_GET_FLAG(RCC_FLAG_SFTRST)) {
printf("軟體復位\n");
}
if (__HAL_PWR_GET_FLAG(PWR_FLAG_SB)) {
printf("Standby 喚醒復位\n");
}
// 清除旗標
__HAL_RCC_CLEAR_RESET_FLAGS();
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
}
五、ESP32 vs STM32 低功耗比較
| 項目 | ESP32 | STM32 (F1/F4) |
|---|---|---|
| 最小睡眠電流 | ~5 μA (Deep-sleep) | ~2 μA (Standby) |
| RTC 保留電流 | ~2.5 μA (Hibernation) | ~1.4 μA (VBAT backup) |
| Wake-up 時間 | ~5 ms (Deep-sleep → Active) | ~500 μs (Standby) |
| SRAM 保留 | Deep-sleep: RTC RAM (8KB) 保留 | Stop: 全部保留 / Standby: 不保留 |
| 喚醒來源 | Timer/GPIO/Touch/ULP | RTC/EXTI/WKUP Pin |
| 睡眠中運算 | ULP 協處理器可執行自訂程式 | 無(可選 RTC 或 LPTIM) |
| WiFi 連線時間 | ~1~3 秒 (連接 AP) | 需外接 WiFi 模組 (ESP8266/AT) |
| 最佳應用 | WiFi 感測器、閘道器 | 超低功耗感測器節點 |
六、生產級 Deep Sleep 設計指南
6.1 上傳資料策略
每次喚醒後上傳資料是最耗電的操作。優化策略:
- 合併資料:記錄多次讀取值後一次上傳(如每小時上傳 60 筆每分鐘資料)
- 使用快速連接:ESP32 WiFi 啟用 ESP-NOW 可省去 AP 連線時間
- 條件式上傳:只有資料變化超過閾值時才上傳
- 批次上傳:利用 RTC RAM(ESP32)或外部 EEPROM 快取資料
6.2 RTC_DATA_ATTR 變數設計
// 保留在 RTC RAM 中的資料結構
RTC_DATA_ATTR struct {
uint32_t boot_count; // 總開機次數
uint32_t error_count; // 錯誤計數
float last_temperature; // 上次測量的溫度
uint8_t data_buffer[64]; // 資料快取區
uint32_t crc32; // 資料完整性檢查
} rtc_data;
// 每次喚醒後更新 RTC 資料
void save_to_rtc_mem(float temp, float hum) {
rtc_data.boot_count++;
rtc_data.last_temperature = temp;
rtc_data.data_buffer[rtc_data.boot_count % 64] = (uint8_t)(temp * 10);
rtc_data.crc32 = esp_crc32_le(0, (uint8_t*)&rtc_data, sizeof(rtc_data) - 4);
}
6.3 電源管理最佳實踐
- 關閉不必要的週邊:進入睡眠前關閉 ADC、I2C、SPI 等週邊時鐘
- GPIO 狀態管理:睡眠前將所有 GPIO 設為高阻抗輸入或固定電平,避免漏電
- 電壓調節器:ESP32 可選擇啟用 RTC 調節器(效率高但啟動慢)或數位調節器(反之)
- Flash 斷電:ESP32 在 Deep Sleep 前自動斷開 Flash 電源,但若使用外部 Flash 則需手動控制
- WiFi 完全關閉:esp_deep_sleep_start() 前呼叫 WiFi.disconnect(true) 和 WiFi.mode(WIFI_OFF)
- RTC 記憶體選擇:只在 RTC_SLOW_MEM 保留必要資料,RTC_FAST_MEM 可關閉節電
七、總結
Deep Sleep 是 IoT 裝置從「桌上原型」走向「實際部署」的關鍵技術。本文涵蓋了 ESP32 的五種喚醒來源與完整的 Arduino/IDF 程式碼、STM32 三級低功耗模式的 HAL 實作,以及電池續航力的估算方法。關鍵在於:每次喚醒都在「做事」,每次睡眠都在「省電」 — 正確設計喚醒週期、資料上傳策略與 GPIO 狀態管理,就能讓你的感測器節點以一顆電池運行數年。
文章評論