STM32F103 独立看门狗与窗口看门狗详解

碎碎念:第一次接触看门狗时很是疑惑,这玩意为什么叫做看门狗?为什么要有看门狗?它能被用来做什么?可惜当时在课堂上并没有学到这些看似没用但实际有助于理解的内容。

联系一下现实中的看门狗,正如其名,一只用来看家护院的狗娃,每当有陌生人靠近家门的时候就会旺旺叫,提醒主人有特殊情况!如此看来,我们的stm32也会遇到特殊情况,这时就需要一个看门狗来警示我们。

有时我们的电子设备并不按照我们的期望去运行,而这就是特殊情况,比如有软件异常(死循环、任务阻塞等),或因外部干扰导致程序跑飞。为了保证设备的正常运行,就需要一个使设备在遇到特殊情况后复位的手段,而这就是看门狗发挥作用的地方。

在官方提供的数据手册中,就很明确的写出了看门狗的主要作用:Both watchdog peripherals (Independent and Window) serve to detect and resolve malfunctions due to software failure, and to trigger system reset or an interrupt (window watchdog only) when the counter reaches a given timeout value.

通过这句话能发现,其实看门狗本质上就是一个计时器,或者说是递减的计数器。当看门狗这个计数器减到一定数值时,就会产生一次复位。这里又引出另外一个问题,当我不想复位时怎么办?于是看门狗就多了一个能力,那就是喂狗,通过每次喂狗让狗娃吃的饱饱的,这样狗就老老实实躺着消食去了,不会产生一次复位。

下面,我们来分别看看这两个看门狗:独立看门狗IWDG和窗口看门狗WWDG。

一、独立看门狗IWDG

独立看门狗IWDG为什么要加上独立呢?那是因为IWDG拥有一个独立的时钟源,即使当主时钟源出现故障,我们这个独立看门狗也依然可以工作。这个独立时钟是一个内部RC时钟,提供40Khz的低速时钟(LSI)。独立看门狗框图如下
IWDG
从框图中可以看出,时钟信号经过一个8位的预分频器后到12位递减计数器,当递减计数器的值为0时,发出IWDG复位信号。下面来看看相关寄存器都是如何工作的。

1.1 相关寄存器

键寄存器(IWDG_KR)
KR

键寄存器IWDG_KR可以向低16位写入三种值,分别对应不同的功能。所谓的喂狗就是向键寄存器IWDG_KR写入0xAAAA。

预分频寄存器(IWDG_PR)
PR
重装载寄存器(IWDG_RLR)
RLR
状态寄存器(IWDG_SR)
SR
该寄存器主要用来判断当前是否正在写入重装载寄存器和预分频寄存器,是由硬件来完成读写的。要注意的是,如果在应用程序中使用了多个重装载值或预分频值,则必须在RVU位被清除后才能重新改变预装载值,在PVU位被清除后才能重新改变预分频值。然而,在预分频和/或重装值更新后,不必等待RVU或PVU复位,可继续执行下面的代码。(即是在低功耗模式下,此写操作仍会被继续执行完成。)

1.2 软件实现

IWDG的使用是很简单的,通常需要根据定时值来计算出分频系数与重装载值。计算公式如下

Tout=((4×2prer)×rlr)/40 T_{out}=((4×2^{prer}) ×rlr) /40 Tout=((4×2prer)×rlr)/40

Tout为定时值,即看门狗溢出时间。
prer是预分频值,即IWDG_PR的值,范围为0-7。
rlr是重装载值,即IWDG_RLR的值。
在设定时,我们先预先设计好定时值,然后设定一个预分频值prer,最后计算出重装载值rlr即可。
代码写起来很简单,首先要实现IWDG初始化函数和喂狗函数。

IWDG_HandleTypeDef g_iwdg_handle;  /* 独立看门狗句柄 */
/**
 * @brief       初始化独立看门狗 
 * @param       prer: IWDG_PRESCALER_4~IWDG_PRESCALER_256,对应4~256分频
 *   @arg       分频因子 = 4 * 2^prer. 但最大值只能是256!
 * @param       rlr: 自动重装载值,0~0XFFF. 
 * @note        时间计算(大概):Tout=((4 * 2^prer) * rlr) / 40 (ms). 
 */
void iwdg_init(uint8_t prer, uint16_t rlr)
{
    g_iwdg_handle.Instance = IWDG;
    g_iwdg_handle.Init.Prescaler = prer; /* 设置IWDG分频系数 */
    g_iwdg_handle.Init.Reload = rlr;     /* 重装载值 */
    HAL_IWDG_Init(&g_iwdg_handle);       /* 初始化IWDG并启动 */
}
/**
 * @brief       喂独立看门狗
 */
void iwdg_feed(void)
{
    HAL_IWDG_Refresh(&g_iwdg_handle);  /* 重装载计数器 */
}

HAL_IWDG_Refresh()喂狗函数底层实现是调用函数__HAL_IWDG_RELOAD_COUNTER(),这个函数向IWDG_KR寄存器写入0xAAAA。

#define IWDG_KEY_RELOAD                 0x0000AAAAU  /*!< IWDG Reload Counter Enable   */
#define __HAL_IWDG_RELOAD_COUNTER(__HANDLE__)       WRITE_REG((__HANDLE__)->Instance->KR, IWDG_KEY_RELOAD)

之后,只需要按时调用函数iwdg_feed()进行喂狗即可。当系统陷入死循环等异常时,由于没有及时进行喂狗,IWDG就会触发复位信号,使设备重新开始工作。这样就保证了设备的稳定性。

二、窗口看门狗WWDG

如果学过计算机网络中的窗口机制,那么就能很快联想到,这里的窗口也是数的范围。WWDG触发复位有两种,当在窗口上限之外喂狗时,会触发复位。当计数器达到窗口下限时,也会触发复位。喂狗操作必须在预设时间窗口内完成。这就是窗口看门狗与独立看门狗不同的地方,简言之,二者触发复位的方式不同。WWDG框图如下
WWDG
WWDG由RCC时钟控制器提供时钟信号,经过预分频器后,比较CFR和CR寄存器的值,来判断是否触发复位信号。

2.1 相关寄存器

控制寄存器(WWDG_CR)
CR
当CR寄存器从0x3F递减到0x40时,会产生复位。刚好对应低八位的值。

配置寄存器(WWDG_CFR)
CFR
W6存放的是窗口的上限,结合WWDG_CR寄存器可知,窗口值的范围在W6<T6<0x3F,在这个范围内喂狗就可以防止发出复位。
该寄存器的EWI很值得关注,将该位置1后,当递减计数器等于 0x40 时产生提前唤醒中断,我们可以在中断里面向 WWDG_CR 重新写入计数器的值,来达到喂狗的目的,这样就可以及时喂狗以避免 WWDG 复位。要注意的是,在进入中断后,必须在不大于 1 个窗口看门狗计数周期的时间(在pclk1 频率为 36M 且 WDGTB为 0 的条件下,该时间为 113us)内重新写 WWDG_CR,否则,看门狗将产生复位!

状态寄存器(WWDG_SR)
SR

2.2 软件实现

与IWDG类似,使用WWDG也需要计算一些东西。公式如下:
TWWDG=(4096×2WDGTB×(T[5:0]+1))/FPCLK1 T_{WWDG} =(4096×2^{WDGTB}×(T[5:0] + 1))/ F_{PCLK1} TWWDG=(4096×2WDGTB×(T[5:0]+1))/FPCLK1
TWWDG: WWDG 超时时间(单位为 ms);
FPCLK1: APB1 的时钟频率(单位为 Khz);
2^WDGTB:是 WWDG_CFR 寄存器设置的预分频系数值;
T[5:0]:窗口看门狗的计数器低 6 位的值。
根据以上公式,假设 FPCLK1=36Mhz,那么可以得到最小-最大超时时间表如下表所示:

WDGTB最小超时值最大超时值
0113µs7.28ms
1227µs14.56ms
2455µs29.12ms
3910µs58.25ms

程序与IWDG类似,不同点在于IWDG喂狗一般是在main()函数里调用,而WWDG喂狗则是在中断里调用。所以我们需要实现初始化以及中断。初始化代码如下:

WWDG_HandleTypeDef g_wwdg_handle;  /* WWDG句柄 */
/**
 * @brief       初始化窗口看门狗
 * @param       tr: T[6:0],计数器值
 * @param       tw: W[6:0],窗口值
 * @note        fprer:分频系数(WDGTB),范围:WWDG_PRESCALER_1~WWDG_PRESCALER_8,表示2^WDGTB分频
 *              Fwwdg=PCLK1/(4096*2^fprer). 一般PCLK1=36Mhz
 * @retval      无
 */
void wwdg_init(uint8_t tr, uint8_t wr, uint32_t fprer)
{
    g_wwdg_handle.Instance = WWDG;
    g_wwdg_handle.Init.Prescaler = fprer;         /* 设置分频系数 */
    g_wwdg_handle.Init.Window = wr;               /* 设置窗口值 */
    g_wwdg_handle.Init.Counter = tr;              /* 设置计数器值 */
    g_wwdg_handle.Init.EWIMode = WWDG_EWI_ENABLE; /* 使能窗口看门狗提前唤醒中断 */
    HAL_WWDG_Init(&g_wwdg_handle);                /* 初始化WWDG */
}

中断配置如下:

/**
 * @brief       WWDG MSP回调
 * @param       WWDG句柄
 * @note        此函数会被HAL_WWDG_Init()调用
 * @retval      无
 */
void HAL_WWDG_MspInit(WWDG_HandleTypeDef *hwwdg)
{
    __HAL_RCC_WWDG_CLK_ENABLE();            /* 使能窗口看门狗时钟 */

    HAL_NVIC_SetPriority(WWDG_IRQn, 2, 3);  /* 抢占优先级2,子优先级为3 */
    HAL_NVIC_EnableIRQ(WWDG_IRQn);          /* 使能窗口看门狗中断 */
}

/**
 * @brief       窗口看门狗中断服务程序
 * @param       无
 * @retval      无
 */
void WWDG_IRQHandler(void)
{
    HAL_WWDG_IRQHandler(&g_wwdg_handle);  /* 调用WWDG共用中断处理函数 */
}

/**
 * @brief       窗口看门狗喂狗提醒中断服务回调函数
 * @param       wwdg句柄
 * @note        此函数会被HAL_WWDG_IRQHandler()调用
 * @retval      无
 */
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg)
{
    HAL_WWDG_Refresh(&g_wwdg_handle); /* 更新窗口看门狗值 */
}

三、应用场景

最后在来简单列举一些看门狗的经典使用场景:

  1. ‌工业控制系统‌:防止因程序死锁导致的生产事故。通过检测程序是否在预定时间内完成关键任务,避免因未及时响应而导致的系统完全崩溃‌。
  2. ‌医疗设备‌:确保关键任务(如心率监测)的实时响应。
  3. 智能家居设备‌:提升长期运行稳定性,减少维护需求‌。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值