0x6A Logbook

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

Watchdog Timer(看門狗計時器)完整教學:從原理到 STM32/ESP32 實作

2026 年 5 月 30 日 6點熱度 0人點贊 0條評論

前言

Watchdog Timer(看門狗計時器,WDT)是嵌入式系統中最重要的可靠性保障機制。在工業控制、IoT 設備、車載電子等需要長時間穩定運行的場景中,WDT 能在系統因軟體錯誤、硬體干擾或電源不穩而「卡住」時自動復位,讓設備自我修復。本文將從 WDT 的基本原理講起,深入 STM32 IWDG 與 ESP32 TWDT 的實作細節,並提供完整的生產級程式碼範例。

一、Watchdog Timer 基本原理

1.1 什麼是 Watchdog Timer?

Watchdog Timer 是一個硬體計時器,其核心邏輯非常簡單:

  1. 啟動計數:系統啟動後 WDT 開始計數(遞減或遞增)
  2. 定期餵狗(Kick):正常運行時,軟體在計數器溢位前將其重置
  3. 系統復位:若軟體因故未在時間內餵狗,WDT 計數器歸零 → 觸發系統硬體復位

WDT Normal Operation

圖 1:Watchdog Timer 正常工作 — 主程式中每次循環末尾餵狗,計數器持續重置

1.2 為什麼需要 WDT?

即使程式寫得再嚴謹,嵌入式系統仍然可能遇到:

  • 硬體干擾:電源雜訊、EMI 導致 PC 指標錯亂或暫存器值異常
  • 通訊卡住:I2C/SPI 從設備無回應,while 迴圈無法跳出
  • 記憶體異常:棧溢出(Stack Overflow)、堆積碎片導致 malloc 失敗
  • RTOS 死鎖:多工環境下的互斥鎖死鎖導致所有任務停擺
  • 低電量行為異常:電壓低於 MCU 最低工作電壓時邏輯行為不可預測

WDT Application Scenarios

圖 5:Watchdog Timer 常見應用場景 — 從硬體保護到工業自動化

二、STM32 IWDG(Independent Watchdog)

2.1 IWDG 硬體架構

STM32 的 IWDG 是獨立看門狗,使用專屬的 LSI(Low Speed Internal,約 32 kHz)振盪器,即使主時鐘故障仍能運作。其內部結構包含:

  • LSI 時鐘源:獨立 RC 振盪器,約 30~60 kHz(典型 32 kHz)
  • Prescaler(除頻器):可設為 4/8/16/32/64/128/256 倍除頻
  • 12-bit 遞減計數器:範圍 0~4095
  • Key Register(KVR):寫入 0xAAAA 重載計數器,寫入 0x5555 允許配置
  • Status Register(SR):PVU(Prescaler Update)和 RVU(Reload Update)旗標

STM32 IWDG Block Diagram

圖 3:STM32 IWDG 內部架構 — LSI 經除頻後驅動 12-bit 計數器

2.2 IWDG 超時計算

除頻值 LSI=32kHz 時 Prescaler 最大超時 (4096 ticks) 典型設定 (Reload=0xFFF)
4 8,192 Hz 0.5 秒 125 μs / tick
8 4,096 Hz 1.0 秒 250 μs / tick
16 2,048 Hz 2.0 秒 500 μs / tick
32 1,024 Hz 4.0 秒 1.0 ms / tick
64 512 Hz 8.0 秒 2.0 ms / tick
128 256 Hz 16.0 秒 4.0 ms / tick
256 128 Hz 32.0 秒 8.0 ms / tick

公式:Timeout = (Prescaler × Reload_Value) / LSI_Frequency

例如 Prescaler=64, Reload=0xFFF(4095):Timeout = (64 × 4096) / 32000 ≈ 8.19 秒

2.3 STM32 HAL IWDG 程式碼

// stm32_iwdg_wdt.c — STM32 IWDG 初始化與餵狗範例
#include "stm32f1xx_hal.h"

IWDG_HandleTypeDef hiwdg;

// IWDG 初始化:超時約 4 秒 (LSI=32kHz, Prescaler=32, Reload=0xFFF)
void WDT_Init(void)
{
    hiwdg.Instance = IWDG;
    hiwdg.Init.Prescaler = IWDG_PRESCALER_32;     // 32 kHz / 32 = 1 kHz
    hiwdg.Init.Reload    = 0xFFF;                  // 4096 ticks × 1ms = ~4.096 秒
    if (HAL_IWDG_Init(&hiwdg) != HAL_OK)
    {
        Error_Handler();  // IWDG 啟動失敗(通常不會發生)
    }
}

// 餵狗函式 — 主循環中定期調用
void WDT_Feed(void)
{
    HAL_IWDG_Refresh(&hiwdg);  // 寫入 0xAAAA 到 KVR
}

// 注意:IWDG 一旦啟動無法關閉!
// 除非觸發系統復位,否則 WDT 持續運行

2.4 STM32 IWDG 注意事項

  • 無法關閉:IWDG 一旦使能(透過 IWDG->KR 寫 0xCCCC),只有系統復位才能停用
  • 寫保護:修改 Prescaler 或 Reload 前須先向 KVR 寫入 0x5555
  • 狀態旗標:修改 Prescaler 後需等待 PVU 清除(約 5~6 個 LSI 週期)
  • 低功耗模式:IWDG 在 Stop 和 Standby 模式下仍能運行
  • HAL 延遲:HAL_IWDG_Init() 內部包含等待 PVU/RVU 的 while 迴圈,建議在 main 初始化時一次性完成

三、ESP32 TWDT(Task Watchdog Timer)

3.1 TWDT 與 IWDG 的差異

ESP32 提供兩種 WDT:

  • TWDT(Task Watchdog Timer):監控 RTOS 任務是否在超時時間內執行特定操作(如 taskYIELD 或餵狗)
  • IWDT(Interrupt Watchdog Timer):監控 ISR 處理是否超過允許時間,防止中斷卡死
  • RTC WDT:由 RTC 模組驅動,可在 Deep Sleep 模式下運作

與 STM32 IWDG 最大的不同在於:ESP32 TWDT 是以任務為單位的監控機制,能精確指出哪個任務卡住,而非僅觸發全域復位。

3.2 ESP32 TWDT 程式碼

// esp32_twdt.c — ESP32 TWDT 初始化與餵狗範例
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_task_wdt.h"

// TWDT 初始化:超時 10 秒,監控所有任務
void WDT_Init(void)
{
    // 初始化 TWDT,超時 10 秒
    esp_task_wdt_config_t twdt_config = {
        .timeout_ms = 10000,          // 10 秒超時
        .trigger_panic = true,        // 超時時觸發 panic(系統復位)
    };
    ESP_ERROR_CHECK(esp_task_wdt_init(&twdt_config));

    // 將當前任務註冊到 TWDT 監控
    ESP_ERROR_CHECK(esp_task_wdt_add(NULL));  // NULL = 當前任務
}

// 餵狗函式 — 在任務主循環中定期調用
void WDT_Feed(void)
{
    esp_task_wdt_reset();  // 重置當前任務的 WDT 計數器
}

// 任務範例:處理感測器資料
void sensor_task(void *pvParameters)
{
    WDT_Init();  // 註冊此任務到 TWDT

    while (1) {
        read_sensor_data();       // 讀取感測器
        process_data();           // 資料處理
        send_via_mqtt();          // MQTT 發送
        WDT_Feed();               // 餵狗
        vTaskDelay(pdMS_TO_TICKS(100));  // 100ms 延遲
    }
}

// 注意:TWDT 預設監控 IDLE 任務
// 若 IDLE 任務未得到 CPU 時間(高優先級任務霸佔 CPU),TWDT 也會觸發

3.3 ESP32 IWDT(Interrupt Watchdog)

// esp32_iwdt.c — 中斷看門狗設定
#include "soc/cpu.h"
#include "esp_intr_alloc.h"

// IWDT 超時時間設定(ISP 級別)
void IWDT_Init(void)
{
    // IWDT 在 ESP32-IDF 中預設啟用
    // 可在 menuconfig 中設定超時:
    //   CONFIG_ESP_INT_WDT_TIMEOUT_MS=300
    //   CONFIG_ESP_INT_WDT=y

    // IWDT 會在 ISR 執行超過設定時間時觸發 panic
    // 幫助開發者找出耗時過長的中斷處理程式
    printf("IWDT active: interrupt watchdog enabled\n");
}

四、WDT 觸發行為與時序分析

4.1 正常工作 vs 超時觸發

WDT Normal

圖 1:正常運作 — 主任務在計數器溢位前寫入 KVR(0xAAAA)重置計數器

WDT Timeout

圖 2:超時觸發 — 任務卡住後未在 WDT 溢位前餵狗,計數器歸零 → 產生系統復位脈衝

4.2 從復位原因診斷問題

系統重啟後,檢查復位原因能快速定位是否由 WDT 觸發:

// STM32 — 檢查復位原因
if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST)) {
    printf("系統復位:IWDG Watchdog 觸發!\n");
}
__HAL_RCC_CLEAR_RESET_FLAGS();

// ESP32 — 檢查復位原因
esp_reset_reason_t reason = esp_reset_reason();
if (reason == ESP_RST_WDT) {
    printf("系統復位:Task Watchdog 觸發!\n");
} else if (reason == ESP_RST_INT_WDT) {
    printf("系統復位:Interrupt Watchdog 觸發!\n");
}

五、STM32 vs ESP32 WDT 比較

WDT Comparison

圖 4:STM32 IWDG 與 ESP32 TWDT 差異比較 — IWDG 為硬體層級獨立計時器,TWDT 為 RTOS 任務監控

項目 STM32 IWDG ESP32 TWDT
時鐘源 獨立 LSI (32 kHz RC) RC 振盪器 (~615 kHz)
計數器 12-bit 遞減 (0~4095) 32-bit 遞增 (0~2³²)
除頻器 4/8/16/32/64/128/256 無硬體除頻
最長超時 ~26.3 秒 (÷256 × 4096) ~7 秒
監控粒度 硬體層級(系統全域) 任務層級(可指定任務)
可否關閉 不可(啟動後永久運行) 可(esp_task_wdt_deinit)
低功耗支援 Stop/Standby Active/Modem-sleep
復位方式 硬體復位(不可遮罩) 硬體復位 / panic 中斷

六、進階技巧與最佳實踐

6.1 多層級 WDT 架構

在工業級產品中,建議採用多層級 WDT:

  1. 第一層 — MCU IWDG:硬體層級最後防線,超時設定在 5~10 秒
  2. 第二層 — RTOS TWDT:監控每個任務的執行週期,超時設定在 2~5 秒
  3. 第三層 — 外部 WDT IC(如 MAX6369):在 MCU 完全失效時由外部硬體切斷電源

6.2 餵狗策略

  • 不要在 ISR 中餵狗:中斷服務程式中餵狗會遮蔽主程式卡住的問題
  • 不要在高優先級任務中餵狗:低優先級任務卡住時高優先級任務仍可正常餵狗
  • 使用「心跳任務」:建立一個專屬任務,只在確認所有子系統正常後才餵狗
  • 分階段餵狗:在長任務中設置多個檢查點,分別餵不同的 WDT

6.3 生產級心跳任務範例

// heartbeat_task.c — 生產級心跳監控任務
void heartbeat_task(void *pvParameters)
{
    TickType_t last_wake = xTaskGetTickCount();
    const TickType_t interval = pdMS_TO_TICKS(1000);  // 1 秒

    while (1) {
        vTaskDelayUntil(&last_wake, interval);

        // 檢查各子系統狀態
        bool comm_ok     = (HAL_GetTick() - last_comm_tick < 5000);
        bool sensor_ok   = (sensor_error_count < 3); bool stack_ok = (uxTaskGetStackHighWaterMark(NULL) > 128);

        if (comm_ok && sensor_ok && stack_ok) {
            WDT_Feed();  // 所有子系統正常,餵狗
            led_green_on();
        } else {
            led_red_on();
            // 記錄錯誤但不餵狗,讓 WDT 復位
            log_error("System unhealthy: comm=%d sensor=%d stack=%d",
                      comm_ok, sensor_ok, stack_ok);
        }
    }
}

6.4 常見陷阱

  • Debugger 干擾:使用偵錯器時 WDT 會持續計數,導致中斷點停留時觸發復位。解決方案:在 DEBUG 模式下禁用 WDT,或在 HardFault_Handler 中無限迴圈
  • Bootloader 相容性:若 IWDG 在 Bootloader 中已啟用,主程式啟動前需先重新初始化超時
  • WDT 初始化時機:應在 main() 的最早階段初始化 WDT,防止系統初始化過程中卡住
  • 任務堆疊不足:TWDT 每次餵狗涉及系統呼叫,堆疊空間不足可能導致餵狗本身失敗

七、總結

Watchdog Timer 是嵌入式系統中投資報酬率最高的可靠性機制 — 只需幾行程式碼就能讓系統在異常時自動復位。無論是 STM32 的硬體 IWDG 還是 ESP32 的任務層級 TWDT,正確設計餵狗策略、搭配復位原因檢查,就能大幅提升產品的長期穩定性。在工業 4.0 和 IoT 應用中,WDT 不是選配,而是標配。

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

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