首页 > 其他分享 >【GD32F303红枫派使用手册】第九节 RTC-万年历实验

【GD32F303红枫派使用手册】第九节 RTC-万年历实验

时间:2024-06-07 13:58:10浏览次数:29  
标签:RTC rtc RCU 第九节 红枫 使用手册 temp 备份 时钟

9.1 实验内容

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

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

9.2 实验原理

9.2.1 RTC简介

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

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

以下为GD32F303的RTC框图:

图中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.2 RTC复位

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

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

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

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

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

9.2.3 RTC实现万年历

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

9.3 硬件设计

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

9.4 代码解析

9.4.1 RTC配置

在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.3 main函数和中断函数实现

以下为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秒,说明万年历有效。

由聚沃科技原创,来源于   【红枫派开发板】第九讲 RTC-万年历实验 - 苏州聚沃电子科技有限公司 (gd32bbs.com)

GD32MCU技术交流群:859440462   

标签:RTC,rtc,RCU,第九节,红枫,使用手册,temp,备份,时钟
From: https://blog.csdn.net/weixin_45254847/article/details/139520157

相关文章

  • 【GD32F303红枫派使用手册】第六节 PMU-低功耗实验
    6.1实验内容通过本实验主要学习以下内容:PMU原理;低功耗的进入以及退出操作;6.2实验原理6.2.1PMU结构原理PMU即电源管理单元,其内部结构下图所示,由该图可知,GD32F303系列MCU具有三个电源域,包括VDD/VDDA电源域、1.2V电源域以及电池备份域,其中,VDD/VDDA域由电源直接供电。在......
  • 【GD32F303红枫派使用手册】第二节 GPIO-流水灯实验
    2.1实验内容通过本实验主要学习以下内容:GPIO结构及原理;GPIO输出功能实现;LED驱动原理。2.2实验原理2.2.1GPIO外设原理GD32F303系列MCU最多可支持112个通用I/O引脚(GPIO),分别为PA0~PA15,PB0~PB15,PC0~PC15,PD0~PD15,PE0~PE15,PF0~PF15和PG0~PG15,各......
  • redis 安装、使用手册
    Linux系统Redis使用手册一、引言Redis是一个开源的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息代理。由于其出色的性能和灵活性,Redis在开发社区中广受欢迎。本手册将详细介绍Redis在Linux系统中的安装、每种数据结构的命令使用以及每种数据类型的应用场景。二......
  • STM32 F1系列 全中文HAL&LL库使用手册 中英双语对照 GPT机翻 共1208页、约40万字
    STM32F1系列全中文HAL_LL库使用手册,中英文双语对照阅读。内容、格式对照官方原文,含标签导航及目录跳转。全文GPT机翻,除人工翻译外,相对更加贴合原文原意,双语版防止翻译错误方便对照。全文:1208页,约40万字。*******下有更多展示图片********由于本汉化不改变官方文档的内容......
  • 中移ML307R模块使用手册
     模块链接:https://item.taobao.com/item.htm?ft=t&id=795927858234实物图  模块信息1.模块型号:  中移ML307R-DL2.频段信息:    LTE-FDDB1/B3/B5/B8(全网通)   LTE-TDDB34/B38/B39/B40/B413.网络协议:    IPv4/IPv6/PING/NTP/DNS/TCP/UDP/HT......
  • 2022年windows的Visual Studio常用插件及使用手册
    前景提要ViusualStudio是一款很好用的C/C++集成开发工具,具有强大的扩展功能,好用的插件,但是,很多人都是只写了有什么插件,但是,没写怎么使用这种插件,使得使用的时候很是不方便,所以,笔者最近本着自己的学习,在这里写下自己关于好用的插件的研究,希望对您的学习/工作有帮助.......
  • pipenv-基本使用手册 解决python包版本冲突
    https://pipenv.pypa.io/python使用pip安装包,默认都是在全局包,当A项目使用openai0.29,B项目使用openai1.10,这个时候,就会出现两个项目只能运行一个的情况。如果安装1.10,会把原来0.29的版本更新掉,导致原来A项目就运行不了。刚接触python,很好奇为啥没有像npm一样的......
  • MySQL官方使用手册(自译)
    Chatper1OverviewOfMySQLDBMSwhatisMySQLMySQL,themostpopularOpenSourceSQLdatabasemanagementsystem,isdeveloped,distributed,andsupportedbyOracleCorporation.TheMySQLwebsite(http://www.mysql.com/)providesthelatestinformationa......
  • 三星刷机 线刷 刷公开版/国行和港版互刷 (三星使用手册)
    原文1:https://www.coolapk.com/feed/30245770?shareKey=YjI0YjZmOTc1OTY2NjJhYmNmYjQ~&shareUid=960037&shareFrom=com.coolapk.market_12.3.1原文2:https://www.coolapk.com/feed/48366338?shareKey=ODg3ZDg1YTE1OGMzNjVlZWQ3ZDU~&shareUid=960037&shareFrom=c......
  • STM32第九节(中级篇):RCC(第二节)——讲解系统时钟配置函数SetSysClockTo72
    目录前言STM32第九节(中级篇):RCC(第二节)——讲解系统时钟配置函数SetSysClockTo72代码内容位置及检索分析代码 代码展示时钟控制使能闪存控制寄存器配置AHP,APB1,APB2的总线时钟配置锁相环时钟 超频操作小结前言    上节课我们讲了理论部分,那么我们这节课......