STM32L475看门狗

独立看门狗(IWDG)与窗口看门狗(WWDG)

IWDG(Independent watchdog ) 独立看门狗是一种检测系统故障的外设. 工作原理是由一个看门狗专用的内部低速时钟驱动, 看门狗寄存器是一个计数器, 初始值为0xFFF, 当这个寄存器的值减为小于等于0时, 触发系统重置.
(WWDG)System window watchdog 窗口看门狗也是检测系统故障的外设, 驱动窗口看门狗的时钟由APB总线时钟分频而来, 也是有一个看门狗寄存器, 但是触发系统重置的条件不同, 窗口看门狗只在一个范围内工作正常(如:0x3F-0xC0), 高于0xC0或低于0x3F都将触发系统重置

两种看门狗的实际应用

独立看门狗主要用于在系统某一个外设有异常时触发重启

窗口看门狗主要用于检测系统主时钟, 中断系统有异常时, 触发重启

当系统已经运行出错, 但恰好IWDG的喂狗操作还正常时, 异常无法检出, 此时只有窗口看门狗能发现异常

本次实验目的就是构建一个Fault Manager错误管理, 用来检测并记录系统的异常

使用CubeMX生成独立看门狗代码

当勾选了IWDG为Activated之后, 所生成的MX_IWDG_Init()代码中会在运行是触发LSI, 所以不需要单独配置IWDG的时钟.

LSI独立时钟频率为32kHz, 通过计算可知喂狗时间范围为125us-32.8s

这里配置 IWDG controller clock prescaler 为64, 所以计算每秒递减值为 32000 ÷ 64 = 500

下面设置IWDG down-counter reload value 为1000, 也就是递减超过1000时触发系统重置, 所以总得只要在2s内喂狗一次, 系统就会正常运行.

然后IWDG window value是可以让独立看门狗实现类似窗口看门狗效果的配置. 假如我们这里设置500, 那么当计数器减到500-1000范围内时是不能喂狗的, 只有计数器位于0-500之间喂狗才不会触发重置.

此处我们设置IWDG window value为最大值4095, 关闭这个功能.


WWDG挂在主时钟APB1上, 时钟频率为80MHz, 设置分频系数为8后, 根据文档描述的计算公式, WWDG的时钟变为:

80MHz / (4096 x 8) ≈ 2.44kHz

意味着计数器每减1, 消耗月0.4096ms

由手册可知, WWDG的计数器只有7位, 上下限为0x3F(63)-0x7F(127)

所以设置WWDG free-running downcounter value为127, 为了获得最长等待时间

Window Value决定着窗口上限, 这里设置为90, 就是只有计数器减到90以下, 没有到0x3F(63)时, 喂狗才有效.

窗口宽度 (90 - 63) x 0.4096 ≈ 10.6ms

太快复位: 说明主循环时间波动太大, 或者窗口开的太小

太慢复位: 说明代码里有耗时操作, 比如有HAL_Delay或阻塞通信

勾选Early Wakeup Interrupt(EWI)会让看门狗在减到0x40时触发中断, 因此要额外在NVIC配置中增加Window watchdog interrupt中断配置.

生成代码

iwdg.h

c 复制代码
#ifndef __IWDG_H__
#define __IWDG_H__

#ifdef __cplusplus
extern "C" {
#endif

#include "main.h"

extern IWDG_HandleTypeDef hiwdg;

void MX_IWDG_Init(void);

#ifdef __cplusplus
}
#endif

#endif /* __IWDG_H__ */

iwdg.c

c 复制代码
#include "iwdg.h"

IWDG_HandleTypeDef hiwdg;

void MX_IWDG_Init(void)
{
  hiwdg.Instance = IWDG;
  hiwdg.Init.Prescaler = IWDG_PRESCALER_64;
  hiwdg.Init.Window = 4095;
  hiwdg.Init.Reload = 1000;
  if (HAL_IWDG_Init(&hiwdg) != HAL_OK)
  {
    Error_Handler();
  }
}

wwdg.h

c 复制代码
#ifndef __WWDG_H__
#define __WWDG_H__

#ifdef __cplusplus
extern "C" {
#endif

#include "main.h"

extern WWDG_HandleTypeDef hwwdg;

void MX_WWDG_Init(void);

#ifdef __cplusplus
}
#endif

#endif /* __WWDG_H__ */

wwdg.c

c 复制代码
#include "wwdg.h"

WWDG_HandleTypeDef hwwdg;

void MX_WWDG_Init(void)
{
  hwwdg.Instance = WWDG;
  hwwdg.Init.Prescaler = WWDG_PRESCALER_8;
  hwwdg.Init.Window = 90;
  hwwdg.Init.Counter = 127;
  hwwdg.Init.EWIMode = WWDG_EWI_ENABLE;
  if (HAL_WWDG_Init(&hwwdg) != HAL_OK)
  {
    Error_Handler();
  }
}

void HAL_WWDG_MspInit(WWDG_HandleTypeDef* wwdgHandle)
{

  if(wwdgHandle->Instance==WWDG)
  {
    __HAL_RCC_WWDG_CLK_ENABLE();

    HAL_NVIC_SetPriority(WWDG_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(WWDG_IRQn);
  }
}

stm32l4xx_it.h

c 复制代码
void WWDG_IRQHandler(void);

stm32l4xx_it.c

c 复制代码
extern WWDG_HandleTypeDef hwwdg;

void WWDG_IRQHandler(void)
{
  HAL_WWDG_IRQHandler(&hwwdg);
}

stm32l4xx_hal_conf.h

c 复制代码
#define HAL_IWDG_MODULE_ENABLED
#define HAL_WWDG_MODULE_ENABLED

main.c

c 复制代码
#include "iwdg.h"
#include "wwdg.h"

int main(void)
{
  MX_IWDG_Init();
  MX_WWDG_Init();
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI|RCC_OSCILLATORTYPE_MSI;
  RCC_OscInitStruct.LSIState = RCC_LSI_ON;
  // ...
}
基于窗口看门狗的监控模版

wwdg.h

这里面主要定义了根据配置自动计算最佳喂狗时间的宏WWDG_FEED_PERIOD

c 复制代码
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    wwdg.h
  * @brief   This file contains all the function prototypes for
  *          the wwdg.c file
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2026 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __WWDG_H__
#define __WWDG_H__

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

extern WWDG_HandleTypeDef hwwdg;

/* USER CODE BEGIN Private defines */
/************************ 从硬件配置提取的参数 ************************/
#define PCLK1_FREQ_HZ    80000000UL  // APB1时钟频率(80MHz)
#define SYSTICK_FREQ_HZ  1000UL      // SysTick中断频率(1ms)

// WWDG硬件配置(和MX_WWDG_Init一致)
#define WWDG_PRESCALER    WWDG_PRESCALER_8  // 预分频配置
#define WWDG_COUNTER_INIT 127UL             // 计数器初始值
#define WWDG_WINDOW_VAL   90UL              // 窗口值
#define WWDG_RESET_LOW_VAL 0x40UL             // 最低重置值

/************************ 自动计算核心宏 ************************/
// 转换预分频枚举到实际分频系数(STM32标准库宏)
#define WWDG_PRESCALER_TO_DIV(pre)  \
    ((pre) == WWDG_PRESCALER_1 ? 1 : \
     (pre) == WWDG_PRESCALER_2 ? 2 : \
     (pre) == WWDG_PRESCALER_4 ? 4 : \
     (pre) == WWDG_PRESCALER_8 ? 8 : 1)

// WWDG计数时钟频率(Hz)= PCLK1 / 4096 / 预分频系数
#define WWDG_COUNT_FREQ_HZ  \
    (PCLK1_FREQ_HZ / 4096UL / WWDG_PRESCALER_TO_DIV(WWDG_PRESCALER))

// 单个计数值的递减时间(μs)= 1e6 / 计数频率
#define WWDG_COUNT_PERIOD_US  \
    (1000000UL / WWDG_COUNT_FREQ_HZ)

// WWDG窗口开启时间(ms)= (初始值 - 窗口值) * 单个计数周期(μs) / 1000
#define WWDG_WINDOW_START_MS  \
    ((WWDG_COUNTER_INIT - WWDG_WINDOW_VAL) * WWDG_COUNT_PERIOD_US / 1000UL)

// WWDG总超时时间(ms)= 初始值 * 单个计数周期(μs) / 1000
#define WWDG_TIMEOUT_TOTAL_MS \
    ((WWDG_COUNTER_INIT - WWDG_RESET_LOW_VAL) * WWDG_COUNT_PERIOD_US / 1000UL)

// 安全喂狗周期(ms):取窗口中间值(窗口开启时间 + 有效窗口时长的50%)
#define WWDG_FEED_PERIOD  \
    (WWDG_WINDOW_START_MS + ((WWDG_TIMEOUT_TOTAL_MS - WWDG_WINDOW_START_MS) / 2))

// 兜底:防止计算值超出范围,增加边界检查
#if WWDG_FEED_PERIOD >= WWDG_TIMEOUT_TOTAL_MS || WWDG_FEED_PERIOD <= WWDG_WINDOW_START_MS
#error "WWDG_FEED_PERIOD out of window range! Check clock/WWDG config."
#endif

/* USER CODE END Private defines */

void MX_WWDG_Init(void);

/* USER CODE BEGIN Prototypes */

/* USER CODE END Prototypes */

#ifdef __cplusplus
}
#endif

#endif /* __WWDG_H__ */

stm32l4xx_it.c

在系统Systic中断里, 到达喂狗时间时, 设置全局标志位

c 复制代码
static uint8_t systic_wwdg_cnt  = 0;
uint8_t wwdg_refresh_request    = 0;
void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */
  systic_wwdg_cnt++;
  Health_TaskAlive(HEALTH_SYSTIC);

  if (systic_wwdg_cnt >= WWDG_FEED_PERIOD) {
    systic_wwdg_cnt = 0;
    wwdg_refresh_request = 1;
  }

  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
  /* USER CODE BEGIN SysTick_IRQn 1 */

  /* USER CODE END SysTick_IRQn 1 */
}

然后主循环中, 检测到窗口看门狗标志位时, 执行喂狗操作

c 复制代码
  while (1)
  {
    // ...
    if (wwdg_refresh_request) {
      Health_TaskAlive(HEALTH_APP);
      wwdg_refresh_request = 0;
      Health_feed_wwdg();
    }
  }

health.h

封装健康信息处理模块

c 复制代码
#ifndef HEALTH_H
#define HEALTH_H

#include <stdint.h>

void Health_TaskAlive(uint8_t id);
void Health_feed_wwdg(void);

enum
{
    HEALTH_APP = 0,
    HEALTH_SYSTIC,
    HEALTH_MAX,
};

#endif

health.c

主要功能, 标记单独模块工作正常, 确认所有模块正常, 喂狗并清除标记位

c 复制代码
#include "health.h"
#include "wwdg.h"

static volatile uint8_t health_flag[HEALTH_MAX];

void Health_TaskAlive(uint8_t id)
{
    if (id < HEALTH_MAX)
        health_flag[id] = 1;
}

uint8_t Health_AllOk(void)
{
    for (int i = 0; i < HEALTH_MAX; i++)
    {
        if (health_flag[i] == 0)
            return 0;
    }
    return 1;
}

void Health_ClearAll(void)
{
    for (int i = 0; i < HEALTH_MAX; i++)
        health_flag[i] = 0;
}

void Health_feed_wwdg()
{
    if (Health_AllOk()) {
        HAL_WWDG_Refresh(&hwwdg);
    }

    Health_ClearAll();
}

fault.h

错误信息处理模块

c 复制代码
#ifndef FAULT_H
#define FAULT_H

#include <stdint.h>

typedef enum
{
    FAULT_NONE = 0,

    /* 系统级 */
    FAULT_LOW_POWER         = (1 << 0),
    FAULT_WWDG_RESET        = (1 << 1),
    FAULT_IWDG_RESET        = (1 << 2),
    FAULT_SOFTWARE_RESET    = (1 << 3),
    FAULT_BOR_RESET         = (1 << 4),
    FAULT_PIN_RESET         = (1 << 5),
    FAULT_OPTION_BYTE_LOADER_RESET = (1 << 6),
    FAULT_FIREWALL_RESET    = (1 << 7),

    /* 外设级 */
    FAULT_DMA_TIMEOUT    = 0x0101,
    FAULT_UART_STUCK     = 0x0102,

    /* 应用级 */
    FAULT_APP_DEADLOOP   = 0x0201,

} Fault_Code_t;

Fault_Code_t Get_Reset_Fault(void);

#endif /* FAULT_H */

fault.c

返回错误代码

c 复制代码
#include "fault.h"
#include "stm32l4xx_hal.h"

Fault_Code_t Get_Reset_Fault(void) {
    Fault_Code_t f_code = FAULT_NONE;

    uint32_t rcc_csr = RCC->CSR;    // 读取RCC复位状态寄存器

    __HAL_RCC_CLEAR_RESET_FLAGS();       // 读取RCC后立即清除复位标志, 避免延迟

    if (rcc_csr & RCC_CSR_LPWRRSTF)  f_code |= FAULT_LOW_POWER;
    if (rcc_csr & RCC_CSR_WWDGRSTF)  f_code |= FAULT_WWDG_RESET;
    if (rcc_csr & RCC_CSR_IWDGRSTF)  f_code |= FAULT_IWDG_RESET;
    if (rcc_csr & RCC_CSR_SFTRSTF)   f_code |= FAULT_SOFTWARE_RESET;
    if (rcc_csr & RCC_CSR_BORRSTF)   f_code |= FAULT_BOR_RESET;
    if (rcc_csr & RCC_CSR_PINRSTF)   f_code |= FAULT_PIN_RESET;
    if (rcc_csr & RCC_CSR_OBLRSTF)   f_code |= FAULT_OPTION_BYTE_LOADER_RESET;
    if (rcc_csr & RCC_CSR_FWRSTF)    f_code |= FAULT_FIREWALL_RESET;

    return f_code;
}

然后主函数启动时检测错误码, 并打印复位日志

main.c

c 复制代码
main(){
  Fault_Code_t fault_code = Get_Reset_Fault();
  // ...
  MX_WWDG_Init();
  // ...
  if (fault_code != FAULT_NONE) {
    if (fault_code & FAULT_LOW_POWER)
      TRACE_ERROR("Fault: Low Power reset detected!");
    if (fault_code & FAULT_WWDG_RESET)
      TRACE_ERROR("Fault: Window watchdog reset detected!");
    if (fault_code & FAULT_IWDG_RESET)
      TRACE_ERROR("Fault: Independent window watchdog reset detected!");
    if (fault_code & FAULT_SOFTWARE_RESET)
      TRACE_ERROR("Fault: Software reset detected!");
    if (fault_code & FAULT_BOR_RESET)
      TRACE_ERROR("Fault: Brown-Out reset detected!");
    if (fault_code & FAULT_PIN_RESET)
      TRACE_ERROR("Fault: Pin reset detected!");
    if (fault_code & FAULT_OPTION_BYTE_LOADER_RESET)
      TRACE_ERROR("Fault: Option byte loader reset detected!");
    if (fault_code & FAULT_FIREWALL_RESET)
      TRACE_ERROR("Fault: Firewall reset detected!");
  }
}
相关推荐
wanglong37132 小时前
51单片机STC8G1K08输出PWM
单片机·嵌入式硬件·51单片机
传感器与混合集成电路13 小时前
210℃与175℃高温存储器选型研究:LHM256MB与LDMF4GA-H架构与可靠性对比(上)
嵌入式硬件·能源
时光找茬13 小时前
【瑞萨AI挑战赛-FPB-RA6E2】+ 从零开始:FPB-RA6E2 开箱测评与 e2 studio 环境配置
c++·单片机·边缘计算
17(无规则自律)14 小时前
【CSAPP 读书笔记】第二章:信息的表示和处理
linux·嵌入式硬件·考研·高考
@good_good_study14 小时前
FreeRTOS内存管理
单片机
Hello_Embed15 小时前
libmodbus 移植 STM32(基础篇)
笔记·stm32·单片机·学习·modbus
qq_3975623116 小时前
QT工程 , 生成别的电脑运行的exe程序
嵌入式硬件·qt
qqssss121dfd17 小时前
STM32H750XBH6的ETH模块移植LWIP
网络·stm32·嵌入式硬件
想放学的刺客19 小时前
单片机嵌入式试题(第27期)设计可移植、可配置的外设驱动框架的关键要点
c语言·stm32·单片机·嵌入式硬件·物联网