关于RTC呢,其实他就是一个计数器,用一直不停的计数来记录时间的流逝。
在STM32F103中,RTC是挂载在APB1总线上的,但是这个APB1总线并不直接给RTC提供时钟源,它只是提供读写操作的,通过APB1接口可以访问RTC的相关寄存器(预分频值,计数器值,闹钟值)。而RTC的计数时钟源可以来自于以下三种
HSE时钟除以128
LSE振荡器时钟
LSI振荡器时钟
HSE来自于外部的晶振(4-16MHz),精度较高;
LSE为外部固定晶振产生(32.768KHz),一般用于RTC;
LSI为内部RC振荡器(40KHz)产生,被用于RTC时钟或者独立看门狗时钟IWDGCLK;
由于RTC是产生时钟的,也就是时间,一旦时间开启了,我们就希望它可以一直走下去,所以来说,我们单片机系统的复位并不会影响RTC的工作,而RTC要想复位,必须通过后备域,后备域我也不多说了,我也没查太多关于他的资料。
RTC工作框图
其中RTCCLK就是RTC的时钟源,RTC_DIV是我们设置的分频系数,就是我们把RTC的时钟源分频之后产生的时钟信号给了RTC_CNT计数器,RTC_PRL就是我们存储我们设置的分频系数的寄存器。注意我们经过分频器出来的时钟TR_CLK,它的频率最低是1Hz,也就是可以产生最长1秒的时基信号。
然后32为可编程计数器RTC_CNT就是我们记录时间流逝的计数器,来一个时钟,他就加1。然后RTC_ALR装载的就是我们设置的闹钟时间,当计数器中的数值和RTC_ALR中的数值相等的时候,就会引发闹钟中断,如果我们写了闹钟中断服务函数并且使能了闹钟中断,那么就会进入闹钟中断服务函数。再往右边看,就是3个中断
SECIE:秒中断
OWIE:溢出中断
ALRIE:闹钟中断
同时还有三个中断标志位
SECF:秒中断标志位
OWF:溢出中断标志位
ALRF:闹钟中断标志位
RTC内核完全独立于APB1接口,软件通过APB1接口对RTC相关寄存器访问。但是相关寄存器只在RTC APB1时钟进行重新同步的RTC时钟的上升沿被更新。所以软件必须先等待寄存器同步标志位(RTC_CRL的RSF位)被硬件置1才读。
BKP备份寄存器
备份寄存器是42个16位的寄存器。可用来存储84个字节数据。
它们处在备份区域,当VDD电源切断,仍然由VBAT维持供电。
当系统在待机模式下被唤醒,或者系统复位或者电源复位,它们也不会复位。
执行以下操作将使能对后备寄存器和RTC访问:
设置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能电源和后备时钟。
设置寄存器PWR_CR的DBP位,使能对RTC和后备寄存器的访问。
这个备份寄存器主要存储的时一些系统配置信息和一些中断标志位。当系统电源VDD被切断时,备份寄存器由备份电源供电。
关于RTC的各个寄存器我就不再一一列出来了,我们主要列出一些重要的库函数
RTC时钟源和时钟操作函数:
void RCC_RTCCLKConfig(uint32_t CLKSource);//时钟源选择
void RCC_RTCCLKCmd(FunctionalState NewState)//时钟使能
RTC配置函数(预分频,计数值:
void RTC_SetPrescaler(uint32_t PrescalerValue);//预分频配置:PRLH/PRLL
void RTC_SetCounter(uint32_t CounterValue);//设置计数器值:CNTH/CNTL
void RTC_SetAlarm(uint32_t AlarmValue);//闹钟设置:ALRH/ALRL
RTC中断设置函数:
void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);//CRH
RTC允许配置和退出配置函数:
void RTC_EnterConfigMode(void);//允许RTC配置 :CRL位 CNF
void RTC_ExitConfigMode(void);//退出配置模式:CRL位 CNF
同步函数:
void RTC_WaitForLastTask(void);//等待上次操作完成:CRL位RTOFF
void RTC_WaitForSynchro(void);//等待时钟同步:CRL位RSF
相关状态位获取清除函数:
FlagStatus RTC_GetFlagStatus(uint16_t RTC_FLAG);
void RTC_ClearFlag(uint16_t RTC_FLAG);
ITStatus RTC_GetITStatus(uint16_t RTC_IT);
void RTC_ClearITPendingBit(uint16_t RTC_IT);
其他相关函数(BKP等)
PWR_BackupAccessCmd();//BKP后备区域访问使能
RCC_APB1PeriphClockCmd();//使能PWR和BKP时钟
RCC_LSEConfig();//开启LSE,RTC选择LSE作为时钟源
PWR_BackupAccessCmd();//BKP后备区域访问使能
uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);//读BKP寄存器
void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);//写BKP
RTC配置一般步骤
1、使能PWR和BKP时钟:RCC_APB1PeriphClockCmd();
2、使能后备寄存器访问: PWR_BackupAccessCmd();
3、配置RTC时钟源,使能RTC时钟:
RCC_RTCCLKConfig();
RCC_RTCCLKCmd();
如果使用LSE,要打开LSE:RCC_LSEConfig(RCC_LSE_ON);
4、设置RTC预分频系数:RTC_SetPrescaler();
5、设置时间:RTC_SetCounter();
6、开启相关中断(如果需要):RTC_ITConfig();
7、编写中断服务函数:RTC_IRQHandler();
8、部分操作要等待写操作完成和同步。
RTC_WaitForLastTask();//等待最近一次对RTC寄存器的写操作完成
RTC_WaitForSynchro(); //等待RTC寄存器同步
部分代码
//实时时钟配置
//初始化RTC时钟,同时检测时钟是否工作正常
//BKP->DR1用于保存是否第一次配置的设置
//返回0:正常
//其他:错误代码
u8 RTC_Init(void)
{
//检查是不是第一次配置时钟
u8 temp=0;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟
PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问
if (BKP_ReadBackupRegister(BKP_DR1) != 0x5050) //从指定的后备寄存器中读出数据:读出了与写入的指定数据不相乎,其中0X5050这个数是随便写的,随便写一个数据都行,它只是起到一个标志位的作用,但是在这里设的数值要和一会下面设的数值保持一致
{
BKP_DeInit(); //复位备份区域
RCC_LSEConfig(RCC_LSE_ON); //设置外部低速晶振(LSE),使用外设低速晶振
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&&temp<250) //检查指定的RCC标志位设置与否,等待低速晶振就绪,但是也不是一直无限等待下去,设一个temp变量,让他隔段时间加1,当它加到250的时候,我们就不再等了
{
temp++;
delay_ms(10);
}
if(temp>=250)return 1;//初始化时钟失败,晶振有问题
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //设置RTC时钟(RTCCLK),选择LSE作为RTC时钟
RCC_RTCCLKCmd(ENABLE); //使能RTC时钟
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_WaitForSynchro(); //等待RTC寄存器同步
RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能RTC秒中断
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_EnterConfigMode();/// 允许配置
RTC_SetPrescaler(32767); //设置RTC预分频的值
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
RTC_Set(2018,8,31,16,28,50); //设置时间
RTC_ExitConfigMode(); //退出配置模式
BKP_WriteBackupRegister(BKP_DR1, 0X5050); //向指定的后备寄存器中写入用户程序数据
}
else//系统继续计时
{
RTC_WaitForSynchro(); //等待最近一次对RTC寄存器的写操作完成
RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能RTC秒中断
RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成
}
RTC_NVIC_Config();//RCT中断分组设置
RTC_Get();//更新时间
return 0; //ok
}
上段代码中我们就是大概看一下配置RTC的整体过程即可,里面调用的一些函数我并没有贴出来,比如说RTC_Set(2018,8,31,16,28,50);
和RTC_Get();
在这两个函数里面,我们主要是把计数器中的数值转变为我们能够直接看懂的时间值,时间起点是1970年1月1日0时。