1.看门狗简介
看门狗起始就是一个定时器,从功能上说它可以让微控制器在程序发生意外(程序进入死循环或跑飞)的时候,能重新恢复到系统刚上电状态,以保障系统出问题的时候可以重启一次。说的简单一点,看门狗就是能让程序出问题时能重新启动系统。STM32有两个看门狗,独立看门狗和窗口看门狗。
1.1独立看门狗(Independent Watchdog,IWDG)
独立看门狗号称宠物狗,它有一个12位的递减计数器,当计数器的值从某个值一直减到0的时候,系统就会产生一个复位信号,即IWDG_RESET。如果在计数器没有减到0之前,刷新了计数器的值,那么就不会产生复位信号,这个动作就是我们经常说的喂狗。
1.2独立看门狗时钟
独立看门狗的时候由独立的RC振荡器LSI提供,即使主时钟发生故障它仍然有效,非常独立。LSI的频率一般在30~60KHz之间,所以独立看门狗的定时时间并不一定非常准确,只适用于对时间精度要求比较低的场合。
上图中,配置的IWDG时钟CK_IWDG=32KHz。
1.3独立看门狗计数器时钟
递减计数器的时钟由LSI经过一个8位的预分频器得到,我们可以操作预分频器寄存器IWDG_PR来设置分频因子,分频因子可以是:[4,8,16,32,64,128,256],计数器时钟CK_CNT=CK_IWDG/IWDG_PR。
1.4重装载寄存器
重装载寄存器是一个12位的寄存器,里面装着要刷新到计数器的值,这个值的大小决定着独立看门狗的溢出时间。超时时间Tout=1/CK_CNT*rlv,rlv是重装载寄存器的值。
若IWDG_PR=32,rlv=2000,所以CK_CNT=32KHz/32=1KHz,Tout=1/1000*2000=2s。意味着2s之内我们就得喂狗,不然系统就会重启。
1.5标准库演示
#ifndef __BSP_IWDG_H
#define __BSP_IWDG_H
#ifdef __cplusplus
extern "C"{
#endif
#include "stm32f4xx.h"
void Init_IWDG(uint8_t prv,uint16_t rlv);
void IWDG_Feed(void);
#ifdef __cplusplus
}
#endif
#endifc
#include "bsp_iwdg.h"
/*
* 设置 IWDG 的超时时间
* Tout = prv/LSICLK * rlv (s)
* prv可以是[4,8,16,32,64,128,256]
* prv:预分频器值,取值如下:
* @arg IWDG_Prescaler_4: IWDG prescaler set to 4
* @arg IWDG_Prescaler_8: IWDG prescaler set to 8
* @arg IWDG_Prescaler_16: IWDG prescaler set to 16
* @arg IWDG_Prescaler_32: IWDG prescaler set to 32
* @arg IWDG_Prescaler_64: IWDG prescaler set to 64
* @arg IWDG_Prescaler_128: IWDG prescaler set to 128
* @arg IWDG_Prescaler_256: IWDG prescaler set to 256
*
* 独立看门狗使用LSI作为时钟。
* LSI 的频率一般在 30~60KHZ 之间,根据温度和工作场合会有一定的漂移,我
* 们的STM32F407中为32KHz,所以独立看门狗的定时时间并不一定非常精确,只适用于对时间精度
* 要求比较低的场合。
*
* rlv:预分频器值,取值范围为:0-0XFFF
* 函数调用举例:
* Init_IWDG(IWDG_Prescaler_32 ,1000); // IWDG 1s 超时溢出
* (32/LSICLK)*1000 = 1s
*/
void Init_IWDG(uint8_t prv,uint16_t rlv)
{
// 使能 预分频寄存器PR和重装载寄存器RLR可写
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
// 设置预分频器值
IWDG_SetPrescaler(prv);
// 设置重装载寄存器值
IWDG_SetReload(rlv);
// 把重装载寄存器的值放到计数器中
IWDG_ReloadCounter();
// 使能 IWDG
IWDG_Enable();
}
void IWDG_Feed(void)
{
// 把重装载寄存器的值放到计数器中,喂狗,防止IWDG复位
// 当计数器的值减到0的时候会产生系统复位
IWDG_ReloadCounter();
}
int main(void)
{
Init_LED();
// //设置中断分组
// NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
//
// Init_USART();
Init_IWDG(IWDG_Prescaler_32,1000);
/* Infinite loop */
while (1)
{
}
}
上述代码思路为:Init_LED()中会将LED点亮,然后开启独立看门狗,设置的溢出时间为1s,那么1s后由于没有喂狗,mcu会复位,LED也会灭,但是由于时间太快了,肉眼应该无法看到。所以,我们使用了示波器进行测试LED的输入电平。
int main(void)
{
Init_LED();
// //设置中断分组
// NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
//
// Init_USART();
Init_IWDG(IWDG_Prescaler_32,1000);
/* Infinite loop */
while (1)
{
IWDG_Feed();
}
}
如果我们在巡航中一直喂狗,那么mcu就不会复位。
2.窗口看门狗(Window Watchdog,WWDG)
窗口看门狗号称警犬,它也有一个递减计数器不断的往下递减计数,当减到一个固定值0x40时还不喂狗的话,就会产生复位,这个值叫窗口的下限,是固定值,不能改变。不同的是,窗口看门狗的计数器在减到某一个数之前喂狗也会产生复位,这个值叫窗口的上限,上限值由用户独立设置。窗口看门狗计数器的值必须在上窗口和下窗口之间才可以喂狗,这就是窗口看门狗中窗口两个字的含义。
RLR是重装载寄存器,用来设置独立看门狗的计数器的值。TR是窗口看门狗的计数器的值,由用户独立设置,WR是窗口看门狗的上窗口值,由用户独立设置。
在出现下述两种情况之一时产生看门狗复位:
- 当喂狗的时候如果计数器的值大于窗口上限值。
- 当计数器的数值从0x40减到0x3F。
如果启动了看门狗并且使能中断,当递减计数器等于0x40时产生早期唤醒中断(EWI),这个中断我们称它为死前中断或者叫遗嘱中断, 在中断函数里面我们应该出来最重要的事情,而且必须得快,因为递减计数器再减一次,就会产生系统复位。
注意事项:
- 上限值必须大于0x40,否则就无窗口了。
- 窗口看门狗时钟来源PCLK1(APB1总线时钟)分频后。
2.1标准库演示
#ifndef __BSP_WWDG_H
#define __BSP_WWDG_H
#ifdef __cplusplus
extern "C"{
#endif
#include "stm32f4xx.h"
#define WWDG_CNT 0x7F
void Init_WWDG(uint8_t tr, uint8_t wr, uint32_t prv);
void WWDG_Feed(void);
#ifdef __cplusplus
}
#endif
#endif
#include "bsp_wwdg.h"
#include "stdio.h"
/* WWDG 配置函数
* tr :递减计时器的值, 取值范围为:0x7f~0x40
* wr :窗口值,取值范围为:0x7f~0x40
* prv:预分频器值,取值可以是
* @arg WWDG_Prescaler_1: WWDG counter clock = (PCLK1/4096)/1
* @arg WWDG_Prescaler_2: WWDG counter clock = (PCLK1/4096)/2
* @arg WWDG_Prescaler_4: WWDG counter clock = (PCLK1/4096)/4
* @arg WWDG_Prescaler_8: WWDG counter clock = (PCLK1/4096)/8
*/
void Init_WWDG(uint8_t tr, uint8_t wr, uint32_t prv)
{
//使能窗口看门狗时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG,ENABLE);
// 设置预分频器值
WWDG_SetPrescaler(prv);
// 设置重装载寄存器值
WWDG_SetWindowValue(wr);
// 使能 WWDG
WWDG_Enable(WWDG_CNT&tr);
//配置中断控制器并使能中断
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel=WWDG_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=3;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;
NVIC_Init(&NVIC_InitStruct);
WWDG_ClearFlag();//清楚标志位
WWDG_EnableIT();//使能中断
}
void WWDG_IRQHandler(void)
{
WWDG_SetCounter(WWDG_CNT);
WWDG_ClearFlag();
}
void WWDG_Feed(void)
{
printf("WWDG_Feed\r\n");
// 把重装载寄存器的值放到计数器中,
WWDG_SetCounter(WWDG_CNT);
}
int main(void)
{
Init_USART();
Init_LED();
//设置中断分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
Init_WWDG(0x7f,0x5f,WWDG_Prescaler_8);
printf("hello,this is stm32f407\r\n");
/* Infinite loop */
while (1)
{
//-----------------------------------------------------
// 这部分应该写需要被WWDG监控的程序,这段程序运行的时间
// 决定了窗口值应该设置成多大。
//-----------------------------------------------------
// 计时器值,初始化成最大0X7F,当开启WWDG时候,这个值会不断减小
// 当计数器的值大于窗口值时喂狗的话,会复位,当计数器减少到0X40
// 还没有喂狗的话就非常非常危险了,计数器再减一次到了0X3F时就复位
// 所以要当计数器的值在窗口值和0X40之间的时候喂狗,其中0X40是固定的。
if ( (WWDG->CR & 0X7F) < 0x5f )
{
// 喂狗,重新设置计数器的值为最大0X7F
WWDG_Feed();
}
}
}
注意,我们试过了在死前中断中喂狗,但是好像来不及,mcu还是重启了。所以,我们在main函数中进行了计数判断喂狗。
主函数中我们把WWDG的计数器的值设置 为0X7F,上窗口值设置为0X5F,分频系数为8分频。在while死循环中,我们不断读取计数器的值, 当计数器的值减小到小于上窗口值的时候,我们喂狗,让计数器重新计数。
在while死循环中,一般是我们需要监控的程序,这部分代码的运行时间,决定了上窗口值应该设置为多少,当监控的程序运行完毕之后, 我们需要执行喂狗程序,比起独立看门狗,这个喂狗的窗口时间是非常短的,对时间要求很精确。如果没有在这个窗口时间内喂狗的话, 那就说明程序出故障了,会产生提前唤醒中断,最后系统复位。