0x6A Logbook

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

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

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

Watchdog Timer 完整教學封面

前言:為什麼量產產品幾乎都有一顆看門狗

在實驗室裡,程式當掉最糟的結果就是「重開就好」。但只要這個裝置被裝在客戶的機房、變電箱或屋頂上,一次死機就代表一次客訴、一趟出差、甚至一次賠償。

看門狗計時器(Watchdog Timer)就是為了這種情境存在的:它是一個獨立於主程式之外的硬體計時器,會在最壞的情況下把系統重新拉起來。

它的設計哲學很粗暴,但非常有效 —— 因為它假設「軟體終究會壞」,而硬體不會陪著一起壞。

看門狗的邏輯只有三步

Watchdog Timer 的基本運作流程

正常運作時你甚至感覺不到它的存在:主迴圈結尾餵一次狗,計數器就一直被歸零。真正有趣的是「沒餵到」的那一刻 —— 那代表軟體已經無法控制局面,讓硬體接手是最後的保險。

為什麼還需要它:五種真實的失效情境

看門狗要處理的五種失效情境

注意這五種情境的共同點:程式沒有當機,只是再也不前進。除錯器看不到例外交代、log 也不再增加,只剩一顆心跳燈還亮著。這種狀態沒有 WDT 就真的救不回來。

STM32 IWDG:獨立到幾乎沒人能救它

STM32 的 IWDG(Independent Watchdog)之所以叫「獨立」,是因為它的時鐘來自獨立的 LSI(約 32 kHz RC 振盪器),不依賴主時鐘。就算你的 PLL 跑掉、主時鐘掛了,它照樣在數。

// 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 持續運行

設定時最容易卡住的是「超時到底怎麼算」。因為它用 LSI 當來源,所以要先把除頻值與 Reload 換算成實際秒數:

除頻值 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

這張表建議存起來。實務上最常用的是除頻 32 或 64 那一列 —— 超時落在 4 到 8 秒之間,對絕大多數應用都夠用,又不會因為一次正常的長運算就誤觸發。

還有一個必須知道的特性:IWDG 一旦啟動就不能關。它上電後就永久運行,連除錯時都得自己想辦法處理(後面「常見陷阱」會講)。

ESP32 TWDT:以「任務」為單位的看門狗

ESP32 的設計思路跟 STM32 很不一樣:它的 TWDT(Task Watchdog Timer)監控的是RTOS 任務,而不是整顆系統。

// 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 也會觸發

差別在哪?當 TWDT 觸發時,它可以告訴你是哪個任務卡住;而 STM32 的 IWDG 只會給你一句「系統復位了,原因不明」。在複雜的多任務系統裡,這個差異會省下好幾天除錯時間。

除了 TWDT,ESP32 還有另外兩顆看門狗:

// 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");
}

ESP32 三種看門狗的分工

其中 IWDT 值得特別提一下:它監控的是中斷服務程式。中斷寫太長、卡在裡面,整個系統的即時性就毀了 —— IWDT 就是專門來抓這種問題的。

觸發之後:先問「是誰讓我重啟的」

從復位原因診斷問題

// 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 與 ESP32 的看門狗比一比

兩邊的設計哲學差很多,這張表是最快的理解方式:

項目 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 中斷

看幾件事就好:STM32 的 IWDG 不可關閉、最長可達 26.3 秒、監控粒度是整顆系統;ESP32 的 TWDT 可以關、最長約 7 秒、但能精確到任務層級。

這兩種取捨沒有誰對誰錯:要「絕對不會被軟體關掉的保險」就選 IWDG 那條路;要「知道是誰卡住、還能針對單一任務監控」就是 TWDT 的強項。工業級產品通常兩個都開。

進階:多層架構與餵狗策略

先看架構。單一顆看門狗只能救「大部分」的失效,真正要求可靠度的產品會這樣疊:

工業級三層看門狗架構

接著是餵狗的位置 —— 這件事比「餵幾次」重要得多:

餵狗策略與原因

原文提供的生產級範例值得完整看一遍,它把「心跳任務」的邏輯寫得很清楚:只有在通訊、感測器、堆疊三項都正常時才餵狗;任何一項異常就只記錄、不餵,讓看門狗自己動手。

// 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);
        }
    }
}

這段程式碼最漂亮的地方是:它把「系統健康判斷」與「處置」分開了。判斷交給心跳任務,處置交給硬體 —— 軟體再怎麼錯,也無法阻止硬體把自己重開。

四個會讓你懷疑人生的陷阱

看門狗的四個常見陷阱

其中 Debugger 那一項幾乎每個人第一次用都會中:你停在斷點思考人生,看門狗也在思考人生 —— 然後把你重開。解法很簡單,在 DEBUG build 把 WDT 關掉,或是在 HardFault_Handler 裡直接死等。

總結

看門狗是那種「平常完全無感、出事時救命」的機制。四個重點收好:

  • 它的前提是軟體會壞:所以救援工作交給硬體
  • 餵狗的位置比次數重要:別在 ISR、別在高優先級任務餵
  • 復位原因一定要記錄:不然你永遠在猜
  • 要求可靠度就疊多層:MCU 硬體層、RTOS 任務層、外部 IC 各司其職

下次你的裝置在客戶那裡掛掉時,你會感謝自己當初多寫了那三行。

標籤: ESP32 工業通訊
最後更新:2026 年 9 月 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