GD32F407VE天空星开发板RTC时钟与看门狗配置完全指南
一、RTC实时时钟配置
1.1 RTC概述
RTC(Real-Time Clock)是用于提供精确日期和时间信息的硬件模块。在GD32F407上,RTC支持完整的日历功能,包括日期(年/月/日)和时间(时/分/秒/亚秒)。
关键特性:
- 日期和时间以BCD码格式显示(亚秒除外)
- 核心是一个以1秒为单位的计数器
- 需要1Hz的精确时钟源驱动
- 具备备份电源域,掉电后仍可工作
1.2 RTC初始化完整流程
c
void RTC_config() {
/* 1. 使能PMU时钟 */
rcu_periph_clock_enable(RCU_PMU);
/* 2. 解除备份域写保护 */
pmu_backup_write_enable();
/* 3. 重置备份域 */
// rcu_bkp_reset_enable(); // 使用备份寄存器,不能重置
rcu_bkp_reset_disable();
/* 4. 配置RTC时钟源为 外部高速晶振 8M */
rcu_osci_on(RCU_HXTAL);
while(SUCCESS != rcu_osci_stab_wait(RCU_HXTAL));
rcu_rtc_clock_config(RCU_RTCSRC_HXTAL_DIV_RTCDIV);
rcu_rtc_div_config(RCU_RTC_HXTAL_DIV25); // =======++ 分频配置 8M/25 = 320K
/* 5. 使能RTC时钟 */
rcu_periph_clock_enable(RCU_RTC);
while(SUCCESS != rtc_register_sync_wait());
}
1.3 时钟源选择与配置

LXTAL(32.768kHz)- 推荐选择
c
// config
// 低速外部晶振 32.768 hz
// 3.1 设置时钟的晶振 rcu_osci_on
rcu_osci_on(RCU_LXTAL);
// 3.2 等待晶振稳定 rcu_osci_stab_wait
rcu_osci_stab_wait(RCU_LXTAL);
// 3.3 给rtc配置晶振 rcu_rtc_clock_config
rcu_rtc_clock_config(RCU_RTCSRC_LXTAL);
// write
// 配置异步和同步分频器数值
ris.factor_asyn = 0x7F; // 7位异步预分频器, 0x0 - 0x7F
ris.factor_syn = 0xFF; // 15位同步预分频器。0x0 - 0x7FFF
LXTAL(8MHz)
c
// 3.1 设置时钟的晶振 HXTAL -> 8M
rcu_osci_on(RCU_HXTAL);
// 3.2 等待晶振稳定
rcu_osci_stab_wait(RCU_HXTAL);
// 3.3 给rtc配置晶振
rcu_rtc_clock_config(RCU_RTCSRC_HXTAL_DIV_RTCDIV);
// 3.4 分频系数(HXTAL时,需要配置,建议选一个能除尽的数) // DIV25 -> 320K
rcu_rtc_div_config(RCU_RTC_HXTAL_DIV25);
// write
rps.factor_asyn = 127; // 7位异步预分频器, 0x0 - 0x7F
rps.factor_syn = 2499; // 15位同步预分频器。0x0 - 0x7FFF
IRC32K(内部32kHz)
c
// config
// 3.1 设置时钟的晶振 IRC32K
rcu_osci_on(RCU_IRC32K);
// 3.2 等待晶振稳定
rcu_osci_stab_wait(RCU_IRC32K);
// 3.3 给rtc配置晶振
rcu_rtc_clock_config(RCU_RTCSRC_IRC32K);
// write
// 配置异步和同步分频器数值
rps.factor_asyn = 0x7F; // 7位异步预分频器, 0x0 - 0x7F
rps.factor_syn = 0xF9; // 15位同步预分频器。0x0 - 0x7FFF
1.4 时间读写操作
c
// 十位取出左移4位 + 个位 (得到BCD数)
#define WRITE_BCD(val) ((val / 10) << 4) + (val % 10)
// 将高4位乘以10 + 低四位 (得到10进制数)
#define READ_BCD(val) (val >> 4) * 10 + (val & 0x0F)
void RTC_read() {
rtc_parameter_struct ris;
rtc_current_time_get(&ris); // 获取时间
printf("%02d-%02d-%02d\n", READ_BCD(ris.year) + 2000, READ_BCD(ris.month), READ_BCD(ris.date));
printf("weekday: %02d\n", READ_BCD(ris.day_of_week));
printf("%02d:%02d:%02d\n", READ_BCD(ris.hour), READ_BCD(ris.minute), READ_BCD(ris.second));
}
void RTC_write() {
rtc_parameter_struct ris = {0};
ris.year = WRITE_BCD(25), ris.month = WRITE_BCD(10), ris.date = WRITE_BCD(10);
ris.day_of_week = WRITE_BCD(5);
ris.hour = WRITE_BCD(23), ris.minute = WRITE_BCD(59), ris.second = WRITE_BCD(54);
ris.display_format = RTC_24HOUR;
// write
// 配置异步和同步分频器数值
ris.factor_asyn = 0x7F; // 7位异步预分频器, 0x0 - 0x7F
ris.factor_syn = 2499; // 15位同步预分频器。0x0 - 0x7FFF
/*************
// 1s
HXTAL_DIV25 -> 8M/25 -> 320K
ck_spre = rtc_clk / ((FACTOR_A + 1)*( FACTOR_S + 1))
1 = 320K / ((0x7F + 1)*( FACTOR_S + 1))
FACTOR_S = 320K / 0x80 - 1 = 32K / 128 - 1 = 2499
**************/
rtc_init(&ris);
}
1.5 闹钟功能配置
c
void RTC_alarm() {
rtc_alarm_struct alarm_config = {0};
// 配置闹钟日期和时间
// alarm_config.alarm_mask = RTC_ALARM_DATE_MASK; // 不匹配日期
// alarm_config.alarm_mask = RTC_ALARM_DATE_MASK | RTC_ALARM_HOUR_MASK; // 不匹配日期,小时
alarm_config.alarm_mask = RTC_ALARM_NONE_MASK;// 不忽略任何字段,即精确匹配日期和时间
alarm_config.weekday_or_date = RTC_ALARM_DATE_SELECTED;
// 上面配置了RTC_ALARM_DATE_SELECTED, alarm_day成员代表几号
// 上面配置了RTC_ALARM_WEEKDAY_SELECTED, alarm_day成员代表星期几
// alarm_config.alarm_day = WRITE_BCD(10); // 10号, 日期不起作用
// alarm_config.alarm_hour = WRITE_BCD(23); // 23点 小时不起作用
alarm_config.alarm_day = WRITE_BCD(11);
alarm_config.alarm_hour = WRITE_BCD(0);
alarm_config.alarm_minute = WRITE_BCD(0); // 0分
alarm_config.alarm_second = WRITE_BCD(0); // 0秒
// 配置闹钟0
rtc_alarm_config(RTC_ALARM0, &alarm_config);
// 使能闹钟中断
exti_init(EXTI_17, EXTI_INTERRUPT, EXTI_TRIG_RISING);
exti_interrupt_enable(EXTI_17);
nvic_irq_enable(RTC_Alarm_IRQn, 2, 2);
rtc_interrupt_enable(RTC_INT_ALARM0);
// 使能闹钟
rtc_alarm_enable(RTC_ALARM0);
}
// 闹钟中断处理函数
void RTC_Alarm_IRQHandler(void) {
if(RESET != exti_interrupt_flag_get(EXTI_17)) {
exti_interrupt_flag_clear(EXTI_17); // 外部中断的标志位
// RTC_FLAG_ALRM0 是 ALRM0 不是 ALARM0
rtc_flag_clear(RTC_FLAG_ALRM0); // 清除RTC标志位
printf("Alarm Triggered!\n");
RTC_write(); // 重新写时间,为了重复验证闹钟
}
}
二、看门狗配置
2.1 独立看门狗(FWDGT)
独立看门狗使用内部32kHz时钟,适合基本的系统监控。
c
//第一种独立看门狗
void fwdgt_init(uint32_t timeout_ms)
{
// 使能IRC32K时钟
rcu_osci_on(RCU_IRC32K);
rcu_osci_stab_wait(RCU_IRC32K);
// 计算重载值(分频32时,1ms计数1次)
uint32_t reload_value = timeout_ms;
// 配置看门狗
fwdgt_config(reload_value, FWDGT_PSC_DIV32);
fwdgt_enable();
printf("独立看门狗初始化完成,超时时间: %ld ms\n", timeout_ms);
}
//第二种独立看门狗
void WDGT_config() {
// 配置时钟源
rcu_osci_on(RCU_IRC32K);
while(SUCCESS != rcu_osci_stab_wait(RCU_IRC32K));
/* 独立看门狗配置 fwdgt_config
参数1:重载计数值(这个不是时间的值,是计数值,需要通过时间,计算出这个计数值)
0x0000 - 0x0FFF 12位向下递减计数器初始值, Max: 4095
参数2:预分频系数,将32kHZ进行降频 IRC32
32000Hz / 32 = 1000Hz 每秒数1000次,每次1ms
32000Hz / 64 = 500Hz 每秒数 500次,每次2ms
假设
target_ms = 2000ms 超过此时间不喂狗(fwdgt_counter_reload),触发看门狗重启
PSC = FWDGT_PSC_DIV32
参数1(重载计数值) = target_ms / 记一次数的时长(ms)
= target_ms / (1000 / Freq)ms
= target_ms / (1000 / (32000Hz / PSC))ms
= target_ms / (1000 / (32000Hz / 32))ms
= target_ms / (1000 / 1000)ms
= 2000ms / 1ms
= 2000
参数1(重载计数值) = target_ms / 记一次数的时长(ms)
= target_ms / (1000 / Freq)ms
= target_ms / (1000 / (32000Hz / PSC))ms
= target_ms / (1000 / (32000Hz / 64))ms
= target_ms / (1000 / 500)ms
= 2000ms / 2ms
= 1000 // ==============> 计数次数
*/
// 2s时间, 32K做64分频
fwdgt_config(1000, FWDGT_PSC_DIV64);
// 开启独立看门狗计数器 fwdgt_enable
fwdgt_enable();
}
// 喂狗函数
void fwdgt_feed(void)
{
fwdgt_counter_reload();
}
2.2 窗口看门狗(WWDGT)
窗口看门狗提供更精确的喂狗时间窗口控制。
c
void WDGT_config() {
// 初始化RCU时钟 rcu_periph_clock_enable
rcu_periph_clock_enable(RCU_WWDGT);
// 窗口看门狗重置 wwdgt_deinit
wwdgt_deinit();
/*
// 窗口看门狗配置 wwdgt_config
参数1: 计数器值,用于设置看门狗计数器的初始值
参数2: 窗口值,该值必须小于 counter 值
参数3: 分频系数
假设 WWDGT_CFG_PSC_DIV4
Hz = (PCLK1 / 4096) / 4 每秒钟计数值
= 42M / 4096 / 4
= 2563.48 Hz
求窗口时间范围:
1/4分频系数 -> 1次计数耗时 : 1000ms / 2563.48 Hz = 0.39ms
窗口开始时间:(counter - window) * 1次计数耗时 = (127 - 80) * 0.39ms = 18.33ms
窗口结束时间:(counter - 0x3F) * 1次计数耗时 = (127 - 63) * 0.39ms = 24.96ms
需要在窗口时间内喂狗(wwdgt_counter_update),否则会触发重启
18.33ms < duration < 24.96ms
*/
wwdgt_config(127, 80, WWDGT_CFG_PSC_DIV4);
// 启动窗口看门狗 wwdgt_enable
wwdgt_enable();
}
// 喂狗函数(必须在窗口时间内调用)
void wwdgt_feed(void)
{
wwdgt_counter_update(127);
}
三、实用示例代码
3.1 完整的RTC应用示例
c
#include "gd32f4xx.h"
#include "systick.h"
#include "USART0.h"
// 收到串口0数据,回调函数
void USART0_on_recv(uint8_t* data, uint32_t len) {
printf("recv[%d] = %s\n", len, data);
}
// 十位取出左移4位 + 个位 (得到BCD数)
#define WRITE_BCD(val) ((val / 10) << 4) + (val % 10)
// 将高4位乘以10 + 低四位 (得到10进制数)
#define READ_BCD(val) (val >> 4) * 10 + (val & 0x0F)
void RTC_config() {
/* 1. 使能PMU时钟 */
rcu_periph_clock_enable(RCU_PMU);
/* 2. 解除备份域写保护 */
pmu_backup_write_enable();
/* 3. 重置备份域 */
rcu_bkp_reset_enable();
rcu_bkp_reset_disable();
/* 4. 配置RTC时钟源为 外部高速晶振 8M */
rcu_osci_on(RCU_HXTAL);
while(SUCCESS != rcu_osci_stab_wait(RCU_HXTAL));
rcu_rtc_clock_config(RCU_RTCSRC_HXTAL_DIV_RTCDIV);
rcu_rtc_div_config(RCU_RTC_HXTAL_DIV25); // =======++ 分频配置 8M/25 = 320K
/* 5. 使能RTC时钟 */
rcu_periph_clock_enable(RCU_RTC);
while(SUCCESS != rtc_register_sync_wait());
}
void RTC_read() {
rtc_parameter_struct ris;
rtc_current_time_get(&ris); // 获取时间
printf("%02d-%02d-%02d\n", READ_BCD(ris.year) + 2000, READ_BCD(ris.month), READ_BCD(ris.date));
printf("weekday: %02d\n", READ_BCD(ris.day_of_week));
printf("%02d:%02d:%02d\n", READ_BCD(ris.hour), READ_BCD(ris.minute), READ_BCD(ris.second));
}
void RTC_write() {
rtc_parameter_struct ris = {0};
ris.year = WRITE_BCD(25), ris.month = WRITE_BCD(10), ris.date = WRITE_BCD(10);
ris.day_of_week = WRITE_BCD(5);
ris.hour = WRITE_BCD(23), ris.minute = WRITE_BCD(59), ris.second = WRITE_BCD(54);
ris.display_format = RTC_24HOUR;
// write
// 配置异步和同步分频器数值
ris.factor_asyn = 0x7F; // 7位异步预分频器, 0x0 - 0x7F
ris.factor_syn = 2499; // 15位同步预分频器。0x0 - 0x7FFF
/*************
// 1s
HXTAL_DIV25 -> 8M/25 -> 320K
ck_spre = rtc_clk / ((FACTOR_A + 1)*( FACTOR_S + 1))
1 = 320K / ((0x7F + 1)*( FACTOR_S + 1))
FACTOR_S = 320K / 0x80 - 1 = 32K / 128 - 1 = 2499
**************/
rtc_init(&ris);
}
void RTC_alarm() {
rtc_alarm_struct alarm_config = {0};
// 配置闹钟日期和时间
// alarm_config.alarm_mask = RTC_ALARM_DATE_MASK; // 不匹配日期
// alarm_config.alarm_mask = RTC_ALARM_DATE_MASK | RTC_ALARM_HOUR_MASK; // 不匹配日期,小时
alarm_config.alarm_mask = RTC_ALARM_NONE_MASK;
alarm_config.weekday_or_date = RTC_ALARM_DATE_SELECTED;
// 上面配置了RTC_ALARM_DATE_SELECTED, alarm_day成员代表几号
// 上面配置了RTC_ALARM_WEEKDAY_SELECTED, alarm_day成员代表星期几
// alarm_config.alarm_day = WRITE_BCD(10); // 10号, 日期不起作用
// alarm_config.alarm_hour = WRITE_BCD(23); // 23点 小时不起作用
alarm_config.alarm_day = WRITE_BCD(11);
alarm_config.alarm_hour = WRITE_BCD(0);
alarm_config.alarm_minute = WRITE_BCD(0); // 0分
alarm_config.alarm_second = WRITE_BCD(0); // 0秒
// 配置闹钟0
rtc_alarm_config(RTC_ALARM0, &alarm_config);
// 使能闹钟中断
exti_init(EXTI_17, EXTI_INTERRUPT, EXTI_TRIG_RISING);
exti_interrupt_enable(EXTI_17);
nvic_irq_enable(RTC_Alarm_IRQn, 2, 2);
rtc_interrupt_enable(RTC_INT_ALARM0);
// 使能闹钟
rtc_alarm_enable(RTC_ALARM0);
}
// 闹钟中断处理函数
void RTC_Alarm_IRQHandler(void) {
if(RESET != exti_interrupt_flag_get(EXTI_17)) {
exti_interrupt_flag_clear(EXTI_17); // 外部中断的标志位
// RTC_FLAG_ALRM0 是 ALRM0 不是 ALARM0
rtc_flag_clear(RTC_FLAG_ALRM0); // 清除RTC标志位
printf("Alarm Triggered!\n");
RTC_write(); // 重新写时间,为了重复验证闹钟
}
}
int main(void) {
// 抢占 [0, 3] 响应 [0, 3]
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
// 系统滴答定时器初始化
systick_config();
USART0_init(); // 串口初始化调用
RTC_config(); // RTC
RTC_write(); // 写时间
RTC_alarm(); // 闹钟
while(1) {
RTC_read(); // 读时间
delay_1ms(1000);
}
}
3.2 看门狗应用示例
独立看门狗:
c
#include "gd32f4xx.h"
#include "systick.h"
#include "USART0.h"
// 收到串口0数据,回调函数
void USART0_on_recv(uint8_t* data, uint32_t len) {
printf("recv[%d] = %#x\n", len, data[0]);
if (data[0] == 0x04) {
// while(1);
delay_1ms(3000);
}
}
void WDGT_config() {
// 配置时钟源
rcu_osci_on(RCU_IRC32K);
while(SUCCESS != rcu_osci_stab_wait(RCU_IRC32K));
/* 独立看门狗配置 fwdgt_config
参数1:重载计数值(这个不是时间的值,是计数值,需要通过时间,计算出这个计数值)
0x0000 - 0x0FFF 12位向下递减计数器初始值, Max: 4095
参数2:预分频系数,将32kHZ进行降频 IRC32
32000Hz / 32 = 1000Hz 每秒数1000次,每次1ms
32000Hz / 64 = 500Hz 每秒数 500次,每次2ms
假设
target_ms = 2000ms 超过此时间不喂狗(fwdgt_counter_reload),触发看门狗重启
PSC = FWDGT_PSC_DIV32
参数1(重载计数值) = target_ms / 记一次数的时长(ms)
= target_ms / (1000 / Freq)ms
= target_ms / (1000 / (32000Hz / PSC))ms
= target_ms / (1000 / (32000Hz / 32))ms
= target_ms / (1000 / 1000)ms
= 2000ms / 1ms
= 2000
参数1(重载计数值) = target_ms / 记一次数的时长(ms)
= target_ms / (1000 / Freq)ms
= target_ms / (1000 / (32000Hz / PSC))ms
= target_ms / (1000 / (32000Hz / 64))ms
= target_ms / (1000 / 500)ms
= 2000ms / 2ms
= 1000 // ==============> 计数次数
*/
// 2s时间, 32K做64分频
fwdgt_config(1000, FWDGT_PSC_DIV64);
// 开启独立看门狗计数器 fwdgt_enable
fwdgt_enable();
}
int main(void) {
// 抢占 [0, 3] 响应 [0, 3]
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
// 系统滴答定时器初始化
systick_config();
USART0_init(); // 串口初始化调用
WDGT_config(); // 看门狗配置
printf("==================reboot system=====================\n");
uint32_t cnt = 0;
while(1) {
cnt++;
if (cnt == 15) {
cnt = 0;
// 重载计数值(喂狗)
fwdgt_counter_reload();
}
printf("cnt = %d\n", cnt);
delay_1ms(100);
}
}
窗口看门狗:
c
#include "gd32f4xx.h"
#include "systick.h"
#include "USART0.h"
// 收到串口0数据,回调函数
void USART0_on_recv(uint8_t* data, uint32_t len) {
printf("recv[%d] = %#x\n", len, data[0]);
if (data[0] == 0x04) {
// while(1);
delay_1ms(3000);
}
}
void WDGT_config() {
// 初始化RCU时钟 rcu_periph_clock_enable
rcu_periph_clock_enable(RCU_WWDGT);
// 窗口看门狗重置 wwdgt_deinit
wwdgt_deinit();
/*
// 窗口看门狗配置 wwdgt_config
参数1: 计数器值,用于设置看门狗计数器的初始值
参数2: 窗口值,该值必须小于 counter 值
参数3: 分频系数
假设 WWDGT_CFG_PSC_DIV4
Hz = (PCLK1 / 4096) / 4 每秒钟计数值
= 42M / 4096 / 4
= 2563.48 Hz
求窗口时间范围:
1/4分频系数 -> 1次计数耗时 : 1000ms / 2563.48 Hz = 0.39ms
窗口开始时间:(counter - window) * 1次计数耗时 = (127 - 80) * 0.39ms = 18.33ms
窗口结束时间:(counter - 0x3F) * 1次计数耗时 = (127 - 63) * 0.39ms = 24.96ms
需要在窗口时间内喂狗(wwdgt_counter_update),否则会触发重启
18.33ms < duration < 24.96ms
*/
wwdgt_config(127, 80, WWDGT_CFG_PSC_DIV4);
// 启动窗口看门狗 wwdgt_enable
wwdgt_enable();
}
int main(void) {
// 抢占 [0, 3] 响应 [0, 3]
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
// 系统滴答定时器初始化
systick_config();
USART0_init(); // 串口初始化调用
printf("==================reboot system=====================\n");
WDGT_config(); // 看门狗配置
while(1) {
// delay_1ms(17); // 重启, 时间太早了
delay_1ms(20); // 时间合适
// delay_1ms(26); // 重启, 时间太晚了
wwdgt_counter_update(127);
printf("wwdgt_counter_update ok\n");
}
}
四、关键注意事项
4.1 RTC配置要点
- 备份域保护:写操作前必须解除保护,操作完成后建议恢复保护
- 时钟稳定:配置外部晶振后必须等待稳定
- 备份寄存器:用于标记初始化状态,避免重复设置时间
- 中断清理:闹钟中断后必须及时清除标志位
4.2 看门狗使用建议
- 超时选择:根据任务周期合理设置看门狗超时时间
- 喂狗时机:在关键任务完成后喂狗,避免正常业务被误重启
- 窗口时间:窗口看门狗必须在指定时间窗口内喂狗
- 调试注意:调试时可暂时禁用看门狗,发布时务必启用
- RTC:提供精确的时间基准,支持日历功能和闹钟提醒
- 看门狗:确保系统在异常情况下能够自动恢复,提高系统可靠性