目的
虽然RTC简单,但是其牵扯的内容却蛮多的,例如时钟控制单元,电源控制,备份寄存器,最主要的目的还是想把下面3章引出来。
原理
RTC(Real_Time Clock)实时时钟,用于得到年、月、日、时、分、秒等时间日期信息。目前几乎已经是统一标准了,如图,32.768K经过15次分频后,恰好是1秒,其它时间只要在1秒频率下计数即可,RTC本质上就是一个1秒计数器。为了方便程序使用,内部会转化成年月日时分秒格式存储,并提供通信接口。
GD32内置RTC简化了上面的逻辑,直接使用一个32位计数器(2个16bit寄存器拼起来的)存储秒数,例如,1970年1月1号21点30分54秒=0x0+0x0+0x0+21x60x60+30×60+54=77454s,直接写入32位计数器中累加,只要读出该寄存器秒数,再反向运算也就知道日期时间了。
我们经常会遇到下面的需求,
- 希望当MCU复位、异常重启时,时间不会丢
- 希望MCU断电重启时,时间不会丢
- 希望当到达某个时间后,唤醒MCU,其它时间MCU能处于低功耗状态
为此,
1. GD32将RTC分成两部分,把内核部分(预分频器、分频器、计数器、闹钟)放在备份域(后面章节会详解介绍),达到复位重启不丢时间的目的,其它(APB1接口)放在VDD电源域(电源控制章节详细介绍)跟随系统复位初始化,如下1图
2. 增加电池,当VDD断电后,自动切换到电池供电(VBAT),达到MCU断电不丢时间的目的,如下2图
3. 支持各种闹钟,各种中断,直接挂在NVIC上,用于中断响应、唤醒等功能
功能设计
实现一个时钟,具有如下功能
- 每秒在串口输出当前时间,格式xxxx-xx-xx xx:xx:xx
- 首次上电时,需要配置时间,串口输入
- 复位,断电都不会丢失时间
代码如下:
-
判断是否首次上电,此处用到了备份域的知识,详细参考下面备份域章节
/* TRUE 第一次启动 */ BOOL DRV_POWER_IsFirstBoot(VOID) { if (POWER_FIRSTFLAG_VALUE != BKP_ReadBackupRegister(POWER_FIRSTFLAG_REG)) { RCC_APB1PeriphClock_Enable(RCC_APB1PERIPH_PWR | RCC_APB1PERIPH_BKP, ENABLE); PWR_BackupAccess_Enable(ENABLE); BKP_DeInit(); BKP_WriteBackupRegister(POWER_FIRSTFLAG_REG, POWER_FIRSTFLAG_VALUE); return TRUE; } else { return FALSE; } }
-
首次上电,需要初始化RTC,就三步
-
选择时钟源,如图有三个选择,代码配置的是第二路,
-
配置分频,最终得到1Hz的频率供RTC计数器使用
static RTC_FirstInit(VOID) { DRV_TRACE("First power on need to configure RTC."); /* 选择晶振LSE,低速外部晶振,即32.768Khz */ RCC_RTCCLKConfig(RCC_RTCCLKSOURCE_LSE); RCC_LSEConfig(RCC_LSE_EN); while (RCC_GetBitState(RCC_FLAG_LSESTB) == RESET) { } RCC_RTCCLK_Enable(ENABLE); RTC_WaitRSF(); RTC_WaitLWOFF(); /* 分频,即1Hz */ RTC_SetPrescaler(32768-1); /* 1s */ RTC_WaitLWOFF(); }
-
通过串口设置日期时间,并配置到RTC寄存器中
static time_t RTC_SetTime(VOID) { U32 year = 0xFF; U32 mon = 0xFF; U32 day = 0xFF; U32 hour = 0xFF; U32 min = 0xFF; U32 sec = 0xFF; struct tm t; memset(&t, 0, sizeof(t)); printf("Please Set Time:\r\n"); printf("Please input year:\r\n"); scanf("%u", &year); printf("year:%u\r\n", year); printf("Please input mon:\r\n"); scanf("%u", &mon); printf("mon:%u\r\n", mon); printf("Please input day:\r\n"); scanf("%u", &day); printf("day:%u\r\n", day); printf("Please input hour:\r\n"); scanf("%u", &hour); printf("hour:%u\r\n", hour); printf("Please input min:\r\n"); scanf("%u", &min); printf("min:%u\r\n", min); printf("Please input sec:\r\n"); scanf("%u", &sec); printf("sec:%u\r\n", sec); t.tm_sec = sec; t.tm_min = min; t.tm_hour = hour; t.tm_mday = day; t.tm_mon = mon-1; /* 0-11 */ t.tm_year = year-1900; /* 从1900年开始开始计算的 */ APP_DEBUG("%u-%u-%u %u:%u:%u", t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec); return(mktime(&t)); } VOID DRV_RTC_TimeAdjust(IN U32 sec) { RTC_WaitLWOFF(); RTC_SetCounter(sec); RTC_WaitLWOFF(); }
-
-
非首次上电,不需要重新配置RTC,只需要等待AHB接口时钟同步即可,因为RTC内核在备份域,AHB接口VDD供电,所以RTC内核配置不会断电,AHB接口需要同步
static VOID RTC_NotFristInit(VOID) { DRV_TRACE("Just need wait clock synchronized."); RTC_WaitRSF(); }
-
获取并显示时间
#define time DRV_RTC_GetTime time_t DRV_RTC_GetTime(time_t *timer) { U32 c = 0; c = RTC_GetCounter(); if (timer != NULL) { *timer = c; } return c; } VOID APP_RTC_Test(VOID) { BOOL firstBootFlag = FALSE; time_t now = 0; DRV_POWER_DumpBootReason(); firstBootFlag = DRV_POWER_IsFirstBoot(); DRV_RTC_Init(firstBootFlag); if (TRUE == firstBootFlag) { DRV_RTC_TimeAdjust(RTC_SetTime()); } while(1) { APP_Delay(1000); now = time(NULL); printf("%s", ctime(&now)); } }