1、什么是看门狗
看门狗,顾名思义,主要作用就是看家护院,随时监控异常情况的。用比较接地气的话来说就是,当你在家里睡着或者在干别的事情时候,看门狗帮你看好家里的大门(监控程序),一旦发现有贼或者不对劲的时候(发现异常),就会大叫,把你叫过来(程序复位)。
通过上面的例子,已经对看门狗有了一定的认识和了解,那么下面就用正规一点的方式介绍一下单片机的看门狗。
在由单片机构成的微型计算机系统中,由于单片机的工作常常会受到来自外界电磁场的干扰,造成各种寄存器和内存的数据混乱,会导致程序指针错误,不在程序区,取出错误的程序指令等,都会陷入死循环,程序的正常运行被打断,由单片机控制的系统无法继续正常工作,会造成整个系统的陷入停滞状态,发生不可预料的后果。简单的总结就是,窗口看门狗可以避免程序跑飞,死循环。
看门狗(守护)就是定期的查看芯片内部的情况,一旦发生错误就向芯片发出重启信号的电路。看门狗命令在程序的中断中拥有最高的优先级。
看门狗电路的应用,使单片机可以在无人状态下实现连续工作,其工作原理是:看门狗芯片和单片机的一个I/O引脚相连,该I/O引脚通过程序控制它定时地往看门狗的这个引脚上送入脉冲信号,这一程序语句是分散地放在单片机其他控制语句中间的,一旦单片机由于干扰造成程序跑飞后而陷入某一程序段进入死循环状态时,写看门狗引脚的程序便不能被执行,这个时候,看门狗电路就会由于得不到单片机送来的信号,便在它和单片机复位引脚相连的引脚上送出一个复位信号,使单片机发生复位。即程序从程序存储器的起始位置开始执行,这样便实现了单片机的自动复位。
**总结:**看门狗就是一个监控程序是否可靠运行的电路,如果不能可靠运行,则复位CPU。
在STM32中,有两种看门狗外设,一种是独立看门狗,另一种是窗口看门狗。
2、独立看门狗
2.1、独立看门狗介绍
如图所示为独立看门狗的框图,用通俗一点的话来解释就是一个12位的递减计数器(2^12-1=4095),当计数器的值从某个值一直减到0的时候,系统就会产生一个复位信号,即IWDG_RESET。 如果在计数没减到0之前,刷新了计数器的值的话,那么就不会产生复位信号,这个动作就是我们经常说的喂狗。
看门狗功能由 VDD 电压域供电, 在停止模式和待机模式下仍能工作。独立看门狗适用于对时间精度要求较低的场合,如常见的手机系统崩溃死机重启。
独立看门狗 (IWDG) 由其专用低速时钟 (LSI) 驱动,因此即便在主时钟发生故障时仍然保持工作状态。

2.2、独立看门狗寄存器
①、关键字寄存器(IWDG_KR)
键寄存器IWDG_KR可以说是独立看门狗的一个控制寄存器,主要有三种控制方式,往这个寄存器写入下面三个不同的值有不同的效果。
通过写往键寄存器写0XCCC来启动看门狗是属于软件启动的方式,一旦独立看门狗启动,它就关不掉,只有复位才能关掉。

②、预分频寄存器(IWDG_PR)

③、重载寄存器 (IWDG_RLR)
重装载寄存器是一个12位的寄存器,里面装着要刷新到计数器的值,这个值的大小决定着独立看门狗的溢出时间。 超时时间Tout = (4*2^prv) / 40 * rlv (s) ,prv是预分频器寄存器的值,rlv是重装载寄存器的值。

④、状态寄存器(IWDG_SR)
状态寄存器SR只有位0:PVU和位1:RVU有效,这两位只能由硬件操作,软件操作不了。

2.3、独立看门狗程序设计
①、独立看门狗配置
cpp
void IWDG_Config(void)
{
//RCC模块开启LSI晶振
RCC_LSICmd(ENABLE);
//等待LSI晶振稳定
while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET);
#if 1 //库函数配置独立看门狗
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);//关闭写保护
IWDG_SetPrescaler(IWDG_Prescaler_32);//32分频
IWDG_SetReload(500);
//500ms倒计时
IWDG_ReloadCounter();
//喂狗
IWDG_Enable();//开启看门狗
#else //寄存器配置独立看门狗
IWDG->KR = 0x5555; //关闭写保护
IWDG->PR = 0x03; //011-->32分频
IWDG->RLR = 2000; //2s倒计时
IWDG->KR = 0xAAAA; //喂狗
IWDG->KR = 0xCCCC; //开启
#endif
}
②、喂狗
在裸机代码环境下,喂狗操作在定时器中断服务函数(中断抢占优先级一定要是最高)里面进行,为了监控主程序是否能够正常执行,还得增加一个变量,统计主程序的运行次数。
如果有实时的操作系统的加持,可以在线程任务里面添加喂狗动作,如果操作系统崩溃了,能够检测到软件的错误,触发CPU的复位。
裸机情况下喂狗程序demo
cpp
/* 记录主程序执行的次数 */
static volatile uint32_t g_run_cnt=0;
int main(void)
{
while(1)
{
//业务逻辑1:读取温湿度
//业务逻辑2:OLED显示
//业务逻辑3:RFID读写
//.......
g_run_cnt++;
}
}
/* 定时器中断服务函数每100ms触发一次 */
void 定时器中断服务函数(void)
{
static uint32_t i=0;
i++;
//每20秒检测g_run_cnt的值
if(i>=200)
{
//主程序存在异常
if(g_run_cnt == 0)
{
//等待看门狗超时触发复位
while(1);
}
g_run_cnt=0;
}
//关总中断
//执行看门狗喂狗操作
IWDG_ReloadCounter();
//开总中断
}
带操作系统情况下喂狗程序demo
cpp
void app_task_iwdg(void *parg)
{
while(1)
{
//关总中断
//执行看门狗喂狗操作
IWDG_ReloadCounter();
//开总中断
//睡眠延时一会
OSTimeDly(nms,OS_OPT_TIME_PERIODIC,&err);
}
}
3、窗口看门狗
3.1、窗口看门狗介绍
通过前面的内容,其实就已经知道了,独立看门狗的工作原理就是一个递减计数器不断的往下递减计数, 当减到0之前如果没有喂狗的话,产生复位。
窗口看门狗跟独立看门狗一样,也是一个递减计数器不断的往下递减计数, 当减到一个固定值0X40时还不喂狗的话,产生复位,这个值叫窗口的下限,是固定的值,不能改变。需要注意的是,窗口看门狗,提前喂狗也会导致复位,需要在窗口区间范围内喂狗才不会被复位掉。
窗口看门狗通常被用来监测,由外部干扰或不可预见的逻辑条件造成的应用程序背离正常的运行序列而产生的软件故障。WWDG最适合那些要求看门狗在精确计时窗口起作用的应用程序。
上窗口的值可以改变, 具体的由配置寄存器CFR的位6:0 W[6:0]设置。其值必须大于0X40,且也不能大于计数器的值, 所以必须得小于0X7F。

3.2、窗口看门狗寄存器
在窗口看门狗中,一共有3个核心的寄存器,分别是:控制寄存器 (WWDG_CR)、配置寄存器 (WWDG_CFR)和状态寄存器 (WWDG_SR)。
从下面的窗口看门狗框图可以非常直接的看出来,其依赖的主时钟是PCLK1(来源于APB1,默认36MHz),也就是说,如果系统主时钟出现了故障,那么窗口定时器同样失效!!!
框图中所示的比较器CMP就是上面我们介绍窗口定时器时,喂狗时间不能晚于窗口时间实际执行处理的地方(T6:0>W6:0)。
而WWDG_CR就是喂狗时写入的值,WDGA则是用于使能启动窗口看门狗。
写入 WWDG_CR 寄存器时,始终将 1 写入 T6 位,以避免生成立即复位。

①、控制寄存器 (WWDG_CR)

②、配置寄存器 (WWDG_CFR)

③、状态寄存器 (WWDG_SR)

3.3、窗口看门狗程序设计
①、窗口看门狗配置
cpp
//窗口看门狗
void WWDOG_Config(void)
{
//窗口看门狗配置
//使能窗口看门狗时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE);
//设置窗口串口看门狗分频系数 PCLK1/4096/8 = 42Mhz/4096/8 = 1281.7Hz 0.78ms/脉冲
WWDG_SetPrescaler(WWDG_Prescaler_8);
//设置窗口的上限CFR寄存器(40-80) (127-80)*0.78 = 36.6ms内不能喂狗 (127-0x40)*0.78=49.14前喂狗
WWDG_SetWindowValue(80);
//使能串口看门狗,并设置CNT计数器从127开始倒计数
WWDG_Enable(127);
//设置上述步骤后在合适位置喂狗
//WWDG_SetCounter(127);
}
//窗口看门狗定时器初始化
void WWDG_TIM7_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
//使能TIM7时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7, ENABLE);
//定时器配置
TIM_TimeBaseStructure.TIM_Period = 40000-1;
TIM_TimeBaseStructure.TIM_Prescaler = 84-1;//84分频,1ms一个脉冲
//TIM_TimeBaseStructure.TIM_ClockDivision = 0;
//TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM7, &TIM_TimeBaseStructure);
//中断优先级配置
NVIC_InitStructure.NVIC_IRQChannel = TIM7_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
//开启定时器7
TIM_ITConfig(TIM7,TIM_IT_Update ,ENABLE);
TIM_Cmd(TIM7, ENABLE);
}
②、喂狗
在定时器的中断处理函数中,对窗口看门狗进行喂狗操作。
需要注意的是,这里的喂狗操作是在定时器中周期性的进行喂狗操作,并不是在窗口看门狗的中断处理函数中进行的!!!
窗口看门狗的中断处理函数将会在已经出现窗口看门狗异常时才被调用,那个时候喂狗,已经晚了。
cpp
//定时器7中断处理函数
void TIM7_IRQHandler(void)
{
//判断标志位是否触发了中断
if (TIM_GetITStatus(TIM7, TIM_IT_Update) == SET)
{
//喂狗
WWDG_SetCounter(127);
//清空标志位
TIM_ClearITPendingBit(TIM7, TIM_IT_Update);
}
}