RTC的数据保存在后备域中,在主电源供电时,由VDD给RTC供电;当VDD掉电后,由备份域电源Vbat给RTC供电,在系统复位或者从待机模式唤醒后,RTC的设置会依然保持,时间继续走;当主电源VDD和Vbat都掉电时,备份域中的数据丢失。RTC的时钟源有LSE,HSE,LSI,当VDD掉电时,HSE和LSI都会有影响,所以一般选择LSE作为时钟源,另一个原因是精度比较高;LSI精度低,误差差不多1%。
stm32f030为最小化功耗,将预分频器氛围2个可编程预分频器,一个7位异步预分频,一个15位同步预分频,可将LSE时钟(刚好32768Hz = 2^ 15) 15分频得到,1hz的频率,即周期为1s的计数
(为降低功耗,建议将异步预分频值设置为最大值,即2^ 7 =128,将7位异步分频值设为128,15位同步分频值设为255)
f = 32.768/(127+1)/(255+1)=1Hz
typedef struct
{
u16 Year;
u8 Month;
u8 Day;
u8 Week;
u8 Hour;
u8 Minute;
u8 Second;
}_DateTime;
_DateTime SystemTime;
void RTC_Config(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE); //使能电源和后备接口时钟
PWR_BackupAccessCmd(ENABLE); //使能后备寄存器,以被允许访问RTC相关寄存器
if(RTC_ReadBackupRegister(RTC_BKP_DR0) != 0x5a5a)
{
RCC_LSEConfig(RCC_LSE_ON); //使能LSE
while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET);//等待LSE就绪
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);/ /外部低速时钟作为RTC时钟源,32.768MHz
SynchPrediv = 0xFF; //255
AsynchPrediv = 0x7F; //127 32.768/(255+1)/(127+1)=1Hz
RCC_RTCCLKCmd(ENABLE); //使能RTC时钟
RTC_WaitForSynchro(); //RTC区域时钟比APB时钟慢,访问前需要进行时钟同步,确保被读出来的RTC寄存器是正确的
RTC_InitStructure.RTC_AsynchPrediv = AsynchPrediv; //异步预分频值
RTC_InitStructure.RTC_SynchPrediv = SynchPrediv; //同步预分频值
RTC_InitStructure.RTC_HourFormat = RTC_HourFormat_24;
while(RTC_Init(&RTC_InitStructure) == ERROR);
SystemTime.Year = 2020;
SystemTime.Month = 11;
SystemTime.Day = 19;
SystemTime.Week = 4;
SystemTime.Hour = 13;
SystemTime.Minute = 0;
SystemTime.Second = 0;
RTC_SetRTCTime(&SystemTime);
DelayMs(50);
RTC_GetRTCTime( &SystemTime);
RTC_WriteBackupRegister(RTC_BKP_DR0, 0x5a5a);
}
}
* Note : BIN = BCD/10 *16+BCD%10,如果RTC输入事件格式为BCD,而在读取时间使用BIN,则会导致
* 时分秒跳跃增加(9:9(跳跃)10:16,11:17...18:24,19:25(跳跃)20:32),秒数最大会变为
* 89(59读出来为0x59=89)
*************************************************************************************/
u8 RTC_SetRTCTime(_DateTime *pDate)
{
RTC_DateTypeDef RTC_DateStructure;
RTC_TimeTypeDef RTC_TimeStructure;
RTC_DateStructure.RTC_Year = pDate->Year - 2000;
RTC_DateStructure.RTC_Month = pDate->Month;
RTC_DateStructure.RTC_Date = pDate->Day;
RTC_DateStructure.RTC_WeekDay = pDate->Week;
RTC_SetDate(RTC_Format_BIN, &RTC_DateStructure);
RTC_TimeStructure.RTC_H12 = RTC_H12_AM;
RTC_TimeStructure.RTC_Hours = pDate->Hour;
RTC_TimeStructure.RTC_Minutes = pDate->Minute;
RTC_TimeStructure.RTC_Seconds = pDate->Second;
RTC_SetTime(RTC_Format_BIN, &RTC_TimeStructure);
}
void RTC_GetRTCTime(_DateTime *DateStr)
{
RTC_DateTypeDef RTC_DateStructure;
RTC_TimeTypeDef RTC_TimeStructure;
memset(&RTC_DateStructure, 0, sizeof(RTC_DateStructure));
memset(&RTC_TimeStructure, 0, sizeof(RTC_TimeStructure));
RTC_GetDate(RTC_Format_BIN, &RTC_DateStructure);
RTC_GetTime(RTC_Format_BIN, &RTC_TimeStructure);
DateStr->Year = RTC_DateStructure.RTC_Year + 2000;
DateStr->Month = RTC_DateStructure.RTC_Month;
DateStr->Day = RTC_DateStructure.RTC_Date;
DateStr->Week = RTC_DateStructure.RTC_WeekDay;
DateStr->Hour = RTC_TimeStructure.RTC_Hours;
DateStr->Minute = RTC_TimeStructure.RTC_Minutes;
DateStr->Second = RTC_TimeStructure.RTC_Seconds;
time2Stamp(DateStr);
}
void time2Stamp(_DateTime *pDate)
{
u8 month;
u16 i;
u32 seccount = 0;
if((pDate->Year < 1970) || (pDate->Year > 2099)) return;
for(i = 1970; i < pDate->Year; i++) //把所有年份的秒钟相加
{
if(Is_Leap_Year(i))
{
seccount += 31622400; //闰年的秒钟数 366*24*60*60
}
else
{
seccount += 31536000; //平年的秒钟数 365*24*60*60
}
}
month = pDate->Month - 1;
for(i = 0; i < month; i++) //把前面月份的秒钟数相加
{
seccount += (u32)mon_table[i] * 86400; //月份秒钟数相加24*60*60
if(Is_Leap_Year(pDate->Year) && i == 1) seccount += 86400; //闰年2月份增加一天的秒钟数
}
seccount += (u32)(pDate->Day-1) * 86400; //把前面日期的秒钟数相加
seccount += (u32)pDate->Hour * 3600; //小时秒钟数
seccount += (u32)pDate->Minute * 60; //分钟秒钟数
seccount += pDate->Second; //最后的秒钟加上去
TimeBase = seccount - 8 * 60 * 60; //东8区
}
//判断是否是闰年 -----------------
u8 Is_Leap_Year(u16 year)
{
if(((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
{
return 1;
}
return 0;
}
【注】
在系统复位之后,RTC寄存器会通过清除PWR_CR中的DBP位被保护,防止意外写入,可以通过置位PWR_CR中的DBP位使能访问RTC寄存器
RTC初始化及配置
在RTC域复位之后,所有的RTC寄存器会进入写保护状态,通过在RTC_WPR寄存器以此写入0xCA,0x53解除写保护(如果写入错误键值,会重新激活写保护)。系统复位不会影响写保护,只有RTC后备域复位时才会。
1.置RTC_ISR(初始化状态寄存器)初始化状态寄存器的INIT为1进入初始化模式,在此模式下,日历计数器会停止,写入的值允许更新
2.等待大约2个RTCCLK时钟(用于时钟同步),检测RTC_ISP寄存器INITF位是否为1,判断是否成功进入初始化模式
3.在RTC_PREP寄存器中写入两个预分频值,给日历计数器产生1hz时钟
4.将时间和日期值载入影子寄存器(RTC_TR和RTC_DR),通过RTC_CR的FMT位配置12或24小时制
5.清零RTC_ISR寄存器的INIT位退出初始化模式,在4个RTC时钟后,写入的日历值会被自动载入
初始完成后,日历开始计数,在读取日历值时,应该先检测
RTC_ISR的RSF是否为1
【注】
-
当RTC_CR寄存器中BYPSHAD位为0时:
日历值从影子寄存器中获取,每两个RTCCLK日历寄存器的值(RTC_SSR亚秒、RTC_TR时间、RTC_DR日期)被复制到影子寄存器,影子寄存器每RTCCLK时钟更新一次。另外注意APB时钟频率必须大于或等于RTC时钟频率的7倍(同步问题),否则必须读两次日历时间和日期寄存器,如果两次读取结果相同,表示读取正确,否则就要读第3次。每次日历寄存器的值被复制到影子寄存器时,RTC_ISR的RSF位会被置位。为保证三个值的连续性,读RTC_SSR或者RTC_TR会锁住影子寄存器,这样日期寄存器就不会被刷新,直到RTC_DR也被读取。为防止程序在小于2个RTCCLK周期内读取日历,RTC_ISR的RSF位必须在第一次读日历时清零。并且在下次读取RTC_SSR、RTC_TR、RTC_DR寄存器时,必须先等到RTC_ISR的RSF置1。 -
当RTC_CR寄存器中BYPSHAD位为1时:
日历值直接从日历计数器获取,因此不需要等RTC_ISR的RSF被置位。由于当设备进入停止模式时,影子寄存器的值不会被更新,所以这种模式在退出低功耗模式(停止或待机)时特别适用。但是这种情况下,如果在两次读寄存器之间不在一个RTCCLK时钟内,不同寄存器的值可能会不连续,(比如2020-3-31 23:59:59,如果前一个RTCCLK读取RTC_TR,时间是23点59分59秒,下一个RTCCLK读取RTC_DR,这时候日历值已经刷新,日期是2020年4月1,现象就是程序读取时间前一秒是2020-3-31 23:59:58,后一秒是2020-4-1 23:59:59)。程序必须读两次所有的寄存器,然后比较结果来确认数据是不是连续且正确。
【注】
复位BYPSHAD位为0,日历影子寄存器(RTC_SSR、RTC_TR、RTC_DR)和状态寄存器RTC_ISR会在系统复位之后复位到默认值,其余的RTC寄存器只有在RTC后备域复位时才会恢复到默认值,不受系统复位影响。
应用
在1s定时中断中调用直接调用RTC_GetRTCTime(&SystemTime)即可实时获得时间,但是如果读取时间间隔很短,就要注意以上问题,避免出现时间读取出错。
配合GSM模组就可以在模组注册上网络之后,获取网络同步时间,并设置到RTC中RTC_SetRTCTime(&M26Time);