1. 什么是窗口看门狗
窗口看门狗和独立看门狗差不多,不过也有区别,这俩都是倒计时的计时器,都是满足一定条件以后发生复位,但是喂狗的时候有一些区别,独立看门狗只要开始计时,在发生复位之前任何时候都可以喂狗,而窗口看门狗只能在某一阶段喂狗,不能太早也不能太晚。太早和太晚都会产生系统复位,所以称之为窗口。STM32F107芯片的窗口看门狗的下界一定是0x40,这个值不能修改,也就是说从0x40变到0x3F就会产生复位,不会出现小于0x3F的情况,窗口看门狗的上界是由用户进行配置的,在允许的范围内可以进行配置。
2. 窗口看门狗的框图与寄存器
上图就是窗口看门狗的框图了,可以看到窗口看门狗的时钟来源是PCLK1,根据时钟章节的内容可以知道这是APB1总线上的时钟,说明窗口看门狗是APB1总线上的外设,PCLK1时钟然后会经过看门狗预分频器后到达6位递减计数器,这个计数器的值存储在看门狗控制寄存器(WWDG_CR)的低6位中,第7位是窗口看门狗的使能位,可以看出最终的复位信号是由一个与门输出的,与门的输入之一就是这个寄存器的第7位输出的,这个寄存器的第6位T6连接到了两个地方,第一个地方是进行非运算之后通过一个或门,或门的输出连接到了复位与门的另一个输入端,这个的意思就是确定窗口看门狗计数器喂狗的下界值,因为数字0x40递减到0x3F的时候刚好是T6位从1变到0,所以这一位确定了窗口看门狗的下界,这个寄存器第6位连接的另一个地方是与其余T5到T0位一起输入到一个比较器中来和用户配置的窗口看门狗喂狗上界去比较,比较的结果与喂狗信息组成一个与门,如果比用户配置的值小,比较器输出0,同时这时候喂狗的话,与门输出0,在计数器没有达到喂狗下界的情况下不会复位,如果比用户配置的值大,比较器输出1,同时这时候喂狗的话,与门输出1,无论计数器有没有达到喂狗下界,系统都会复位。数据手册和教程里都有工作示意图,没啥意思,这个窗口看门狗的原理也很简单,不看这个原理图,只看别的基本也能够理解。
窗口看门狗相关的寄存器比独立看门狗更少,独立看门狗有四个寄存器,窗口看门狗只有三个。
- 控制寄存器(WWDG_CR)
这个寄存器低8位有效,第7位是窗口看门狗的使能位,置1使能看门狗,置0就不使能,而且一旦置1就只能等硬件复位后清除,软件无法清除,第6位到第0位存储计数器的数字,计数器的第T位从1变到0就会复位,说明这个计数器理论上来说最大能计的不会产生复位的数字是低7位全为1,最小能计的不会产生复位的数字是低6位全为1,所以第6位其实就是个标记,等于0就说明该复位了,所以也可以手动给第6位写0来产生复位,为了防止使能看门狗之后立刻复位,所以使能看门狗的同时也应该给T6位手动置1。 - 配置寄存器(WWDG_CFR)
这个寄存器的低10位有效,第9位是中断使能位,当计数器达到最后一个不会复位的值0x40的时候就会触发中断,和之前的窗口看门狗使能位一样,一旦置1就只能等硬件复位后清除,软件无法清除,所以一般就会使能这个中断,然后在中断里面进行喂狗,第8和第7位来共同决定预分频器的设置,具体设置规则如上图,第6位到第0位来决定窗口看门狗的上界值,用来和当前的计数器数字进行比较。 - 状态寄存器(WWDG_SR)
这个寄存器只有第0位有效,是提前唤醒的中断标记位,无论中断是否使能,看门狗计数达到0x40的时候这个位都会被硬件置1,然后通过软件来写1.
3. 窗口看门狗的配置
3.1. 窗口看门狗的配置
和独立看门狗的配置差不多,窗口看门狗的配置也分三步,有一些细微的差异。
- 配置分频系数和窗口看门狗上界值;
分频系数的配置就是通过配置寄存器(WWDG_CFR)的第8和第7位来进行配置,可以看到,传入预分频器的时钟频率固定是要除以4096的,可以看到控制寄存器(WWDG_CR)的描述:每 4096 × 2 W D G T B [ 2 : 0 ] 4096×2^{WDGTB[2:0]} 4096×2WDGTB[2:0]个PCLK1周期计数减1,PCLK1是AHB1总线的时钟频率,2分频32MHz,根据这些信息在结合实际需求就可以计算出来分频系数和看门狗上界值应该是多少。
T W W D G = 4096 × 2 W D G T B × ( T [ 5 : 0 ] + 1 ) P C L K 1 T_{WWDG} =\frac{4096× 2^{WDGTB}× (T[5:0] + 1)}{PCLK1} TWWDG=PCLK14096×2WDGTB×(T[5:0]+1)
T W W D G T_{WWDG} TWWDG是窗口看门狗的超时时间,T[5:0]是控制寄存器低6位的值,加1的意思是只有计数器从0x40减到0x3F才会复位,0x40并不会复位,所以计数器至少得减一下,根据以上信息就可以得到手册中的这个表了:
- 配置喂狗中断;
喂狗中断就是给寄存器(WWDG_CFR)的提前唤醒中断使能位置1,然后在中断服务函数中编写相关喂狗代码。 - 启动窗口看门狗
3.2. 独立看门狗的代码编写
3.1.1 看门狗初始化
uint8_t g_wwdg_cnt = 0x7f; /* 默认设置WWDG的最大计数值为允许的最大值. */
void wwdg_init(uint8_t tr, uint8_t wr, uint8_t fprer)
{
RCC->APB1ENR |= 1 << 11; /* 使能wwdg时钟 */
g_wwdg_cnt &= tr; /* 配置WWDG窗口的计数上限值. */
WWDG->CFR |= fprer << 7; /* 根据计算好的fprer配置计数器的分频系数 */
WWDG->CFR &= 0XFF80; /* 清零CFG寄存器的窗口值部分以便下一步写入窗口值 */
WWDG->CFR |= wr; /* 设定窗口值 */
WWDG->CR |= g_wwdg_cnt; /* 设定计数器值 */
WWDG->CR |= (0B11 << 6); /* 开启看门狗的同时为了避免立即复位,手动给T6位置1 */
sys_nvic_init(2, 3, WWDG_IRQn, 2); /* 配置中断优先级,抢占优先级2,响应优先级3,组2 */
WWDG->SR = 0X00; /* 清除提前唤醒中断标志位 */
WWDG->CFR |= 1 << 9; /* 使能提前唤醒中断 */
}
这个代码给了我 一个启示,让我发现了我一直以来的一个误区,以前在大公司做的都是修修改改的工作,让我一直以来都觉得配置某一个外设寄存器的时候就应该一次性把这个寄存器的所有功能都配置好,实际上就拿这里的中断使能来看,配置寄存器应该是以某一个功能为主,有可能某一寄存器由于芯片设计的原因会被反复配置,实际上在一开始系统时钟寄存器的配置那里就应该看出来的,很多外设的寄存器都是根据功能的需要进行反复配置的。
3.1.2 喂狗操作
喂狗操作在中断内进行,根据之前的讨论,进入中断的时候就说明当前一定是在窗口看门狗的窗口时间内的,所以中断内喂狗是最合适的。
void WWDG_IRQHandler(void)
{
if (WWDG->SR & 0X01) /* 先判断是否发生了WWDG提前唤醒中断*/
{
WWDG->SR = 0X00; /* 清除提前唤醒中断标志位 */
WWDG->CR = (cnt & 0x7F); /* 喂狗操作,重设窗口看门狗的值! */
}
}
我觉得第一行的if判断没必要,已经进入了提前唤醒中断,那么提前唤醒中断必然是发生了的,就像记者在回家的火车上采访大家,发现大家都买到了回家的火车票一样,但是官方教程这样写了就抄过来了。