32. 窗口看门狗

一、WWDG简介

  窗口看门狗(WWDG)通常被用来监测由外部干扰或不可预见的逻辑条件造成的应用程序背离正常的运行序列而产生的软件故障。窗口看门狗跟独立看门狗一样,也是一个递减计数器,不同的是它们的复位条件不一样。窗口看门狗产生复位信号有两个条件:

  • 当递减计数器的数值从 0x40 减到 0x3F 时(T6 位跳变到 0)。
  • 当喂狗的时候如果计数器的值大于 W[6:0]时,此数值在 WWDG_CFR 寄存器定义。

  上述的两个条件详细解释是,当计数器的值减到 0x40 时还不喂狗的话,到下一个计数就会产生复位,这个值称为 窗口的下限值,是固定的值,不能改变。这个跟独立看门狗类似,不同的是窗口看门狗的计数器的值在减到某一个数之前喂狗的话也会产生复位,这个值叫 窗口的上限上限值 W[6:0] 由用户设置。窗口看门狗计数器的上限值和下限值就是窗口的含义,喂狗也必须在窗口之内,否则就会复位。

二、WWDG框图

WWDG逻辑框图

  WWDG 有一个来自 RCC 的 PCLK1 输入时钟,经过一个 4096 的分频器(4096 分频在设计时已经设定死了,图中并没有给出来,但我们可以通过查看寄存器 WWDG_CFR 的 WDGTB 位的描述知道),再经过一个分频系数可选(1、2、4、8)的可编程预分频器提供时钟给一个 7 位递减计数器。

三、WWDG工作原理

  结合寄存器分析窗口看门狗的上限值和下限值。W[6:0] 是 WWDG_CFR 寄存器的低 7 位,用于与递减计数器 T[6:0] 比较的窗口值,也就是我们说的上限值,由用户设置。0x40 就是下限值,递减计数器达到这个值就会产生复位。T6 位就是 WWDG_CR 寄存器的位 6,即递减计数器 T[6:0] 的最高位。

窗口看门狗工作示意图

  图中可以看出,递减计数器的值递减过程中,当 T[6:0] > W[6:0] 是不允许刷新 T[6:0]的值,即不允许喂狗,否则会产生复位。只有在 W[6:0] < T[6:0]< 0x3F这个时间可以喂狗,这就是喂狗的窗口时间。当 T[6:0]=0x3F,即 T6 位为 0 这一刻,也会产生复位。

  上限值 W[6:0] 是由用户自己设置,但是一定要确保大于 0x40,否则就不存在上图的窗口了,下限值 0x40 是固定的,不可修改。

四、WWDG溢出时间计算

  看门狗的喂狗时间(也就是看门狗溢出时间)的计算方式为:

\[T_{out} = \frac{4096 * 2^{WDGTB} * (T[5:0] + 1)}{F_{WWDG}} \]

  其中:\(T_{out}\):WWDG 超时时间(单位为 ms)。\(T_{PCLK1}\):APB1 以 ms 为单位的时钟间隔(即单位为 1 /KHz)。T[5:0]:窗口看门狗的计数器低 6 位(T6 位固定 1,范围 0x7F ~ 0x40,对应值 0x3F ~ 0x00)。

  根据以上公式,我们来计算一下对应当重装值为 0x40 时,分频系数 WDGTB = 3 时对应,假设 PCLK1 = 42MHz , 下 一 个 计数将发生复位,到达下个复位的时间是:\(\frac{1}{42MHz}1 * 4096 * 2^3*(0+1) =780.19us\)

五、WWDG常用的寄存器

5.1、控制寄存器

控制寄存器

  该寄存器只有低八位有效,其中 T[6:0] 用来存储看门狗的计数器的值,随时更新的,每隔( \(4096×2^{WDGTB[2:0]}\))PCLK 个周期减 1。当该计数器的值从 0x40 变为 0x3F 的时候,将产生看门狗复位。

  WDGA 位则是看门狗的激活位,该位由软件置 1,启动看门狗,并且一定要注意的是该位一旦设置,就只能在硬件复位后才能清零了。

5.2、配置寄存器

配置寄存器

  该寄存器中的位 9 EWI 位是提前唤醒中断,如果该位置 1,当递减计数器等于 0x40 时产生提前唤醒中断,我们就可以及时喂狗以避免 WWDG 复位。因此,我们一般都会用该位来设置中断,当窗口看门狗的计数器值减到 0x40 的时候,如果该位设置,并开启了中断,则会产生中断,我们可以在中断里面向 WWDG_CR 重新写入计数器的值,来达到喂狗的目的。注意这里在进入中断后,必须在不大于 1 个窗口看门狗计数周期的时间(在 PCLK1 频率为 42M 且 WDGTB 为0 的条件下,该时间为 97.52us)内重新写 WWDG_CR,否则,看门狗将产生复位!

5.3、状态寄存器

状态寄存器

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

六、WWDG配置步骤

6.1、使能WWDG时钟

  WWDG 不同于 IWDG,IWDG 有自己独立的 32KHz 时钟。而 WWDG 使用的是 PCLK1 的时钟,需要先使能时钟。方法是:

#define __HAL_RCC_WWDG_CLK_ENABLE()     do { \
                                        __IO uint32_t tmpreg = 0x00U; \
                                        SET_BIT(RCC->APB1ENR, RCC_APB1ENR_WWDGEN);\
                                        /* Delay after an RCC peripheral clock enabling */ \
                                        tmpreg = READ_BIT(RCC->APB1ENR, RCC_APB1ENR_WWDGEN);\
                                        UNUSED(tmpreg); \
                                          } while(0U)

6.2、配置WWDG工作参数

  WWDG 在 HAL 库中的驱动代码在 stm32f4xx_hal_wwdg.c 文件(及其头文件)中。

HAL_StatusTypeDef HAL_WWDG_Init(WWDG_HandleTypeDef *hwwdg);

  形参 hwwdgWWDG 句柄,WWDG_HandleTypeDef 结构体类型,其定义如下:

typedef struct
{
    WWDG_TypeDef Instance;      //  WWDG寄存器基地址
    WWDG_InitTypeDef Init;      //  WWDG初始化结构体
} WWDG_HandleTypeDef;

  Instance指向 WWDG 寄存器基地址,可选值如下:

#define WWDG                ((WWDG_TypeDef *) WWDG_BASE)

  InitWWDG 初始化结构体,用于配置计数器的相关参数。

typedef struct
{
    uint32_t Prescaler;     // 预分频系数
    uint32_t Window;        // 窗口值
    uint32_t Counter;       // 计数器值
    uint32_t EWIMode ;      // 提前唤醒中断使能
} WWDG_InitTypeDef;

  Prescaler预分频系数,可选值如下:

#define WWDG_PRESCALER_1                    0x00000000u                              /*!< WWDG counter clock = (PCLK1/4096)/1 */
#define WWDG_PRESCALER_2                    WWDG_CFR_WDGTB_0                         /*!< WWDG counter clock = (PCLK1/4096)/2 */
#define WWDG_PRESCALER_4                    WWDG_CFR_WDGTB_1                         /*!< WWDG counter clock = (PCLK1/4096)/4 */
#define WWDG_PRESCALER_8                    (WWDG_CFR_WDGTB_1 | WWDG_CFR_WDGTB_0)    /*!< WWDG counter clock = (PCLK1/4096)/8 */

  Window窗口值,即上限值。

  Counter计数器值,用于保存要设置计数器的值。

  EWIMode提前唤醒中断使能,可选值如下:

#define WWDG_EWI_DISABLE                    0x00000000u       /*!< EWI Disable */
#define WWDG_EWI_ENABLE                     WWDG_CFR_EWI      /*!< EWI Enable */

  该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功HAL_ERROR 表示 错误HAL_BUSY 表示 忙碌HAL_TIMEOUT 表示 超时

typedef enum 
{
    HAL_OK = 0x00U,             // 成功
    HAL_ERROR = 0x01U,          // 错误
    HAL_BUSY = 0x02U,           // 忙碌
    HAL_TIMEOUT = 0x03U         // 超时
} HAL_StatusTypeDef;

6.3、使能中断

6.3.1、设置中断优先级分组

  HAL_NVIC_SetPriorityGrouping() 函数是设置中断优先级分组函数。其声明如下:

void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup);

  其中,参数 PriorityGroup中断优先级分组号,可以选择范围如下:

#define NVIC_PRIORITYGROUP_0         0x00000007U /*!< 0 bits for pre-emption priority
                                                      4 bits for subpriority */
#define NVIC_PRIORITYGROUP_1         0x00000006U /*!< 1 bits for pre-emption priority
                                                      3 bits for subpriority */
#define NVIC_PRIORITYGROUP_2         0x00000005U /*!< 2 bits for pre-emption priority
                                                      2 bits for subpriority */
#define NVIC_PRIORITYGROUP_3         0x00000004U /*!< 3 bits for pre-emption priority
                                                      1 bits for subpriority */
#define NVIC_PRIORITYGROUP_4         0x00000003U /*!< 4 bits for pre-emption priority
                                                      0 bits for subpriority */

  这个函数在一个工程里基本只调用一次,而且是在程序 HAL 库初始化函数里面已经被调用,后续就不会再调用了。因为当后续调用设置成不同的中断优先级分组时,有可能造成前面设置好的抢占优先级和响应优先级不匹配。如果调用了多次,则以最后一次为准。

6.3.2、设置中断优先级

  HAL_NVIC_SetPriority() 函数是设置中断优先级函数。其声明如下:

void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority);

  其中,参数 IRQn中断号,可以选择范围:IRQn_Type 定义的枚举类型,定义在 stm32f407xx.h。

typedef enum
{
  WWDG_IRQn                   = 0,      /*!< Window WatchDog Interrupt                                         */
} IRQn_Type;

  参数 PreemptPriority抢占优先级,可以选择范围:0 到 15,具体根据中断优先级分组决定。

  参数 SubPriority响应优先级,可以选择范围:0 到 15,具体根据中断优先级分组决定。

6.3.3、使能中断

  HAL_NVIC_EnableIRQ() 函数是中断使能函数。其声明如下:

void HAL_NVIC_EnableIRQ(IRQn_Type IRQn);

  其中,参数 IRQn中断号,可以选择范围:IRQn_Type 定义的枚举类型,定义在 stm32f407xx.h。

6.4、喂狗

  在 HAL 中重载计数值的函数是 HAL_IWDG_Refresh(),该函数的作用是把值 0xAAAA 写入到 IWDG_KR 寄存器,从而触发计数器重载,即实现独立看门狗的喂狗操作。

HAL_StatusTypeDef HAL_WWDG_Refresh(WWDG_HandleTypeDef *hwwdg);

  形参 hwwdgWWDG 句柄WWDG_HandleTypeDef 结构体类型。

  该函数的返回值是 HAL_StatusTypeDef 枚举类型的值,有 4 个,分别是 HAL_OK 表示 成功HAL_ERROR 表示 错误HAL_BUSY 表示 忙碌HAL_TIMEOUT 表示 超时

6.5、编写中断服务函数

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

void WWDG_IRQHandler(void);

  HAL 库定义了一个 WWDG 中断处理共用函数 HAL_WWDG_IRQHandler(),我们在 WWDG 中断服务函数中会调用该函数。同时该函数会调用回调函数 HAL_WWDG_EarlyWakeupCallback(),提前唤醒中断逻辑。

void HAL_WWDG_IRQHandler(WWDG_HandleTypeDef *hwwdg)
{
  /* Check if Early Wakeup Interrupt is enable */
  if (__HAL_WWDG_GET_IT_SOURCE(hwwdg, WWDG_IT_EWI) != RESET)
  {
    /* Check if WWDG Early Wakeup Interrupt occurred */
    if (__HAL_WWDG_GET_FLAG(hwwdg, WWDG_FLAG_EWIF) != RESET)
    {
      /* Clear the WWDG Early Wakeup flag */
      __HAL_WWDG_CLEAR_FLAG(hwwdg, WWDG_FLAG_EWIF);

#if (USE_HAL_WWDG_REGISTER_CALLBACKS == 1)
      /* Early Wakeup registered callback */
      hwwdg->EwiCallback(hwwdg);
#else
      /* Early Wakeup callback */
      HAL_WWDG_EarlyWakeupCallback(hwwdg);
#endif /* USE_HAL_WWDG_REGISTER_CALLBACKS */
    }
  }
}

  HAL 库常用的回调函数如下:

__weak void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg);

七、程序源码

  WWDG 初始化函数:

WWDG_HandleTypeDef g_wwdg_handle;

/**
 * @brief WWDG初始化函数
 * 
 * @param prescaler 预分频值
 * @param count 计数值
 * @param window 窗口值
 */
void WWDG_Init(uint16_t prescaler, uint8_t count, uint8_t window)
{
    g_wwdg_handle.Instance = WWDG;                                              // WWDG寄存器地址
    g_wwdg_handle.Init.Prescaler = prescaler;                                   // 预分频值
    g_wwdg_handle.Init.Counter = count;                                         // 计数值
    g_wwdg_handle.Init.Window = window;                                         // 窗口值 
    g_wwdg_handle.Init.EWIMode = WWDG_EWI_ENABLE;                               // 提前唤醒中断使能
    HAL_WWDG_Init(&g_wwdg_handle);
}

  WWDG 底层初始化函数:

/**
 * @brief WWDG底层初始化函数
 * 
 * @param hwwdg WWDG句柄
 */
void HAL_WWDG_MspInit(WWDG_HandleTypeDef *hwwdg)
{
    if (hwwdg->Instance == WWDG)
    {
        __HAL_RCC_WWDG_CLK_ENABLE();                                            // 使能WWDG时钟

        HAL_NVIC_SetPriority(WWDG_IRQn, 2, 0);                                  // 设置WWDG中断优先级
        HAL_NVIC_EnableIRQ(WWDG_IRQn);                                          // 使能WWDG中断
    }
}

  WWDG 中断服务函数:

/**
 * @brief WWDG中断服务函数
 * 
 */
void WWDG_IRQHandler(void)
{
    if (__HAL_WWDG_GET_FLAG(&g_wwdg_handle, WWDG_FLAG_EWIF) != RESET)           // 检测WWDG提前唤醒中断标志位
    {
        __HAL_WWDG_CLEAR_FLAG(&g_wwdg_handle, WWDG_FLAG_EWIF);                  // 清除WWDG提前唤醒中断标志位

        HAL_WWDG_Refresh(&g_wwdg_handle);                                       // 喂狗
    }
}

  main() 函数:

int main(void)
{
    HAL_Init();
    System_Clock_Init(8, 336, 2, 7);
    Delay_Init(168);

    UART_Init(&g_usart1_handle, USART1, 115200);
    Key_Init();

    if (__HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST) != RESET)
    {
        printf("窗口看门狗复位\r\n");
        __HAL_RCC_CLEAR_RESET_FLAGS();                                          // 清除复位标志位
    }
    else
    {
        printf("外部复位!\r\n");
    }

    printf("你还没有喂狗,请及时喂狗!\r\n");

    WWDG_Init(WWDG_PRESCALER_8, 0x7F, 0x5F);

    while (1)
    {
        if (Key_Scan(0) == WKUP_PRESS)
        {
            HAL_WWDG_Refresh(&g_wwdg_handle);                                   // 喂狗
            printf("你已经进行喂狗了!\r\n");
        }
        HAL_Delay(10); 
    }
  
    return 0;
}
WWDG_Init(WWDG_PRESCALER_8, 0x7F, 0x5F);

  该语句,设置计数器值为 0x7F,窗口寄存器为 0x5F,分频数为 8,然后可由前面的公式得到窗口上限时间 \(T_{wwdg} = \frac{4096 × 8 × (0x7F- 0x5F)}{42MHz} = 24.98ms\),窗口下限时间 \(T_{wwdg} = \frac{4096 * 8 * (0x7F - 0x3F)}{42MHz} = 49.97ms\),即喂狗的窗口区间为 24.98 ~ 49.97ms。所以程序默认会在 49.97ms 左右进入中断喂狗一次

posted @ 2023-12-22 22:23  星光樱梦  阅读(55)  评论(0编辑  收藏  举报