首先是了解再干什么,重点就是看懂这个原理图
1.功能
是个独立的定时器,提供日历功能,超级独立:配置的参数存到后备存储区BKP,由独立的电源供电,即使系统复位或者掉电,都不会影响RTC的配置参数,并且想要修改他的参数,需要层层关卡,就是为了当成一个合格准确又严谨的日历,他甚至还使用了更为精确的外部时钟作为自己的时钟源。
2.原理和特征
关于特征直接罗列显得人很头大,但是先搞懂原理图就能较快的掌握特征了
总的来说是两大部分:APB1接口部分和RTC核心部分,我们需要重点理解的就是核心部分,核心部分又分成两部分,时钟的产生和中断的产生。设置了APB1接口应该是为了访问方便与保护。首先是时钟的产生即预分频模块:RTCCLK是时钟源:这就涉及到了STM32的时钟系统的复习(同一个电路,时钟频率越高功耗越大,同时抗电磁干扰能力越弱),来看看吧:
时钟模块
1.时钟有内部时钟和外部时钟,即RC振荡器和晶振
2.由振荡器或者晶振产生锁相环PLL作为系统时钟,最高达72MHz
3.系统时钟分频得到AHB,AHB再预分频得到低速总线APB1(最高36MHz)和高速总线APB2(最高72MHz),上面挂载的模块有区别的
4.RTC时钟的选择和产生:LSE(32.768kHz)、LSI(40kHz)、HSE/128
5.还能对外输出个时钟:MCO引脚
STM32中有5个时钟源:
1.HSI:高速内部时钟,由RC振荡器产生,频率为8MHz
2.HSE:高速外部时钟,由外部晶振产生,范围为4~16MHz,开发板上是8
3.LSI:低速内部时钟,RC振荡器产生,40kHz,独立看门口只能用LSI;LSI也可供给RTC用
4.LSE:低速外部时钟,由外部晶振产生,32.768kHz,是RTC的时钟源
5.PLL:锁相环倍频输出,其时钟输入源可选择为HSI/2,HSE或HSE/2,倍频选择2~16倍;最高为72MHz
A:对外输出时钟源,可选PLL/2,HSI,HSE,系统时钟
B: RTC时钟源,可选HSE/128,LSI,LSE
C:全速功能的USB模块,串口引擎需要一个48MHz时钟且只能从PLL输出端获取,可选择1.5分频或1分频。则使用USB模块是,PLL必须使能,并且时钟频率配置为48MHz或72MHz
D:系统时钟,可选PLL,HSI,HSE,最大为72MHz
E:指其他外设,最终来源系统时钟,经过AHB分频器分频后共其他模块使用
馋鬼步骤:
1.选择LSE作为时钟源,然后预分频,产生了TR_CLK,作为RTC的时钟。
2.接着配置了秒中断、溢出中断以及闹钟中断
在这两个过程我们能发现:
RTC时钟源有哪些呢?三种,HSE/128或者LSE或者LSI
预分频系数够大吗?影响着分频因子即最低时钟频率:这是20位的,因此分频系数最高达2的20次方
观察发现:APB1和RTC核心各自有复位系统,一个是连着系统的,一个是自带的存在BKP
三个可屏蔽中断:秒中断、溢出中断以及闹钟中断
2个分离时钟:
32位可编程计数器:计数器范围要足够大呢,能记录136年,够用了
3.又到了喜闻乐见的寄存器环节
1-时钟配置:RTCCLK / RTC_PRL = TR_CLK,配置PRL的值就能配置RTC的时钟频率,决定着最小指令周期时间;DIV是一个自减计数器,其最初的加载值是PRL,然后随着RTCCLK的指令周期一个一个减,减到0时,正好对外输出个TR_CLK,因为分频嘛就是有个PRL的倍数关系,输出TR_CLK后也刚好再把PRL加载到DIV中继续自减。DIV的每一步是根据RTCCLK的指令周期的,精度更高点,比如说,为40kHz,指令周期为1/40k = 25us,也就是DIV每减少1就是过了25us,若PRL取100,则TR_CLK的指令周期只有10025 = 2500us,显然分辨率第一点。当DIV的值为80,我们能知道过了(100-80) * 25us = 500us,提高了分辨率,如果此时还检测到TR_CLK已经过了2个指令周期,那么总走过的时间是:22500+25=5025us。
2-定时器中断:首先是一个秒中断:RTC定时器每走过1秒的时间就会触发秒中断;然后是溢出中断:当计数器RTC_CNT溢出并变回0时触发中断,RTC_CNT每过1秒才会增加1,并且是个32位的寄存器,存储范围很大的,另外是把1970年对应RTC_CNT为0,然后根据CNT当前的值就可以基于1970年这个时间远点推测当前是什么日期和时间,举个随意的值,比如此时RTC_CNT 被设置为1004556,说明距离1970年已经过了1004556秒,假设计算和后刚好是2017年5月20号13点14分25秒(这只是随口说的,并没有仔细去算,大概理解过程就行),这不就起到了日历的作用了吗。并且RTC_CNT这个最大能记录到136年后,足够用了。最后是闹钟中断:把RTC_ALR设置成某个数,当CNT=ALR时就会触发闹钟中断。
下面是仔细介绍寄存器
控制寄存器显然功能比较多:决定三个中断的开启;带有三个中断的标志位;带有RTC操作开启的标志,带有允许配置的标志,带有寄存器同步标志 —— 只有这三步都允许了才能对RTC参数进行修改即至少三道关卡;
分频是常规寄存器
CNT寄存器:就是用来计数,每过秒就增加1
闹钟寄存器:写入个值,当CNT等于这个值就触发闹钟中断而已
4.库函数
5.必会步骤呢:读取参数和写入参数的一般过程:需要打开很多开关哦
这是读取参数:RSF这一关
这是配置写入:RSF、CNF、RTOFF这三个标志位即三关
6.一般步骤
说白了RTC不过就是个定时器,不同之处在于对其参数配置时需要经过层层关卡(至少三关吧),至于日历功能,其实很简单,计数器CNT记录了多少秒,然后基于1970年对应CNT=0这个规定,进行推算,就能得到当前时间。
程序
static void RTC_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn; //RTC全局中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //中断优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能中断
NVIC_Init(&NVIC_InitStructure); //使能NVIC
}
u8 RTC_Init(void)
{
//检查是不是第一次配置RTC
u8 temp=0;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能时钟
PWR_BackupAccessCmd(ENABLE); //使能后备寄存器的访问即允许访问
if (BKP_ReadBackupRegister(BKP_DR1) != 0x5050) //读个值,0x5050是随便取得,一旦修改了0x5050就会进入RTC的配置过程呢
{
BKP_DeInit(); //复位备份区域,因为要重新设置了
RCC_LSEConfig(RCC_LSE_ON); //使用外部低速晶振LSE
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&&temp<250) //等待LSE就绪
{
temp++;
delay_ms(10);
}
if(temp>=250)return 1;//初始化失败,LSE有问题
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //选择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(2015,1,14,17,42,55); //设置CNT即当前时间
RTC_ExitConfigMode(); //退出配置模式
BKP_WriteBackupRegister(BKP_DR1, 0X5050); //向某个指定的后备寄存器中写入数据
}
else//系统继续计时即没有进入RTC配置,配置一次后即使系统复位也不会进入RTC配置,系统复位不会导致RTC复位的
{
RTC_WaitForSynchro(); //等待RTC寄存器同步
RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能秒中断
RTC_WaitForLastTask(); //等待最近一次对RTC的写操作结束,每写一次数据都要等待完成同步
}
RTC_NVIC_Config();//中断分组
RTC_Get();//更新时间
return 0; //ok
}
//RTC中断:三个中断公用这一个中断服务函数?
void RTC_IRQHandler(void)
{
if (RTC_GetITStatus(RTC_IT_SEC) != RESET)//秒中断
{
RTC_Get();//更新时间 :根据CNT的值推算出当前是什么时间
}
if(RTC_GetITStatus(RTC_IT_ALR)!= RESET)//闹钟中断
{
RTC_ClearITPendingBit(RTC_IT_ALR); //清楚闹钟中断标志
RTC_Get(); //更新时间
printf("Alarm Time:%d-%d-%d %d:%d:%d\n",calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec);//把闹钟时间打印出来
}
RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW); //清除秒中断和闹钟中断
RTC_WaitForLastTask();
}