跳转至

看门狗定时器

概述

看门狗定时器(Watchdog Timer,WDT)是一种硬件或软件机制,用于检测系统故障并在系统挂起时自动复位。

看门狗原理

Text Only
1
2
3
4
启动看门狗 -> 定期喂狗 -> 系统正常 -> 继续运行
                ▼ (超时未喂狗)
            系统复位

看门狗类型

类型 说明 特点
独立看门狗 独立时钟源 不受系统时钟影响
窗口看门狗 必须在窗口内喂狗 更严格的监控
软看门狗 软件实现 灵活但依赖系统

STM32看门狗编程

独立看门狗(IWDG)

C
#include "stm32f4xx_hal.h"

IWDG_HandleTypeDef hiwdg;

void IWDG_Init(void)
{
    hiwdg.Instance = IWDG;
    hiwdg.Init.Prescaler = IWDG_PRESCALER_256;
    hiwdg.Init.Reload = 0xFFF;  // 约32秒超时
    // 超时时间 = (Reload + 1) * Prescaler / LSI频率
    // 例:(4096 * 256) / 32000 ≈ 32.7秒
    
    HAL_IWDG_Init(&hiwdg);
}

// 喂狗
void feed_watchdog(void)
{
    HAL_IWDG_Refresh(&hiwdg);
}

// 在主循环中定期喂狗
void main(void)
{
    IWDG_Init();
    
    while (1) {
        // 执行任务
        
        // 任务完成后喂狗
        feed_watchdog();
        
        HAL_Delay(100);
    }
}

窗口看门狗(WWDG)

C
WWDG_HandleTypeDef hwwdg;

void WWDG_Init(void)
{
    hwwdg.Instance = WWDG;
    hwwdg.Init.Prescaler = WWDG_PRESCALER_8;
    hwwdg.Init.Window = 0x50;    // 窗口值
    hwwdg.Init.Counter = 0x7F;   // 计数器值
    hwwdg.Init.EWIMode = WWDG_EWI_ENABLE;  // 使能早期唤醒中断
    
    HAL_WWDG_Init(&hwwdg);
}

// 窗口看门狗必须在窗口内喂狗
// 不能太早(计数器 > Window)也不能太晚(计数器 = 0)

// 早期唤醒中断(计数器到达0x40)
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg)
{
    // 在这里喂狗
    HAL_WWDG_Refresh(hwwdg, 0x7F);
}

Linux看门狗编程

使用watchdog设备

Bash
1
2
3
4
5
6
7
# 查看看门狗设备
ls /dev/watchdog*

# 查看看门狗信息
cat /sys/class/watchdog/watchdog0/identity
cat /sys/class/watchdog/watchdog0/timeleft
cat /sys/class/watchdog/watchdog0/timeout

C语言编程

C
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/watchdog.h>

int watchdog_fd;

void watchdog_init(void)
{
    watchdog_fd = open("/dev/watchdog", O_WRONLY);
    
    // 设置超时时间
    int timeout = 30;
    ioctl(watchdog_fd, WDIOC_SETTIMEOUT, &timeout);
    
    // 获取超时时间
    ioctl(watchdog_fd, WDIOC_GETTIMEOUT, &timeout);
}

// 喂狗
void watchdog_feed(void)
{
    write(watchdog_fd, "\0", 1);
    // 或
    ioctl(watchdog_fd, WDIOC_KEEPALIVE, 0);
}

// 关闭看门狗(需要支持)
void watchdog_disable(void)
{
    write(watchdog_fd, "V", 1);
    close(watchdog_fd);
}

// 使用示例
int main(void)
{
    watchdog_init();
    
    while (1) {
        // 执行任务
        
        watchdog_feed();
        sleep(10);
    }
    
    watchdog_disable();
    return 0;
}

Magic Close

C
1
2
3
4
5
6
// 写入"V"字符后关闭,看门狗停止
write(watchdog_fd, "V", 1);
close(watchdog_fd);

// 如果直接关闭,看门狗继续运行
close(watchdog_fd);  // 风险:系统可能意外复位

看门狗守护进程

systemd watchdog

Bash
# /etc/systemd/system/my-service.service
[Unit]
Description=My Service

[Service]
Type=notify
WatchdogSec=30s
ExecStart=/usr/bin/my-service
Restart=on-failure

[Install]
WantedBy=multi-user.target
C
#include <systemd/sd-daemon.h>

void main(void)
{
    // 通知systemd服务已启动
    sd_notify(0, "READY=1");
    
    while (1) {
        // 执行任务
        
        // 通知systemd服务正常
        sd_notify(0, "WATCHDOG=1");
        
        sleep(10);
    }
}

FreeRTOS看门狗任务

C
#include "FreeRTOS.h"
#include "task.h"
#include "stm32f4xx_hal.h"

#define WATCHDOG_TIMEOUT_MS    5000
#define FEED_INTERVAL_MS       1000

static TaskHandle_t watchdog_task_handle;

void watchdog_task(void *pvParameters)
{
    IWDG_HandleTypeDef hiwdg;
    
    // 初始化看门狗
    hiwdg.Instance = IWDG;
    hiwdg.Init.Prescaler = IWDG_PRESCALER_256;
    hiwdg.Init.Reload = 0xFFF;
    HAL_IWDG_Init(&hiwdg);
    
    while (1) {
        // 检查各任务状态
        if (all_tasks_healthy()) {
            HAL_IWDG_Refresh(&hiwdg);
        } else {
            // 有任务异常,不喂狗,触发复位
            vTaskSuspend(NULL);
        }
        
        vTaskDelay(pdMS_TO_TICKS(FEED_INTERVAL_MS));
    }
}

void create_watchdog_task(void)
{
    xTaskCreate(watchdog_task, "Watchdog", 128, NULL, 1, &watchdog_task_handle);
}

看门狗监控多个任务

C
#define NUM_TASKS    3

typedef struct {
    TaskHandle_t handle;
    uint32_t last_feed;
    uint32_t timeout_ms;
    bool healthy;
} task_monitor_t;

static task_monitor_t task_monitors[NUM_TASKS];

// 任务注册监控
void register_task(int index, TaskHandle_t handle, uint32_t timeout_ms)
{
    task_monitors[index].handle = handle;
    task_monitors[index].timeout_ms = timeout_ms;
    task_monitors[index].healthy = true;
    task_monitors[index].last_feed = xTaskGetTickCount();
}

// 任务报告存活
void task_alive(int index)
{
    task_monitors[index].last_feed = xTaskGetTickCount();
}

// 检查所有任务
bool check_all_tasks(void)
{
    uint32_t now = xTaskGetTickCount();
    
    for (int i = 0; i < NUM_TASKS; i++) {
        uint32_t elapsed = (now - task_monitors[i].last_feed) * portTICK_PERIOD_MS;
        
        if (elapsed > task_monitors[i].timeout_ms) {
            task_monitors[i].healthy = false;
            return false;  // 有任务超时
        }
    }
    
    return true;
}

void watchdog_task(void *pvParameters)
{
    while (1) {
        if (check_all_tasks()) {
            HAL_IWDG_Refresh(&hiwdg);
        }
        
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

看门狗超时计算

STM32 IWDG

Text Only
1
2
3
4
5
LSI频率 = 32kHz(典型值)
超时时间 = (Reload + 1) × Prescaler / LSI

例:Prescaler = 256, Reload = 4095
超时 = 4096 × 256 / 32000 = 32.768秒

常用配置

C
// 1秒超时
hiwdg.Init.Prescaler = IWDG_PRESCALER_32;
hiwdg.Init.Reload = 999;  // 1000 × 32 / 32000 = 1秒

// 5秒超时
hiwdg.Init.Prescaler = IWDG_PRESCALER_256;
hiwdg.Init.Reload = 624;  // 625 × 256 / 32000 = 5秒

// 最大超时(约32秒)
hiwdg.Init.Prescaler = IWDG_PRESCALER_256;
hiwdg.Init.Reload = 0xFFF;

看门狗设计建议

  1. 超时时间:设置为最长任务执行时间的2-3倍
  2. 喂狗位置:在主循环或任务末尾喂狗
  3. 任务监控:监控所有关键任务的健康状态
  4. 复位记录:复位后检查复位原因,判断是否为看门狗复位
  5. 禁止意外关闭:生产环境禁用看门狗关闭功能

复位原因检测

C
void check_reset_reason(void)
{
    if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST)) {
        printf("系统由独立看门狗复位\n");
        __HAL_RCC_CLEAR_RESET_FLAGS();
    }
    
    if (__HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST)) {
        printf("系统由窗口看门狗复位\n");
        __HAL_RCC_CLEAR_RESET_FLAGS();
    }
    
    if (__HAL_RCC_GET_FLAG(RCC_FLAG_PORRST)) {
        printf("系统上电复位\n");
    }
    
    if (__HAL_RCC_GET_FLAG(RCC_FLAG_SFTRST)) {
        printf("软件复位\n");
    }
}

参考资料