0x6A Logbook

0x6A Logbook
Shi6a的筆記本
  1. 首頁
  2. 程式開發
  3. 正文

Deep Sleep(深度睡眠)完整教學:從原理到 STM32/ESP32 實作

2026 年 6 月 2 日 3點熱度 0人點贊 0條評論

前言

Deep Sleep(深度睡眠)是電池供電 IoT 裝置續航力的核心技術。一個感測器節點若 24/7 全速運行,2000 mAh 電池只能撐一天;但若善用 Deep Sleep 讓裝置在 99% 時間處於休眠狀態,續航力可長達數月甚至數年。ESP32 與 STM32 都提供多級低功耗模式,本文將從硬體架構切入,詳細解析 RTC Timer 定時喚醒、GPIO 外部喚醒、Touch 喚醒的機制與程式碼實作,並提供電池續航力估算方法。

一、為何需要 Deep Sleep?

在 IoT 應用中,裝置通常不需要持續運作:

  • 感測器節點:每分鐘讀取一次溫度,其餘時間休眠
  • 智慧門鎖:平時待命,只有門磁觸發或藍牙開鎖時喚醒
  • 追蹤器:每小時回報一次 GPS 座標
  • 氣象站:每 15 分鐘上傳一次資料

Battery Life Estimation

圖 6:2000 mAh 電池在不同喚醒週期下的續航力 — 每小時喚醒 1 秒可達 7.9 年

Sleep-Wake Cycle

圖 1:Deep Sleep 工作週期 — Active 模式消耗 ~80 mA,Deep Sleep 僅 ~5 μA,RTC Timer 或 GPIO 喚醒

二、ESP32 低功耗模式

ESP32 Power Modes

圖 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() {
    // 永遠不會執行到這裡
}

RTC Timer Wake-up

圖 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() {}

GPIO Wake-up

圖 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 為例:

STM32 Sleep Modes

圖 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 狀態管理,就能讓你的感測器節點以一顆電池運行數年。

標籤: ESP32 工業通訊
最後更新:2026 年 6 月 2 日

shi6a

這個人很懶,什麼都沒留下

點贊
< 上一篇

文章評論

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回覆

COPYRIGHT © 2026 0x6A Logbook. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang