前言:为什么需要看门狗?
在嵌入式开发中,我们经常会遇到这样的问题:程序明明在实验室调试时运行正常,但一到现场就会不定期"死机";或者受到强电磁干扰后,程序进入异常状态无法恢复。这些问题的根源往往是程序跑飞——由于指针越界、栈溢出、中断异常等原因,CPU脱离了正常的执行流程,陷入死循环或无效指令中。
此时,看门狗(WatchDog) 就成了最后的"救命稻草"。看门狗本质上是一个定时器,它的核心逻辑是:如果程序在规定时间内没有"喂狗"(重置定时器),就触发系统复位,强制程序重新启动,从而恢复正常运行。
STM32系列MCU提供了两种看门狗外设:
独立看门狗(IWDG):由独立低速时钟(LSI)驱动,不受主时钟影响,适合监控整个系统的运行
窗口看门狗(WWDG):由APB1时钟驱动,具有时间窗口特性,适合监控程序在特定时间段内的运行状态
本文将从原理到实战,详细讲解两种看门狗的工作机制、配置方法和实战技巧,帮助大家彻底掌握STM32看门狗的使用。
一、看门狗核心原理:定时喂狗与超时复位
无论IWDG还是WWDG,其核心原理都是**“定时检查+强制复位”**,具体流程如下:
初始化看门狗:设置超时时间(超过该时间未喂狗则复位)
启动看门狗:定时器开始倒计时
正常运行时喂狗:程序在超时前通过特定操作重置定时器(喂狗),避免复位
程序异常时:若超时未喂狗,看门狗触发复位信号,系统重启
这个过程类似我们给植物浇水:如果在植物枯萎前浇水(喂狗),植物就能存活;如果忘记浇水(超时),植物就会死亡(系统复位)。
二、独立看门狗(IWDG):不受主时钟影响的"独立卫士"
2.1 IWDG的核心特性
独立看门狗(Independent WatchDog)是STM32中最常用的看门狗,其核心特性如下:
独立时钟源:由内部低速时钟(LSI,约40kHz)驱动,与系统主时钟(HSE、PLL等)完全独立,即使主时钟故障仍能正常工作
不可屏蔽:一旦启动,无法通过软件关闭,只能通过复位停止
超时时间可调:通过预分频器和重装载值设置,超时范围从约128μs到约32秒(具体取决于型号)
适用场景:监控整个系统的运行状态,尤其适合对可靠性要求高的场景(如工业控制、汽车电子)
2.2 IWDG的硬件结构
IWDG的硬件结构主要包括以下部分:
预分频器(Prescaler):对LSI时钟分频,降低计数频率
计数器(Downcounter):从预设的重装载值开始递减计数
重装载寄存器(RLR):存储计数器的初始值(喂狗时会将该值重新加载到计数器)
键寄存器(KR):通过写入特定值控制IWDG(启动、喂狗、解锁配置)
其工作流程如图1所示:
启动IWDG后,计数器从RLR的值开始以分频后的频率递减
当计数器减到0时,触发系统复位
喂狗时,计数器重新加载RLR的值,重新开始计数
2.3 IWDG关键寄存器详解
(1)键寄存器(IWDG_KR)
这是一个只写寄存器,通过写入不同的值实现不同功能:
写入0xCCCC:启动IWDG(一旦写入,无法停止,除非复位)
写入0xAAAA:喂狗(将RLR的值重新加载到计数器,防止超时)
写入0x5555:解锁PR和RLR寄存器(允许修改配置)
(2)预分频寄存器(IWDG_PR)
用于设置预分频系数,控制计数器的递减频率,共4种可选值:
(3)重装载寄存器(IWDG_RLR)
存储计数器的初始值,范围为0~0xFFF(4095)。计数器从该值开始递减,每步时间由预分频后的频率决定。
2.4 IWDG超时时间计算
超时时间(即计数器从RLR值减到0的时间)计算公式:
超时时间 = (4 × 2^PR) × RLR / LSI频率
其中:
PR是预分频器的值(0~6,对应表1中的位[2:0])
RLR是重装载寄存器的值(1~4095)
LSI频率默认约为40kHz(实际可能有±30%误差,需注意)
示例:当PR=2(预分频系数16),RLR=1000时:
超时时间 = (4 × 2^2) × 1000 / 40000 = (4×4)×1000/40000 = 16×1000/40000 = 0.4秒 = 400ms
2.5 IWDG配置步骤(HAL库)
使用HAL库配置IWDG的步骤如下:
步骤1:解锁PR和RLR寄存器
由于PR和RLR默认是锁定的,需要先写入0x5555到KR寄存器解锁:
HAL_IWDG_EnableWriteAccess(&hiwdg); // 内部实现:IWDG->KR = 0x5555;
步骤2:设置预分频系数和重装载值
根据所需超时时间计算PR和RLR,通过HAL_IWDG_Init函数配置:
IWDG_HandleTypeDef hiwdg;
hiwdg.Instance = IWDG;
hiwdg.Init.Prescaler = IWDG_PRESCALER_16; // 预分频系数16
hiwdg.Init.Reload = 1000; // 重装载值1000
if (HAL_IWDG_Init(&hiwdg) != HAL_OK)
{
Error_Handler();
}
步骤3:启动IWDG
写入0xCCCC到KR寄存器启动看门狗:
HAL_IWDG_Start(&hiwdg); // 内部实现:IWDG->KR = 0xCCCC;
步骤4:定时喂狗
在程序正常运行的地方(如主循环)定期写入0xAAAA到KR寄存器喂狗:
while (1)
{
// 正常业务逻辑
HAL_IWDG_Refresh(&hiwdg); // 喂狗:IWDG->KR = 0xAAAA;
HAL_Delay(100); // 假设每100ms喂一次狗(需小于超时时间)
}
2.6 IWDG实战代码(STM32F103为例)
以下是一个完整的IWDG示例,实现"1秒超时,每500ms喂狗"的功能:
#include "stm32f1xx_hal.h"
IWDG_HandleTypeDef hiwdg;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_IWDG_Init(void);
int main(void)
{
// 初始化HAL库
HAL_Init();
// 配置系统时钟
SystemClock_Config();
// 初始化外设
MX_GPIO_Init();
MX_IWDG_Init(); // 初始化并启动IWDG
// 启动IWDG(也可在MX_IWDG_Init中启动)
HAL_IWDG_Start(&hiwdg);
// 主循环
while (1)
{
// 模拟正常业务:翻转LED
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
// 喂狗(每500ms一次,小于1秒超时时间)
HAL_IWDG_Refresh(&hiwdg);
// 延时500ms
HAL_Delay(500);
// 若取消下面的注释,程序会在1秒后复位(因为1.5秒未喂狗)
// HAL_Delay(1000);
}
}
// IWDG初始化函数
static void MX_IWDG_Init(void)
{
hiwdg.Instance = IWDG;
// 计算参数:超时时间1秒
// LSI=40kHz,预分频系数16(PR=2),则每步时间=4*2^2 /40000 = 16/40000 = 0.4ms
// 所需RLR = 1000ms / 0.4ms = 2500
hiwdg.Init.Prescaler = IWDG_PRESCALER_16;
hiwdg.Init.Reload = 2500;
// 解锁配置寄存器
if (HAL_IWDG_EnableWriteAccess(&hiwdg) != HAL_OK)
{
Error_Handler();
}
// 应用配置
if (HAL_IWDG_Init(&hiwdg) != HAL_OK)
{
Error_Handler();
}
}
// 系统时钟配置(省略,根据实际硬件配置)
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
// 配置时钟...
}
// GPIO初始化(配置PC13为LED输出)
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOC_CLK_ENABLE();
// PC13配置为推挽输出
GPIO_InitStruct.Pin = GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
}
// 错误处理函数
void Error_Handler(void)
{
// 点亮LED表示错误
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
while (1)
{
}
}
2.7 IWDG使用注意事项
LSI时钟误差:LSI的实际频率可能与40kHz有±30%的误差(不同芯片差异),若对超时时间精度要求高,需通过校准补偿(可通过TIM测量LSI周期)。
喂狗时机:喂狗必须在超时前完成,且不能过于频繁(但频繁喂狗不会导致错误,只是冗余操作)。
不可停止:一旦通过0xCCCC启动IWDG,只能通过系统复位关闭,因此调试时可先注释启动代码,避免频繁复位。
复位标志:可通过RCC->CSR寄存器的IWDGRSTF位判断是否由IWDG触发的复位,便于故障排查:
if (RCC->CSR & RCC_CSR_IWDGRSTF)
{
// 由IWDG复位
// 清除标志
RCC->CSR |= RCC_CSR_RMVF;
}
三、窗口看门狗(WWDG):带时间窗口的"精确监控者"
3.1 WWDG与IWDG的核心区别
窗口看门狗(Window WatchDog)与IWDG的最大区别是引入了时间窗口机制:
IWDG只要在超时前喂狗即可,无论何时
WWDG必须在特定的"窗口时间"内喂狗,太早或太晚都会触发复位
这种特性让WWDG更适合监控程序在特定时间段内的运行状态(如确保某段关键代码在10~20ms内执行完成)。
3.2 WWDG的核心特性
时钟源:由APB1时钟分频得到(APB1时钟 ≤ 48MHz,分频系数固定为4096)
超时时间:由重装载值和预分频器决定,范围较小(通常从几微秒到几十毫秒)
窗口机制:喂狗必须在"下窗口"(计数器=0x40)和"上窗口"(用户设置的窗口值)之间,否则复位
早期唤醒中断(EWI):当计数器减到0x40时可触发中断,用于紧急处理(如保存数据)
3.3 WWDG的硬件结构与工作原理
WWDG的核心是一个7位递减计数器(值范围0x40~0x7F,当减到0x3F时触发复位),其工作流程如下:
计数器从初始值(重装载值,必须≤0x7F)开始递减
当计数器 > 窗口值时喂狗:太早,触发复位
当计数器 < 0x40时喂狗:太晚,触发复位
只有当窗口值 < 计数器 < 0x7F时喂狗才有效(重新加载计数器)
当计数器减到0x3F时,无论是否喂狗,都会触发复位
其时间窗口如图2所示,阴影部分为有效喂狗区间。
3.4 WWDG关键寄存器详解
(1)控制寄存器(WWDG_CR)
位7(WDGA):看门狗激活位(1=启动,0=关闭,一旦置1无法清零)
位6:0(T[6:0]):计数器值(范围0x40~0x7F,写入时会自动加载到计数器)
(2)配置寄存器(WWDG_CFR)
位9:7(WDGTB[2:0]):预分频系数(控制计数器递减频率)
位6:0(W[6:0]):窗口值(喂狗必须在计数器 > W时进行)
位9(EWI):早期唤醒中断使能(1=使能,当计数器=0x40时触发中断)
(3)状态寄存器(WWDG_SR)
位0(EWIF):早期唤醒中断标志(计数器=0x40时置1,需软件清零)
3.5 WWDG超时时间计算
WWDG的计数器时钟频率为:
计数器时钟 = APB1时钟 / 4096 / (2^WDGTB)
其中WDGTB是预分频系数(0~7),对应分频倍数如下:
计数器从T[6:0](重装载值)减到0x3F的总时间(超时时间)为:
超时时间 = (T - 0x3F) / 计数器时钟
窗口时间(有效喂狗区间)为:
窗口时间 = (T - W) / 计数器时钟
其中W是窗口寄存器的值(必须 < T)。
示例:APB1=36MHz,WDGTB=1(分频2),T=0x7F(127),W=0x50(80):
计数器时钟 = 36MHz /4096 /2 ≈ 4394Hz → 周期≈227.6μs
超时时间 = (127 - 63) × 227.6μs = 64 × 227.6μs ≈ 14.57ms
窗口时间 = (127 - 80) × 227.6μs = 47 × 227.6μs ≈ 10.7ms(即必须在计数器从127减到81的10.7ms内喂狗)
3.6 WWDG配置步骤(HAL库)
使用HAL库配置WWDG的步骤如下:
步骤1:初始化WWDG参数
设置预分频系数、窗口值和重装载值:
WWDG_HandleTypeDef hwwdg;
hwwdg.Instance = WWDG;
hwwdg.Init.Prescaler = WWDG_PRESCALER_2; // 预分频系数2
hwwdg.Init.Window = 0x50; // 窗口值
hwwdg.Init.Counter = 0x7F; // 重装载值
hwwdg.Init.EWIMode = WWDG_EWI_DISABLE; // 禁用早期唤醒中断
if (HAL_WWDG_Init(&hwwdg) != HAL_OK)
{
Error_Handler();
}
步骤2:启动WWDG
通过设置WWDG_CR的WDGA位启动:
HAL_WWDG_Start(&hwwdg); // 内部实现:WWDG->CR |= WWDG_CR_WDGA;
步骤3:在窗口内喂狗
喂狗需通过写入WWDG_CR寄存器实现(写入的值必须包含新的计数器值,且高字节为0x7F):
HAL_WWDG_Refresh(&hwwdg, 0x7F); // 喂狗,重新加载计数器为0x7F
步骤4:(可选)配置早期唤醒中断
若需在计数器减到0x40时触发中断(如保存数据),需使能EWI:
// 在初始化时使能EWI
hwwdg.Init.EWIMode = WWDG_EWI_ENABLE;
// 中断服务函数
void WWDG_IRQHandler(void)
{
HAL_WWDG_IRQHandler(&hwwdg);
}
// EWI回调函数
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg)
{
// 计数器即将到0x3F,可在此保存关键数据
printf("WWDG即将超时!\r\n");
}
3.7 WWDG实战代码(STM32F103为例)
以下是一个WWDG示例,实现"超时时间14ms,窗口值80,必须在计数器70~127时喂狗":
#include "stm32f1xx_hal.h"
WWDG_HandleTypeDef hwwdg;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_WWDG_Init(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_WWDG_Init(); // 初始化并启动WWDG
// 启动WWDG
HAL_WWDG_Start(&hwwdg);
uint32_t tick = 0;
while (1)
{
// 模拟业务逻辑:每5ms执行一次
HAL_Delay(5);
tick += 5;
// 翻转LED
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
// 喂狗时机控制:
// 总超时14ms,窗口值80,有效喂狗区间为计数器81~127(对应时间约0~10.7ms)
// 此处设置在第8ms喂狗(在有效区间内)
if (tick >= 8)
{
HAL_WWDG_Refresh(&hwwdg, 0x7F); // 喂狗
tick = 0; // 重置计时
}
// 若将喂狗时间改为12ms(超过窗口),则会触发复位
// if (tick >= 12) { ... }
}
}
// WWDG初始化函数
static void MX_WWDG_Init(void)
{
hwwdg.Instance = WWDG;
// 配置参数:
// APB1=36MHz,WDGTB=1(分频2)→ 计数器时钟=36e6/(4096*2)≈4394Hz → 周期≈227.6μs
// T=0x7F(127),W=0x50(80)
// 超时时间=(127-63)*227.6μs≈14.57ms
// 窗口时间=(127-80)*227.6μs≈10.7ms
hwwdg.Init.Prescaler = WWDG_PRESCALER_2;
hwwdg.Init.Window = 0x50;
hwwdg.Init.Counter = 0x7F;
hwwdg.Init.EWIMode = WWDG_EWI_DISABLE;
if (HAL_WWDG_Init(&hwwdg) != HAL_OK)
{
Error_Handler();
}
}
// 中断服务函数(若启用EWI)
void WWDG_IRQHandler(void)
{
HAL_WWDG_IRQHandler(&hwwdg);
}
// EWI回调函数
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg)
{
// 计数器到0x40,即将超时
printf("WWDG Early Wakeup!\r\n");
}
// 其他函数(时钟、GPIO)省略...
3.8 WWDG使用注意事项
窗口值设置:窗口值W必须小于重装载值T,否则有效喂狗区间为空(任何时候喂狗都错误)。
计数器范围:WWDG计数器是7位,最大值为0x7F,当减到0x3F时立即复位,因此T必须≥0x40(否则启动后立即复位)。
时钟依赖:WWDG依赖APB1时钟,若APB1时钟故障(如被关闭),WWDG会停止工作,因此不适合监控时钟故障。
喂狗数据:喂狗时写入的计数器值必须≤0x7F,否则会导致立即复位(因为计数器是7位)。
四、IWDG与WWDG的对比及选型建议
选型建议:
若需监控整个系统运行,且要求在主时钟故障时仍能工作 → 选IWDG
若需监控特定代码段的执行时间(如确保某任务在10~20ms内完成) → 选WWDG
若需同时满足两种需求 → 可同时使用IWDG和WWDG(如IWDG监控整体,WWDG监控关键任务)
五、看门狗常见问题及解决方案
问题1:程序频繁复位,怀疑看门狗误触发
可能原因:
喂狗不及时(实际执行时间超过超时时间)
程序跑飞后未执行到喂狗代码
看门狗超时时间设置过短
排查步骤:
注释掉看门狗启动代码,观察程序是否稳定运行(判断是否确实由看门狗导致)
增加调试信息,打印喂狗时间间隔,确认是否超过超时时间
检查喂狗代码是否被条件语句跳过(如if判断错误导致未喂狗)
增大超时时间,逐步排查是否由特定代码段执行时间过长导致
问题2:IWDG超时时间与计算值不符
可能原因:
LSI时钟频率与默认值(40kHz)偏差过大
预分频系数或重装载值计算错误
喂狗代码执行时间过长,导致实际间隔超过预期
解决方案:
测量实际LSI频率(通过TIM捕获LSI输出):
// 示例:用TIM2测量LSI频率
TIM_HandleTypeDef htim2;
void MeasureLSI(void)
{
// 配置TIM2捕获LSI脉冲
// ...(省略配置代码)
// 计算频率:频率 = 捕获次数 / 时间
}
根据实际LSI频率重新计算PR和RLR值
在喂狗前后记录时间戳,确认实际喂狗间隔
问题3:WWDG总是在喂狗时复位
可能原因:
喂狗时机太早(计数器 > 窗口值)
喂狗时机太晚(计数器 < 0x40)
窗口值设置错误(≥重装载值)
解决方案:
用调试器观察喂狗时的计数器值(WWDG->CR & 0x7F),确认是否在窗口内
重新计算窗口值和重装载值,确保窗口值 < 重装载值
调整喂狗时机,可通过定时器精确控制喂狗时间
问题4:调试时频繁触发看门狗复位,影响调试
解决方案:
调试阶段通过宏定义关闭看门狗:
#ifdef DEBUG
// 调试时不启动看门狗
#define WDG_START()
#else
// release版本启动
#define WDG_START() HAL_IWDG_Start(&hiwdg)
#endif
使用IDE的"复位后暂停"功能,在看门狗启动前暂停程序
增大超时时间(如设为最大32秒),给调试留出足够时间
六、看门狗在实际项目中的最佳实践
6.1 多级监控策略
在复杂系统中,可采用"多级看门狗"策略:
IWDG:监控整个系统(超时时间设为系统最大响应时间,如1秒)
WWDG:监控关键任务(如数据采集任务,超时时间设为任务周期的1.5倍)
软件看门狗:监控任务调度器(如RTOS中,由空闲任务喂狗,确保调度正常)
6.2 喂狗位置选择
喂狗应放在程序正常运行的必经之路,但需避免放在:
中断服务程序中(若中断被屏蔽,会导致喂狗失败)
长时间阻塞的代码段中(可能导致喂狗不及时)
条件判断内部(可能因条件不满足而跳过)
推荐喂狗位置:
主循环末尾(确保主循环能正常执行)
任务调度器的空闲任务中(RTOS环境)
关键流程的完成节点(如数据处理完成后)
6.3 故障诊断与记录
通过记录看门狗复位信息,可快速定位系统问题:
在复位后检查复位标志(RCC->CSR),区分复位原因(IWDG/WWDG/外部复位等)
用EEPROM或FLASH记录复位次数、时间戳、复位前的系统状态(如任务运行状态、传感器数据)
设计"故障码"机制,不同场景的喂狗失败对应不同故障码
示例代码:
// 记录复位信息
void RecordResetInfo(void)
{
ResetInfo_t info;
// 读取已有记录
EEPROM_Read(0, &info, sizeof(info));
// 递增复位次数
info.count++;
// 记录复位原因
if (RCC->CSR & RCC_CSR_IWDGRSTF)
info.reason = RESET_REASON_IWDG;
else if (RCC->CSR & RCC_CSR_WWDGRSTF)
info.reason = RESET_REASON_WWDG;
else
info.reason = RESET_REASON_OTHER;
// 记录时间戳(假设有时钟模块)
info.timestamp = GetCurrentTime();
// 保存到EEPROM
EEPROM_Write(0, &info, sizeof(info));
// 清除复位标志
RCC->CSR |= RCC_CSR_RMVF;
}
6.4 抗干扰设计
在强干扰环境(如工业现场),需结合硬件设计增强看门狗可靠性:
外部独立看门狗芯片(如MAX706):作为STM32内部看门狗的备份,防止MCU本身故障
电源监控:当电源电压低于阈值时,触发复位(许多外部看门狗芯片集成此功能)
硬件滤波:对MCU的电源和复位引脚增加RC滤波,减少干扰导致的误复位
七、总结
看门狗是嵌入式系统可靠性设计的核心组件,STM32的IWDG和WWDG各有侧重:
IWDG 适合作为系统级的"最后防线",独立于主时钟,确保系统在任何情况下都能复位
WWDG 适合监控程序在特定时间段内的运行状态,提供更精细的监控能力
使用看门狗时,需注意:
合理设置超时时间(既不能太长导致故障无法恢复,也不能太短导致误复位)
喂狗位置必须是程序正常运行的必经之路
结合复位标志和日志记录,便于故障排查
掌握看门狗的使用,能显著提高嵌入式系统的抗干扰能力和可靠性,尤其在工业控制、汽车电子等对稳定性要求极高的领域,看门狗更是不可或缺的"安全卫士"。
附录:STM32看门狗相关寄存器速查表
(注:具体寄存器位定义请参考对应型号的《参考手册》)
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq2745567641/article/details/149265865
|
|