十五、WDG看门狗
十三、WDG看门狗
WDG简介
- WDG(Watchdog)看门狗
- 看门狗可以监控程序的运行状态,当程序因为设计漏洞、硬件故障、电磁干扰等原因,出现卡死或跑飞现象时,看门狗能及时复位程序,避免程序陷入长时间的罢工状态,保证系统的可靠性和安全性
- 看门狗本质上是一个定时器,当指定时间范围内,程序没有执行喂狗(重置计数器)操作时,看门狗硬件电路就自动产生复位信号
- WDG开启后就无法关闭。这是防止程序意外关闭看门狗
- IWDG开启后会自动开启LSI时钟
RCC_GetFlagStatus(RCC_FLAG_IWDGRST);
和RCC_GetFlagStatus(RCC_FLAG_WWDGRST);
可以判断是否是看门狗产生的复位信号
STM32内置两个看门狗
-
独立看门狗(IWDG):独立工作,对时间精度要求较低
独立看门狗的时钟是专用的 LSI,内部低速时钟(40KHz),即使主时钟出现问题了,看门狗也能正常工作,这也是独立看门狗,独立的得名原因。
对时间精度要求较低就是独立看门狗只有一个最晚时间界限,你喂狗间隔只要不超过这个最晚界限就行了,
-
窗口看门狗(WWDG):要求看门狗在精确计时窗口起作用
窗口看门狗使用的是APB1时钟(36MHz)
要求看门狗在精确计时窗口起作用,意思就是喂狗的时间有个最晚的界限,也有个最早的界限,必须在这个界限的窗口内喂狗,这也是窗口看门狗,窗口的得名原因。
因为对于独立看门狗来说,可能程序就卡死在喂狗的部分了;或者程序跑飞,但是喂狗代码也意外执行了;或者程序有时候很快喂狗,有时候又比较慢喂狗,那这些状态,独立看门狗就检测不到了,但是窗口看门狗是可以检测到这些问题的,因为它对喂狗的时间,可以卡的很死,快了,慢了,都不行。
IWDG独立看门狗框图
与定时器的时基单元基本是一样的结构,运行流程如下
-
在预分频器之前,输入时钟是 LSI,内部低速时钟,时钟频率为 40KHz
-
时钟进入预分频器进行分频,这个预分频器只有 8 位,所以它最大只能进行 256 分频,上面这个预分频寄存器 IWDG_PR,可以配置分频系数,
-
经过预分频器分频之后,时钟驱动递减计数器,每来一个时钟,自减一个数,另外这个计数器是 12 位的,所以最大值是 212 - 1 = 4095,然后,当自减到 0 之后,产生 IWDG 复位
-
正常运行时,为了避免复位呢,我们可以提前在重装载寄存器IWDG_RLR中写一个值,用来更新递减计数器的值
-
在这个键寄存器里写一个特定数据,可以控制电路进行喂狗,这时重装值就会复制到当前的计数器中,这样计数器就会回到重装值重新自减运行了
-
状态寄存器 SR,这就是标志电路运行的状态了,其实这个 SR 里没什么东西,就只有两个更新同步位,基本不用看。
-
上面这些寄存器位于 1.8V 供电区,下面主要的工作电路,都位于 VDD 供电区,即在停机和待机模式时仍能正常工作,所以独立看门狗,也是唤醒待机模式的 4 个条件之 1。
IWDG键寄存器
键寄存器本质上是控制寄存器,用于控制硬件电路的工作。
比如我们刚才说的喂狗操作,就是通过在键寄存器写入 0xAAAA 完成的
在可能存在干扰的情况下,一般通过在整个键寄存器写入特定值来代替控制寄存器写入一位的功能,以降低硬件电路受到干扰的概率
为什么能降低干扰呢。独立看门狗工作的环境是程序跑飞,受到电磁干扰等等,程序做出任何操作都是有可能的,如果你只在寄存器中设置一个位,那这一位就有可能会在误操作中,变成 1 或者变成 0,这个概率是比较大的,所以单独设置 1 位就来执行控制,在这里比较危险。这时,我们就可以通过在整个寄存器写入一个特定值,来代替写入一个位的操作。
写入键寄存器的值 | 作用 |
---|---|
0xCCCC | 启用独立看门狗 |
0xAAAA | IWDG_RLR中的值重新加载到计数器(喂狗) |
0x5555 | 解除 IWDG_PR 和 IWDG_RLR 的写保护 |
0x5555之外的其他值 | 启用 IWDG_PR 和 IWDG_RLR 的写保护 |
最后两条,是写保护的逻辑,想对IWDG的寄存器执行写指令,必须写入指定的键值
PR、SR 和 RLR 三个寄存器,它们也要有防止误操作的功能,SR 是只读的,这个不用保护;剩下的,对 PR 和 RLR 的写操作,可以设置一个写保护措施,然后只有在键寄存器写入 5555,才能解除写保护,一旦写入其他值,PR 和 RLR 再次被保护,这样 PR 和 RLR,就跟随键寄存器一起被保护了起来,防止误操作
IWDG最大喂狗时间
也就是定时器的溢出时间。
这里有个公式,超时时间:TIWDG = TLSI × PR预分频系数 × (RL + 1)
其中:TLSI = 1 / FLSI
- FLSI:LSI时钟频率
- TLSI:LSI一个周期的时间
- PR:预分频系数
- RL:重装载值
例如预分频系数为4,RL重装载值为4096
那么超时复位的时间为:1/40KHz * 4 * (4095 + 1) = 409.6ms
PR预分频寄存器:PR写0表示4分频;写1表示8分频;写2表示16分频。。。
简单来说,和定时器定时时间的计算是一样的
WWDG窗口看门狗框图
-
WWDG_CR:
-
WDGA位:WDGA=1时表示开启窗口看门狗
-
T6位:溢出标志位。T6=0时表示定时器溢出,没有及时喂狗
-
T0~T5:6位递减计数器。写入最大喂狗时间,自减为0后产生复位信号
-
注意: 对于硬件电路来说,T6 位其实也是计数器的一部分,只不过 T6 位被单独拎出来,当作标志位了而已
举个例子,比如这个计数器,初始值,我们给 111 1111,那么来一个计数脉冲,值减 1,变为 111 1110,再来一个,变为 111 1101,以此类推,不断自减,直到减为 100 0000,减到这个数时,是一个关键节点,此时包括 T6 位在内的数,是 100 0000,转为十六进制是 0x40。也就是说,此时,如果把 T6 位也当作计数器的一部分,那计数器的值实际上才减一半;但是,如果我们把 T6 位剥离出去,当作溢出标志位,低 6 位,当作计数器,那此时的状态就是标志位为 1,计数器为 00 0000,已经减到 0 了,再减一次,下一个值是什么呢?就是 011 1111,这时最高位 T6,由 1 变为 0,即代表计数器溢出,这时,最高位 T6,就会通过线路产生复位信号,这就是这个计数器的工作流程和溢出条件。
-
-
WWDG_CFR:喂狗时间的最早界限。 计算出一个最早界限的计数值,写入到这里的 W6~W0 中,这些值写入之后是固定不变的
WWDG工作流程
-
喂狗的时间超时
-
输入时钟是PCLK1,来自APB1的时钟,时钟频率为36MHz
-
经过看门狗预分频器分频后提供给计数器进行递减计数
-
WWDG_CR的T[5:0]6位递减计数器开始递减计数,T[5:0]=0后,再来一个时钟周期,T6=0,经过非门取反后等于1,再经过或门输出1,;如果开启窗口看门狗检测,WDGA=1,两条线路都输出1,经过与门输出1产生复位信号,复位电路
-
-
过早喂狗
- 软件写入WWDG_CR寄存器表示喂狗,并输出1到与门
- 在写入WWDG_CR之前,将当前WWDG_CR的T[6:0]与WWDG_CFR的W[6:0]比较,如果T[6:0] > W[6:0],则说明喂狗的时间太早了,输出比较结果为1
- 写入WWDG_CR输出1,过早喂狗输出1,两条线路经过与门的结果就是1。再通过或门输出1。如果WDGA=1,经过与门后输出1产生复位信号,复位电路
WWDG 工作特性
-
递减计数器 T[6:0] 的值小于 0x40 时,WWDG 产生复位
注意这里写的是 T[6:0],包含 T6 位,所以是值减到 0x40 之后,再减一次就复位。
-
递减计数器T[6:0]在窗口W[6:0]外被重新装载时,WWDG产生复位
这就是不能过早喂狗。
-
递减计数器 T[6:0] 等于 0x40 时可以产生早期唤醒中断(EWI),用于重装载计数器以避免 WWDG 复位
注意这里是等于 0x40 时,可以产生一个早期中断,上面是小于 0x40 时,产生复位,0x40,这时 T6 还是 1
这里的意思是减到 0x40 时,产生中断,这个中断也可以称作“死前中断”,马上就要溢出复位了,再提醒一下你,要不要干点啥。所以在这个早期唤醒中断里,我们一般可以用来执行一些紧急操作,比如保存重要数据、关闭危险设备等等;或者,还有一种写法,就是虽然超时喂狗了,但是我可以在中断里执行一些代码进行解决;或者这个任务不是很危险,超时了我就只想做一些提示,不想让它复位了,这样的话,我们就可以在这个早期唤醒中断里,直接执行喂狗,阻止系统复位,然后提示一些信息就完事了
-
定期写入WWDG_CR寄存器(喂狗)以避免WWDG复位
WWDG时序
纵轴是 T[6:0],包含 T6 位的 CNT,递减计数器,喂狗之后,在这个数值往下递减,如果这个计数值还很大,高于这个 W[6:0] 窗口值了,你就不能急着喂狗,也就是在这一块,是不允许刷新的;
之后,当计数值小于窗口值了,进入到刷新窗口,可以喂狗,当然喂狗也不能太晚,要在减到 3F 之前喂,3F 这个值,就是 T6 位刚等于 0 的时刻,所以下面这里,T6 位,在这个时刻变为 0,同时复位信号也在这个时刻发送
WWDG喂狗时间
超时时间:TWWDG = TPCLK1 × 4096 × WDGTB预分频系数 × (T[5:0] + 1)
喂狗的最晚时间。和独立看门狗的基本一样。
PCLK1 × 4096:PCLK1 进来之后,其实是先执行了一个固定的 4096 分频,这里框图没画出来,因为 36 M 的频率还是太快了,先来个固定分频降一降。
WDGTB 预分频系数:分频系数 = 2^WDGTB,当 WDGTB = 0 时,就是 1 分频;WDGTB = 1,就是 2 分频;WDGTB = 2,就是 4 分频;WDGTB = 3,就是 8 分频,分频系数只有这 4 种选择
T[5:0]:写入六位计数器的值
窗口时间:TWIN = TPCLK1 × 4096 × WDGTB预分频系数 × (T[5:0] - W[5:0])
就是喂狗的最早时间。这个公式手册里并没有给出,不过也总结出来了。这个和上面差不多,主要区别就是后面这里,上面这个超时时间,是计数器减到 0 的时间,而这个,是计数器减到窗口值的时间,所以我们拿 T[5:0] - W[5:0] 就可以算出窗口时间了,同时也要注意,这个 W[5:0] 也是不包含 W6 这一位的。然后 WWDG 框图这里,从这里可以看出,计数器等于窗口值时,就已经可以喂狗了,所以这里公式后面,就不用再 +1 了,这就是窗口时间的公式。
其中:TPCLK1 = 1 / FPCLK1
IWDG和WWDG对比
IWDG独立看门狗 | WWDG窗口看门狗 | |
---|---|---|
复位 | 计数器减到0后 | 计数器T[5:0]减到0后、过早重装计数器 |
中断 | 无 | 早期唤醒中断 |
时钟源 | LSI(40KHz) | PCLK1(36MHz) |
预分频系数 | 4、8、32、64、128、256 | 1、2、4、8 |
计数器 | 12位 | 6位(有效计数) |
超时时间 | 0.1ms~26214.4ms | 113us~58.25ms |
喂狗方式 | 写入键寄存器,重装固定值RLR | 直接写入计数器,写多少重装多少 |
防误操作 | 键寄存器和写保护 | 无 |
用途 | 独立工作,对时间精度要求较低 | 要求看门狗在精确计时窗口起作用 |
IWDG相关库函数
// 启用或禁用对IWDG_PR和IWDG_RLR寄存器的写访问。
void IWDG_WriteAccessCmd(uint16_t IWDG_WriteAccess);
// 写IWDG的预分频值
void IWDG_SetPrescaler(uint8_t IWDG_Prescaler);
// 写IWDG的重装载值
void IWDG_SetReload(uint16_t Reload);
// 喂狗。将重装载寄存器的值写入自减计数器中
void IWDG_ReloadCounter(void);
// 使能IWDG
void IWDG_Enable(void);
// 获取IWDG的相关标志位
FlagStatus IWDG_GetFlagStatus(uint16_t IWDG_FLAG);
WWDG相关库函数
// WWDG复位
void WWDG_DeInit(void);
// 写WWDG的预分频值
void WWDG_SetPrescaler(uint32_t WWDG_Prescaler);
// 写WWDG的窗口值W[5:0]。最早喂狗的时间
void WWDG_SetWindowValue(uint8_t WindowValue);
// 使能中断。自减计数器T[5:0]=0(下一个时钟周期就要溢出产生复位信号)时可以触发一次中断
void WWDG_EnableIT(void);
// 喂狗。写自减计数器T[6:0]
void WWDG_SetCounter(uint8_t Counter);
// 使能WWDG。这里要传入的值会写入自减计数器T[6:0]。为了防止使能WWDG时T6=0,直接产生复位信号,所以使能前要先写T[6:0]
void WWDG_Enable(uint8_t Counter);
// 获取WWDG相关标志位
FlagStatus WWDG_GetFlagStatus(void);
// 清除WWDG相关标志位
void WWDG_ClearFlag(void);
案例
IWDG
示例代码
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "Delay.h"
// 初始化按键,上拉输入
void Key_Init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
}
void Key()
{
// 当按键按住不放时进入死循环
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == Bit_RESET)
{
while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == Bit_RESET);
}
}
int main()
{
OLED_Init();
Key_Init();
OLED_ShowString(1,1,"IWDG:");
// 判断是否是独立看门狗产生的复位
if(RCC_GetFlagStatus(RCC_FLAG_IWDGRST) == SET)
{
OLED_ShowString(2,1,"IWDG_Reset");
// 一定要记得清除标志位,因为复位标志位即使复位了也不会自动清除
RCC_ClearFlag();
}
else
{
OLED_ShowString(2,1,"Normal_Reset");
}
// IWDG的寄存器写使能
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
// 写预分频寄存器。分频系数16
IWDG_SetPrescaler(IWDG_Prescaler_16);
// 写重装载寄存器。
IWDG_SetReload(2499 + 1); // 最大喂狗时间为:1 / 40KHz * 16 * 2500 = 1000ms
// 喂狗。将重装载值写入计数器。该函数底层是在键寄存器中写入0xAAAA,正好打开了IWDG寄存器的写保护
// 在使能IWDG前喂一次狗防止计数器为0,直接复位
IWDG_ReloadCounter();
// 使能IWDG
IWDG_Enable();
while(1)
{
Key();
Delay_ms(400);
IWDG_ReloadCounter();
OLED_ShowString(3,1,"WWW");
Delay_ms(300);
OLED_ShowString(3,1," ");
}
}
WWDG
接线图
示例代码
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "Delay.h"
// 初始化按键,上拉输入
void Key_Init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
}
void Key()
{
// 当按键按住不放时进入死循环
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == Bit_RESET)
{
while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == Bit_RESET);
}
}
int main()
{
OLED_Init();
Key_Init();
OLED_ShowString(1,1,"WWDG:");
// 判断是否是窗口看门狗产生的复位
if(RCC_GetFlagStatus(RCC_FLAG_WWDGRST) == SET)
{
OLED_ShowString(2,1,"WWDG_Reset");
// 一定要记得清除标志位,因为复位标志位即使复位了也不会自动清除
RCC_ClearFlag();
}
else
{
OLED_ShowString(2,1,"Normal_Reset");
}
// WWDG输入APB1外设,开启WWDG时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE);
// 设置预分频系数为8分频
WWDG_SetPrescaler(WWDG_Prescaler_8);
// W[5:0]=11;加上0x40是将W6位置1。因为喂狗时是将T[6:0]与W[6:0]比大小判断是否提前喂狗。在30ms前喂狗会产生复位信号
WWDG_SetWindowValue(0x40 + 21); // 1 / 36000KHz * 4096 * 8 * (54 - 21) 约等于30ms
// 使能WWDG。使能WWDG的同时写入自减计数器。T[5:0]=54;加上0x40是将T6位复位标志位置1。超过50ms没有喂狗就会产生复位信号
WWDG_Enable(0x40 + 54); // 1 / 36000KHz * 4096 * 8 * (54 + 1) 约等于50ms
while(1)
{
Key();
Delay_ms(20);
OLED_ShowString(3,1,"WWW");
Delay_ms(20);
OLED_ShowString(3,1," ");
// 喂狗。写自减计数器
WWDG_SetCounter(0x40 + 54);
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY