实战:RTC
简介:
实时时钟是一个独立的定时器。 RTC模块拥有一组连续计数的计数器,在相应软件配置下,可
提供时钟日历的功能。修改计数器的值可以重新设置系统当前的时间和日期。
RTC模块和时钟配置系统(RCC_BDCR寄存器)处于后备区域,即在系统复位或从待机模式唤醒
后, RTC的设置和时间维持不变。
系统复位后,对后备寄存器和RTC的访问被禁止,这是为了防止对后备区域(BKP)的意外写操
作。执行以下操作将使能对后备寄存器和RTC的访问:
● 设置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能电源和后备接口时钟
● 设置寄存器PWR_CR的DBP位,使能对后备寄存器和RTC
主要特性
● 可编程的预分频系数:分频系数最高为220。
● 32位的可编程计数器,可用于较长时间段的测量。
● 2个分离的时钟:用于APB1接口的PCLK1和RTC时钟(RTC时钟的频率必须小于PCLK1时钟频率的四分之一以上)。
● 可以选择以下三种RTC的时钟源:
─ HSE时钟除以128;
─ LSE振荡器时钟;
─ LSI振荡器时钟
● 2个独立的复位类型:
─ APB1接口由系统复位;
─ RTC核心(预分频器、闹钟、计数器和分频器)只能由后备域复位(详见6.1.3节)。
● 3个专门的可屏蔽中断:
─ 闹钟中断,用来产生一个软件可编程的闹钟中断。
─ 秒中断,用来产生一个可编程的周期性中断信号(最长可达1秒)。
─ 溢出中断,指示内部可编程计数器溢出并回转为0
功能概述:
RTC由两个主要部分组成(参见下图)。第一部分(APB1接口)用来和APB1总线相连。此单元还包
含一组16位寄存器,可通过APB1总线对其进行读写操作。 APB1接口由APB1总线时钟驱动。
另一部分(RTC核心)由一组可编程计数器组成,分成两个主要模块。第一个模块是RTC的预分频
模块,它可编程产生最长为1秒的RTC时间基准TR_CLK。 RTC的预分频模块包含了一个20位的
可编程分频器(RTC预分频器)。如果在RTC_CR寄存器中设置了相应的允许位,则在每个
TR_CLK周期中RTC产生一个中断(秒中断)。第二个模块是一个32位的可编程计数器,可被初始
化为当前的系统时间。系统时间按TR_CLK周期累加并与存储在RTC_ALR寄存器中的可编程时
间相比较,如果RTC_CR控制寄存器中设置了相应允许位,比较匹配时将产生一个闹钟中断。
读RTC寄存器
RTC核完全独立于RTC APB1接口。
软件通过APB1接口访问RTC的预分频值、 计数器值和闹钟值。但是,相关的可读寄存器只在与
RTC APB1时钟进行重新同步的RTC时钟的上升沿被更新。 RTC标志也是如此的。
这意味着,如果APB1接口曾经被关闭,而读操作又是在刚刚重新开启APB1之后,则在第一次
的内部寄存器更新之前,从APB1上读出的RTC寄存器数值可能被破坏了(通常读到0)。下述几种
情况下能够发生这种情形:
● 发生系统复位或电源复位
● 系统刚从待机模式唤醒
● 系统刚从停机模式唤醒
所有以上情况中, APB1接口被禁止时(复位、无时钟或断电)RTC核仍保持运行状态。
因此,若在读取RTC寄存器时, RTC的APB1接口曾经处于禁止状态,则软件首先必须等待
RTC_CRL寄存器中的RSF位(寄存器同步标志)被硬件置’1’。
注: RTC 的APB1 接口不受WFI 和WFE 等低功耗模式的影响。
RTC配置过程:
配置过程:
1. 查询RTOFF位,直到RTOFF的值变为’1’
2. 置CNF值为1,进入配置模式
3. 对一个或多个RTC寄存器进行写操作
4. 清除CNF标志位,退出配置模式
5. 查询RTOFF,直至RTOFF位变为’1’以确认写操作已经完成。
仅当CNF标志位被清除时,写操作才能进行,这个过程至少需要3个RTCCLK周期。
RTC几个主要配置寄存器
RTC 预分频器余数寄存器,该寄存器也有2个寄存器组成RTC_DIVH和RTC_DIVL,这两个寄存
器的作用就是用来获得比秒钟更为准确的时钟,比如可以得到0.1秒,或者0.01秒等。该寄存
器的值自减的,用于保存还需要多少时钟周期获得一个秒信号。在一次秒钟更新后,由硬件重新
装载。这两个寄存器和RTC预分频装载寄存器的各位是一样的,
RTC计数器寄存器 (RTC_CNTH / RTC_CNTL):
RTC核有一个32位可编程的计数器,可通过两个16位的寄存器访问。计数器以预分频器产生的
TR_CLK时间基准为参考进行计数。 RTC_CNT寄存器用来存放计数器的计数值。他们受RTC_CR
的位RTOFF写保护,仅当RTOFF值为’1’ 时,允许写操作在高或寄存器(RTC_CNTH或RTC_CNTL)
上的写操作,能够直接装载到相应的可编程计数器,并且重新装载RTC预分频器。当进行读操作时,
直接返回计数器内的计数值(系统时间)。
RTC闹钟寄存器(RTC_ALRH/RTC_ALRL)
当可编程计数器的值与RTC_ALR中的32位值相等时,即触发一个闹钟事件,并且产生RTC闹钟
中断。此寄存器受RTC_CR寄存器里的RTOFF位写保护,仅当RTOFF值为’1’时,允许写操作。
备份寄存器是
42 个 16 位的寄存器(
Mini 开发板就是大容量的),可用来存储 84 个字节的
用户应用程序数据。他们处在备份域里,当 VDD 电源被切断,他们仍然由 VBAT 维持供电。
即使系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位。
BKP简介
备份寄存器是42个16位的寄存器,可用来存储84个字节的用户应用程序数据。他们处在备份域
里,当VDD电源被切断,他们仍然由VBAT维持供电。当系统在待机模式下被唤醒,或系统复位或
电源复位时,他们也不会被复位。
此外, BKP控制寄存器用来管理侵入检测和RTC校准功能。
复位后,对备份寄存器和RTC的访问被禁止,并且备份域被保护以防止可能存在的意外的写操
作。执行以下操作可以使能对备份寄存器和RTC的访问。
● 通过设置寄存器RCC_APB1ENR的PWREN和BKPEN位来打开电源和后备接口的时钟
● 电源控制寄存器(PWR_CR)的DBP位来使能对后备寄存器和RTC
RTC 正常工作的一般配置步骤如下:
1) 使能电源时钟和备份区域时钟。
寄存器:RCC->APB1ENR|=1<<28;
//使能电源时
RCC->APB1ENR|=1<<27; //使能备份时钟
库函数:RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR
| RCC_APB1Periph_BKP, ENABLE);
2) 取消备份区写保护。
寄存器:PWR->CR|=1<<8; //取消备份区写保护
库函数:PWR_BackupAccessCmd(ENABLE);
//使能
RTC 和后备寄存器访问
3) 复位备份区域,开启外部低速振荡器。
寄存器:RCC->BDCR|=1<<16; //备份区域软复位(复位整个备份域)
RCC->BDCR&=~(1<<16); //备份区域软复位结束
RCC->BDCR|=1<<0;
//开启外部低速振荡器
库函数:BKP_DeInit(); //复位备份区域
RCC_LSEConfig(RCC_LSE_ON);// 开启外部低速振荡器
4)选择 RTC 时钟,并使能。
寄存器:RCC->BDCR|=1<<8; //LSI作为RTC时钟
RCC->BDCR|=1<<15;//RTC时钟使能
库函数:RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
//选择
LSE 作为
RTC 时钟
RCC_RTCCLKCmd(ENABLE); //使能 RTC 时钟
5)设置 RTC的分频,以及配置RTC时钟。
寄存器:RTC->CRL|=1<<4; //允许配置
RTC->RLH=0X0000;
RTC->RLL=32767; //时钟周期设置(有待观察,看是否跑慢了?)理论值:32767
库函数:RTC_EnterConfigMode(); //允许配置
RTC_ExitConfigMode(); //退出配置模式,更新配置
void RTC_SetPrescaler(uint32_t
PrescalerValue);// RTC时钟的分频数
6)更新配置,设置RTC中断。
寄存器:R RTC->CRL&=~(1<<4); //配置更新
while(!(RTC->CRL&(1<<5))); //等待RTC寄存器操作完成 BKP->DR1=0X5050; //写入 0X5050 代表我们已经初始化过时钟
MY_NVIC_Init(0,0,RTC_IRQn,2);//优先级设置
库函数:RTC_ExitConfigMode(); //退出配置模式,更新配置
BKP_WriteBackupRegister(BKP_DR1,
0X5050); //写入
0X5050 代表我们已经初始化过时钟了
RTC_NVIC_Config(); //RCT中断分组设置
7)编写中断服务函数。
RTC初始化的过程设置还是比较麻烦的,每次对RTC寄存器的写操作后都要等待写操作完成,在向
RTC_CNT,RTC_ALR,RTC_PRL写入数据的时候还要允许配置,配置完后还要退出配置,在读取秒钟
数之前还要等待RTC寄存器同步,要注意的细节比较多。
下面是时间设置程序,这个程序是LINUX的源码移植过来的,算法简直超神了,不用考虑闰年,月份差距的事,就几行搞定。如果坛友有需求我可以发出来,有带解析,如果没有解析我估计没几个人看得懂呀
[mw_shl_code=c,true]u8 RTC_Set(unsigned int year, unsigned int mon,
unsigned int day, unsigned int hour,
unsigned int min, unsigned int sec)
{[/mw_shl_code]
[mw_shl_code=c,true] if(year<1970||year>2099)return 1;
if (0 >= (int) (mon -= 2))
{ /**//* 1..12 -> 11,12,1..10 */
mon += 12; /**//* Puts Feb last since it has leap day */
year -= 1;
}
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟
PWR_BackupAccessCmd(ENABLE); //使能RTC和后备寄存器访问
RTC_SetCounter((((//设置RTC计数器的值
(unsigned long) (year/4 - year/100 + year/400 + 367*mon/12 + day) +
year*365 - 719499
)*24 + hour /**//* now have hours */
)*60 + min /**//* now have minutes */
)*60 + sec); /**//* finally seconds */
return 0;
}
[/mw_shl_code]
获取时间和周期的代码和原子哥的没什么差别就不贴了,LCD显示部分自己写了个彩笔,加了个艺术字的字体上去,下面是效果图
|