在設計物聯網(IoT)或電池供電的嵌入式專案時,功耗控制是決定專案成敗的關鍵因素。如果讓 ESP32 一直處於正常運作狀態(約 100mA 至 240mA 的電流消耗),電池很快就會耗盡。
幸運的是,ESP32 提供了強大的深度睡眠(Deep Sleep)模式。在深度睡眠中,主要 CPU、大部分的 RAM 以及所有數位週邊都會被關閉,僅保留 RTC(即時時鐘)控制器、RTC 週邊與 RTC 記憶體運作,此時功耗可以降至 10µA 至 150µA 之間。
本教學將帶您完整掌握如何在 Arduino IDE 中設定 ESP32 進入深度睡眠,並詳細解說四種不同的喚醒機制:定時器喚醒、觸控喚醒、外部單腳喚醒 (ext0) 與外部多腳喚醒 (ext1),並附上接線原理圖與完整程式碼。
1. ESP32 功耗模式與為什麼選擇深度睡眠?
ESP32 支援 5 種不同的電源模式,我們可以根據應用需求靈活切換:
| 電源模式 | CPU | Wi-Fi / 藍牙 | RTC 週邊 | 核心功耗(典型值) |
|---|---|---|---|---|
| Active (運作) | 運作中 | 運作中 | 運作中 | 95mA ~ 240mA |
| Modem-sleep | 運作中 | 關閉 | 運作中 | 15mA ~ 20mA |
| Light-sleep | 暫停 (泡泡) | 關閉 | 運作中 | ~0.8mA |
| Deep-sleep | 關閉 | 關閉 | 運作中 | 10µA ~ 150µA |
| Hibernation (冬眠) | 關閉 | 關閉 | 關閉 | ~5µA |





深度睡眠的喚醒本質:軟重啟
需要特別注意的是,當 ESP32 從深度睡眠中喚醒時,它不會從它睡著的那一行代碼繼續執行,而是會重新執行 setup()。這相當於一次軟重啟(Soft Reset)。
如何在重啟後保留數據?(RTC 記憶體應用)
因為重啟後一般 RAM 中的變數都會遺失,如果我們想在睡眠重啟後保留某些數據(例如:記錄累計開機次數 bootCount),我們必須使用關鍵字 RTC_DATA_ATTR 將變數宣告儲存在 RTC 記憶體(RTC Fast Memory)中:
RTC_DATA_ATTR int bootCount = 0; // 此變數將儲存在 RTC 記憶體中,睡眠重啟後不會遺失
2. 喚醒源實作一:定時器喚醒(Timer Wake Up)
定時器喚醒是最簡單、最常用的喚醒方式。讓 ESP32 沉睡指定的秒數後,自動醒來執行任務(例如讀取感測器、上傳資料到雲端),隨後再次入睡。


接線示意圖
此模式純屬內部時鐘計時,不需要連接任何外部硬體腳位。
示範程式碼
/*
* ESP32 定時器喚醒範例
*/
#define uS_TO_S_FACTOR 1000000ULL // 微秒轉換為秒的係數
#define TIME_TO_SLEEP 5 // 睡眠時間(秒)
RTC_DATA_ATTR int bootCount = 0; // 記錄開機次數
void print_wakeup_reason(){
esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
switch(wakeup_reason) {
case ESP_SLEEP_WAKEUP_TIMER: Serial.println("喚醒原因:定時器時間到"); break;
case ESP_SLEEP_WAKEUP_TOUCHPAD: Serial.println("喚醒原因:觸控喚醒"); break;
case ESP_SLEEP_WAKEUP_EXT0: Serial.println("喚醒原因:外部信號喚醒 (ext0)"); break;
case ESP_SLEEP_WAKEUP_EXT1: Serial.println("喚醒原因:外部信號喚醒 (ext1)"); break;
default: Serial.printf("喚醒原因:非深度睡眠喚醒 (代碼: %d)\n", wakeup_reason); break;
}
}
void setup(){
Serial.begin(115200);
delay(1000); // 讓串口監視器有時間初始化
bootCount++;
Serial.println("開機/喚醒次數: " + String(bootCount));
print_wakeup_reason();
// 設定定時喚醒源
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
Serial.println("設定 ESP32 進入深度睡眠,將在 " + String(TIME_TO_SLEEP) + " 秒後醒來...");
Serial.println("正在入睡...");
Serial.flush();
esp_deep_sleep_start();
Serial.println("這行代碼永遠不會被執行");
}
void loop(){
// 深度睡眠下 loop 內不會運行
}
3. 喚醒源實作二:觸控喚醒(Touch Wake Up)
ESP32 內建電容式觸摸感應器,擁有多個觸摸引腳(Touch Pins)。我們可以利用這些引腳,讓使用者透過「摸一下導線」來喚醒睡眠中的 ESP32。
ESP32 觸控引腳對照
ESP32 共有 10 個觸控腳位,在 Arduino IDE 中分別以 T0 到 T9 標註:
- T0 (GPIO 4)
- T1 (GPIO 0)
- T2 (GPIO 2)
- T3 (GPIO 15)
- T4 (GPIO 13)
- T5 (GPIO 12)
- T6 (GPIO 14)
- T7 (GPIO 27)
- T8 (GPIO 33)
- T9 (GPIO 32)
接線示意圖
在觸控腳位(例如 T3, 即 GPIO 15)連接一根杜邦線,並在其末端接上一片銅片(或直接觸摸杜邦線的金屬裸露端即可)。



示範程式碼
/*
* ESP32 觸控喚醒範例
*/
#define Threshold 40 // 觸摸閾值。數值越大越靈敏,若常誤觸請調低(通常在 20-50 之間)
RTC_DATA_ATTR int bootCount = 0;
void print_wakeup_reason(){
esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
if(wakeup_reason == ESP_SLEEP_WAKEUP_TOUCHPAD){
Serial.println("喚醒原因:觸控引腳觸發");
}
}
void print_wakeup_touchpad(){
touch_pad_t touchPin = esp_sleep_get_touchpad_wakeup_status();
Serial.printf("觸發喚醒的觸控通道為 T%d (GPIO 腳位對應請對照表)\n", touchPin);
}
void callback(){
// 必須宣告此回呼函數,但由於深度睡眠後會直接走 setup 重啟,因此內容可留空
}
void setup(){
Serial.begin(115200);
delay(1000);
bootCount++;
Serial.println("開機/喚醒次數: " + String(bootCount));
print_wakeup_reason();
if(esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_TOUCHPAD){
print_wakeup_touchpad();
}
// 設定觸控中斷腳位:我們使用 T3 (GPIO 15)
touchAttachInterrupt(T3, callback, Threshold);
// 啟用觸控喚醒源
esp_sleep_enable_touchpad_wakeup();
Serial.println("設定觸控喚醒完畢,正在入睡...");
Serial.flush();
esp_deep_sleep_start();
}
void loop(){}
4. 喚醒源實作三:外部單腳喚醒 (ext0)
外部喚醒利用 GPIO 的電位變化(高電位或低電位)來觸起喚醒。 這需要用到 ESP32 內部擁有 RTC 週邊功能 的腳位(RTC_GPIO)。當 ESP32 處於深度睡眠時,只有以下這些 RTC_GPIO 引腳可以正常工作:
💡 可用於外部喚醒的 RTC_GPIO 腳位:0, 2, 4, 12, 13, 14, 15, 25, 26, 27, 32, 33, 34, 35, 36, 37, 38, 39。
ext0 模式特點
ext0 是針對單一腳位的外部喚醒。當指定的 RTC_GPIO 達到指定電平(1 高電位 或 0 低電位)時,就會喚醒。此模式會保持 RTC 週邊在睡眠時開啟,功耗稍高於 ext1。
接線示意圖
我們將一個按鍵開關連接到 GPIO 33(屬於 RTC_IO)。按鍵一端接 VCC,另一端接 GPIO 33,並加上一個 10KΩ 的下拉電阻(Pull-down)接到 GND,確保按鍵未按下時處於穩定的低電位。



示範程式碼
/*
* ESP32 外部單腳喚醒 (ext0) 範例
*/
RTC_DATA_ATTR int bootCount = 0;
void setup(){
Serial.begin(115200);
delay(1000);
bootCount++;
Serial.println("開機/喚醒次數: " + String(bootCount));
if(esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_EXT0){
Serial.println("喚醒原因:由 RTC_IO 外部訊號觸發 (ext0)");
}
// 設定 ext0 喚醒:當 GPIO 33 偵測到 高電位 (1) 時喚醒
esp_sleep_enable_ext0_wakeup(GPIO_NUM_33, 1);
Serial.println("設定 ext0 喚醒,正在入睡...");
Serial.flush();
esp_deep_sleep_start();
}
void loop(){}
5. 喚醒源實作四:外部多腳喚醒 (ext1)
如果您想使用多個不同的按鍵都可以喚醒 ESP32,您就必須使用 ext1 模式。 ext1 模式使用 RTC 控制器(RTC Controller),因此在睡眠期間不需要開啟 RTC 週邊,功耗最低。
ext1 觸發模式
ESP_EXT1_WAKEUP_ANY_HIGH:當遮罩中任意一個指定的 GPIO 變為高電位時喚醒。ESP_EXT1_WAKEUP_ALL_LOW:當遮罩中所有指定的 GPIO 同時變為低電位時喚醒。
GPIO 位元遮罩(Bitmask)計算
ext1 需要輸入一個 64 位元的十六進位數值來代表哪些 GPIO 腳位被選用。 計算公式為: $$\text{Bitmask} = 2^{\text{GPIO\_NUM}}$$
例如,若我們想使用 GPIO 2 和 GPIO 15 作為喚醒源:
- GPIO 2 對應為:$2^2 = 4$
- GPIO 15 對應為:$2^{15} = 32768$
- 合併後的位元值為:$4 + 32768 = 32772$
- 將 32772 轉換為十六進位即為:
0x8004
在 Arduino 代碼中,可以直接寫成:
#define BUTTON_PIN_BITMASK 0x8004 // 二進位即代表 GPIO 2 與 GPIO 15
接線示意圖
將兩個按鍵分別接到 GPIO 2 和 GPIO 15。按鍵未按下時均透過 10KΩ 下拉電阻接到 GND;按下時接通 VCC(高電平)。




示範程式碼
/*
* ESP32 外部多腳喚醒 (ext1) 範例
*/
#define BUTTON_PIN_BITMASK 0x8004 // GPIO 2 和 GPIO 15 的位元遮罩
RTC_DATA_ATTR int bootCount = 0;
void print_GPIO_wake_up(){
// 獲取觸發喚醒的 GPIO 狀態(回傳的是 64bit 的 Bitmask)
uint64_t GPIO_reason = esp_sleep_get_ext1_wakeup_status();
// 計算出到底是哪一個 GPIO 觸發了喚醒
int gpio_num = log(GPIO_reason) / log(2);
Serial.printf("觸發喚醒的實體腳位為:GPIO %d\n", gpio_num);
}
void setup(){
Serial.begin(115200);
delay(1000);
bootCount++;
Serial.println("開機/喚醒次數: " + String(bootCount));
if(esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_EXT1){
Serial.println("喚醒原因:外部多腳訊號觸發 (ext1)");
print_GPIO_wake_up();
}
// 設定 ext1 喚醒:當遮罩中的任意腳位 (GPIO 2 或 15) 變為高電位時喚醒
esp_sleep_enable_ext1_wakeup(BUTTON_PIN_BITMASK, ESP_EXT1_WAKEUP_ANY_HIGH);
Serial.println("設定 ext1 喚醒,正在入睡...");
Serial.flush();
esp_deep_sleep_start();
}
void loop(){}
💡 總結
ESP32 的深度睡眠模式是物聯網開發者的強大工具。透過不同的喚醒源:
- 定時器喚醒:適合定時上傳數據的遙測系統。
- 觸控喚醒:適合智慧開關、手動觸發的精緻小家電。
- 外部喚醒 (ext0 & ext1):適合警報感應器、開門開關,或低功耗遙控器按鍵。
善用 RTC_DATA_ATTR 來在睡眠期間暫存資料,並根據週邊按鍵數靈活選擇 ext0(單按鍵)或 ext1(多按鍵,功耗更低),能讓您的 ESP32 電池供電專案壽命從原本的數天延長至數月甚至數年!
如果您對 ESP32 在物聯網通訊(如 MQTT 控制)有興趣,歡迎參考我的另一篇文章:物聯網接案實戰:打造無線 Modbus 控制盒與 APP 遠端控制電熱水器系統。
如果您在實作睡眠電路時遇到任何問題,歡迎在下方留言討論!



