BLOG8
跳转至

看门狗驱动与系统可靠性

概述

看门狗(Watchdog)是嵌入式系统中一个重要的安全机制,用于检测和恢复系统故障。当系统因软件错误、硬件故障或外部干扰而陷入死循环或停止响应时,看门狗能够自动复位系统,使其恢复正常运行。这对于无人值守的嵌入式系统尤为重要。

本文将系统介绍看门狗的工作原理、类型、配置方法和应用策略,帮助你设计更可靠的嵌入式系统。

完成本文学习后,你将能够:

  • 理解看门狗的工作原理和作用
  • 掌握独立看门狗(IWDG)和窗口看门狗(WWDG)的区别
  • 配置和使用看门狗定时器
  • 设计合理的喂狗策略
  • 实现系统故障检测和自动恢复
  • 提高系统的可靠性和稳定性

背景知识

看门狗的作用

看门狗是一个硬件定时器,其核心功能是监控系统运行状态。如果系统在规定时间内没有"喂狗"(重置看门狗计数器),看门狗就会触发系统复位。

主要作用:

  1. 故障检测:检测系统是否陷入死循环或停止响应
  2. 自动恢复:在检测到故障后自动复位系统
  3. 提高可靠性:减少人工干预,提高系统可用性
  4. 安全保障:防止系统长时间处于异常状态

典型应用场景:

  • 工业控制系统(无人值守)
  • 汽车电子系统(安全关键)
  • 医疗设备(高可靠性要求)
  • 物联网设备(远程部署)
  • 航空航天系统(极端环境)

看门狗的工作原理

看门狗的工作原理类似于一个倒计时定时器:

初始化 → 启动计数 → 定期喂狗 → 继续计数
                ↓
            超时未喂狗
                ↓
            触发系统复位

关键概念:

  1. 超时时间:看门狗计数器从初始值减到0所需的时间
  2. 喂狗操作:重新加载看门狗计数器,防止超时
  3. 复位触发:计数器减到0时,触发系统复位
  4. 喂狗窗口:某些看门狗要求在特定时间窗口内喂狗

看门狗的类型

STM32微控制器通常提供两种类型的看门狗:

1. 独立看门狗(IWDG - Independent Watchdog)

  • 时钟源:独立的低速内部时钟(LSI,约32kHz)
  • 特点:完全独立于主系统时钟,即使主时钟失效也能工作
  • 超时范围:几毫秒到几秒
  • 应用:检测系统级故障,如时钟失效、程序跑飞
  • 优势:可靠性高,不受主系统影响

2. 窗口看门狗(WWDG - Window Watchdog)

  • 时钟源:APB1时钟
  • 特点:具有时间窗口限制,喂狗必须在窗口期内
  • 超时范围:几十微秒到几十毫秒
  • 应用:检测程序执行时序异常
  • 优势:能检测程序执行过快或过慢

对比表:

特性 独立看门狗(IWDG) 窗口看门狗(WWDG)
时钟源 LSI(32kHz) APB1时钟
独立性 完全独立 依赖系统时钟
超时范围 0.125ms - 32s 几十μs - 几十ms
窗口功能 无 有
中断功能 无 有(提前中断)
停止模式 继续运行 停止运行
调试模式 可配置 可配置
应用场景 系统级故障检测 程序时序检测

看门狗的时钟计算

理解看门狗的时钟计算对于正确配置超时时间至关重要。

独立看门狗(IWDG)时钟计算:

LSI频率:约32kHz(实际可能在30-40kHz之间)

预分频器:4, 8, 16, 32, 64, 128, 256

计数器频率 = LSI频率 / 预分频器

超时时间 = 重载值 / 计数器频率

示例:
LSI = 32kHz
预分频器 = 64
重载值 = 4095(最大值)

计数器频率 = 32000 / 64 = 500Hz
超时时间 = 4095 / 500 = 8.19秒

窗口看门狗(WWDG)时钟计算:

WWDG时钟 = APB1时钟 / 4096

预分频器:1, 2, 4, 8

计数器频率 = WWDG时钟 / 预分频器

超时时间 = (计数器值 - 窗口值) / 计数器频率

示例:
APB1 = 42MHz
预分频器 = 8
计数器值 = 127(最大值)
窗口值 = 80

WWDG时钟 = 42000000 / 4096 = 10254Hz
计数器频率 = 10254 / 8 = 1281.75Hz
超时时间 = (127 - 80) / 1281.75 = 36.7ms

核心内容

1. 独立看门狗(IWDG)配置

独立看门狗是最常用的看门狗类型,配置简单,可靠性高。

主要寄存器:

寄存器 全称 功能
KR Key Register 控制寄存器(写入特定值执行操作)
PR Prescaler Register 预分频器寄存器
RLR Reload Register 重载值寄存器
SR Status Register 状态寄存器

配置步骤:

  1. 使能写保护解除(写入0x5555到KR)
  2. 配置预分频器(PR)
  3. 配置重载值(RLR)
  4. 等待寄存器更新完成
  5. 启动看门狗(写入0xCCCC到KR)
  6. 定期喂狗(写入0xAAAA到KR)

代码示例:

#include "stm32f4xx.h"

/**
 * @brief  初始化独立看门狗
 * @param  timeout_ms: 超时时间(毫秒)
 * @retval 无
 */
void IWDG_Init(uint32_t timeout_ms) {
    // 1. 使能写保护解除
    IWDG->KR = 0x5555;

    // 2. 配置预分频器
    // PR = 6 表示预分频器为256
    // LSI频率约32kHz,计数器频率 = 32000/256 = 125Hz
    IWDG->PR = 6;  // 预分频器256

    // 3. 计算并配置重载值
    // 超时时间(ms) = RLR / (LSI / 预分频器) * 1000
    // RLR = 超时时间(ms) * (LSI / 预分频器) / 1000
    uint32_t reload = (timeout_ms * 125) / 1000;

    // 限制重载值范围(0-4095)
    if (reload > 4095) reload = 4095;
    if (reload < 1) reload = 1;

    IWDG->RLR = reload;

    // 4. 等待寄存器更新完成
    while (IWDG->SR != 0);

    // 5. 启动看门狗
    IWDG->KR = 0xCCCC;
}

/**
 * @brief  喂狗(重载看门狗计数器)
 * @param  无
 * @retval 无
 */
void IWDG_Feed(void) {
    IWDG->KR = 0xAAAA;
}

/**
 * @brief  主函数示例
 */
int main(void) {
    // 系统初始化
    SystemInit();

    // 初始化看门狗,超时时间1000ms
    IWDG_Init(1000);

    // 主循环
    while (1) {
        // 执行任务
        DoTask();

        // 定期喂狗(必须在1000ms内)
        IWDG_Feed();

        // 延时
        HAL_Delay(500);
    }
}

配置说明:

  1. 预分频器选择:
  2. PR = 0: 预分频器4(超时范围:0.125ms - 512ms)
  3. PR = 1: 预分频器8(超时范围:0.25ms - 1024ms)
  4. PR = 2: 预分频器16(超时范围:0.5ms - 2048ms)
  5. PR = 3: 预分频器32(超时范围:1ms - 4096ms)
  6. PR = 4: 预分频器64(超时范围:2ms - 8192ms)
  7. PR = 5: 预分频器128(超时范围:4ms - 16384ms)
  8. PR = 6: 预分频器256(超时范围:8ms - 32768ms)

  9. 重载值计算:

  10. 根据所需超时时间和预分频器计算
  11. 重载值范围:0-4095
  12. 超时时间 = RLR × 预分频器 / LSI频率

  13. 写保护机制:

  14. PR和RLR寄存器有写保护
  15. 必须先写入0x5555解除保护
  16. 修改完成后自动恢复保护

2. 窗口看门狗(WWDG)配置

窗口看门狗提供更精确的时序监控,能检测程序执行过快或过慢的情况。

主要寄存器:

寄存器 全称 功能
CR Control Register 控制寄存器(计数器值和使能位)
CFR Configuration Register 配置寄存器(窗口值和预分频器)
SR Status Register 状态寄存器(中断标志)

配置步骤:

  1. 使能WWDG时钟
  2. 配置预分频器
  3. 配置窗口值
  4. 配置计数器初始值
  5. 使能提前唤醒中断(可选)
  6. 启动看门狗
  7. 在窗口期内喂狗

代码示例:

/**
 * @brief  初始化窗口看门狗
 * @param  无
 * @retval 无
 */
void WWDG_Init(void) {
    // 1. 使能WWDG时钟
    RCC->APB1ENR |= RCC_APB1ENR_WWDGEN;

    // 2. 配置预分频器和窗口值
    // WDGTB = 3: 预分频器8
    // W[6:0] = 80: 窗口值
    WWDG->CFR = (3 << 7) | 80;

    // 3. 使能提前唤醒中断
    WWDG->CFR |= WWDG_CFR_EWI;

    // 4. 配置NVIC中断
    NVIC_SetPriority(WWDG_IRQn, 2);
    NVIC_EnableIRQ(WWDG_IRQn);

    // 5. 设置计数器初始值并启动
    // T[6:0] = 127: 计数器初始值
    // WDGA = 1: 启动看门狗
    WWDG->CR = (1 << 7) | 127;
}

/**
 * @brief  喂狗(重载窗口看门狗计数器)
 * @param  无
 * @retval 无
 * @note   必须在窗口期内调用
 */
void WWDG_Feed(void) {
    // 重载计数器值(必须大于窗口值)
    WWDG->CR = (1 << 7) | 127;
}

/**
 * @brief  WWDG中断服务函数
 * @param  无
 * @retval 无
 */
void WWDG_IRQHandler(void) {
    // 检查提前唤醒中断标志
    if (WWDG->SR & WWDG_SR_EWIF) {
        // 清除中断标志
        WWDG->SR &= ~WWDG_SR_EWIF;

        // 在中断中喂狗
        WWDG_Feed();

        // 可以在这里执行其他操作
        // 例如:记录日志、LED指示等
    }
}

/**
 * @brief  主函数示例
 */
int main(void) {
    SystemInit();

    // 初始化窗口看门狗
    WWDG_Init();

    while (1) {
        // 执行任务
        DoTask();

        // 注意:喂狗操作在中断中完成
        // 主循环只需要正常执行任务

        HAL_Delay(10);
    }
}

窗口看门狗特点:

  1. 时间窗口:
  2. 喂狗必须在计数器值小于窗口值时进行
  3. 过早喂狗(计数器 > 窗口值)会触发复位
  4. 过晚喂狗(计数器 < 0x40)会触发复位

  5. 提前唤醒中断:

  6. 计数器减到0x40时触发中断
  7. 可以在中断中喂狗
  8. 提供额外的安全保障

  9. 时序监控:

  10. 能检测程序执行过快(过早喂狗)
  11. 能检测程序执行过慢(超时)
  12. 适合对时序要求严格的应用

3. 喂狗策略设计

合理的喂狗策略是保证系统可靠性的关键。

策略1:主循环喂狗

最简单的策略,在主循环中定期喂狗。

int main(void) {
    SystemInit();
    IWDG_Init(1000);  // 1秒超时

    while (1) {
        // 任务1
        Task1();

        // 任务2
        Task2();

        // 任务3
        Task3();

        // 喂狗
        IWDG_Feed();

        // 延时
        HAL_Delay(500);
    }
}

优点: - 实现简单 - 适合简单的单任务系统

缺点: - 如果某个任务卡死,无法喂狗 - 无法检测任务执行异常

策略2:定时器喂狗

使用定时器中断定期喂狗。

volatile uint8_t g_task_flags = 0;

// 定时器中断(每100ms)
void TIM6_IRQHandler(void) {
    if (TIM6->SR & TIM_SR_UIF) {
        TIM6->SR &= ~TIM_SR_UIF;

        // 检查所有任务是否正常执行
        if (g_task_flags == 0x07) {  // 所有任务都执行了
            IWDG_Feed();
            g_task_flags = 0;  // 清除标志
        }
        // 如果有任务未执行,不喂狗,触发复位
    }
}

int main(void) {
    SystemInit();
    IWDG_Init(1000);
    TIM6_Init();  // 初始化定时器

    while (1) {
        // 任务1
        Task1();
        g_task_flags |= 0x01;

        // 任务2
        Task2();
        g_task_flags |= 0x02;

        // 任务3
        Task3();
        g_task_flags |= 0x04;

        HAL_Delay(50);
    }
}

优点: - 能检测任务是否正常执行 - 更可靠的故障检测

缺点: - 实现稍复杂 - 需要额外的定时器资源

策略3:RTOS任务喂狗

在RTOS环境中,使用专门的看门狗任务。

// 看门狗任务
void WatchdogTask(void *pvParameters) {
    uint32_t task_status = 0;

    while (1) {
        // 等待所有任务的信号
        task_status = 0;

        // 检查任务1状态
        if (xSemaphoreTake(Task1Semaphore, 100) == pdTRUE) {
            task_status |= 0x01;
        }

        // 检查任务2状态
        if (xSemaphoreTake(Task2Semaphore, 100) == pdTRUE) {
            task_status |= 0x02;
        }

        // 检查任务3状态
        if (xSemaphoreTake(Task3Semaphore, 100) == pdTRUE) {
            task_status |= 0x04;
        }

        // 所有任务正常才喂狗
        if (task_status == 0x07) {
            IWDG_Feed();
        }

        vTaskDelay(500);  // 延时500ms
    }
}

// 其他任务在执行完成后发送信号
void Task1(void *pvParameters) {
    while (1) {
        // 执行任务
        DoWork();

        // 发送信号表示任务正常
        xSemaphoreGive(Task1Semaphore);

        vTaskDelay(100);
    }
}

优点: - 适合多任务系统 - 能监控所有任务状态 - 灵活性高

缺点: - 需要RTOS支持 - 实现较复杂

4. 复位原因检测

系统复位后,需要判断是否由看门狗触发,以便采取相应措施。

复位标志寄存器:

STM32的RCC_CSR寄存器记录了复位原因。

/**
 * @brief  检测复位原因
 * @param  无
 * @retval 复位原因
 */
typedef enum {
    RESET_CAUSE_UNKNOWN = 0,
    RESET_CAUSE_LOW_POWER,
    RESET_CAUSE_WINDOW_WATCHDOG,
    RESET_CAUSE_INDEPENDENT_WATCHDOG,
    RESET_CAUSE_SOFTWARE,
    RESET_CAUSE_POR_PDR,
    RESET_CAUSE_PIN,
    RESET_CAUSE_BOR
} ResetCause_t;

ResetCause_t GetResetCause(void) {
    ResetCause_t cause = RESET_CAUSE_UNKNOWN;

    // 读取复位标志
    uint32_t csr = RCC->CSR;

    if (csr & RCC_CSR_LPWRRSTF) {
        cause = RESET_CAUSE_LOW_POWER;
    }
    else if (csr & RCC_CSR_WWDGRSTF) {
        cause = RESET_CAUSE_WINDOW_WATCHDOG;
    }
    else if (csr & RCC_CSR_IWDGRSTF) {
        cause = RESET_CAUSE_INDEPENDENT_WATCHDOG;
    }
    else if (csr & RCC_CSR_SFTRSTF) {
        cause = RESET_CAUSE_SOFTWARE;
    }
    else if (csr & RCC_CSR_PORRSTF) {
        cause = RESET_CAUSE_POR_PDR;
    }
    else if (csr & RCC_CSR_PINRSTF) {
        cause = RESET_CAUSE_PIN;
    }
    else if (csr & RCC_CSR_BORRSTF) {
        cause = RESET_CAUSE_BOR;
    }

    // 清除复位标志
    RCC->CSR |= RCC_CSR_RMVF;

    return cause;
}

/**
 * @brief  处理复位原因
 * @param  无
 * @retval 无
 */
void HandleResetCause(void) {
    ResetCause_t cause = GetResetCause();

    switch (cause) {
        case RESET_CAUSE_INDEPENDENT_WATCHDOG:
            // 独立看门狗复位
            printf("System reset by IWDG\r\n");
            // 记录日志、发送告警等
            LogError("IWDG Reset");
            break;

        case RESET_CAUSE_WINDOW_WATCHDOG:
            // 窗口看门狗复位
            printf("System reset by WWDG\r\n");
            LogError("WWDG Reset");
            break;

        case RESET_CAUSE_SOFTWARE:
            // 软件复位
            printf("System reset by software\r\n");
            break;

        case RESET_CAUSE_PIN:
            // 引脚复位(按键复位)
            printf("System reset by pin\r\n");
            break;

        case RESET_CAUSE_POR_PDR:
            // 上电复位
            printf("System power on reset\r\n");
            break;

        default:
            printf("Unknown reset cause\r\n");
            break;
    }
}

/**
 * @brief  主函数
 */
int main(void) {
    SystemInit();
    UART_Init();  // 初始化串口用于调试

    // 检测并处理复位原因
    HandleResetCause();

    // 初始化看门狗
    IWDG_Init(1000);

    while (1) {
        // 正常任务
        DoTask();
        IWDG_Feed();
        HAL_Delay(500);
    }
}

复位原因应用:

  1. 故障统计:
  2. 记录看门狗复位次数
  3. 分析系统稳定性
  4. 发现潜在问题

  5. 故障恢复:

  6. 根据复位原因采取不同措施
  7. 看门狗复位后进入安全模式
  8. 记录故障日志

  9. 调试辅助:

  10. 开发阶段识别问题
  11. 通过串口输出复位原因
  12. 帮助定位故障点

设计考虑

1. 超时时间选择

选择合适的看门狗超时时间是关键。

选择原则:

  1. 任务周期分析:
  2. 分析最长任务执行时间
  3. 考虑最坏情况(中断、延时等)
  4. 留有足够的安全余量

  5. 超时时间计算:

    超时时间 = 最长任务时间 × 安全系数
    
    安全系数建议:
    - 简单系统:1.5 - 2.0
    - 复杂系统:2.0 - 3.0
    - 实时系统:1.2 - 1.5
    

  6. 实际示例:

    假设系统任务:
    - 任务1:50ms
    - 任务2:100ms
    - 任务3:150ms
    - 总计:300ms
    
    超时时间 = 300ms × 2 = 600ms
    实际配置:1000ms(留有更多余量)
    

常见超时时间:

应用场景 推荐超时时间 说明
快速响应系统 100-500ms 实时控制、快速反馈
一般应用 500-2000ms 数据采集、通信
慢速系统 2-10秒 复杂计算、大数据处理
超长任务 10-30秒 文件操作、网络传输

2. 调试模式配置

开发阶段,看门狗可能影响调试,需要合理配置。

调试选项:

/**
 * @brief  配置看门狗调试模式
 * @param  无
 * @retval 无
 */
void IWDG_DebugConfig(void) {
    // 在调试模式下停止看门狗
    // 需要在调试器中配置,或使用DBGMCU寄存器

    #ifdef DEBUG
    // 调试模式下停止IWDG
    DBGMCU->APB1FZ |= DBGMCU_APB1_FZ_DBG_IWDG_STOP;

    // 调试模式下停止WWDG
    DBGMCU->APB1FZ |= DBGMCU_APB1_FZ_DBG_WWDG_STOP;
    #endif
}

调试策略:

  1. 开发阶段:
  2. 禁用看门狗或设置很长的超时时间
  3. 使用条件编译控制
  4. 在调试器中停止看门狗

  5. 测试阶段:

  6. 启用看门狗,使用正常超时时间
  7. 测试故障恢复功能
  8. 验证喂狗策略

  9. 发布版本:

  10. 必须启用看门狗
  11. 使用经过验证的配置
  12. 移除调试代码

3. 多看门狗组合

在某些应用中,可以同时使用IWDG和WWDG。

组合策略:

/**
 * @brief  初始化双看门狗系统
 * @param  无
 * @retval 无
 */
void DualWatchdog_Init(void) {
    // IWDG:监控系统级故障(长超时)
    IWDG_Init(5000);  // 5秒超时

    // WWDG:监控程序时序(短超时)
    WWDG_Init();      // 约50ms超时
}

/**
 * @brief  双看门狗喂狗
 * @param  无
 * @retval 无
 */
void DualWatchdog_Feed(void) {
    // 喂IWDG
    IWDG_Feed();

    // WWDG在中断中自动喂狗
}

应用场景:

  • IWDG:检测系统级故障(时钟失效、程序跑飞)
  • WWDG:检测程序时序异常(任务执行异常)
  • 组合使用:提供多层保护

4. 故障记录与分析

记录看门狗复位信息有助于分析系统问题。

故障记录实现:

// 故障记录结构体
typedef struct {
    uint32_t reset_count;        // 复位次数
    uint32_t last_reset_time;    // 最后复位时间
    ResetCause_t last_cause;     // 最后复位原因
    uint32_t task_state;         // 任务状态
} FaultRecord_t;

// 使用备份寄存器或Flash存储
#define FAULT_RECORD_ADDR  0x08080000  // Flash地址

/**
 * @brief  保存故障记录
 * @param  record: 故障记录指针
 * @retval 无
 */
void SaveFaultRecord(FaultRecord_t *record) {
    // 解锁Flash
    HAL_FLASH_Unlock();

    // 擦除扇区
    FLASH_EraseInitTypeDef erase;
    erase.TypeErase = FLASH_TYPEERASE_SECTORS;
    erase.Sector = FLASH_SECTOR_11;
    erase.NbSectors = 1;
    erase.VoltageRange = FLASH_VOLTAGE_RANGE_3;

    uint32_t error;
    HAL_FLASHEx_Erase(&erase, &error);

    // 写入数据
    uint32_t *data = (uint32_t *)record;
    for (int i = 0; i < sizeof(FaultRecord_t) / 4; i++) {
        HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, 
                         FAULT_RECORD_ADDR + i * 4, 
                         data[i]);
    }

    // 锁定Flash
    HAL_FLASH_Lock();
}

/**
 * @brief  读取故障记录
 * @param  record: 故障记录指针
 * @retval 无
 */
void LoadFaultRecord(FaultRecord_t *record) {
    uint32_t *src = (uint32_t *)FAULT_RECORD_ADDR;
    uint32_t *dst = (uint32_t *)record;

    for (int i = 0; i < sizeof(FaultRecord_t) / 4; i++) {
        dst[i] = src[i];
    }
}

/**
 * @brief  处理看门狗复位
 * @param  无
 * @retval 无
 */
void HandleWatchdogReset(void) {
    FaultRecord_t record;

    // 读取故障记录
    LoadFaultRecord(&record);

    // 更新记录
    record.reset_count++;
    record.last_reset_time = HAL_GetTick();
    record.last_cause = GetResetCause();

    // 保存记录
    SaveFaultRecord(&record);

    // 输出故障信息
    printf("Watchdog reset #%d\r\n", record.reset_count);
    printf("Last reset: %d ms ago\r\n", 
           HAL_GetTick() - record.last_reset_time);

    // 如果复位次数过多,进入安全模式
    if (record.reset_count > 10) {
        EnterSafeMode();
    }
}

故障分析:

  1. 统计分析:
  2. 复位频率
  3. 复位原因分布
  4. 故障模式识别

  5. 趋势分析:

  6. 复位次数增加趋势
  7. 特定条件下的复位
  8. 环境因素影响

  9. 预防措施:

  10. 根据分析结果优化代码
  11. 调整看门狗参数
  12. 改进系统设计

常见问题

Q1: 看门狗复位后系统无法正常启动?

可能原因:

  1. 初始化代码执行时间过长
  2. 看门狗在启动时自动启动(选项字节配置)
  3. 超时时间设置过短

解决方案:

int main(void) {
    // 1. 尽快喂狗
    IWDG_Feed();

    // 2. 快速初始化关键模块
    SystemInit();
    IWDG_Feed();

    // 3. 初始化其他模块
    GPIO_Init();
    IWDG_Feed();

    UART_Init();
    IWDG_Feed();

    // 4. 重新配置看门狗(如果需要)
    IWDG_Init(2000);  // 增加超时时间

    while (1) {
        // 正常运行
    }
}

Q2: 如何在调试时避免看门狗复位?

方法1:条件编译

int main(void) {
    SystemInit();

    #ifndef DEBUG
    // 只在发布版本中启用看门狗
    IWDG_Init(1000);
    #endif

    while (1) {
        DoTask();

        #ifndef DEBUG
        IWDG_Feed();
        #endif
    }
}

方法2:调试器配置

void IWDG_DebugInit(void) {
    #ifdef DEBUG
    // 调试模式下停止看门狗
    DBGMCU->APB1FZ |= DBGMCU_APB1_FZ_DBG_IWDG_STOP;
    #endif

    IWDG_Init(1000);
}

方法3:使用很长的超时时间

#ifdef DEBUG
#define WATCHDOG_TIMEOUT  30000  // 30秒
#else
#define WATCHDOG_TIMEOUT  1000   // 1秒
#endif

IWDG_Init(WATCHDOG_TIMEOUT);

Q3: 看门狗超时时间不准确?

可能原因:

  1. LSI时钟频率偏差(IWDG)
  2. 计算公式错误
  3. 预分频器配置错误

解决方案:

/**
 * @brief  校准LSI时钟频率
 * @param  无
 * @retval LSI实际频率(Hz)
 */
uint32_t CalibrateLS I(void) {
    // 使用TIM5测量LSI频率
    // 1. 使能TIM5和LSI
    RCC->APB1ENR |= RCC_APB1ENR_TIM5EN;
    RCC->CSR |= RCC_CSR_LSION;
    while (!(RCC->CSR & RCC_CSR_LSIRDY));

    // 2. 配置TIM5输入捕获
    TIM5->PSC = 0;
    TIM5->ARR = 0xFFFFFFFF;
    TIM5->CCMR1 = TIM_CCMR1_CC1S_0;  // IC1映射到TI1
    TIM5->CCER = TIM_CCER_CC1E;

    // 3. 选择LSI作为TIM5输入
    TIM5->OR = TIM5_OR_TI1_RMP_0;

    // 4. 启动TIM5
    TIM5->CR1 = TIM_CR1_CEN;

    // 5. 捕获两次上升沿
    while (!(TIM5->SR & TIM_SR_CC1IF));
    uint32_t capture1 = TIM5->CCR1;
    TIM5->SR &= ~TIM_SR_CC1IF;

    while (!(TIM5->SR & TIM_SR_CC1IF));
    uint32_t capture2 = TIM5->CCR1;

    // 6. 计算LSI频率
    uint32_t lsi_freq = SystemCoreClock / (capture2 - capture1);

    return lsi_freq;
}

/**
 * @brief  精确初始化IWDG
 * @param  timeout_ms: 超时时间(毫秒)
 * @retval 无
 */
void IWDG_InitAccurate(uint32_t timeout_ms) {
    // 校准LSI频率
    uint32_t lsi_freq = CalibrateLSI();

    // 选择合适的预分频器
    uint8_t prescaler = 6;  // 256
    uint32_t counter_freq = lsi_freq / 256;

    // 计算重载值
    uint32_t reload = (timeout_ms * counter_freq) / 1000;
    if (reload > 4095) reload = 4095;

    // 配置IWDG
    IWDG->KR = 0x5555;
    IWDG->PR = prescaler;
    IWDG->RLR = reload;
    while (IWDG->SR != 0);
    IWDG->KR = 0xCCCC;
}

Q4: 如何判断是否需要使用看门狗?

需要使用看门狗的场景:

  1. 无人值守系统:
  2. 工业控制
  3. 远程监控
  4. 物联网设备

  5. 安全关键系统:

  6. 汽车电子
  7. 医疗设备
  8. 航空航天

  9. 长期运行系统:

  10. 服务器
  11. 基站设备
  12. 数据采集器

  13. 恶劣环境:

  14. 强电磁干扰
  15. 极端温度
  16. 振动冲击

不需要看门狗的场景:

  1. 开发调试阶段
  2. 有人值守的系统
  3. 可以随时重启的系统
  4. 对可靠性要求不高的应用

Q5: 看门狗和软件复位有什么区别?

看门狗复位:

  • 硬件触发
  • 自动执行
  • 用于故障恢复
  • 可以记录复位原因

软件复位:

/**
 * @brief  软件复位系统
 * @param  无
 * @retval 无
 */
void SystemSoftwareReset(void) {
    // 使用NVIC系统复位
    NVIC_SystemReset();
}
  • 软件触发
  • 主动执行
  • 用于正常重启
  • 可以在复位前保存状态

使用场景对比:

场景 看门狗复位 软件复位
系统死机 ✓ ✗
程序跑飞 ✓ ✗
固件升级 ✗ ✓
配置更新 ✗ ✓
故障恢复 ✓ ✗
正常重启 ✗ ✓

最佳实践

1. 看门狗使用原则

DO(推荐做法):

✅ 在产品发布版本中启用看门狗 ✅ 选择合适的超时时间(留有余量) ✅ 设计可靠的喂狗策略 ✅ 记录和分析复位原因 ✅ 在调试模式下禁用或延长超时 ✅ 测试看门狗功能是否正常 ✅ 在初始化早期喂狗 ✅ 监控所有关键任务

DON'T(避免做法):

❌ 在中断中频繁喂狗 ❌ 设置过短的超时时间 ❌ 忽略复位原因 ❌ 在调试时忘记禁用看门狗 ❌ 在不需要的地方使用看门狗 ❌ 过度依赖看门狗(掩盖问题) ❌ 在多个地方随意喂狗 ❌ 忽略看门狗配置测试

2. 可靠性设计

多层保护策略:

// 第一层:软件自检
typedef struct {
    uint32_t magic;          // 魔术字
    uint32_t checksum;       // 校验和
    uint32_t task_counter;   // 任务计数器
} SystemHealth_t;

SystemHealth_t g_health = {
    .magic = 0xDEADBEEF,
    .checksum = 0,
    .task_counter = 0
};

/**
 * @brief  系统健康检查
 * @param  无
 * @retval 1=健康,0=异常
 */
uint8_t SystemHealthCheck(void) {
    // 检查魔术字
    if (g_health.magic != 0xDEADBEEF) {
        return 0;
    }

    // 检查任务计数器
    static uint32_t last_counter = 0;
    if (g_health.task_counter == last_counter) {
        return 0;  // 任务未执行
    }
    last_counter = g_health.task_counter;

    return 1;
}

// 第二层:看门狗保护
void WatchdogTask(void) {
    if (SystemHealthCheck()) {
        IWDG_Feed();  // 系统健康才喂狗
    }
    // 否则让看门狗复位系统
}

// 第三层:外部看门狗
// 使用外部看门狗芯片(如MAX706)提供额外保护

3. 故障恢复策略

渐进式恢复:

typedef enum {
    RECOVERY_LEVEL_NORMAL = 0,
    RECOVERY_LEVEL_SAFE_MODE,
    RECOVERY_LEVEL_MINIMAL,
    RECOVERY_LEVEL_FACTORY_RESET
} RecoveryLevel_t;

/**
 * @brief  根据复位次数选择恢复策略
 * @param  reset_count: 复位次数
 * @retval 恢复级别
 */
RecoveryLevel_t GetRecoveryLevel(uint32_t reset_count) {
    if (reset_count < 3) {
        return RECOVERY_LEVEL_NORMAL;
    }
    else if (reset_count < 5) {
        return RECOVERY_LEVEL_SAFE_MODE;
    }
    else if (reset_count < 10) {
        return RECOVERY_LEVEL_MINIMAL;
    }
    else {
        return RECOVERY_LEVEL_FACTORY_RESET;
    }
}

/**
 * @brief  执行恢复策略
 * @param  level: 恢复级别
 * @retval 无
 */
void ExecuteRecovery(RecoveryLevel_t level) {
    switch (level) {
        case RECOVERY_LEVEL_NORMAL:
            // 正常启动,加载所有功能
            LoadAllModules();
            break;

        case RECOVERY_LEVEL_SAFE_MODE:
            // 安全模式,只加载核心功能
            LoadCoreModules();
            DisableOptionalFeatures();
            break;

        case RECOVERY_LEVEL_MINIMAL:
            // 最小模式,只保留基本功能
            LoadMinimalModules();
            EnableDiagnostics();
            break;

        case RECOVERY_LEVEL_FACTORY_RESET:
            // 恢复出厂设置
            FactoryReset();
            LoadDefaultConfig();
            break;
    }
}

4. 测试方法

功能测试:

/**
 * @brief  测试看门狗功能
 * @param  无
 * @retval 无
 */
void TestWatchdog(void) {
    printf("Testing watchdog...\r\n");

    // 初始化看门狗(短超时用于测试)
    IWDG_Init(1000);  // 1秒超时

    // 测试1:正常喂狗
    printf("Test 1: Normal feeding\r\n");
    for (int i = 0; i < 5; i++) {
        HAL_Delay(500);
        IWDG_Feed();
        printf("  Fed at %d ms\r\n", HAL_GetTick());
    }
    printf("Test 1: PASS\r\n");

    // 测试2:故意不喂狗,触发复位
    printf("Test 2: Trigger reset\r\n");
    printf("System will reset in 1 second...\r\n");
    HAL_Delay(2000);  // 等待超时

    // 如果执行到这里,说明看门狗未工作
    printf("Test 2: FAIL - Watchdog not working!\r\n");
}

/**
 * @brief  测试复位检测
 * @param  无
 * @retval 无
 */
void TestResetDetection(void) {
    ResetCause_t cause = GetResetCause();

    if (cause == RESET_CAUSE_INDEPENDENT_WATCHDOG) {
        printf("Watchdog reset detected - Test PASS\r\n");
    }
    else {
        printf("Reset cause: %d\r\n", cause);
    }
}

压力测试:

/**
 * @brief  看门狗压力测试
 * @param  无
 * @retval 无
 */
void StressTestWatchdog(void) {
    IWDG_Init(1000);

    // 模拟各种异常情况
    for (int test = 0; test < 10; test++) {
        switch (test) {
            case 0:
                // 正常情况
                for (int i = 0; i < 100; i++) {
                    HAL_Delay(500);
                    IWDG_Feed();
                }
                break;

            case 1:
                // 任务执行时间过长
                HAL_Delay(1500);  // 超时
                break;

            case 2:
                // 死循环
                while (1);
                break;

            case 3:
                // 中断风暴
                while (1) {
                    __disable_irq();
                }
                break;
        }
    }
}

总结

看门狗是嵌入式系统可靠性设计的重要组成部分。通过本文的学习,你应该掌握了看门狗的核心知识和应用方法。

核心要点回顾:

  1. 看门狗类型:
  2. 独立看门狗(IWDG):完全独立,用于系统级故障检测
  3. 窗口看门狗(WWDG):时间窗口限制,用于程序时序检测

  4. 配置要点:

  5. 选择合适的超时时间(留有安全余量)
  6. 正确计算预分频器和重载值
  7. 理解时钟源和计算公式

  8. 喂狗策略:

  9. 主循环喂狗:简单但可靠性有限
  10. 定时器喂狗:能监控任务执行状态
  11. RTOS任务喂狗:适合多任务系统

  12. 故障处理:

  13. 检测复位原因
  14. 记录故障信息
  15. 实施恢复策略

  16. 可靠性设计:

  17. 多层保护机制
  18. 渐进式恢复策略
  19. 完善的测试验证

实践建议:

  1. 开发阶段:
  2. 在调试时禁用或延长超时
  3. 使用条件编译控制
  4. 充分测试看门狗功能

  5. 测试阶段:

  6. 验证超时时间准确性
  7. 测试故障恢复功能
  8. 进行压力测试

  9. 发布阶段:

  10. 确保看门狗已启用
  11. 使用经过验证的配置
  12. 保留故障记录功能

设计原则:

  • 看门狗是最后一道防线,不是解决问题的手段
  • 应该首先提高代码质量,减少故障发生
  • 看门狗用于处理无法预见的异常情况
  • 合理的喂狗策略比看门狗本身更重要

下一步学习:

  • 深入学习系统可靠性设计
  • 掌握故障诊断和分析方法
  • 学习RTOS中的看门狗应用
  • 研究外部看门狗芯片的使用

看门狗虽然简单,但在系统可靠性设计中起着关键作用。正确使用看门狗,能够显著提高系统的稳定性和可用性,特别是在无人值守和安全关键的应用中。

相关内容

同模块内容: - GPIO驱动开发:LED控制实战 - 学习GPIO基础 - 定时器驱动基础与应用 - 理解定时器原理 - DMA驱动开发:高效数据传输 - 提高系统性能

相关主题: - 中断系统基础概念 - 理解中断机制 - 系统可靠性设计 - 可靠性设计方法

官方文档: - STM32F4xx参考手册 - 看门狗章节 - STM32F4xx数据手册 - 看门狗特性 - AN4435: 看门狗应用笔记 - 官方应用指南

参考资料: 1. STM32F4xx参考手册 - STMicroelectronics 2. 嵌入式系统可靠性设计 - 李明 3. 工业控制系统设计 - 王强 4. 看门狗定时器应用指南 - ST官方 5. 嵌入式系统故障诊断 - 张伟

练习题

基础练习

  1. 看门狗配置练习:
  2. 配置IWDG,超时时间为2秒
  3. 在主循环中每1秒喂狗一次
  4. 通过LED指示系统运行状态

  5. 复位检测练习:

  6. 实现复位原因检测功能
  7. 通过串口输出复位原因
  8. 统计看门狗复位次数

  9. 窗口看门狗练习:

  10. 配置WWDG,窗口时间为30-50ms
  11. 在中断中喂狗
  12. 测试窗口限制功能

进阶练习

  1. 喂狗策略设计:
  2. 设计一个多任务系统
  3. 实现任务监控机制
  4. 只有所有任务正常才喂狗

  5. 故障恢复系统:

  6. 实现故障记录功能
  7. 设计渐进式恢复策略
  8. 测试不同级别的恢复

  9. 双看门狗系统:

  10. 同时使用IWDG和WWDG
  11. IWDG监控系统级故障
  12. WWDG监控程序时序

思考题

  1. 为什么独立看门狗使用LSI时钟而不是主系统时钟?

  2. 窗口看门狗的时间窗口有什么作用?在什么场景下需要使用?

  3. 如何设计一个可靠的喂狗策略,既能检测故障又不会误触发?

  4. 看门狗复位和软件复位有什么区别?各自适用于什么场景?

  5. 在RTOS系统中,应该如何设计看门狗监控机制?

实验任务

任务1:基本看门狗应用(必做)

要求: - 配置IWDG,超时时间1秒 - 在主循环中定期喂狗 - 通过按键模拟系统故障(停止喂狗) - LED指示系统状态

验收标准: - 正常运行时LED闪烁 - 按键按下后系统复位 - 串口输出复位原因

任务2:任务监控系统(必做)

要求: - 创建3个模拟任务 - 实现任务状态监控 - 只有所有任务正常才喂狗 - 记录故障任务信息

验收标准: - 能检测任务异常 - 异常时触发看门狗复位 - 输出故障任务编号

任务3:故障记录系统(选做)

要求: - 使用Flash保存故障记录 - 记录复位次数和原因 - 实现故障历史查询 - 支持故障统计分析

验收标准: - 故障信息持久化保存 - 可查询历史记录 - 提供统计报告

任务4:可靠性测试(选做)

要求: - 设计完整的测试方案 - 测试各种故障场景 - 验证恢复策略 - 生成测试报告

评分标准: - 功能完整性(30分) - 代码质量(25分) - 可靠性设计(25分) - 测试覆盖率(20分)


下一步学习建议:

完成本文学习后,建议按以下顺序继续:

  1. DMA驱动开发:高效数据传输 - 学习DMA提高性能
  2. 中断系统基础概念 - 深入理解中断
  3. 系统可靠性设计 - 全面的可靠性设计

学习路线图:

graph LR
    A[看门狗驱动] --> B[中断系统]
    A --> C[定时器驱动]
    B --> D[系统可靠性]
    C --> D
    D --> E[RTOS应用]
    E --> F[工业控制]

祝你学习顺利!如有问题,欢迎在社区讨论。


文档信息: - 最后更新:2024-01-15 - 版本:v1.0 - 作者:嵌入式知识平台 - 许可:CC BY-NC-SA 4.0