1. 项目概述
作为一名机械专业的学生,在准备智能+5G主题的机创比赛时,我偶然发现了GD32VW553这款功能强大的MCU。它集成了Wi-Fi 6和蓝牙5.2功能,特别适合物联网应用开发。在实际开发过程中,系统稳定性至关重要,而看门狗定时器(WDT)正是保障系统可靠性的关键组件。
看门狗定时器本质上是一个倒计时器,如果主程序未能定期"喂狗"(重置计数器),看门狗就会触发系统复位,防止程序卡死或跑飞。GD32VW553提供了两种看门狗:独立看门狗(IWDG)和窗口看门狗(WWDG),它们各有特点,适用于不同的应用场景。
2. 看门狗类型与工作原理
2.1 独立看门狗(IWDG)
独立看门狗是最基础的看门狗类型,它具有以下特点:
- 使用独立的32kHz低速内部时钟(LSI),不受主时钟影响
- 计时精度较低但稳定性高
- 配置简单,适合作为系统最后的安全保障
工作原理:
- 初始化时设置预分频值和重装载值
- 看门狗开始倒计时
- 程序需要在计数器归零前调用喂狗函数
- 如果超时未喂狗,看门狗触发系统复位
2.2 窗口看门狗(WWDG)
窗口看门狗提供了更精细的控制:
- 使用APB1总线时钟(默认42MHz)
- 可以设置喂狗的"时间窗口"
- 适合监控关键任务的执行时序
工作原理:
- 初始化时设置窗口上限值和计数器初始值
- 看门狗开始倒计时
- 程序只能在计数器值处于窗口下限(0x3F)和窗口上限之间时喂狗
- 过早或过晚喂狗都会触发复位
3. GD32VW553看门狗配置详解
3.1 独立看门狗配置步骤
- 启用32kHz低速内部时钟:
c复制rcu_osci_on(RCU_IRC32K);
- 解除写保护并配置看门狗:
c复制fwdgt_write_enable(); // 解除写保护
fwdgt_config(4000, FWDGT_PSC_DIV16); // 重装载值4000,16分频
fwdgt_write_disable(); // 重新启用写保护
- 启用看门狗:
c复制fwdgt_enable();
- 定期喂狗(建议在主循环中调用):
c复制fwdgt_write_enable();
fwdgt_counter_reload();
fwdgt_write_disable();
注意:喂狗操作需要先解除写保护,操作完成后应立即重新启用写保护,防止意外修改配置。
3.2 窗口看门狗配置步骤
- 启用WWDG时钟:
c复制rcu_periph_clock_enable(RCU_WWDGT);
- 初始化配置:
c复制wwdgt_deinit(); // 恢复默认配置
wwdgt_config(127, 111, WWDGT_CFG_PSC_DIV4); // 窗口上限127,计数器初始值111,4分频
wwdgt_counter_update(127); // 确保计数器初始值
wwdgt_enable(); // 启用看门狗
- 窗口喂狗(在合适的时间窗口内调用):
c复制uint8_t current_cnt = WWDGT_CTL & 0x7F; // 获取当前计数器值
if(current_cnt > 64 && current_cnt <= 111) { // 检查是否在喂狗窗口内
wwdgt_counter_update(127); // 重置计数器
}
4. 看门狗时间计算
4.1 独立看门狗超时计算
独立看门狗的时钟频率为32kHz,经过预分频后的实际时钟频率:
- 4分频:32kHz/4 = 8kHz
- 8分频:32kHz/8 = 4kHz
- 16分频:32kHz/16 = 2kHz
- 32分频:32kHz/32 = 1kHz
- 64分频:32kHz/64 = 500Hz
- 128分频:32kHz/128 = 250Hz
- 256分频:32kHz/256 = 125Hz
超时时间计算公式:
code复制超时时间 = (重装载值 + 1) / (分频后时钟频率)
示例:重装载值4000,16分频(2kHz)
code复制超时时间 = (4000 + 1) / 2000 ≈ 2秒
4.2 窗口看门狗时间计算
窗口看门狗使用APB1时钟(默认42MHz),经过预分频后的时钟频率:
- 1分频:42MHz
- 2分频:21MHz
- 4分频:10.5MHz
- 8分频:5.25MHz
窗口看门狗计数器每4096个时钟周期减1,因此计数器减1的时间:
code复制T = 4096 / (分频后时钟频率)
喂狗窗口时间:
code复制窗口时间 = (窗口上限 - 下限) × T
示例:4分频(10.5MHz),窗口上限111,下限63
code复制T = 4096 / 10.5MHz ≈ 390μs
窗口时间 = (111 - 63) × 390μs ≈ 18.72ms
5. 实际应用中的注意事项
5.1 独立看门狗使用技巧
- 喂狗频率应略小于超时时间,建议保留10%-20%余量
- 避免在中断服务程序中喂狗,除非能确保中断不会被长时间阻塞
- 调试时可暂时禁用看门狗,但正式发布前必须启用
- 对于关键任务,可以在任务开始时记录状态,喂狗前检查任务完成情况
5.2 窗口看门狗使用技巧
- 窗口上限设置应考虑任务的最短执行时间
- 将喂狗操作放在任务完成后的确定位置
- 对于周期性任务,窗口时间应大于任务平均执行时间但小于周期时间
- 可以使用多个窗口看门狗监控不同的任务模块
5.3 常见问题排查
-
系统频繁复位:
- 检查喂狗间隔是否过长
- 确认看门狗时钟源是否稳定
- 检查是否有死循环或阻塞操作
-
窗口看门狗过早复位:
- 检查喂狗是否在窗口期内进行
- 确认窗口上下限设置是否合理
- 检查任务执行时间是否波动过大
-
看门狗不工作:
- 确认看门狗是否已使能
- 检查时钟源是否启用
- 验证配置寄存器值是否正确写入
6. 完整代码实现
6.1 看门狗初始化与喂狗函数
c复制#include "gd32vw55x.h"
#include "systick.h"
#include <stdio.h>
#include "gd32vw553h_eval.h"
typedef enum {
FreeDog, // 独立看门狗
Windog // 窗口看门狗
} WatchDog_Type;
void dog_Init(WatchDog_Type type) {
if (type == FreeDog) {
// 独立看门狗初始化
rcu_osci_on(RCU_IRC32K);
fwdgt_write_enable();
fwdgt_config(4000, FWDGT_PSC_DIV16); // 2秒超时
fwdgt_write_disable();
fwdgt_enable();
}
else if (type == Windog) {
// 窗口看门狗初始化
rcu_periph_clock_enable(RCU_WWDGT);
wwdgt_deinit();
wwdgt_config(127, 111, WWDGT_CFG_PSC_DIV4); // 约18.72ms窗口
wwdgt_counter_update(127);
wwdgt_enable();
}
}
void Feeddog(WatchDog_Type type) {
if (type == FreeDog) {
// 独立看门狗喂狗
fwdgt_write_enable();
fwdgt_counter_reload();
fwdgt_write_disable();
}
if (type == Windog) {
// 窗口看门狗喂狗
uint8_t current_cnt = WWDGT_CTL & 0x7F;
if(current_cnt > 64 && current_cnt <= 111) {
wwdgt_counter_update(127);
}
}
}
6.2 延时函数实现
c复制#include "gd32vw55x.h"
#include "systick.h"
static uint32_t fac_us = 0;
static uint32_t fac_ms = 0;
void Delay_Init(void) {
SysTimer->MTIMECTL &= ~SysTimer_MTIMECTL_CMPCLREN_Msk;
SysTimer_SetCompareValue(SystemCoreClock / 4000);
fac_us = SystemCoreClock / 4000000U;
fac_ms = fac_us * 1000U;
}
static void delay_ticks(uint64_t ticks) {
uint64_t start = SysTimer->MTIMER;
volatile uint64_t current;
while (1) {
current = SysTimer->MTIMER;
if ((uint64_t)(current - start) >= ticks) {
break;
}
}
}
void delay_us(uint32_t us) {
uint64_t ticks = (uint64_t)us * fac_us;
delay_ticks(ticks);
}
void delay_ms(uint32_t ms) {
uint64_t ticks = (uint64_t)ms * fac_ms;
delay_ticks(ticks);
}
6.3 串口通信实现
c复制#include "gd32vw55x.h"
#include "systick.h"
#include <stdio.h>
#include "gd32vw553h_eval.h"
void Uart_Init(void) {
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_USART0);
gpio_af_set(GPIOB, GPIO_AF_8, GPIO_PIN_15);
gpio_af_set(GPIOA, GPIO_AF_2, GPIO_PIN_8);
gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_15);
gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_8);
gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_15);
usart_baudrate_set(USART0, 115200);
usart_parity_config(USART0, USART_PM_NONE);
usart_word_length_set(USART0, USART_WL_8BIT);
usart_stop_bit_set(USART0, USART_STB_1BIT);
usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
usart_receive_config(USART0, USART_RECEIVE_ENABLE);
usart_enable(USART0);
}
int fputc(int ch, FILE *f) {
usart_data_transmit(USART0, (uint8_t)ch);
while (usart_flag_get(USART0, USART_FLAG_TC) == RESET);
return (ch);
}
int fgetc(FILE *f) {
while (usart_flag_get(USART0, USART_FLAG_RBNE) == RESET);
return (int)usart_data_receive(USART0);
}
void Uart_SendData(uint16_t Data) {
usart_data_transmit(USART0, Data);
while(RESET == usart_flag_get(USART0, USART_FLAG_TC));
}
void USART_Polling_Send(uint8_t* data, uint32_t len) {
while(len--) {
while(RESET == usart_flag_get(USART0, USART_FLAG_TC));
usart_data_transmit(USART0, *data++);
}
}
uint8_t USART_Polling_Receive(void) {
uint8_t data = 0;
while(RESET == usart_flag_get(USART0, USART_FLAG_RBNE));
data = usart_data_receive(USART0);
usart_flag_clear(USART0, USART_FLAG_RBNE);
return data;
}
7. 项目实战建议
在实际项目中,我总结了以下几点经验:
-
对于主循环结构简单的应用,独立看门狗通常足够;对于多任务系统,建议结合窗口看门狗使用。
-
喂狗操作应该放在主循环的关键路径上,确保即使某部分功能异常,仍能正常喂狗。
-
调试阶段可以延长看门狗超时时间,方便定位问题,但正式发布前应调整为合理值。
-
记录看门狗复位次数和最后一次喂狗时间,有助于分析系统稳定性问题。
-
对于GD32VW553的Wi-Fi/BLE功能,建议在网络通信关键流程中添加喂狗点,防止网络异常导致系统锁死。
通过合理配置和使用看门狗,可以显著提高嵌入式系统的可靠性。在本次机创比赛中,这套机制成功避免了多次因外部干扰导致的系统死机问题。
