笔者在学习看门狗的视频后,对看门狗仍然是一知半解,后面在实际应用中发现它是一个很好用的检测或者调试工具。所以总结一下笔者作为初学小白对看门狗的理解。
主函数初始化阶段、循环阶段和复位
众所周知,程序的运行一般是这样的:
程序在进入循环阶段之前,会在初始化阶段将每个寄存器或者某些变量赋值。初始化阶段的代码执行一次后,就不再执行了。而循环阶段的代码会执行很多次,一直循环反复的执行下去。这时,如果进行了 复位,程序就会从头开始,执行一次初始化代码,再反复执行循环代码。
而如何进行 复位 呢?常用的方法就是**"RESET" 键** ,也就是复位键 。在程序故障、跑飞或者卡死的时候,让它重头开始跑一遍,避免程序陷入到长时间的罢工状态。 不过复位键是人为按下,在程序自动运行的应用环境中,显然不适用。于是,在程序中使用 看门狗 就可以代替 复位键来重启程序。
避免程序陷入到长时间的罢工状态。 这一句话非常关键,比如下面这个检测按键是否被按下 的程序当中,**while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0);**会等待按键是否被释放(松手)。
如果这个按键一直被按下,或者这个按键出现了故障,导致这个while循环一直出不来,就会造成程序始终被卡在这个地方。
因此,另一种复位方式------看门狗,它可以通过定时来重新启动程序。 看门狗本质上是一个递减的定时器,如果程序在某个循环卡住了,当看门狗定时器时间跑完,看门狗就会复位程序,从而跳出循环,重头开始。
喂狗
看门狗只是一个检测程序故障的工具。 当我们在程序的初始化阶段设置了一个看门狗,当看门狗定时器时间跑完就会进行复位,重头开始。而我们的程序是一个循环过程,这就导致了我们需要用一个方法,让看门狗在程序正常时不作为,又要在程序故障时起作用。
这时就要进行喂狗操作 。喂狗一般是在循环阶段的最后进行,喂狗的本质是将定时器重装,从头递减,一旦喂了狗,看门狗就会重新定时,不会执行复位操作。而遇到程序卡死在某一个等待循环时,就不会执行到喂狗操作函数。这个时候,看门狗一到时间就会进行复位。另外,如果没有及时喂狗,在看门狗定时器倒计时结束前还没有喂狗,也会重启程序。也就是说循环程序执行一次 的时间需要在看门狗定时器的时间规定范围内。
独立看门狗和窗口看门狗
独立看门狗和窗口看门狗都是规定了程序循环时间,一旦时间到了,就会执行复位操作。独立看门狗是规定了一个最大时间点 ,时间从这个最大时间点递减为0,即倒计时为0后,执行复位。而窗口看门狗是规定了一个时间段 ,如果没有在这个时间段范围进行喂狗,也会执行复位。
|----------------|------------------------|
| 写入键寄存器的值 | 作用 |
| 0xCCCC | 启用独立看门狗 |
| 0xAAAA | 重新加载到计数器 (喂狗) |
| 0x5555 | 解除IWDG_PR和IWDG_RLR的写保护 |
| 除0x5555其他值 | 启用IWDG_PR和IWDG_RLR的写保护 |
键寄存器(KR)是一个控制寄存器,通过写入值来控制看门狗的操作。
独立看门狗(IWDG)初始化
首先,要设置看门狗需要有个钥匙,这个钥匙是保护寄存器被其他程序随意修改,降低干扰。
IWDG_WriteAccess_Enable 将写保护解除,后续可以设置预分频寄存器和重装寄存器
IWDG_Prescaler_16 设置预分频寄存器为16分频。
独立看门狗时钟是由一个 独立 的内部低速时钟LSI 提供。LSI = 40kHz,IWDG超时时间计算公式:T(IWDG) = 1 / LSI * PR预分频系数 * (RL重装值+1)
上面代码预分频器为16分频,40k/16=2500,即一秒可以计数2500,设置重装值为2499,最大定时时间为一秒。
IWDG_ReloadCounter(); 为喂狗操作,这里作用是重新计数,在每次循环都需要进行喂狗操作。
程序完整版:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
Key_Init(); //按键初始化
/*显示静态字符串*/
OLED_ShowString(1, 1, "IWDG TEST");
/*判断复位信号来源*/
if (RCC_GetFlagStatus(RCC_FLAG_IWDGRST) == SET) //如果是独立看门狗复位
{
OLED_ShowString(2, 1, "IWDGRST"); //OLED闪烁IWDGRST字符串
Delay_ms(500);
OLED_ShowString(2, 1, " ");
Delay_ms(100);
RCC_ClearFlag(); //清除标志位
}
else //否则,即为其他复位
{
OLED_ShowString(3, 1, "RST"); //OLED闪烁RST字符串
Delay_ms(500);
OLED_ShowString(3, 1, " ");
Delay_ms(100);
}
/*IWDG初始化*/
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); //独立看门狗写使能
IWDG_SetPrescaler(IWDG_Prescaler_16); //设置预分频为16
IWDG_SetReload(2499); //设置重装值为2499,独立看门狗的超时时间为1000ms
IWDG_ReloadCounter(); //重装计数器,喂狗
IWDG_Enable(); //独立看门狗使能
while (1)
{
Key_GetNum(); //调用阻塞式的按键扫描函数,模拟主循环卡死
IWDG_ReloadCounter(); //重装计数器,喂狗
OLED_ShowString(4, 1, "FEED"); //OLED闪烁FEED字符串
Delay_ms(200); //喂狗间隔为200+600=800ms
OLED_ShowString(4, 1, " ");
Delay_ms(600);
}
}
窗口看门狗(WWDG)初始化
窗口看门狗时钟是由APB1时钟分频得到,PCLK=36MHz。
|-----------|------------|--------------|
| 预分频系数 | 最小超时值 | 最大超时值 |
| 1 | 113 μs | 7.28 ms |
| 2 | 227 μs | 14.56 ms |
| 4 | 455 μs | 29.12 ms |
| 8 | 910 μs | 58.25 ms |
WWDG超时时间计算公式:T(WWDG) = 1 / PCLK * 4096 * WDG预分频系数 * (T[5:0]+1)
WWDG窗口时间计算公式:T(WIN) = 1 / PCLK * 4096 * WDG预分频系数 * ( T[5:0] - W[5:0] )
图中,想让T(WWDG) = 50 ms , 50 ms = 1 / 36 Mhz * 4096 * 8 *(T[5:0]+1),求得T[5:0]=54
想让T(WIN) = 30 ms , 30 ms = 1 / 36 Mhz * 4096 * 8 *(T[5:0]- W[5:0]),求得T[5:0]=21
WWDG_SetCounter(0x40 | 54); 为喂狗操作,这里作用是重新计数,在每次循环都需要进行喂狗操作。
循环程序时间应该在30ms和50ms之间,低于或者超过都会 让看门狗进行复位。
程序完整版:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
Key_Init(); //按键初始化
/*显示静态字符串*/
OLED_ShowString(1, 1, "WWDG TEST");
/*判断复位信号来源*/
if (RCC_GetFlagStatus(RCC_FLAG_WWDGRST) == SET) //如果是窗口看门狗复位
{
OLED_ShowString(2, 1, "WWDGRST"); //OLED闪烁WWDGRST字符串
Delay_ms(500);
OLED_ShowString(2, 1, " ");
Delay_ms(100);
RCC_ClearFlag(); //清除标志位
}
else //否则,即为其他复位
{
OLED_ShowString(3, 1, "RST"); //OLED闪烁RST字符串
Delay_ms(500);
OLED_ShowString(3, 1, " ");
Delay_ms(100);
}
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE); //开启WWDG的时钟
/*WWDG初始化*/
WWDG_SetPrescaler(WWDG_Prescaler_8); //设置预分频为8
WWDG_SetWindowValue(0x40 | 21); //设置窗口值,窗口时间为30ms
WWDG_Enable(0x40 | 54); //使能并第一次喂狗,超时时间为50ms
while (1)
{
Key_GetNum(); //调用阻塞式的按键扫描函数,模拟主循环卡死
OLED_ShowString(4, 1, "FEED"); //OLED闪烁FEED字符串
Delay_ms(20); //喂狗间隔为20+20=40ms
OLED_ShowString(4, 1, " ");
Delay_ms(20);
WWDG_SetCounter(0x40 | 54); //重装计数器,喂狗
}
}