STM32 有两个看门狗,一个是独立看门狗另外一个是窗口看门狗,独立看门狗号称宠物狗,窗口看门狗号称警犬,本章我们主要分析独立看门狗的功能框图和它的应用。独立看门狗用通俗一点的话来解释就是一个 12 位的递减计数器,当计数器的值从某个值一直减到 0 的时候,系统就会产生一个复位信号,即 IWDG_RESET。如果在计数没减到 0 之前,刷新了计数器的值的话,那么就不会产生复位信号,这个动作就是我们经常说的喂狗。看门狗功能由 VDD 电压域供电,在停止模式和待机模式下仍能工作。

1 IWDG—独立看门狗

MCU微控制器构成的微型计算机系统中,由于微控制器的工作常常会受到来自外界电磁场的干扰,造成各种寄存器和内存的数据混乱,从而导致程序指针错误、不在程序区、取出错误的程序指令等,都有可能会导致程序执行陷入死循环,程序的正常运行被打断,由微控制器控制的系统无法继续正常工作,导致整个系统的陷入停滞状态,发生不可预料的后果。

为了解决以上问题,在微控制器内部集成了一个定时器复位电路,即看门狗电路。STM32 的独立看门狗由内部专门的 40Khz 低速时钟驱动,即使主时钟发生故障,它也仍然有效。这里需要注意独立看门狗的时钟是一个内部 RC 时钟,所以并不是准确的 40Khz,而是在 30~60Khz 之间的一个可变化的时钟。

在stm32微控制器中集成了两个看门狗外设,分别是独立看门狗和窗口看门狗,提供了更高的安全性,时间的精确性和使用灵活性,两个看门狗设备(独立看门狗、窗口看门狗)可以用来监测和解决由软件错误引起的故障,当计算器达到给定的超时值时,产生系统复位或者触发一个中断(仅适用于窗口看门狗)。

1.1 独立看门狗(IWDG)

由专用的低速时钟(LSI)驱动,即使主时钟发生故障,任能够继续有效。独立看门狗适用于需要看门狗作为一个在主程序之外能够完全独立工作,并且对时间精度要求低的场合。

1.1.1 IWDG主要性能

  • 自由运行的递减计数器
  • 时钟由独立的RC振荡器提供(可在停止待机模式下工作)
  • 看门狗被激活后,则在计数器计数至0x000时产生复位

1.1.2 看门狗框架

1.2 时钟

LSI提供时钟,时钟频率40KHz,经过预分频器分频后的时钟,提供给12bit递减计数器,作为向下技术的频率。预分频器的分频系数由IWDG_PR预分频寄存器设置:地址偏移:0x04 复位值:0x00000000

  • 在键值寄存器(IWDG_KR)中写入0xCCCC,开始启用独立看门狗;此时计数器开始从其复位值 0xFFF 递减计数。当计数器计数到末尾 0x000 时,会产生一个复位信号(IWDG_RESET)。无论何时,只要键寄存器IWDG_KR中被写入 0xAAAAIWDG_RLR中的值就会被重新加载到计数器中从而避免产生看门狗复位 。
  • IWDG_PR 和 IWDG_RLR 寄存器具有写保护功能。要修改这两个寄存器的值,必须先向IWDG_KR 寄存器中写入 0x5555。将其他值写入这个寄存器将会打乱操作顺序,寄存器将重新被保护。重装载操作(即写入 0xAAAA)也会启动写保护功能。
  • 还有两个寄存器,一个预分频寄存器(IWDG_PR),该寄存器用来设置看门狗时钟的分频系数。另一个重装载寄存器。该寄存器用来保存重装载到计数器中的值。该寄存器也是一个 32位寄存器,但是只有低 12 位是有效的。

1.3 键寄存器

  • 地址偏移:0x00
  • 复位值:0x0000 0000 (在待机模式复位)

  • IWDG_PRIWDG_RLR寄存器具有写保护功能。要修改这两个寄存器的值,必须先向IWDG_KR寄存器中写入0x5555。重装载操作(即写入0xAAAA)也会启动写保护功能。

1.4 重装载寄存器

  • 地址偏移:0x08
  • 复位值:0x0000 0FFF(待机模式时复位)

STM32 的独立看门狗,启动过程可以按如下步骤实现(独立看门狗相关的库函数和定义分布在文件stm32f10x_iwdg.hstm32f10x_iwdg.c中)。

==取消寄存器写保护(向 IWDG_KR 写入 0X5555)==

通过这步,我们取消 IWDG_PR 和 IWDG_RLR 的写保护,使后面可以操作这两个寄存器,设置 IWDG_PR 和 IWDG_RLR 的值。

IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);

==设置独立看门狗的预分频系数和重装载值==

设置看门狗的分频系数的函数

void IWDG_SetPrescaler(uint8_t IWDG_Prescaler); //设置 IWDG 预分频值

==设置看门狗的重装载值的函数==

void IWDG_SetReload(uint16_t Reload); //设置 IWDG 重装载值

设置好看门狗的分频系数 prer 和重装载值就可以知道看门狗的喂狗时间(也就是看门狗溢出时间),该时间的计算方式为:

Tout=((4×2^prer) ×rlr) /40
  • Tout 为看门狗溢出时间(单位为 ms)
  • prer 为看门狗时钟预分频值(IWDG_PR 值),范围为 0~7
  • rlr 为看门狗的重装载值(IWDG_RLR 的值);

==重载计数值喂狗(向 IWDG_KR 写入 0XAAAA)==

将使 STM32 重新加载 IWDG_RLR 的值到看门狗计数器里面。

IWDG_ReloadCounter(); //按照 IWDG 重装载寄存器的值重装载 IWDG 计数器

注意 IWDG 在一旦启用,就不能再被关闭!想要关闭,只能重启,并且重启之后不能打开 IWDG,否则问题依旧,所以在这里提醒大家,如果不用 IWDG 的话,就不要去打开它,免得麻烦。

==下面是初始化函数和喂独立看门狗函数==

#include "wdg.h"
//初始化独立看门狗
//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); //①使能对寄存器 I 写操作
    IWDG_SetPrescaler(prer); //②设置 IWDG 预分频值:设置 IWDG 预分频值
    IWDG_SetReload(rlr); //②设置 IWDG 重装载值
    IWDG_ReloadCounter(); //③按照 IWDG 重装载寄存器的值重装载 IWDG 计数器
    IWDG_Enable(); //④使能 IWDG
}
//喂独立看门狗
void IWDG_Feed(void)
{ 
    IWDG_ReloadCounter();//reload 
}

2 窗口看门狗( wwdg)

STM32 有两个看门狗,一个是独立看门狗,一个是窗口看门狗。我们知道独立看门狗的工作原理就是一个递减计数器不断的往下递减计数,当减到 0 之前如果没有喂狗的话,产生复位。窗口看门狗跟独立看门狗一样,也是一个递减计数器不断的往下递减计数,当减到一个固定值 0X40时还不喂狗的话,产生复位,这个值叫窗口的下限,是固定的值,不能改变。这个是跟独立看门狗类似的地方,不同的地方是窗口看门狗的计数器的值在减到某一个数之前喂狗的话也会产生复位,这个值叫窗口的上限,上限值由用户独立设置。窗口看门狗计数器的值必须在上窗口和下窗口之间才可以喂狗,这就是窗口看门狗中窗口两个字的含义。

通常被用来监测,由外部干扰或不可预见的逻辑条件造成的应用程序背离正常的运行序列而产生的软件故障。窗口看门狗(WWDG)通常被用来监测由外部干扰或不可预见的逻辑条件造成的应用程序背离正常的运行序列而产生的软件故障。除非递减计数器的值在 T6 位(WWDG->CR 的第六位)变成 0 前被刷新,看门狗电路在达到预置的时间周期时,会产生一个 MCU 复位。在递减计数器达到窗口配置寄存器(WWDG->CFR)数值之前,如果 7 位的递减计数器数值(在控制寄存器中)被刷新, 那么也将产生一个 MCU 复位。这表明递减计数器需要在一个有限的时间窗口中被刷新。

由从APB1时钟分频后得到的时钟驱动,通过可配置的时间窗口来监测应用程序非正常的过迟或过早操作。窗口看门狗最合适那些要求看门狗在精确计时窗口起作用的程序。

2.1 WWDG框架图

产生复位的两种情况

==第一种==

1和2经过与门后,产生复位。即:WDGA位为1,T6为0(取反后为1,经过或门电路后路径2为1),也就是WWDG_CR寄存器递减到0x40后,再减1,编程0x3F的时候,T6位,由1变为0。

==第二种==

WDGA位为1时,当T6:0 > W6:0 且写入WWDG_CR(即刷新计数值)产生复位中断。

==WWDG时序==

  • 配置寄存器(WWDG_CFR)中包含窗口的上限值:要避免产生复位,递减计数器必须在其值小于窗口寄存器的数值并且大于0x3F时被重新装载。
  • T[6:0]就是WWDG_CR 的低七位,W[6:0]即是WWDG->CFR 的低位。T[6:0]就是窗口看门狗的计数器,而 W[6:0]则是窗口看门狗的上窗口,下窗口值是固定的(0X40)。当窗口看门狗的计数器在上窗口值之外被刷新,或者低于下窗口值都会产生复位。
  • 上窗口值(W[6:0])是由用户自己设定的,根据实际要求来设计窗口值,但是一定要确保窗口值大于0X40,否则窗口就不存在了。

窗口看门狗的超时公式如下:

Twwdg=(4096×2^WDGTB×(T[5:0]+1)) /Fpclk1
  • Twwdg:WWDG 超时时间(单位为 ms)
  • Fpclk1:APB1 的时钟频率(单位为 Khz)
  • WDGTB:WWDG 的预分频系数
  • T[5:0]:窗口看门狗的计数器低 6 位

2.2 时钟

WWDG时钟来自于PCLK1(36MHz),由窗口看门狗WDGTB预分频器分频后,提供给6bit递减计数器作为向下计数得频率。

==配置寄存器(WWDG_CFR)==

==2.控制寄存器(WWDG_CR)==

  • WWDG_CR 只有低八位有效,T[6:0]用来存储看门狗的计数器值,随时更新的,每个窗口看门狗计数周期(4096×2^ WDGTB)减 1。当该计数器的值从 0X40 变为 0X3F 的时候,将产生看门狗复位。
  • WDGA位则是看门狗的激活位,该位由软件置 1,以启动看门狗,并且一定要注意的是该位一旦设置,就只能在硬件复位后才能清零了。

==配置寄存器(WWDG_CFR)==

该位中的 EWI 是提前唤醒中断,也就是在快要产生复位的前一段时间(T[6:0]=0X40)来提醒我们,需要进行喂狗了,否则将复位!因此,我们一般用该位来设置中断,当窗口看门狗的计数器值减到 0X40 的时候,如果该位设置,并开启了中断,则会产生中断,我们可以在中断里面向 WWDG_CR 重新写入计数器的值,来达到喂狗的目的。注意这里在进入中断后,必须在不大于 1 个窗口看门狗计数周期的时间(在 PCLK1 频率为 36M 且 WDGTB 为 0 的条件下,该时间为 113us)内重新写 WWDG_CR,否则,看门狗将产生复位!

状态寄存器(WWDG_SR),该寄存器用来记录当前是否有提前唤醒的标志。该寄存器仅有位 0 有效,其他都是保留位。当计数器值达到 40h 时,此位由硬件置 1。它必须通过软件写 0 来清除。对此位写 1 无效。即使中断未被使能,在计数器的值达到 0X40的时候,此位也会被置 1。

窗口看门狗库函数相关源码和定义分布在文件stm32f10x_wwdg.c 文件和头文件stm32f10x_wwdg.h

==使能 WWDG 时钟==

WWDG 不同于 IWDG,IWDG 有自己独立的 40Khz 时钟,不存在使能问题。而 WWDG使用的是 PCLK1 的时钟,需要先使能时钟。

RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE); // WWDG 时钟使能

==设置窗口值和分频数==

void WWDG_SetWindowValue(uint8_t WindowValue);//这个函数的入口参数 WindowValue 用来设置看门狗的上窗口值。
void WWDG_SetPrescaler(uint32_t WWDG_Prescaler); //设置分频数的函数

==开启 WWDG 中断并分组==

WWDG_EnableIT(); //开启窗口看门狗中断

接下来是进行中断优先级配置,这里就不重复了,使用 NVIC_Init()函数即可。

==设置计数器初始值并使能看门狗==

void WWDG_Enable(uint8_t Counter);//该函数既设置了计数器初始值,同时使能了窗口看门狗。

==编写中断服务函数==

在最后,还是要编写窗口看门狗的中断服务函数,通过该函数来喂狗,喂狗要快,否则当窗口看门狗计数器值减到 0X3F 的时候,就会引起软复位了。在中断服务函数里面也要将状态寄存器的 EWIF 位清空。

//保存 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 状态翻转
}