独立看门狗(IWDG)与窗口看门狗(WWDG)
IWDG(Independent watchdog ) 独立看门狗是一种检测系统故障的外设. 工作原理是由一个看门狗专用的内部低速时钟驱动, 看门狗寄存器是一个计数器, 初始值为0xFFF, 当这个寄存器的值减为小于等于0时, 触发系统重置.
(WWDG)System window watchdog 窗口看门狗也是检测系统故障的外设, 驱动窗口看门狗的时钟由APB总线时钟分频而来, 也是有一个看门狗寄存器, 但是触发系统重置的条件不同, 窗口看门狗只在一个范围内工作正常(如:0x3F-0xC0), 高于0xC0或低于0x3F都将触发系统重置
两种看门狗的实际应用
独立看门狗主要用于在系统某一个外设有异常时触发重启
窗口看门狗主要用于检测系统主时钟, 中断系统有异常时, 触发重启
当系统已经运行出错, 但恰好IWDG的喂狗操作还正常时, 异常无法检出, 此时只有窗口看门狗能发现异常
本次实验目的就是构建一个Fault Manager错误管理, 用来检测并记录系统的异常
使用CubeMX生成独立看门狗代码

当勾选了IWDG为Activated之后, 所生成的MX_IWDG_Init()代码中会在运行是触发LSI, 所以不需要单独配置IWDG的时钟.

LSI独立时钟频率为32kHz, 通过计算可知喂狗时间范围为125us-32.8s
这里配置 IWDG controller clock prescaler 为64, 所以计算每秒递减值为 32000 ÷ 64 = 500
下面设置IWDG down-counter reload value 为1000, 也就是递减超过1000时触发系统重置, 所以总得只要在2s内喂狗一次, 系统就会正常运行.
然后IWDG window value是可以让独立看门狗实现类似窗口看门狗效果的配置. 假如我们这里设置500, 那么当计数器减到500-1000范围内时是不能喂狗的, 只有计数器位于0-500之间喂狗才不会触发重置.
此处我们设置IWDG window value为最大值4095, 关闭这个功能.


WWDG挂在主时钟APB1上, 时钟频率为80MHz, 设置分频系数为8后, 根据文档描述的计算公式, WWDG的时钟变为:
80MHz / (4096 x 8) ≈ 2.44kHz
意味着计数器每减1, 消耗月0.4096ms

由手册可知, WWDG的计数器只有7位, 上下限为0x3F(63)-0x7F(127)
所以设置WWDG free-running downcounter value为127, 为了获得最长等待时间
Window Value决定着窗口上限, 这里设置为90, 就是只有计数器减到90以下, 没有到0x3F(63)时, 喂狗才有效.
窗口宽度 (90 - 63) x 0.4096 ≈ 10.6ms
太快复位: 说明主循环时间波动太大, 或者窗口开的太小
太慢复位: 说明代码里有耗时操作, 比如有HAL_Delay或阻塞通信
勾选Early Wakeup Interrupt(EWI)会让看门狗在减到0x40时触发中断, 因此要额外在NVIC配置中增加Window watchdog interrupt中断配置.

生成代码
iwdg.h
c
#ifndef __IWDG_H__
#define __IWDG_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
extern IWDG_HandleTypeDef hiwdg;
void MX_IWDG_Init(void);
#ifdef __cplusplus
}
#endif
#endif /* __IWDG_H__ */
iwdg.c
c
#include "iwdg.h"
IWDG_HandleTypeDef hiwdg;
void MX_IWDG_Init(void)
{
hiwdg.Instance = IWDG;
hiwdg.Init.Prescaler = IWDG_PRESCALER_64;
hiwdg.Init.Window = 4095;
hiwdg.Init.Reload = 1000;
if (HAL_IWDG_Init(&hiwdg) != HAL_OK)
{
Error_Handler();
}
}
wwdg.h
c
#ifndef __WWDG_H__
#define __WWDG_H__
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
extern WWDG_HandleTypeDef hwwdg;
void MX_WWDG_Init(void);
#ifdef __cplusplus
}
#endif
#endif /* __WWDG_H__ */
wwdg.c
c
#include "wwdg.h"
WWDG_HandleTypeDef hwwdg;
void MX_WWDG_Init(void)
{
hwwdg.Instance = WWDG;
hwwdg.Init.Prescaler = WWDG_PRESCALER_8;
hwwdg.Init.Window = 90;
hwwdg.Init.Counter = 127;
hwwdg.Init.EWIMode = WWDG_EWI_ENABLE;
if (HAL_WWDG_Init(&hwwdg) != HAL_OK)
{
Error_Handler();
}
}
void HAL_WWDG_MspInit(WWDG_HandleTypeDef* wwdgHandle)
{
if(wwdgHandle->Instance==WWDG)
{
__HAL_RCC_WWDG_CLK_ENABLE();
HAL_NVIC_SetPriority(WWDG_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(WWDG_IRQn);
}
}
stm32l4xx_it.h
c
void WWDG_IRQHandler(void);
stm32l4xx_it.c
c
extern WWDG_HandleTypeDef hwwdg;
void WWDG_IRQHandler(void)
{
HAL_WWDG_IRQHandler(&hwwdg);
}
stm32l4xx_hal_conf.h
c
#define HAL_IWDG_MODULE_ENABLED
#define HAL_WWDG_MODULE_ENABLED
main.c
c
#include "iwdg.h"
#include "wwdg.h"
int main(void)
{
MX_IWDG_Init();
MX_WWDG_Init();
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI|RCC_OSCILLATORTYPE_MSI;
RCC_OscInitStruct.LSIState = RCC_LSI_ON;
// ...
}
基于窗口看门狗的监控模版
wwdg.h
这里面主要定义了根据配置自动计算最佳喂狗时间的宏WWDG_FEED_PERIOD
c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file wwdg.h
* @brief This file contains all the function prototypes for
* the wwdg.c file
******************************************************************************
* @attention
*
* Copyright (c) 2026 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __WWDG_H__
#define __WWDG_H__
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
extern WWDG_HandleTypeDef hwwdg;
/* USER CODE BEGIN Private defines */
/************************ 从硬件配置提取的参数 ************************/
#define PCLK1_FREQ_HZ 80000000UL // APB1时钟频率(80MHz)
#define SYSTICK_FREQ_HZ 1000UL // SysTick中断频率(1ms)
// WWDG硬件配置(和MX_WWDG_Init一致)
#define WWDG_PRESCALER WWDG_PRESCALER_8 // 预分频配置
#define WWDG_COUNTER_INIT 127UL // 计数器初始值
#define WWDG_WINDOW_VAL 90UL // 窗口值
#define WWDG_RESET_LOW_VAL 0x40UL // 最低重置值
/************************ 自动计算核心宏 ************************/
// 转换预分频枚举到实际分频系数(STM32标准库宏)
#define WWDG_PRESCALER_TO_DIV(pre) \
((pre) == WWDG_PRESCALER_1 ? 1 : \
(pre) == WWDG_PRESCALER_2 ? 2 : \
(pre) == WWDG_PRESCALER_4 ? 4 : \
(pre) == WWDG_PRESCALER_8 ? 8 : 1)
// WWDG计数时钟频率(Hz)= PCLK1 / 4096 / 预分频系数
#define WWDG_COUNT_FREQ_HZ \
(PCLK1_FREQ_HZ / 4096UL / WWDG_PRESCALER_TO_DIV(WWDG_PRESCALER))
// 单个计数值的递减时间(μs)= 1e6 / 计数频率
#define WWDG_COUNT_PERIOD_US \
(1000000UL / WWDG_COUNT_FREQ_HZ)
// WWDG窗口开启时间(ms)= (初始值 - 窗口值) * 单个计数周期(μs) / 1000
#define WWDG_WINDOW_START_MS \
((WWDG_COUNTER_INIT - WWDG_WINDOW_VAL) * WWDG_COUNT_PERIOD_US / 1000UL)
// WWDG总超时时间(ms)= 初始值 * 单个计数周期(μs) / 1000
#define WWDG_TIMEOUT_TOTAL_MS \
((WWDG_COUNTER_INIT - WWDG_RESET_LOW_VAL) * WWDG_COUNT_PERIOD_US / 1000UL)
// 安全喂狗周期(ms):取窗口中间值(窗口开启时间 + 有效窗口时长的50%)
#define WWDG_FEED_PERIOD \
(WWDG_WINDOW_START_MS + ((WWDG_TIMEOUT_TOTAL_MS - WWDG_WINDOW_START_MS) / 2))
// 兜底:防止计算值超出范围,增加边界检查
#if WWDG_FEED_PERIOD >= WWDG_TIMEOUT_TOTAL_MS || WWDG_FEED_PERIOD <= WWDG_WINDOW_START_MS
#error "WWDG_FEED_PERIOD out of window range! Check clock/WWDG config."
#endif
/* USER CODE END Private defines */
void MX_WWDG_Init(void);
/* USER CODE BEGIN Prototypes */
/* USER CODE END Prototypes */
#ifdef __cplusplus
}
#endif
#endif /* __WWDG_H__ */
stm32l4xx_it.c
在系统Systic中断里, 到达喂狗时间时, 设置全局标志位
c
static uint8_t systic_wwdg_cnt = 0;
uint8_t wwdg_refresh_request = 0;
void SysTick_Handler(void)
{
/* USER CODE BEGIN SysTick_IRQn 0 */
systic_wwdg_cnt++;
Health_TaskAlive(HEALTH_SYSTIC);
if (systic_wwdg_cnt >= WWDG_FEED_PERIOD) {
systic_wwdg_cnt = 0;
wwdg_refresh_request = 1;
}
/* USER CODE END SysTick_IRQn 0 */
HAL_IncTick();
/* USER CODE BEGIN SysTick_IRQn 1 */
/* USER CODE END SysTick_IRQn 1 */
}
然后主循环中, 检测到窗口看门狗标志位时, 执行喂狗操作
c
while (1)
{
// ...
if (wwdg_refresh_request) {
Health_TaskAlive(HEALTH_APP);
wwdg_refresh_request = 0;
Health_feed_wwdg();
}
}
health.h
封装健康信息处理模块
c
#ifndef HEALTH_H
#define HEALTH_H
#include <stdint.h>
void Health_TaskAlive(uint8_t id);
void Health_feed_wwdg(void);
enum
{
HEALTH_APP = 0,
HEALTH_SYSTIC,
HEALTH_MAX,
};
#endif
health.c
主要功能, 标记单独模块工作正常, 确认所有模块正常, 喂狗并清除标记位
c
#include "health.h"
#include "wwdg.h"
static volatile uint8_t health_flag[HEALTH_MAX];
void Health_TaskAlive(uint8_t id)
{
if (id < HEALTH_MAX)
health_flag[id] = 1;
}
uint8_t Health_AllOk(void)
{
for (int i = 0; i < HEALTH_MAX; i++)
{
if (health_flag[i] == 0)
return 0;
}
return 1;
}
void Health_ClearAll(void)
{
for (int i = 0; i < HEALTH_MAX; i++)
health_flag[i] = 0;
}
void Health_feed_wwdg()
{
if (Health_AllOk()) {
HAL_WWDG_Refresh(&hwwdg);
}
Health_ClearAll();
}
fault.h
错误信息处理模块
c
#ifndef FAULT_H
#define FAULT_H
#include <stdint.h>
typedef enum
{
FAULT_NONE = 0,
/* 系统级 */
FAULT_LOW_POWER = (1 << 0),
FAULT_WWDG_RESET = (1 << 1),
FAULT_IWDG_RESET = (1 << 2),
FAULT_SOFTWARE_RESET = (1 << 3),
FAULT_BOR_RESET = (1 << 4),
FAULT_PIN_RESET = (1 << 5),
FAULT_OPTION_BYTE_LOADER_RESET = (1 << 6),
FAULT_FIREWALL_RESET = (1 << 7),
/* 外设级 */
FAULT_DMA_TIMEOUT = 0x0101,
FAULT_UART_STUCK = 0x0102,
/* 应用级 */
FAULT_APP_DEADLOOP = 0x0201,
} Fault_Code_t;
Fault_Code_t Get_Reset_Fault(void);
#endif /* FAULT_H */
fault.c
返回错误代码
c
#include "fault.h"
#include "stm32l4xx_hal.h"
Fault_Code_t Get_Reset_Fault(void) {
Fault_Code_t f_code = FAULT_NONE;
uint32_t rcc_csr = RCC->CSR; // 读取RCC复位状态寄存器
__HAL_RCC_CLEAR_RESET_FLAGS(); // 读取RCC后立即清除复位标志, 避免延迟
if (rcc_csr & RCC_CSR_LPWRRSTF) f_code |= FAULT_LOW_POWER;
if (rcc_csr & RCC_CSR_WWDGRSTF) f_code |= FAULT_WWDG_RESET;
if (rcc_csr & RCC_CSR_IWDGRSTF) f_code |= FAULT_IWDG_RESET;
if (rcc_csr & RCC_CSR_SFTRSTF) f_code |= FAULT_SOFTWARE_RESET;
if (rcc_csr & RCC_CSR_BORRSTF) f_code |= FAULT_BOR_RESET;
if (rcc_csr & RCC_CSR_PINRSTF) f_code |= FAULT_PIN_RESET;
if (rcc_csr & RCC_CSR_OBLRSTF) f_code |= FAULT_OPTION_BYTE_LOADER_RESET;
if (rcc_csr & RCC_CSR_FWRSTF) f_code |= FAULT_FIREWALL_RESET;
return f_code;
}
然后主函数启动时检测错误码, 并打印复位日志
main.c
c
main(){
Fault_Code_t fault_code = Get_Reset_Fault();
// ...
MX_WWDG_Init();
// ...
if (fault_code != FAULT_NONE) {
if (fault_code & FAULT_LOW_POWER)
TRACE_ERROR("Fault: Low Power reset detected!");
if (fault_code & FAULT_WWDG_RESET)
TRACE_ERROR("Fault: Window watchdog reset detected!");
if (fault_code & FAULT_IWDG_RESET)
TRACE_ERROR("Fault: Independent window watchdog reset detected!");
if (fault_code & FAULT_SOFTWARE_RESET)
TRACE_ERROR("Fault: Software reset detected!");
if (fault_code & FAULT_BOR_RESET)
TRACE_ERROR("Fault: Brown-Out reset detected!");
if (fault_code & FAULT_PIN_RESET)
TRACE_ERROR("Fault: Pin reset detected!");
if (fault_code & FAULT_OPTION_BYTE_LOADER_RESET)
TRACE_ERROR("Fault: Option byte loader reset detected!");
if (fault_code & FAULT_FIREWALL_RESET)
TRACE_ERROR("Fault: Firewall reset detected!");
}
}