0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

【GD32F303红枫派开发板使用手册】第九讲 RTC-万年历实验

聚沃科技 2024-06-07 09:43 次阅读
wKgaomZVdiiAfR9BAB3mDFhHnZc972.png

9.1实验内容

通过本实验主要学习以下内容:

  • RTC简介
  • RTC复位
  • RTC实现万年历
  • RTC使用注意事项

9.2实验原理

9.2.1RTC简介

RTC(Real Time Clock)——实时时钟定时器,可以用作日历。RTC电路分两个电源域部分,其一位于备份域中,该部分包括一个32位的累加计数器、一个闹钟、一个预分频器、一个分频器以及RTC时钟配置寄存器。备份域这部分电路不会因为系统复位或者MCU进入低功耗而丢失数据,所以在系统复位或MCU从低功耗下唤醒,RTC的设置和时间都可以保持不变。另一部分位于VDD电源域中,该部分只包括APB接口以及一组控制寄存器。

关于RTC的两个电源域及分布在两个电源域中的内容,需要读者牢记,否则会由于细节没处理好导致RTC工作异常。

以下为GD32F303的RTC框图:

wKgaomZiZNyAGxZyAAE4ZXdvRok560.png

图中RTC_CNT为计数值,每个SC_CLK时钟到来时,这个计数值增加+1。SC_CLK时钟源有三个:LXTAL(外部低速晶振)、IRC40K(内部40K晶振)和HXTAL/128,三个时钟源经过RTC_DIV产生SC_CLK。RTC_DIV的配置是通过RTC_PSC加载所得,RTC_PSC由寄存器RTC预分频寄存器高位(RTC_PSCH)和RTC预分频寄存器低位(RTC_PSCL)配置,有效位20bit,即RTC的时钟分频器最大值为2的20次方,完全足够应用了。

了解了上面的内容,RTC的工作原理就很好理解了。举个例子,RTC的时钟源选择LXTAL,频率为32.768KHz,然后设置RTC_PSC为32768,那么SC_CLK即为1Hz,也就是说,每秒钟RTC_CNT值+1,所以我们只要获得一段时间内RTC_CNT值增加了多少数,也就知道经过了多少秒。

我们从手册中可以看到RTC_CNT由RTC计数寄存器高位(RTC_CNTH)和RTC计数寄存器低位(RTC_CNTL)设置,这两个寄存器组合起来的有效位为32bit,即RTC_CNT可以记录2的32次方,即4,294,967,296个数,按照每秒增加一次的话,可以记录136多年,

除了基础的记时间的功能,RTC还有一个闹钟功能,RTC运行时,当RTC_CNT的值增加到和RTC_ALRM(由RTC闹钟寄存器高位(RTC_ALRMH)和RTC闹钟寄存器低位(RTC_ALRML)设置)相等时,则会产生ALRM中断,当然,程序中需要实现使能ALARM中断(ALRMIE)。

RTC还有另外两个中断,一个是秒中断,另一个是溢出中断。秒中断好理解,即每秒钟进入一个中断;溢出中断则是当RTC_CNT溢出时产生中断。

9.2.2RTC复位

这里把RTC的复位单独作为一个章节来说,是因为这里很容易出错导致想要实现的功能无法实现。

前面说过,RTC分为两个电源域——备份域和VDD电源域,而一般的复位比如NRST脚复位、软件复位等只能复位VDD和VDDA电源域 ,而无法复位备份域;备份域复位需要VBAT掉电或者通过备份域控制寄存器(RCU_BDCTL)的BKPDRST来进行备份域复位。

上节中的RTC框图中被深色框住的即属于备份域,这里提到一个很容易出错的地方,即RTC的时钟源选择。从框图看到时钟源由RTCSRC[1:0]来设置,这个位域属于备份域控制寄存器(RCU_BDCTL)。

备份域控制寄存器( RCU_BDCTL):

wKgaomZiZOyADpW2AACTeOREhls922.pngwKgZomZiZOyAO7mLAAEvIGqW0OQ864.png

寄存器描述中指出,一旦RTC的时钟源选择后,除了将备份域复位,否则时钟不能被改变。举个例子:一个产品选择LXTAL作为RTC时钟,但可能因为某些原因LXTAL停振了,需要将时钟源切换到IRC40K,程序如何实现呢?没错,需要复位备份域(控制BKPDRST位)才能重新选择时钟源,但一旦备份域进行了复位,包括RTC_CNT等数据都会丢失,所以在备份域复位前需要对RTC内的各个数据进行保存处理,待备份域复位后再重新写入。

9.2.3RTC实现万年历

本实验用RTC做一个万年历,其中还需要考虑到闰年闰月的情况。实验设置的基准时间是1970年,即当RTC_CNT为0时,为1970年,实验最高可记录到2106年(1970+136)。

9.3硬件设计

本实验实现每秒钟通过串口打印实时时间,即需要使用到开发板USB串口模块。另外RTC时钟源使用的是外部低速晶振,外部晶振原理图如下:

wKgZomZiZQ6AaL37AABaIpa9qvg566.png

9.4代码解析

9.4.1RTC配置

在driver_rtc.c中定义了RTC配置函数rtc_configuration

C
void rtc_configuration(void)
{
/*使能备份域和PMU时钟*/
rcu_periph_clock_enable(RCU_BKPI);
rcu_periph_clock_enable(RCU_PMU);
/*使能备份域写功能*/
pmu_backup_write_enable();
/*备份域复位*/
bkp_deinit();
/*依据选择的时钟源进行时钟配置*/
/*使用LXTAL*/
#ifdef RTC_CLOCK_SOURCE_LXTAL
/* 使能 LXTAL */
rcu_osci_on(RCU_LXTAL);
/* 等待LXTAL Ready */
rcu_osci_stab_wait(RCU_LXTAL);
/*选择LXTAL作为RTC时钟*/
rcu_rtc_clock_config(RCU_RTCSRC_LXTAL);
#endif
/*使用IRC40K*/
#ifdef RTC_CLOCK_SOURCE_IRC40K
/* 使能 IRC40K*/
rcu_osci_on(RCU_IRC40K);
/* 等待IRC40K Ready */
rcu_osci_stab_wait(RCU_IRC40K);
/*选择IRC40K作为RTC时钟*/
rcu_rtc_clock_config(RCU_RTCSRC_IRC40K);
#endif

#ifdef RTC_CLOCK_SOURCE_HXTAL_DIV_128
/* 使能LXTAL */
rcu_osci_on(RCU_HXTAL);
/* 等待HXTAL Ready */
rcu_osci_stab_wait(RCU_HXTAL);

/*选择HXTAL/128作为RTC时钟*/
rcu_rtc_clock_config(RCU_RTCSRC_HXTAL_DIV_128);
#endif
/*使能RTC时钟*/
rcu_periph_clock_enable(RCU_RTC);
/*等待RTC寄存器同步*/
rtc_register_sync_wait();
/*等待上一次操作完成*/
rtc_lwoff_wait();
/*使能RTC秒中断*/
rtc_interrupt_enable(RTC_INT_SECOND);
/*等待上一次操作完成*/
rtc_lwoff_wait();
/*依据选择的时钟源来设置分频,使RTC周期为1s*/
#ifdef RTC_CLOCK_SOURCE_LXTAL
rtc_prescaler_set(32767);
#endif
#ifdef RTC_CLOCK_SOURCE_IRC40K
rtc_prescaler_set(40000);
#endif
#ifdef RTC_CLOCK_SOURCE_HXTAL_DIV_128
rtc_prescaler_set(HXTAL_VALUE/128);
#endif
/*等待上一次操作完成*/
rtc_lwoff_wait();
}

时钟源通过driver_rtc.h中的宏定义来选择:

C
#define RTC_CLOCK_SOURCE_LXTAL
//#define RTC_CLOCK_SOURCE_IRC40K
//#define RTC_CLOCK_SOURCE_HXTAL_DIV_128

9.4.2万年历实现

在bsp_rtc.c中定义了实现万年历的几个函数:rtc_time_set、rtc_time_display、is_leap_year等。

rtc_time_set函数——第一次需要手动设置当前时间:

C
uint32_t rtc_time_set(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second)
{
uint16_t t;
uint32_t seccount = 0;
if (bkp_read_data(BKP_DATA_0) != 0xA5A5)
{
rtc_configuration();

if(year < 1970 || year > 2099)
return 1;

for(t = 1970; t < year; t++){
if(is_leap_year(t)){
seccount += 31622400;
}else{
seccount += 31536000;
}
}
month -= 1;

for(t=0; t < month; t++){
seccount += (uint32_t)month_table[t] * 86400;
if(is_leap_year(year) && t==1){
seccount+=86400;
}
}
seccount += (uint32_t)(day-1) * 86400;
seccount += (uint32_t)hour * 3600;
seccount += (uint32_t)minute * 60;
seccount += second;
rtc_counter_set(seccount);
bkp_write_data(BKP_DATA_0, 0xA5A5);
return 0;
}
else
{
if (rcu_flag_get(RCU_FLAG_PORRST) != RESET){
printf("Power On Reset occurred....\r\n");
}else if (rcu_flag_get(RCU_FLAG_EPRST) != RESET){
printf("External Reset occurred....\r\n");
}
rcu_all_reset_flag_clear();
rcu_periph_clock_enable(RCU_PMU);
pmu_backup_write_enable();
rtc_register_sync_wait();
rtc_interrupt_enable(RTC_INT_SECOND);
rtc_lwoff_wait();
return 0;
}
}

该函数中在第一次上电运行时,会进行初始时间设置,然后写入特定数据到备份域数据寄存器中。当发生系统复位但Vbat未掉电的情况下,则不会重新进行时间设置,但需要重新开启秒时钟中断,因为SCIE处于VDD电源域而不在备份域。

rtc_time_display函数——打印实时时间:

C
void rtc_time_display(uint32_t timevar)
{
static uint16_t daycnt = 0;
uint32_t temp = 0;
uint16_t temp1 = 0;
temp = timevar / 86400;

if(daycnt != temp) {
daycnt = temp;
temp1 = 1970;

while(temp >= 365){
if(is_leap_year(temp1)){
if(temp >= 366)
temp-=366;
else
break;
}else
temp -= 365;
temp1++;
}

calendar.years = temp1;
temp1=0;

while(temp >= 28)
{
if(is_leap_year(calendar.years) && temp1 == 1){
if(temp >= 29)
temp -= 29;
else
break;
}else{
if(temp >= month_table[temp1])
temp -= month_table[temp1];
else
break;
}
temp1++;
}
calendar.months = temp1 + 1;
calendar.days = temp + 1;
}

temp = timevar % 86400;
calendar.hours = temp / 3600;
calendar.minutes = (temp % 3600) / 60;
calendar.seconds = (temp % 3600) % 60;

printf("Time: %0.4d-%0.2d-%0.2d,%0.2d:%0.2d:%0.2d\r\n", calendar.years, calendar.months, calendar.days, calendar.hours, calendar.minutes, calendar.seconds);
}

is_leap_year函数——判断当前年是否为闰年:

C
uint8_t is_leap_year(uint16_t year)
{
if((year%4 == 0 && year % 100 != 0) || (year % 400 == 0)){
return 1;
}else{
return 0;
}
}

9.4.3main函数和中断函数实现

以下为main函数代码:

C
int main(void)
{
delay_init();//delay函数初始化
bsp_uart_init(&BOARD_UART);//BOARD_UART串口初始化
nvic_irq_enable(RTC_IRQn,1,0);//打开RTC的NVIC
rtc_time_set(year_set,month_set,day_set,hour_set,minute_set,second_set); //设置当前时间
while (1){
/* 判断是否到1s了*/
if (timedisplay == 1){
/* 显示实时时间*/
rtc_time_display(rtc_counter_get());
timedisplay = 0;
}
}
}

本例程main函数首先进行了延时函数初始化,再初始化开发板USB串口,开启RTC的NVIC后设置了当前时间,在while(1)循环中等待RTC数据更新,然后将实时时间打印出来。

中断函数代码:

C
void RTC_IRQHandler(void)
{
if (rtc_flag_get(RTC_FLAG_SECOND) != RESET){
/* 清除RTC秒中断标志位*/
rtc_flag_clear(RTC_FLAG_SECOND);
timedisplay = 1;
/* 等待上一次操作完成 */
rtc_lwoff_wait();

}
}

9.5实验结果

为了验证万年历是否工作正常,设定初始时间为2022年12月31日23时59分50s,当程序开始运行时,每秒钟打印时间,经过10s后,可以看到时间变为2023年1月1日0时0分0秒,说明万年历有效。

wKgZomZiZXaAM8nXAAAfiD_RaEg908.png

教程GD32 MCU方案商聚沃科技原创发布,了解更多GD32 MCU教程,关注聚沃科技官网

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 单片机
    +关注

    关注

    6011

    文章

    44160

    浏览量

    624468
  • 开发板
    +关注

    关注

    25

    文章

    4611

    浏览量

    95136
  • 万年历
    +关注

    关注

    3

    文章

    185

    浏览量

    23742
  • RTC
    RTC
    +关注

    关注

    2

    文章

    494

    浏览量

    65641
收藏 人收藏

    评论

    相关推荐

    [分享]万年历算法

    万年历算法
    发表于 02-25 17:33

    电子万年历设计

    电子万年历设计
    发表于 08-20 22:46

    万年历

    如何在万年历加上闹钟
    发表于 09-24 15:29

    万年历

    万年历
    发表于 02-16 22:57

    万年历电路图

    万年历电路图万年历电路图
    发表于 08-05 14:59

    万年历

    本人刚做的万年历,有不妥之处望大师指教
    发表于 12-19 00:39

    谁有在开发板上用2.4寸彩屏显示万年历的完整程序

    谁有在开发板上用2.4寸彩屏显示万年历的完整程序
    发表于 04-25 14:14

    万年历

    万年历万年历万年历万年历
    发表于 03-20 21:08

    关于万年历的问题

    新手求个关于51单片机万年历的仿真及代码 谢谢大家
    发表于 03-31 21:32

    采用PCF8563的万年历

    万年历,都喜欢用DS1302。给大家一个采用PCF8563的万年历,1602液晶显示,三个按键分别是设置键、增加键,减小键。仿真和线路都经过调试,基本通过。提供大家参考。请多多指教。
    发表于 06-06 22:58

    万年历 仿真

    游戏 万年历
    发表于 07-08 11:19

    基于FPGA的verilog万年历程序_万年历设计_明德扬资料

    万年历工程说明在FPGA设计中,数字万年历属于小规模集成电路。从原理上来讲,是典型的数字电路,包括组合逻辑电路和时序电路。基于FPGA开发除设计简便、开发成本低、电路简洁等,更具备功能
    发表于 08-02 18:05

    STM32RTC万年历制作本设计

    STM32RTC万年历制作本设计是用STM32F103c8t6制作的简单万年历首先是配置RTC时钟然后是配置时钟,年月日等的处理头文件本设计
    发表于 08-12 06:26

    如何去实现一种基于STM32F103的可调时钟万年历

    如何用STM32F103内部RTC实现可调时钟万年历?如何去实现一种基于STM32F103的可调时钟万年历呢?
    发表于 11-22 06:44

    星空GD32F303开发板的相关资料下载

    一、开发板介绍星空GD开发板是由旗点科技推出的一款GD32开发板,板载
    发表于 12-10 08:27