看到 ‘看门狗’ 这个词对于刚入门的人来说是个很新鲜的词,那么什么是看门狗呢?
看门狗的作用就是监控程序运行是否异常,在程序出现异常进入死循环的时候,可以自动系统复位,保证程序正常运行。
出于对单片机运行状态进行实时监测的考虑,便产生了一种专门用于监测单片机程序运行状态的模块或者芯片,俗称“看门狗”(watchdog) 。
STM32内置了两个看门狗,1个是独立看门狗、1个是窗口看门狗,现我们先列举一下这两个看门狗的异同,再分别做详细的介绍。
1、概述
1)独立看门狗
独立看门狗(IWDG)由内部专门的40Khz低速时钟(LSI)驱动,即使主时钟发生故障它仍有效。
独立看门狗适合应用于需要看门狗作为一个在主程序之外 能够完全独立工作,并且对时间精度要求低的场合。
2)窗口看门狗
窗口看门狗由从APB1时钟分频后得到时钟驱动。通过可配置的时间窗口来检测应用程序非正常的过迟或过早操作。
窗口看门狗最适合那些要求看门狗在精确计时窗口起作用的程序,通常被用来监测由外部干扰或不可预见的逻辑条件造成的应用程序背离正常的运行序列而产生的软件故障。
2、二者区别
窗口看门狗最适合那些要求看门狗在精确计时窗口起作用的程序。
1)独立看门狗没有中断,窗口看门狗有中断
2)独立看门狗有硬件软件之分,窗口看门狗只能软件控制
3)独立看门狗只有下限,窗口看门狗有下限和上限
4)独立看门狗是12位递减的。窗口看门狗是7位递减的
5)独立看门狗是用的内部的大约40KHZ RC振荡器,窗口看门狗是用的系统时钟APB1ENR
3、详细介绍
1)独立看门狗
上图是独立看门狗的框图,我们看一下独立看门狗如何工作?
①往键寄存器(IWDG_KR)中写入0xCCCC,开启独立看门狗,开启独立看门狗后,计数器开始从其复位值0xFFF递减
②当计数器值计数到尾值0x000时会产生一个复位信号(IWDG_RESET)
③无论什么时候,在键寄存器(IWDG_KR)中写入0xAAAA(通常说的喂狗),自动重装寄存器(IWDG_RLR)的值就会重新加载到计数器,从而避免看门狗复位。
④如果程序异常,就无法正常喂狗,从而系统复位。
独立看门狗寄存器介绍
①IWDG_KR键寄存器
②IWDG_RLR重装载寄存器
③IWDG_PR预分频寄存器
预分频值如何确定?说到该值我们应该知道独立看门狗的超时时间如何计算?
看门狗超时时间公式如下:
Tout=4∗2prer∗rlr40(ms)
该公式中
prer:预分频系数0、1、2…6,下图中的PR[2:0]位
rlr:IWDG_RLR中的重装载值
prer与rlr确定示例:
如果要求独立看门狗IWDG 超时复位时间是1s,则Tout=1s,若prer=0,则rlr=10000;若prer=2,则rlr=625(十进制),其16进制是0x271
上图的输入时钟频率LSI=40Khz,公式的分子才是40。实际上,MCU内部的RC频率会在30KHz到60Khz之间变化。
最长超时时间=IWDG_RLR寄存器最大值0xFFF 算出的
④IWDG_SR状态寄存器
从上可以看出:
键值寄存器IWDG_KR: 0~15位有效,用于启动看门狗、喂狗
预分频寄存器IWDG_PR:0~2位有效,用于设置预分频,具有写保护功能,要操作先取消写保护。
重装载寄存器IWDG_RLR:0~11位有效,用于设置计数初值,具有写保护功能,要操作先取消写保护。
状态寄存器IWDG_SR:0~1位有效
独立看门狗编程步骤
① 取消寄存器写保护:
IWDG_WriteAccessCmd(uint16_t IWDG_WriteAccess););//取消写保护:0x5555使能
② 设置独立看门狗的预分频系数,确定时钟:
IWDG_SetPrescaler(uint8_t IWDG_Prescaler);//设置预分频系数:写PR
③ 设置看门狗重装载值,确定溢出时间:
IWDG_SetReload(uint16_t Reload);//设置重装载值:写RLR
④ 使能看门狗
IWDG_ReloadCounter(void);//喂狗:写0xAAAA到KR
⑤ 应用程序喂狗:
IWDG_Enable(void);//使能看门狗:写0xCCCC到KR
独立看门狗常用库函数
void IWDG_WriteAccessCmd(uint16_t IWDG_WriteAccess);//取消写保护:0x5555使能
void IWDG_SetPrescaler(uint8_t IWDG_Prescaler);//设置预分频系数:写PR
void IWDG_SetReload(uint16_t Reload);//设置重装载值:写RLR
void IWDG_ReloadCounter(void);//喂狗:写0xAAAA到KR
void IWDG_Enable(void);//使能看门狗:写0xCCCC到KR
FlagStatus IWDG_GetFlagStatus(uint16_t IWDG_FLAG);//状态:重装载/预分频 更新
2)窗口看门狗
使用窗口看门口的优势:对于一般的看门狗,程序可以在它产生复位前的任意时刻刷新看门狗,但这有一个隐患,有可能程序跑乱了又跑回到正常的地方,或跑乱的程序正好执行了刷新看门狗操作,这样的情况下一般的看门狗就检测不出来了;
如果使用窗口看门狗,程序员可以根据程序正常执行的时间设置刷新看门狗的一个时间窗口,保证不会提前刷新看门狗也不会滞后刷新看门狗,这样可以检测出程序没有按照正常的路径运行非正常地跳过了某些程序段的情况。
窗口看门狗如何工作,请看下面:
应用程序在正常运行过程中必须定期地写入WWDG_CR寄存器以防止MCU发生复位。只有当计数器值小于窗口寄存器的值时,才能进行写操作。储存在WWDG_CR寄存器中的数值必须在0xFF和0xC0之间:
- 启动看门狗WWDG_CR
在系统复位后,看门狗总是处于关闭状态,设置WWDG_CR寄存器的WDGA位能够开启看门狗,随后它不能再被关闭,除非发生复位。 - 控制递减计数器 WWDG_CNT
递减计数器处于自由运行状态,即使看门狗被禁止,递减计数器仍继续递减计数。当看门狗被启用时,T6位必须被设置,以防止立即产生一个复位。 T[5:0]位包含了看门狗产生复位之前的计时数目;复位前的延时时间在一个最小值和一个最大值之间变化,这是因为写入WWDG_CR寄存器时,预分频值是未知的。 配置寄存器(WWDG_CFR) 中包含窗口的上限值:要避免产生复位,递减计数器必须在其值小于窗口寄存器的数值并且大于0x3F时被重新装载,0描述了窗口寄存器的工作过程。 另一个重装载计数器的方法是利用早期唤醒中断(EWI)。设置WWDG_CFR寄存器中的WEI位开启该中断。当递减计数器到达0x40时,则产生此中断,相应的中断服务程序(ISR)可以用来加载计数器以防止WWDG复位。在状态寄存器(WWDG_SR)中写’0’可以清除该中断。 注: 可以用T6位产生一个软件复位(设置WDGA位为’1’,T6位为’0’)。
看门狗框图举例说明
1)若WWDG_CR=0x60(0x01100000),递减计数器值为 0x60,即T6=1、T5=1、T4到T0为0,当递减计数器递减到0x3F,即T6=0时,看图知 会产生复位信号
2)若WWDG_CFR=0x250(0x001001010000),即W6:0值为0x50,即在递减计数器值(T6:0)> W6:0 时,让WWDG_CR的第7位WDGA为1,则 也会产复位信号
总结:
STM32F的窗口看门狗中有一个7位的递减计数器T[6:0],它会在出现下述2种情况之一时产生看门狗复位:
- 当喂狗的时候如果计数器的值大于某一设定数值W[6:0]时,此设定数值在WWDG_CFR寄存器定义。
- 当计数器的数值从0x40减到0x3F时【T6位跳变到0】 。
如果启动了看门狗并且允许中断,当递减计数器等于0x40时产生早期唤醒中断(EWI),它可以用于喂狗以避免WWDG复位。
窗口看门狗超时公式解释
Twwdg=4096∗2WDGTB∗(T[5:0]+1)Fpclk1(ms)
其中:
Twwdg:WWDG超时时间(单位为ms)
Fpclk:APB1的时钟频率(单位为Khz)
WDGTB:WWDG的预分频系数
T[5:0]:窗口看门狗的计数器低6位
窗口看门狗寄存器
①WWDG_CR控制寄存器
该寄存器相关的库函数:
void WWDG_Enable(uint8_t Counter);//启动并设置初始值
void WWDG_SetCounter(uint8_t Counter);//喂狗
②WWDG_CRF配置寄存器
该寄存器相关的库函数:
void WWDG_EnableIT(void);//使能提前唤醒中断
void WWDG_SetPrescaler(uint32_t WWDG_Prescaler);//设置预分频系数
void WWDG_SetWindowValue(uint8_t WindowValue);//设置窗口上限
③ WWDG_SR状态寄存器
该寄存器相关的库函数:
FlagStatus WWDG_GetFlagStatus(void);
void WWDG_ClearFlag(void);
窗口看门狗编程步骤
① 使能看门狗时钟:
RCC_APB1PeriphClockCmd();
② 设置分频系数:
WWDG_SetPrescaler();
③ 设置上窗口值:
WWDG_SetWindowValue();
④ 开启提前唤醒中断并分组(可选):
WWDG_EnableIT();
NVIC_Init();
⑤ 使能看门狗:
WWDG_Enable();
⑥ 喂狗:
WWDG_SetCounter();
⑦编写中断服务函数
WWDG_IRQHandler();
3、示例程序
1)独立看门狗
//初始化独立看门狗
//prer:分频数:0~7(只有低3位有效!)
//分频因子=4*2^prer.但最大值只能是256!
//rlr:重装载寄存器值:低11位有效.
//时间计算(大概):Tout=((4*2^prer)*rlr)/40 (ms).
void IWDG_Init(u8 prer,u16 rlr)
{
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); //使能对寄存器IWDG_PR和IWDG_RLR的写操作
IWDG_SetPrescaler(prer); //设置IWDG预分频值:设置IWDG预分频值为64
IWDG_SetReload(rlr); //设置IWDG重装载值
IWDG_ReloadCounter(); //按照IWDG重装载寄存器的值重装载IWDG计数器
IWDG_Enable(); //使能IWDG
}
//喂独立看门狗
void IWDG_Feed(void)
{
IWDG_ReloadCounter();//reload
}
//该程序思路是 按WKU_PRES按键进行喂狗,则程序不会复位,LED一直亮,否则程序会自动复位,看到的现象是LED灯在闪烁
int main(void)
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
LED_Init(); //初始化与LED连接的硬件接口
KEY_Init(); //按键初始化
IWDG_Init(4,625); //与分频数为64,重载值为625,溢出时间为1s
delay_ms(500); //让人看得到灭
LED0=0; //点亮LED0
while(1)
{
if(KEY_Scan(0)==WKUP_PRES)
{
IWDG_Feed();//如果WK_UP按下,则喂狗
}
delay_ms(10);
};
}
2)窗口看门狗
//保存WWDG计数器的设置值,默认为最大.
u8 WWDG_CNT=0x7f;
//初始化窗口看门狗
//tr :T[6:0],计数器值
//wr :W[6:0],窗口值
//fprer:分频系数(WDGTB),仅最低2位有效
//Fwwdg=PCLK1/(4096*2^fprer).
void WWDG_Init(u8 tr,u8 wr,u32 fprer)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE); // WWDG时钟使能
WWDG_CNT=tr&WWDG_CNT; //初始化WWDG_CNT.
WWDG_SetPrescaler(fprer);设置IWDG预分频值
WWDG_SetWindowValue(wr);//设置窗口值
WWDG_Enable(WWDG_CNT); //使能看门狗 , 设置 counter .
WWDG_ClearFlag();//清除提前唤醒中断标志位
WWDG_NVIC_Init();//初始化窗口看门狗 NVIC
WWDG_EnableIT(); //开启窗口看门狗中断
}
//重设置WWDG计数器的值
void WWDG_Set_Counter(u8 cnt)
{
WWDG_Enable(cnt);//使能看门狗 , 设置 counter .
}
//窗口看门狗中断服务程序
void WWDG_NVIC_Init()
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = WWDG_IRQn; //WWDG中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //抢占2,子优先级3,组2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //抢占2,子优先级3,组2
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);//NVIC初始化
}
void WWDG_IRQHandler(void)
{
WWDG_SetCounter(WWDG_CNT); //当禁掉此句后,窗口看门狗将产生复位
WWDG_ClearFlag(); //清除提前唤醒中断标志位
LED1=!LED1; //LED状态翻转
}
//该程序的现象是 在中断中自动喂狗,程序不会复位,LED0先亮后灭,LED1闪烁
int main(void)
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
LED_Init();
WWDG_Init(0X7F,0X5F,WWDG_Prescaler_8);//计数器值为7f,窗口寄存器为5f,分频数为8
LED0=0;
delay_ms(300);
while(1)
{
LED0=1;
}
}