首页 关于
树枝想去撕裂天空 / 却只戳了几个微小的窟窿 / 它透出天外的光亮 / 人们把它叫做月亮和星星
目录

看门狗

所谓的看门狗(watch dog),实际上就是一个计时器, 它的特点就是当计数溢出时产生一个复位信号,要求处理器重启。为了防止处理器重启,软件上必须每个一段时间对其执行一次清零操作, 也就是常说的“喂狗”。这样,只要程序还在正常运行,那么计数器就不会溢出,系统也就不会重启。 因此,看门狗的作用就是为了防止系统软件因为bug或者一些异常情况的出现,而产生死机等现象,它的存在可以提高系统的可靠性。

在STM32F407中,有一个独立看门狗(Independent WatchDog, IWDG)和一个窗口看门狗(Window WatchDog, WWDG)。独立看门狗是由内部低速时钟(LSI)专门控制的, 与系统时钟无关,即使主时钟产生了故障,IWDG也可以正常工作。WWDG则是由APB1的时钟驱动的,它定义了一个时间窗口进行"喂狗",过早或者过晚地"喂狗"都会触发异常。

本文将详细介绍IWDG和WWDG的结构和使用方法。

1. 独立看门狗IWDG

独立看门狗的特点就是它的驱动时钟是独立的,采用内部低速时钟(LSI)驱动,即使系统的主时钟异常了,它也是可以正常工作的。 缺点就是LSI的时钟精度并不高。它适用于高可靠性要求、低实时性要求的场合。

下图1中描述了IWDG的结构框图。它一共就只有4个寄存器,我们可以通过分频寄存器(IWDG_PR)对输入的LSI,设置看门狗的计数频率。 重装寄存器(IWDG_RLR)则用来描述每次"喂狗"操作时,IWDG向下计数的起始数值。IWDG_PR和IWDG_RLR一起决定了独立看门狗触发超时复位的时间。 状态寄存器(IWDG_SR)则反应了分频寄存器和重装寄存器中数值是否发生了改变。

为了防止误操作修改IWDG_PR和IWDG_RLR两个寄存器,导致系统不正常运行,STM32对这两个寄存器进行了保护。在修改它们中的数值时, 必须先向Key寄存器(IWDG_KR)中写入一次0x5555,之后才可以修改两个寄存器。为了防止看门狗计数溢出导致系统复位, 我们还需要在软件上及时地向IWDG_KR中写入0xAAAA,执行"喂狗"操作,重装计数器初值。

图1 独立看门狗结构框图

使用IWDG之前需要先对其初始化,下面是一个初始化的函数。我们先向IWDG_KR寄存器中写入0x5555,解除对IWDG_PR和IWDG_RLR的保护。 然后设置分频系数、重装载数值以确定“喂狗”时间。然后写KR_KEY_RELOAD到IWDG_KR寄存器中触发看门狗装载计数, 最后写入KR_KEY_ENABLE开启看门狗。

        #define KR_KEY_RELOAD 0xAAAA
        #define KR_KEY_ENABLE 0xCCCC

        void iwdg_init(void) {
            IWDG->KR.bits.key = 0x5555;
            IWDG->PR.bits.pr = IWDG_PR_DIV_16;
            IWDG->RLR.bits.rl = 0xFFF;
            IWDG->KR.bits.key = KR_KEY_RELOAD;
            IWDG->KR.bits.key = KR_KEY_ENABLE;
        }

调用该函数之后,就必须及时“喂狗”,否则将导致系统复位。下面的函数封装了“喂狗”操作。

        void iwdg_feed(void) {
            IWDG->KR.bits.key = KR_KEY_RELOAD;
        }

2. 窗口看门狗WWDG

窗口看门狗WWDG的特点就是它可以定义一个时间窗口,程序只能在这个时间窗口中执行"喂狗"操作。 WWDG由APB1总线时钟驱动,所以相比于IWDG它具有更高的时间精度,适用于实时性要求较高的场合。

下图2是WWDG的结构框图,它一共有三个寄存器:配置寄存器(WWDG_CFR)、控制寄存器(WWDG_CR)和状态寄存器(WWDG_SR),其中WWDG_SR没有体现在该框图中。

图2 窗口看门狗结构框图

WWDG_CR中有一个使能位WDGA,控制着看门狗是否能够触发系统复位。 在控制寄存器中有一个7位的向下计数器,当T6位由1变为0的时候就会产生复位信号,应用程序需要在计数为0x3F之前写入一个大于0x3F的值到WWDG_CR中。 这个计数器是自由运行的,即使没有控制使能,它也会一直向下计数。因此,在使能看门狗之前必须先写一个值到计数器中,保证T6为1,否则将会直接触发系统复位。

配置寄存器WWDG_CFR则用来设置时间窗口的上限的。在图2中,我们可以看到一个比较器对比两个寄存器,判定WWDG_CFR中的数值是否大于WWDG_CR中计数器的值, 其输出和写WWDG_CR信号一起接入一个与门,产生复位信号。这样,当程序执行“喂狗”操作时,如果控制寄存器中计数值大于配置寄存器中的值,也会触发系统复位。

此外,WWDG还提供了一个EWI中断(Early Wakeup Interrupt)。如果我们在配置寄存器的EWI位设置开启中断,那么当向下计数到0x40时就会触发中断。 这样在触发系统复位之前,我们可以在中断服务函数中做一些操作,比如说保存必须的数据、输出提示信息等。甚至可以在中断服务函数中写控制寄存器,阻止看门狗复位。

下面是一个简单的驱动代码,这里目前没有考虑EWI中断的问题。其中一共涉及到两个函数(wwdg_init, wwdg_feed)和一个变量(_start_value)。 wwdg_init()用于初始化看门狗,wwdg_feed()用于喂狗操作,变量_start_value则用于保存喂狗的计数初值。

        #include <stm32f407_rcc.h>
        #include <wwdg.h>

        uint8 _start_value;

        /*
         * wwdg_init - 初始化窗口看门狗
         *
         * @div: 分频系数
         * @w: 窗口上限
         * @s: 计数器初值
         */
        void wwdg_init(uint8 div, uint8 w, uint8 s) {
            // 打开WWDG的驱动时钟
            RCC->APB1ENR.bits.wwdg = 1;

            WWDG->CFR.bits.WDGTB = div;
            WWDG->CFR.bits.W = w;

            _start_value = s;
            WWDG->CR.bits.T = _start_value;
            WWDG->CR.bits.WDGA = 1;
        }

        void wwdg_feed(void) {
            WWDG->CR.bits.T = _start_value;
        }

wwdg_feed()和_start_value没什么好说的,这里解释一下初始化过程。因为窗口看门狗是由APB1总线驱动的,所以需要先在RCC中打开WWDG的驱动时钟。然后写配置寄存器设置分频系数和时间窗口,最后保存计数器初值到_start_value中,并写入控制寄存器。

3. 示例程序

本文的例程可以在这里下载。 为了能够看到系统因为看门狗超时重启的现象,在main函数中,一开始我们先点亮LED_0,LED_1,延迟一段时间之后再继续操作。 其中的延迟函数如下面右部的代码所示,仅仅是一个空的for循环。由于处理器的时钟频率是168MHz, 所以这里的Delay(16800000);基本上延迟了1秒。

        int main(void) {
            led_init();
        
            LED_0 = LED_ON;
            LED_1 = LED_ON;
        
            Delay(16800000);
            iwdg_init(IWDG_PR_DIV_16, 0xfff);

void Delay(uint32 nCount) {
    for(; nCount != 0; nCount--);
}
接下来,对看门狗复位,在while超级循环中控制两个LED交替闪烁,并执行喂狗的操作。其中第10行注释的代码是对窗口看门狗的初始化。
            iwdg_init(IWDG_PR_DIV_16, 0xfff);
            //wwdg_init(WWDG_DIV8, 0x7F, 0x7E);

                while (1) {
                    LED_0 = LED_OFF; LED_1 = LED_ON;
                    Delay(16800000);
                    LED_0 = LED_ON; LED_1 = LED_OFF;
                    iwdg_feed();
                    Delay(16800000);
                }
        }
成功编译之后,烧录到芯片上,我们就可以看到两个LED不断的交替闪烁。如果我们把上述代码中第16行的喂狗操作注释掉, 将看到若干次闪烁之后,系统复位重新点亮两个LED灯。

4. 总结

看门狗存在的意义就是提高系统的可靠性,在程序跑飞以后,它可以帮忙复位系统。对于一个简单的裸机程序而言,它的重要性常常被人们忽略。 但对于一个操作系统而言,如果上下文切换出现了异常,整个系统就很有可能不再受控,这时看门狗的重要作用就体现出来了。 操作系统中需要一个进程不断地喂狗,这样就可以及时检测到上下文切换不正常,防止整个系统产生不可估计的后果。

在STM32中,为我们提供了两个看门狗。一个独立看门狗它不受系统时钟控制,但时间精度很低,适用于高可靠性要求但实时性要求较低的场合。 另一个是窗口看门狗,它受系统时钟控制,时间精度高,适用于实时性要求较高的场合。




Copyright @ 高乙超. All Rights Reserved. 京ICP备16033081号-1