【GD32】从零开始学GD32单片机 | RTC实时时钟+日历例程(GD32F470ZGT6)

1 简介

RTC实时时钟顾名思义作用和墙上挂的时钟差不多,都是用于记录时间和日历,同时也有闹钟的功能。从硬件实现上来说,其实它就是一个特殊的计时器,它内部有一个32位的寄存器用于计时。RTC在低功耗应用中可以说相当重要,因为在使用外部低速晶振的条件下,它在所有的低功耗模式下都可以工作,这使得RTC很适合实现芯片的低功耗唤醒。下面是RTC的框图。

咋一看RTC的内部还挺复杂的。

2 硬件时钟

先看时钟,RTC的时钟可以选择内部32kHz晶振、外部高速晶振分频或外部低速时钟,一般都是使用外部32.768kHz低速晶振来驱动。时钟会先经过数字平滑校准器,用户可以配置进行进行校准;接着进入7位异步预分频器,这个分频器一般设置最大分频,即128分频,因为这个分频器的值越大RTC的功耗会越低;然后时钟进入一个粗校准器,需要注意的是粗校准和前面的数字平滑校准同时只能开启其中一个;之后时钟进入一个15位的同步预分频器,这里一般设置为256分频,这样就刚好能分出1Hz的频率,也就是每秒更新一次RTC。

当然上面介绍的是最常用的RTC时钟配置,根据不同的功能实现还可以有其他的配置。

3 功能

3.1 日历

RTC的日历功能依赖3个寄存器——RTC_DATE(日期寄存器)RTC_TIME(时间寄存器)RTC_SS(亚秒寄存器)。日期寄存器和时间寄存器保存我们熟知的年月日时分秒数据,是以BCD码的方式保存的。亚秒寄存器用于保存毫秒级的时钟数据。

上面的3个寄存器RTC内部会有对应的影子寄存器,在实际应用中我们一般读取它们对应的影子寄存器。每2个RTC周期影子寄存器才会更新一次,虽说影子寄存器的值与实际值会有延迟,但它能保证读出来的值是一致的。如果我们读的是寄存器的实际值,那么有可能在读的过程中RTC对寄存器进行了更新,导致我们读到的值前后不一致

在读取影子寄存器的值时,我们要等待状态寄存器的RSYNF位置1才能读取,此时寄存器的值才是稳定的。另外需注意的是,在深度睡眠和待机模式下,影子寄存器的值是不更新的,因此退出该模式时需要清除RSYNF位

3.2 自动唤醒

在低功耗应用中,RTC的自动唤醒功能可以说是必用到的,RTC内部使用一个16位的向下计数的计数器实现自动唤醒。计数器的驱动时钟可以选择RTC时钟的2/4/8/16分频或内部时钟,一般会选择内部时钟,即ck_spre。如果ck_spre为1Hz的话,唤醒的时间可以设置在1秒到36小时之间。

当计数器到0时,WTF标志位置1,唤醒计数器自动重载RTC_WUT的值。当WTF1后,必须软件清除该标志。如果使能了中断功能,并且芯片处于低功耗模式,唤醒中断会使芯片退出低功耗模式。

3.3 闹钟

RTC内部有2个可配置的闹钟,它与我们日常熟知的闹钟原理是差不多的;通过设置闹钟日期寄存器和闹钟亚秒寄存器来配置唤醒时刻,当到达指定时刻时闹钟会产生中断,该中断也能将芯片从低功耗模式中唤醒。

3.4 时间戳功能

时间戳功能由RTC_TS管脚输入,通过配置TSEN位来使能。当RTC_TS管脚检测到时间戳事件发生时,会将日历的值保存在时间戳寄存器中(RTC_DTS / RTC_TTS / RTC_SSTS),同时时间戳标志(TSF)也将由硬件置1。如果时间戳中断使能被启用(TSIE),时间戳事件会产生一个中断,该中断也能将芯片从低功耗模式中唤醒。

4 例程

4.1 日历

这个例程主要是配置RTC让它正常工作,然后定时读取日历值。

先看main函数。

#include "gd32f4xx.h"
#include "systick.h"
#include "debug.h"
#include "rtc.h"

struct tm time_struct = {
	.tm_year = 2024,
	.tm_mon = 1,
	.tm_mday = 1,
	.tm_hour = 0,
	.tm_min = 0,
	.tm_sec = 0,
	.tm_wday = RTC_MONDAY
};

int main(void)
{
    systick_config();
	debug_init();
	rtc_config(&time_struct);
	
	printf("rtc demo\r\n");

    while(1)
	{
		struct tm ts = {0};
		rtc_get_time(&ts);
		printf("%02d-%02d-%02d %02d:%02d:%02d\r\n", ts.tm_year, ts.tm_mon, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec);
		delay_1ms(1000);
    }
}

 main函数调用rtc_config函数初始化RTC,传入struct tm结构体,这个结构体是time.h里面定义的,是系统库自带的,它的定义如下。

struct tm {
    int tm_sec;   /* seconds after the minute, 0 to 60
                     (0 - 60 allows for the occasional leap second) */
    int tm_min;   /* minutes after the hour, 0 to 59 */
    int tm_hour;  /* hours since midnight, 0 to 23 */
    int tm_mday;  /* day of the month, 1 to 31 */
    int tm_mon;   /* months since January, 0 to 11 */
    int tm_year;  /* years since 1900 */
    int tm_wday;  /* days since Sunday, 0 to 6 */
    int tm_yday;  /* days since January 1, 0 to 365 */
    int tm_isdst; /* Daylight Savings Time flag */
    union {       /* ABI-required extra fields, in a variety of types */
        struct {
            int __extra_1, __extra_2;
        };
        struct {
            long __extra_1_long, __extra_2_long;
        };
        struct {
            char *__extra_1_cptr, *__extra_2_cptr;
        };
        struct {
            void *__extra_1_vptr, *__extra_2_vptr;
        };
    };
};

初始化函数的内部如下。

void rtc_config(struct tm *t)
{
    /* 使能PMU时钟 */
    rcu_periph_clock_enable(RCU_PMU);
    /* 使能RTC寄存器访问 */
    pmu_backup_write_enable();
	/* 使用外部低速晶振 */
	rcu_osci_on(RCU_LXTAL);
	rcu_osci_stab_wait(RCU_LXTAL);
	rcu_rtc_clock_config(RCU_RTCSRC_LXTAL);
    rcu_periph_clock_enable(RCU_RTC);
    rtc_register_sync_wait();
	/* 初始化时钟 */
	rtc_set_time(t);
}

因为RTC使用的是VBAT供电域,默认配置下该供电域的寄存器是写禁止的,因此需要调用pmu_backup_write_enable函数使能写操作。下面就是配置外部低速时钟,然后调用rtc_register_sync_wait等待影子寄存器同步数据。初始化完毕就可以配置日历了。

void rtc_set_time(const struct tm *time_struct)
{
	rtc_parameter_struct rtc_initpara = {0};

	/* RTC时钟频率 = 32.768kHz / (255 + 1) / (127 + 1) = 1Hz */
    rtc_initpara.factor_asyn = 127;
    rtc_initpara.factor_syn = 255;
    rtc_initpara.display_format = RTC_24HOUR;
	rtc_initpara.year = BIN_TO_BCD(time_struct->tm_year - 1970);
	rtc_initpara.month = BIN_TO_BCD(time_struct->tm_mon);
	rtc_initpara.date = BIN_TO_BCD(time_struct->tm_mday);
	rtc_initpara.hour = BIN_TO_BCD(time_struct->tm_hour);
	rtc_initpara.minute = BIN_TO_BCD(time_struct->tm_min);
	rtc_initpara.second = BIN_TO_BCD(time_struct->tm_sec);
	rtc_initpara.day_of_week = time_struct->tm_wday;
	rtc_init(&rtc_initpara);
}

配置日历使用rtc_init函数,按需要填充结构体即可;factor_asyn成员是异步分频器的值,factor_syn是同步分频器的值。另外需要注意日历寄存器内的值是以BCD码形式保存,因此需要写一个简单的转换宏,像下面。

#define BIN_TO_BCD(x) ((((x) / 10) << 4) + ((x) % 10))

还有就是RTC的寄存器年份内容只能保存2个数字,即只能计算99年,所以根据需要填合适的起始值进去,像我这里就是起始年份定为1970年,把当前年份减去1970转换为BCD码后填进寄存器。

 RTC运行时可以随时调用rtc_current_time_get获取当前时间。

void rtc_get_time(struct tm *time_struct)
{
	rtc_parameter_struct rtc_initpara = {0};
	
	rtc_current_time_get(&rtc_initpara);
	time_struct->tm_year = BCD_TO_BIN(rtc_initpara.year) + 1970;
	time_struct->tm_mon = BCD_TO_BIN(rtc_initpara.month);
	time_struct->tm_mday = BCD_TO_BIN(rtc_initpara.date);
	time_struct->tm_hour = BCD_TO_BIN(rtc_initpara.hour);
	time_struct->tm_min = BCD_TO_BIN(rtc_initpara.minute);
	time_struct->tm_sec = BCD_TO_BIN(rtc_initpara.second);
	time_struct->tm_wday = rtc_initpara.day_of_week;
}

 取值的时候也是要将BCD码转成对应的数字,用下面的宏。

#define BCD_TO_BIN(x) (10 * ((x) >> 4) + ((x) & 0x0F))

下面是demo的输出。 

<think>好的,我现在需要帮助用户解决在GD32F470芯片上进行RTC时钟的读取与设定的问题。首先,我需要回忆一下GD32F470RTC相关特性和配置步骤。根据用户提供的引用[2],里面提到了RTC时钟的计算和配置,以及BCD和二进制转换的方法,这应该是关键点。 首先,我应该确认RTC的初始化流程。通常,RTC的配置需要启用电源和备份寄存器访问,可能涉及PCLK1的时钟使能,以及备份域的解锁。然后配置时钟源,比如LSE、LSI或HSE,这里用户可能使用的是LSE,因为外部低速晶振更精确。需要设置预分频器以得到1Hz的时钟,比如32768Hz的LSE需要分频32767得到1秒的基准。 接下来是时间格式的处理,RTC通常使用BCD格式存储时间,所以需要转换函数。用户提到的BCD和二进制转换函数,比如bcd_to_hex和hex_to_bcd,这部分代码可能需要包含在示例中。 然后,读取和设置时间的函数,比如rtc_get_time和rtc_set_time,这些函数需要访问RTC的寄存器,如RTC_TIME寄存器,并通过RTC_WAIT同步确保操作完成。此外,需要考虑备份域的保护,配置前需要解除,配置后重新启用。 用户可能需要示例代码的结构,包括初始化、读取和设置函数,以及主函数中的调用示例。同时,需要提醒用户注意硬件连接,比如LSE的晶振是否正确接入,以及备份电池的存在以确保RTC在断电时保持运行。 还需要检查是否有遗漏的步骤,比如是否需要在初始化前检查RTC是否已经运行,避免重复配置导致问题。此外,引用[3]提到了数据手册中的IO和SPI信息,可能和RTC的引脚配置有关,需要确认RTC相关引脚是否正确配置,但RTC通常使用特定的低频引脚,可能不需要额外配置GPIO。 最后,生成相关问题时要覆盖用户可能进一步想知道的内容,比如时钟源选择、误差处理、低功耗模式下的RTC操作,以及校准方法。这些问题可以帮助用户深入理解RTC的应用和调试。</think>### GD32F470 RTC时钟读取与设定方法 #### 一、硬件准备 1. 确保GD32F470芯片的备份域供电正常(需连接VBAT引脚至3V电池) 2. 检查LSE时钟源配置:外接32.768kHz晶振(典型接法为6pF负载电容) #### 二、配置步骤 1. **时钟使能**: ```c rcu_periph_clock_enable(RCU_PMU); // 电源管理时钟 rcu_periph_clock_enable(RCU_BKPI); // 备份域时钟 rcu_periph_clock_enable(RCU_PCLK1); // APB1总线时钟 ``` 2. **备份域解锁**: ```c pmu_backup_write_enable(); // 允许写备份寄存器 bkp_deinit(); // 复位备份域 ``` 3. **RTC时钟源选择**: ```c // 使用LSE作为时钟源(32768Hz) rcu_osci_on(RCU_LSE); while(!rcu_osci_stab_wait(RCU_LSE)); rcu_rtc_clock_config(RCU_RTCSRC_LSE); ``` 4. **RTC初始化**: ```c rtc_init(RTC_CLOCK_SOURCE_LSE); // 初始化时钟源 rtc_prescaler_set(32767); // 设置分频器得到1Hz时钟 rtc_register_wait_enable(); // 使能寄存器访问等待 ``` #### 三、关键代码示例 ```c // BCD转十进制函数 uint8_t bcd_to_hex(uint8_t bcd) { return ((bcd >> 4) * 10) + (bcd & 0x0F); } // 十进制转BCD函数 uint8_t hex_to_bcd(uint8_t hex) { return ((hex / 10) << 4) | (hex % 10); } // 读取RTC时间 void rtc_get_time(RTC_TIME_TypeDef* time) { time->sec = bcd_to_hex(RTC_TIME & 0x0000007F); time->min = bcd_to_hex((RTC_TIME & 0x00007F00) >> 8); time->hour = bcd_to_hex((RTC_TIME & 0x007F0000) >> 16); } // 设置RTC时间 void rtc_set_time(uint8_t hour, uint8_t min, uint8_t sec) { RTC_TIME = (hex_to_bcd(hour) << 16) | (hex_to_bcd(min) << 8) | hex_to_bcd(sec); rtc_wait_sync(); // 等待寄存器同步 } ``` #### 四、操作注意事项 1. 修改时间前必须解除备份域保护: ```c bkp_write_enable(); // 允许写操作 ``` 2. 操作完成后重新启用保护: ```c bkp_write_disable(); // 禁止写操作 ``` 3. 调试建议:通过备份寄存器BKP_DATA0存储状态标志,防止RTC多次初始化[^2] #### 五、参考手册重点 1. RTC寄存器访问需要等待同步(RTC_WAIT) 2. 时间格式寄存器采用BCD编码 3. 日历寄存器更新频率为1Hz 4. 低功耗模式下RTC保持运行(需VBAT供电)[^3]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

马浩同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值