江协科技STM32 14-1 WDG看门狗
这一节我们要学习的内容是STM32中的看门狗,STM32内置两个看门狗,分别是独立看门狗(IWDG)和窗口看门狗(WWDG),两者的作用基本相同,只是侧重点不一样。看门狗简单来说就是程序运行的一个保障措施,我们得在程序中定期地喂狗,如果程序出问题卡死了,没有在规定的时间里喂狗,那么看门狗硬件电路就会自动帮我们复位一下,防止程序长时间卡死。就像是我们写了个程序,然后突然没动静了,我们就会习惯性地去按一下复位,或者说手机电脑,卡死不动了,我们也会习惯性地重启来解决。看门狗就是完成这样一个操作的硬件电路,在程序卡死的情况下,自动帮我们复位一下
首先,看门狗,英文是Watchdog,简称WDG,它的作用顾名思义就是“看大门”不过这里的大门表示的是程序,所以看门狗可以监控程序的运行状态,当程序因为设计漏洞、硬件故障、电磁干扰等原因,出现卡死或跑飞现象时,看门狗能及时复位程序,避免程序陷入长时间的罢工状态,保证系统的可靠性和安全性;看门狗本质上是一个定时器,当指定时间范围内,程序没有执行喂狗(重置计数器)操作时,看门狗硬件电路就自动产生复位信号;STM32内置两个看门狗,一个是独立看门狗(IWDG),它的特点就是独立运行,对时间精度要求较低。独立运行就是独立看门狗的时钟是专用的LSI,内部低速时钟,即使主时钟出现问题了,看门狗也能正常工作,这就是独立看门狗中独立的得名原因,对时间精度要求较低就是独立看门狗只有一个最晚时间界限,你的喂狗(重置计数器)间隔只要不超过这个最晚界限就行了。另一个是窗口看门狗,它相比较独立看门狗,就严格一些了,要求看门狗在精确计时窗口起作用,意思就是喂狗的时间有个最晚的界限,也有个最早的界限,必须在这个界限的窗口内喂狗,这也是窗口看门狗窗口的得名原因,因为对独立看门狗来说,可能程序就卡死在喂狗的部分了;或者程序跑飞,但是喂狗代码也意外执行了,或者程序有时候很快喂狗,有时候又比较慢喂狗,这些状态,独立看门狗就检测不到了,但是窗口看门狗是可以检测到这些问题的,因为它对喂狗的时间窗口可以卡的很死,快了慢了都不行。最后,窗口看门狗使用的是APB1的时钟,它没有专用的时钟,所以不算是独立。以上就是对看门狗的基本介绍了。
下面看一下独立看门狗的框图,下面的框图,大家可以类比定时器的时基单元来看。在定时器的时基单元中,其由预分频器、计数器和重转载寄存器组成,左边是输入时钟,比如现在是72M,首先经过分频,比如现在是2分频,那么计数器的驱动时钟就是72/2 = 36M,之后计数器可以自增,也可以自减,看门狗使用的是自减运行,自减到0后定时器是产生更新事件和中断,而看门狗是直接产生复位。另外定时器是在更新事件后对重装器自动重装,而看门狗需要我们在自减到0之前手动重装,因为减到0就复位了,正常运行情况下,肯定是不能让它减到0的。那这个手动重装计数器的操作就是喂狗。
接下来看下面的独立看门狗框图,最左侧是预分频器,中间下侧为计数器,中间上侧为重装寄存器。预分频器之前的输入时钟是LSI,即内部低速时钟,时钟频率为40KHz,之后时钟进入预分频器进行分频,这个预分频器只有8位,所以它最大只能进行256分频。其上方的预分频器寄存器IWDG_PR可以配置分频系数,这个PR和定时器的PSC是一个意思,都是Prescaler的缩写。经过预分频器分频之后,时钟驱动递减计数器,每来一个时钟自减一个数,另外这个数是12位的,所以最大值是2^12-1=4095,然后当自减到0之后,产生IWDG复位。正常运行时,为了避免复位,我们可以提前在重装寄存器IWDG_RLR中写一个值,IWDG_RLR和定时器的ARR是一样的,RLR是Reloder,ARR是Auto Reloader。当我们预先写好值之后,在运行的过程中,我们在键寄存器里写一个特定数据,控制电路进行喂狗,这时重装值就会复制到当前的计数器中,这样计数器就会回到重装值,重新自减运行了。然后还会存在一个状态寄存器SR,用于标志电路运行的状态。最后,上面这些寄存器位于1.8V供电区,下面主要的工作电路都位于VDD供电区。所以看门狗功能处于VDD供电区,即在停机和待机模式时仍能正常工作,上节我们说过,独立看门狗,也是唤醒待机模式的四个条件之一。
接下来我们再介绍键寄存器,它有什么用呢?我们看一下下面的表格。键寄存器本质上是控制寄存器,用于控制硬件电路的工作。比如刚才说的喂狗操作,就是通过在键寄存器写入0xAAAA完成的。那为什么要用键寄存器呢?我直接定义一个控制寄存器,其中再定义一个位,这一位写入1,就喂狗,这样不也行吗?我们继续来看第二条:在可能存在干扰的情况下,一般通过在整个键寄存器写入特定值来代替控制寄存器写入一位的功能,以降低硬件电路受到干扰的概率。由于独立看门狗工作的环境是程序可能跑飞,可能受到电磁干扰,程序作出任何操作都是有可能的,如果你只在寄存器中设置一个位,那这一位就有可能会在误操作中变成1或者变成0,所以单独设置1位就来执行控制在这里比较危险。这时我们就可以通过在整个寄存器写入一个特定值来代替写入一个位的操作。比如,这里的键寄存器是16位的,只有在键寄存器写入0xAAAA,这个特定的数,才会执行喂狗的操作,这样就会降低误操作的概率。但这里还有PR、SR和RLR三个寄存器,它们也要有防止误操作的功能,SR是只读的,这个不用保护,剩下的对PR和RLR的写操作,可以设置一个写保护措施,然后只有在键寄存器写入5555,才能接触写保护。一旦写入其他值,PR和RLR再次被保护,这样PR和RLR就跟随键寄存器一起被保护了起来,防止误操作,这就是键寄存器设计的用途。
接着看一下独立看门狗的超时时间,也就是定时器的溢出时间。这里有个公式
PR寄存器中2:0配置不同的数来选择预分频系数,重装载寄存器总共12位,因此其最短时间和最长时间被计算为表格中的形式。
接下来我们讲一下窗口看门狗,窗口看门狗从功能上来说和独立看门狗还是比较像的,大体上来看只是比独立看门狗多了个最早喂狗时间的限制,但是窗口看门狗无论是框图的设计还是寄存器的分布和命名规则或是程序的操作流程和独立看门狗都不是一个思路,可能是两个看门狗侧重点不一样。
下面是窗口看门狗的结构,左下角是时钟源部分,这个时钟源是PCLK1,右边是预分频器,这个预分频器的名字又变了,叫WDGTB,实际上和独立看门狗的PR,定时器的PSC都是一个东西。上面这个是6位递减计数器CNT,这个计数器是位于控制寄存器CR里的,计数器和控制寄存器合二为一了,然后窗口看门狗没有重装寄存器,那如何重装计数器进行喂狗呢?其实我们直接在CNT写入数据就可以了,想写多少就写多少。之后上面这一块是窗口值,也就是喂狗的最早时间界限就写到这里存起来。最后左边就是输出信号的操作逻辑了,什么情况下会产生复位就由这几个逻辑门来确定。
接下来我们详细看一下工作流程,首先还是从左下角开始看,时钟来源是PCLK1,也就是APB1的时钟,这个时钟默认是36MHz,所以就是36M的时钟进来,进来之后还是先经过一个预分频器进行分频,接着分频之后的时钟驱动计数器进行计数,这个计数器和独立看门狗一样也是个递减计数器,每来一个时钟自减一次。不过这个计数器比较特殊,从图上来看,这里写了T6-T0,总共是7个位,但下面却写的是6位递减计数器,这时为什么呢?这其实是因为这个计数器只有T5-T0这六位是有效的计数值,最高位T6,这里用来当作溢出的标志位。T6=1时,表示计数器没溢出;T6=0时,表示计数器溢出,不过对于硬件电路来说,T6位其实也是计数器的一部分,只不过是T6位被单独拎出来当作标志为了而已。举个例子,比如计数器的初始值是111 1111,那么来一个计数脉冲,值减1,变为111 1110,再来一个变为111 1101,以此类推,不断自减,知道减为100 0000,减到这个数时是一个关键节点,此时包括T6位在内的数是100 0000,转为十六进制是0x40,也就是说此时如果把T6位也当作计数器的一部分,那计数器的值实际上才减一半。但是如果我们把T6位剥离出去当作溢出标志位,低6位当作计数器,那此时的状态就是标志位为1,计数器为00 0000,已经减到0了,再减一次,下个值是011 1111,这时最高位T6由1变为0,即代表计数器溢出。这时最高位T6就会通过下面的线路产生复位信号,这就是这个计数器的工作流程和溢出条件。总结一下就是如果你把T6位看作是计数器的一部分,那就是整个计数器值减到0x40之后溢出;而如果你把T6位当成溢出标志位,低6位当作计数器,那就是低6位的计数值减到0之后溢出,这一点尤其要搞清楚。接着看左边的复位信号输出部分,首先WDGA是窗口看门狗的激活位,也就是使能,WDGA写入1,启用窗口看门狗。使能位作用于与门,作为控制信号,控制信号给1,则输出等于输入,开关导通;控制信号给0,则输出等于0,与输入无关,开关断开。开关右边就是复位信号的来源了,这里有两个来源,用或门连接,也就是两个来源任意一个,都可以复位。其中下面这一路来源于溢出标志位T6,当计数器溢出时,T6等于0,输入到或门被取反,0变为1,或门有效,输出1,当最后的使能位给1,开启看门狗后,这个溢出信号就直接通向复位了。所以下面这一路意思就是T6位一旦等于0,就表示计数器溢出,就产生复位信号。在程序正常运行状态下,我们必须始终保证T6位为1,这样才能避免复位。至此,下面这一块实现的功能和独立看门狗基本是一样的,如果不及时喂狗,6位的计数器减到0后,就产生复位。接下来喂狗的时间的最早界限由上面这一块来实现。首先,我们需要计算一个最早界限的计数值,写入到这里的W6-W0中,这些值写入后是固定不变的。之后一旦我们执行写入CR操作时,这个与门开关就会打开。写入CR,其实就是写入计数器,也就是喂狗。在喂狗时,比较器开始工作,一旦它比较,我们当前的计数器T6:0>窗口值W6:0,比较结果就=1。这个1通过或门,也可以去申请复位。这就是喂狗最早时间窗口的实现流程。就是喂狗的时候我把当前计数值和我预设的窗口值进行比较,如果发现你的狗余粮还非常充足,你喂的这么频繁,那肯定有问题啊,我就给你复位一下,不让你喂太早了。这就是窗口看门狗的全部内容了。喂狗太晚,六位计数器减到0了,复位;喂狗太早,计数器的值超过窗口值了,复位。
下面我们来看一下窗口看门狗的工作特性:
1.递减计数器T[6:0]的值小于0x40时,WWDG产生复位
2.递减计数器T[6:0]在窗口W[6:0]外被重新装载时,WWDG产生复位
3.递减计数器T[6:0]等于0x40时可以产生早期唤醒中断(EWI),用于重装载计数器以避免WWDG复位
注意,特性1中只有小于0x40时才产生复位;而特性3是等于0x40时,可以产生一个早期中断。0x40时T6还是1,还没有溢出,再减一个数变为0x3F了,才是溢出。
所以特性3的意思是减到0x40时产生中断,然后再减一个数,到0x3F时产生复位。那这样,这个中断其实就是在溢出的前一刻发生。所以这个中断也可以叫做“死前中断”,我们一般用来执行一些紧急操作,比如保存重要数据、关闭危险设备等。
4.定期写入WWDG_CR寄存器(喂狗)以避免WWDG复位
最后看一下下面这个工作示意图。纵轴是T[6:0],包含T6位的CNT,递减计数器。喂狗之后,在最高的值往下递减。如果这个计数值还很大,高于W[6:0]窗口值了,你就不能急着喂狗,也就是在前半段是不允许刷新的;之后当计数值小于窗口值了进入到刷新窗口,可以喂狗,当然喂狗也不能太晚,要在减到3F之前喂,3F这个值就是T6位刚等于0的时刻,所以下面的T6位在这个时刻变为0,同时复位信号也在这个时刻产生。
然后我们来看一下窗口看门狗的超时时间计算,这里有两个公式,超时时间,就是喂狗的最晚时间;窗口时间就是喂狗的最早时间。
先看超时时间,这个和刚才独立看门狗的基本一样。但是要多乘一个4096,是因为PCLK进来之后,其实是先执行了一个固定的4096分频。
之后再看窗口时间,主要区别就是最后的部分。上面的超时时间是计数器减到0的时间,而这个是计数器减到窗口值的时间。所以我们拿T[5:0]-W[5:0]就可以算出窗口时间了。同时也要注意,这个W[5:0]也是不包含W6这一位的
最后我们总结一下两种看门狗之间的差别
下面进入到代码编写的环节。根据上面所说的内容,我们首先总结一下独立看门狗的配置流程。首先开启LSI时钟,但是这个开启LSI的代码并不需要我们来写,因为参考手册中提到如果独立看门狗已经由硬件选项或软件启动,LSI振荡器将被强制在打开状态,并且不能被关闭。在LSI振荡器稳定后,时钟供应给IWDG。这个意思就是说如果我们开启了独立看门狗,那么LSI就会跟随强制打开,等LSI稳定后,就可以自动为独立看门狗提供时钟了。所以第一步开启LSI的时钟就不需要我们再写代码来执行了。接着我们直接进行下一步,下一步就是写入预分频器和重装寄存器。当然在写入这两个寄存器之前,不要忘了键寄存器中的写保护,要先在键寄存器中写入0x5555键值,接触写保护,然后再写入预分频和重装值。所以第二步是解除写保护,第三步是写入预分频和重装值。预分频和重装值具体写入多少可以通过上面的超时时间公式来计算。最后当这些配置工作做完之后,我们就可以在键寄存器中写入0xCCCC,来启动独立看门狗了。然后在主循环里,我们可以不断将0xAAAA写入到键寄存器来进行喂狗,这就是独立看门狗的配置流程。
下面介绍一下IWDG库函数中的函数:IWDG_WriteAccessCmd用于写使能控制,就是在寄存器中写入0x5555允许对其他寄存器的配置或写入0x0000锁定对其他寄存器的配置;IWDG_SetPrescaler函数用于写预分频器,也就是写PR寄存器;IWDG_SetReload函数用于写重装值,也就是写入RLR寄存器;IWDG_ReloadCounter函数用于重新装载寄存器,就是喂狗,函数操作就是在键寄存器写入0xAAAA;IWDG_Enable函数用于启动独立看门狗,函数操作就是在键寄存器中写入0xCCCC;IWDG_GetFlagStatus函数用于获取标志位状态。此外,我们想知道程序复位时,是看门狗导致的复位还是上电或复位键导致的复位,那就需要通过RCC里的一个标志位来实现,因此我们需要在RCC库函数中的查看标志位函数,查看完之后不要忘了再调用ClearFlag清除一下。
之后是窗口看门狗的配置流程,因为窗口看门狗的时钟来源是PCLK1,所以第一步,我们需要开启窗口看门狗APB1的时钟,这个第一步需要我们自己来执行,不会像独立看门狗那样自动开启,之后第二部就是配置各个寄存器了,比如预分频和窗口值。窗口看门狗没有写保护,所以第二步就可以直接写这些寄存器了,最后第三步,写入控制寄存器CR,控制寄存器包含看门狗使能位、计数器溢出标志位和计数器有效位,这些东西需要一起设置,放在第三步统一执行;之后,在运行过程中,我们不断向计数器写入想要的重装值,这样就可以进行喂狗了。
下面介绍一下WWDG库函数中的函数,WWDG_DeInit,用于恢复缺省配置;WWDG_SetPrescaler,写入预分频器;WWDG_SetWindowValue写入窗口值,初始化配置就用上面这两个函数;WWDG_EnableIT,使能中断,因为它只有一个中断,所以就不需要指定参数了;WWDG_SetCounter,写入计数器,喂狗就用这个函数;WWDG_Enable使能窗口看门狗,初始化之后,启动看门狗就用这个函数,另外这个函数还有个参数,并且和上面SetCounter函数的参数是一样的。这样设置的原因是递减计数器是自由运行状态,你在使能的时候计数器可能是任何值,为了避免刚一使能就立马复位,所以在使能的时候要顺便同时喂一下狗;WWDG_GetFlagStatus和WWDG_ClearFlag就是获取标志位和清除标志位的函数了。