STM32的RTC学习笔记
Mcu:STM32F103RBT6
1、RTC简介
RTC(Real Time Clock)实时时钟,是STM32片内的一个外设,这个外设使用起来跟普通定时器有一点区别,他是独立的一个定时器,并且能产生两个中断,秒中断和闹钟中断,他的时钟源可以由外部或内部驱动,由使用者选择,一些教程说RTC使用内部低速时钟(LSI)的时钟频率不准,可能跑久了以后就会出现误差。
2、RTC配置流程
1、使能RTC外设时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);//RTC时钟使能(电源)
RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);//RTC时钟使能(备份)
2、使能备份寄存器访问
PWR_BackupAccessCmd(ENABLE);//使能备份寄存器访问
开启后才能对备份寄存器进行访问,后期可以讲数据写进备份寄存器里,以防掉电数据丢失。
3、初始化备份寄存器
BKP_DeInit();//初始化备份配置
4、时钟源选择与使能
RCC_LSICmd(ENABLE);//使能内部低速时钟
while(RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == 0 );//内部低速时钟是否已开启
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);//配置RTC时钟为内部低速时钟
RCC_RTCCLKCmd(ENABLE);//使能RTC时钟
因为开发板原因,我用的是内部低速时钟,时钟频率为40K,先使能低速时钟源,等待使能完毕,然后再配置它为RTC的时钟源。
5、写RTC寄存器
RTC_WaitForSynchro();//等待寄存器校准
RTC_WaitForLastTask();//等待RTC寄存器写入完成(每次向RTC寄存器写入后都要调用这个函数)
RTC_ITConfig(RTC_IT_SEC,ENABLE);
RTC_WaitForLastTask();//等待RTC寄存器写入完成(每次向RTC寄存器写入后都要调用这个函数)
Rtc_Nvic_Config();
RTC_SetPrescaler(39999);//设置预分频系数
RTC_WaitForLastTask();//等待RTC寄存器写入完成(每次向RTC寄存器写入后都要调用这个函数)
RTC_SetCounter(hour*3600+min*60+sec);//设置当前时间,单位为秒
RTC_WaitForLastTask();//等待RTC寄存器写入完成(每次向RTC寄存器写入后都要调用这个函数)
<RTC_WaitForSynchro>等待寄存器校准函数是用于等待RTC的计数寄存器,闹钟寄存器和预分频寄存器进行同步;
<RTC_WaitForLastTask>函数则用于,每次向RTC写入数据时候,都要等待数据写入完成后再来进行下面操作;
配置RTC中断我是用于中断判断是否计数值溢出,计数值溢出则已经计了86400个数(一天86400秒),后清0;
预分频系数配置为1Hz,则计数器每一秒计一个数,而预分频系数值为n(输入的值)+1,所以40kHz要分配成1Hz要填入数值为39999;
设置当前时间也是设置当前的计数值,我只设置时间,没有日期。
6、中断优先级
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStruct.NVIC_IRQChannel = RTC_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
因为没有使用其他中断,所以中断优先级随意。
7、中断服务函数
uint16_t data = 31;(自己定义的全局变量)
void RTC_IRQHandler(void)
{
if(RTC_GetITStatus(RTC_IT_SEC) == 1)
{
RTC_ClearITPendingBit(RTC_IT_SEC);//清楚中断标志位
RTC_WaitForLastTask();//等待RTC寄存器写入完成(每次向RTC寄存器写入后都要调用这个函数)
if(RTC_GetCounter() == 86399)
{
data++;//每过一天,日期加1,一天为86400秒
RTC_SetCounter(0); //计数值一到64000就清零,为午夜12点
RTC_WaitForLastTask();//等待RTC寄存器写入完成(每次向RTC寄存器写入后都要调用这个函数)
}
}
}
7、LCD屏显示函数
void RtcTime_Display(void)
{
uint32_t Time_temp = 0;
static uint8_t Run_Ping = 0;//闰年为1,平年为0
static uint16_t hour = 0,min = 0,sec = 0,mon = 12,year = 20;
uint8_t Rtc_arr[20],Counter_arr[20],Rtc_date[20];
Time_temp = RTC_GetCounter();
//判断一年是闰年还是平年:
if(((year%4==0)&&(year%100!=0))||(year%400==0))Run_Ping = 1;
else Run_Ping = 0;
//判断一个月有30天还是31天:
if(((mon==1)||(mon==3)||(mon==5)||(mon==7)||(mon==8)||(mon==10)||(mon==12))&&(data>31))
{
data = 1;
mon++;
}
if(((mon==4)||(mon==6)||(mon==9)||(mon==11))&&(data>30))
{
data = 1;
mon++;
}
//特殊月份2月做特殊处理:
if((mon==2)&&(Run_Ping==1)&&(data>29))
{
data = 1;
mon++;
}
else if((mon==2)&&(Run_Ping==0)&&(data>28))
{
data = 1;
mon++;
}
//准备跨年:
if(mon>12)
{
mon = 1;
year++;
}
//时分秒:
hour = Time_temp/3600;
min = Time_temp%3600/60;
sec = Time_temp%3600%60;
//计数值打印:RTC_GetCounter
sprintf((char *)Counter_arr,"counter = %5d",Time_temp);
LCD_DisplayStringLine(Line6,Counter_arr);
//打印日期,顺便打印润平年:
if(Run_Ping==1)
{
sprintf((char *)Rtc_date," 20%2d-%2d-%2d Run",year,mon,data);
LCD_DisplayStringLine(Line3,Rtc_date);
}
else
{
sprintf((char *)Rtc_date," 20%2d-%2d-%2d Ping",year,mon,data);
LCD_DisplayStringLine(Line3,Rtc_date);
}
//打印时间:
sprintf((char *)Rtc_arr," %2d:%2d:%2d",hour,min,sec);
LCD_DisplayStringLine(Line4,Rtc_arr);
}
心得:
1、RTC在配置上跟普通定时器不一样,但是用法一样,用RTC做一个时钟和用一个通用定时器做一个时钟基本的逻辑是一样的,但在F429这种更高级一点的MCU里添加了月份星期的寄存器,使用起来可能更方便;
2、一定要初始化备份寄存器的配置,不然在后面的寄存器校准就会死循环卡死,因为它在一直等待校准完成,或者可以写一个延时退出;
3、每次向RTC的寄存器写入后都要等待写入完成,若不等待,有可能写入不进去,比如设置当前时间,就不是自己设置的当前时间开始跑了。
来源:CSDN
作者:飞天小白菜!
链接:https://blog.csdn.net/hjy457459/article/details/103970149