看门狗其实就是一个定时器,从功能上说它可以让微控制器在程序发生意外(程序进入死循环或跑飞)的时候,能重新回复到系统刚上电状态,以保障系统出问题的时候可以重启一次。说的复杂一点,看门狗就是能让程序出问题是能重新启动系统。
STM32 有两个看门狗,一个是独立看门狗,一个是窗口看门狗。我们知道独立看门狗的工作原理就是一个递减计数器不断的往下递减计数,当减到 0 之前如果没有喂狗的话,产生复位。窗口看门狗跟独立看门狗一样,也是一个递减计数器不断的往下递减计数,当减到一个固定值 0X40 时还不喂狗的话,产生复位,这个值叫窗口的下限,是固定的值,不能改变。这个是跟独立看门狗类似的地方,不同的地方是窗口看门狗的计数器的值在减到某一个数之前喂狗的话也会产生复位,这个值叫窗口的上限,上限值由用户独立设置。窗口看门狗计数器的值必须在上窗口和下窗口之间才可以喂狗,这就是窗口看门狗中窗口两个字的含义。
**WWDG 一般被用来监测,由外部干扰或不可预见的逻辑条件造成的应用程序背离正常的运行序列而产生的软件故障。**比如一个程序段正常运行的时间是 50ms,在运行完这个段程序之后紧接着进行喂狗,如果在规定的时间窗口内还没有喂狗,那就说明我们监控的程序出故障了,跑飞了,那么就会产生系统复位,让程序重新运行。
1. 打开 STM32CubeMX 软件,点击“新建工程”
2. 选择 MCU 和封装
3. 配置时钟
RCC 设置,选择 HSE(外部高速时钟) 为 Crystal/Ceramic Resonator(晶振/陶瓷谐振器)
选择 Clock Configuration,配置系统时钟 SYSCLK 为 72MHz
修改 HCLK 的值为 72 后,输入回车,软件会自动修改所有配置
4. 配置调试模式
非常重要的一步,否则会造成第一次烧录程序后续无法识别调试器
SYS 设置,选择 Debug 为 Serial Wire
在 System Core
中选择 WWDG
设置,并勾选 Activated
激活
WWDG counter clock prescaler
预分频器值设为 8
WWDG window value
上窗口值设为 90
WWDG free-running downcounter value
计数器值设为 127
超时时间 Twwdg = Tpclk1 x 4096 x 2^wdgtb x (T[5:0] + 1) (ms)
由图知 Tpclk1 为 36 MHz,当 prv 取 WWDG_Prescaler_8,即wdgtb为3
即最小超时值为 910us,最大超时值为 58.25ms。
1
是计数器的初始值2
是我们设置的上窗口值3
是下窗口值(0x3F)
窗口看门狗计数器的值只有在2
和3
之间(上窗口和下窗口之间)才可以喂狗
看门狗中断 Early wakeup interrupt
提前唤醒中断,选择 Enable
使能
输入项目名和项目路径
选择应用的 IDE 开发环境 MDK-ARM V5
每个外设生成独立的 ’.c/.h’
文件
不勾:所有初始化代码都生成在 main.c
勾选:初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。
点击 GENERATE CODE 生成代码
打开 stm32f1xx_it.c
中断服务函数文件,找到 WWDG 中断的服务函数 WWDG_IRQHandler()
中断服务函数里面就调用了串口中断处理函数 HAL_WWDG_IRQHandler()
打开 stm32f1xx_hal_wwdg.c
文件,找到窗口看门狗中断处理函数原型 HAL_WWDG_IRQHandler()
,其主要作用就是判断产生中断,清除中断标识位,然后调用中断回调函数 HAL_WWDG_EarlyWakeupCallback()
。
/* NOTE: This function Should not be modified, when the callback is needed,
the HAL_GPIO_EXTI_Callback could be implemented in the user file
*/
这个函数不应该被改变,如果需要使用回调函数,请重新在用户文件中实现该函数。
HAL_WWDG_EarlyWakeupCallback()
按照官方提示我们应该再次定义该函数,__weak
是一个弱化标识,带有这个的函数就是一个弱化函数,就是你可以在其他地方写一个名称和参数都一模一样的函数,编译器就会忽略这一个函数,而去执行你写的那个函数;而 UNUSED(hwwdg)
,这就是一个防报错的定义,当传进来的窗口看门狗没有做任何处理的时候,编译器也不会报出警告。其实我们在开发的时候已经不需要去理会中断服务函数了,只需要找到这个中断回调函数并将其重写即可而这个回调函数还有一点非常便利的地方这里没有体现出来,就是当同时有多个中断使能的时候,STM32CubeMX会自动地将几个中断的服务函数规整到一起并调用一个回调函数,也就是无论几个中断,我们只需要重写一个回调函并判断传进来的定时器号即可。
接下来我们就在 stm32f1xx_it.c
这个文件的最下面添加 HAL_WWDG_EarlyWakeupCallback()
/* USER CODE BEGIN 1 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
HAL_UART_Transmit(&huart1, (uint8_t *)Buffer, 1, 0xffff);
HAL_UART_Receive_IT(&huart1, (uint8_t *)Buffer, 1);
}
}
/* USER CODE END 1 */
在 while 循环中 1 秒打印一条语句
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_USART1_UART_Init();
MX_WWDG_Init();
/* USER CODE BEGIN 2 */
printf("\n\r***** WWDG Test Start *****\n\r");
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
printf("\n\r Running...\n\r");
HAL_Delay(1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
串口打印功能查看 STM32CubeMX学习笔记(6)——USART串口使用
当去掉 stm32f1xx_it.c 中 HAL_WWDG_EarlyWakeupCallback
的 HAL_WWDG_Refresh(hwwdg);
,也就是不喂狗时,系统约 59 毫秒重启一次。
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg)
{
// HAL_WWDG_Refresh(hwwdg);
}
STM32CubeMX 使用 HAL 库生成的代码:
/**
* @brief WWDG Initialization Function
* @param None
* @retval None
*/
static void MX_WWDG_Init(void)
{
/* USER CODE BEGIN WWDG_Init 0 */
/* USER CODE END WWDG_Init 0 */
/* USER CODE BEGIN WWDG_Init 1 */
/* USER CODE END WWDG_Init 1 */
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();
}
/* USER CODE BEGIN WWDG_Init 2 */
/* USER CODE END WWDG_Init 2 */
}
HAL_WWDG_Refresh(hwwdg);
使用 STM32 标准库的代码:
WWDG_Config(0X7F, 0X5F, WWDG_Prescaler_8);
// WWDG 中断优先级初始化
static void WWDG_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel = WWDG_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
/* WWDG 配置函数
* tr :递减计时器的值, 取值范围为:0x7f~0x40
* wr :窗口值,取值范围为:0x7f~0x40
* prv:预分频器值,取值可以是
* @arg WWDG_Prescaler_1: WWDG counter clock = (PCLK1(36MHZ)/4096)/1
* @arg WWDG_Prescaler_2: WWDG counter clock = (PCLK1(36mhz)/4096)/2
* @arg WWDG_Prescaler_4: WWDG counter clock = (PCLK1(36mhz)/4096)/4
* @arg WWDG_Prescaler_8: WWDG counter clock = (PCLK1(36mhz)/4096)/8
*/
void WWDG_Config(uint8_t tr, uint8_t wr, uint32_t prv)
{
// 开启 WWDG 时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE);
// 设置递减计数器的值
WWDG_SetCounter( tr );
// 设置预分频器的值
WWDG_SetPrescaler( prv );
// 设置上窗口值
WWDG_SetWindowValue( wr );
// 设置计数器的值,使能WWDG
WWDG_Enable(WWDG_CNT);
// 清除提前唤醒中断标志位
WWDG_ClearFlag();
// 配置WWDG中断优先级
WWDG_NVIC_Config();
// 开WWDG 中断
WWDG_EnableIT();
}
WWDG_SetCounter( WWDG_CNT );
MX_WWDG_Init();
对应 WWDG_Config(0X7F, 0X5F, WWDG_Prescaler_8);
HAL_WWDG_Refresh(hwwdg);
对应 WWDG_SetCounter(WWDG_CNT);
用户代码要加在 USER CODE BEGIN N
和 USER CODE END N
之间,否则下次使用 STM32CubeMX 重新生成代码后,会被删除。
• 由 Leung 写于 2021 年 1 月 29 日
• 参考:STM32CubeMX系列教程15:看门狗(WDG)
【STM32】HAL库 STM32CubeMX教程五----看门狗(独立看门狗,窗口看门狗)