基于ARM开发板从零开始学习STM32 08-RTC实时时钟
STM32 的 RTC 外设,实质是一个掉电后还继续运行的定时器。从定时器的角度来说,相对于通用定时器 TIM 外设,它十分简单,只有很纯粹的计时功能,当然,可以触发中断,但从掉电还继续运行的角度来说,它却是STM32中唯一一个具有如此强大功能的外设。所以 RTC 外设的复杂之处并不在于它的定时功能,而在于它掉电还继续运行的特性。
所谓掉电,是指主电源VDD断开的情况,为了RTC外设掉电继续运行,必须给 STM32 芯片通过VBAT引脚接上锂电池。当主电源VDD有效时,由VDD给RTC外设供电。当VDD掉电后,由VBAT给RTC外设供电。但无论由什么电源供电,RTC 中的数据都保存在属于RTC 的备份域中,若主电源VDD和VBAT都掉电,那么备份域中保存的所有数据将丢失。备份域除了RTC模块的寄存器,还有42个16位的寄存器可以在VDD掉电的情况下保存用户程序的数据,系统复位或电源复位时,这些数据也不会被复位。
从 RTC 的定时器特性来说,它是一个32位的计数器,只能向上计数。它使用的时钟源有三种,分别为高速外部时钟的128分频:HSE/128;低速内部时钟LSI;使HSE分频时钟或 LSI 的话,在主电源 VDD 掉电的情况下,这两个时钟来源都会受到影响,因此没法保证RTC 正常工作。因此RTC一般使用 低速外部时钟LSE,频率为实时时钟模块中常用的32.768KHz,这是因为 32768= 2^15,分频容易实现,所以它被广泛应用到RTC模块。在主电源VDD有效的情况下(待机),RTC还可以配置闹钟事件使STM32退出待机模式。
RTC架构
浅灰色的部分都是属于备份域的,在VDD掉电时可在VBAT的驱动下继续运行这部分仅包括 RTC 的分频器,计数器,和闹钟控制器。若VDD电源有效,RTC可以触发RTC_Second(秒中断)、RTC_Overflow(溢出事件)和RTC_Alarm(闹钟中断)。从结构图可以分析到,其中的定时器溢出事件无法被配置为中断。若STM32原本处于待机状态,可由闹钟事件或WKUP事件(外部唤醒事件,属于EXTI模块,不属于RTC)使它退出待机模式。闹钟事件是在计数器RTC_CNT 的值等于闹钟寄存器 RTC_ALR 的值时触发的。
由于RTC的寄存器是属于备份域,所以它的所有寄存器都是16位的。它的计数器 RTC_CNT的32位由 RTC_CNTL 和RTC_CNTH两个寄存器组成,分别保存计数值的低 16 位和高16位。我们配置RTC模块的时钟时,把输入的32768Hz 的 RTCCLK 进行 32768 分频得到实际驱动计数器的时钟TR_CLK =RTCCLK/32768= 1Hz,计时周期为1秒,计时器在TR_CLK 的驱动下计数,即每秒计数器RTC_CNT的值加1。
由于备份域的存在,使得 RTC 核具有了完全独立于 APB1 接口的特性,也因此对RTC 寄存器的访问要遵守一定的规则。系统复位后,禁止访问后备寄存器和RTC,防止对后备区域(BKP)的意外写操作。执行以下操作使能对后备寄存器和RTC的访问:
(1) 设置RCC_APB1ENR寄存器的PWREN和BKPEN位来使能电源和后备接口时钟。
(2) 设置PWR_CR寄存器的DBP位使能对后备寄存器和 RTC 的访问。
设置为可访问后,在第一次通过APB1接口访问RTC时,必须等待APB1与RTC外设同步,确保被读取出来的RTC寄存器值是正确的。若在同步之后,一直没有关闭APB1的RTC外设接口,就不需要再次同步了。
如果内核要对RTC寄存器进行任何的写操作,在内核发出写指令后,RTC模块在3个 RTCCLK 时钟之后,才开始正式的写RTC寄存器操作。我们知道RTCCLK的频率比内核主频低得多,所以必须要检查RTC关闭操作标志位RTOFF,当这个标志被置1时,写操作才正式完成。
下面是一个利用RTC秒计时并串口打印的例子。
RTC的参数配置
void RTCInit(void)
{
/* 使能 PWR 和 BKP 时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
/* 允许访问 BKP,备份域 */
PWR_BackupAccessCmd(ENABLE);
if(BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)
{
/* 使能低速外部时钟 LSE */
RCC_LSEConfig(RCC_LSE_ON);
/* 等待 LSE 起振稳定 */
while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)
{
}
/* 选择 LSE 作为 RTC 外设的时钟*/
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
/* 便能 RTC 时钟 */
RCC_RTCCLKCmd(ENABLE);
BKP_TamperPinCmd(DISABLE);
BKP_RTCOutputConfig(DISABLE);
/* 等待 RTC 寄存器与 APB1 同步*/
RTC_WaitForSynchro();
/* 等待对 RTC 的写操作完成*/
RTC_WaitForLastTask();
/*修改当前 RTC 计数寄存器内容 */
RTC_SetCounter(0);
BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);
}
}
(1) i f语句部分首先调用库函数 BKP_ReadBackupRegister()读取RTC备份域寄存器里面的值,看看备份寄存器里面的值是否正确— —在后面的代码中,若RTC配置成功,我们会向备份域寄存器写入数值0XA5A5,这个数值在VDD掉电后仍然保存,如果 VBAT 也掉电,那么备份域、RTC 所有寄存器将被复位,这时本寄存器的值将不等于 0XA5A5,RTC的计数器的值自然也是无效的。简言之,我们写入的这个数值用作标志RTC 是否从未被配置或配置是否已经失效,因此,其实我们可以写入任何数值到任何一个备份域寄存器,只要检查的时候与此匹配就行了,所以读者如果想修改时间的话,可以把 VDD 、VBAT 掉电,或在代码上修改这个检查值。
(2) 如果 RTC 从未被配置或配置已经失效(备份域寄存器写入数值不等于 0XA5A5)这两种情况有任何一种发生的话,则调用用户函数RTC_Configuration()来初始化RTC,配置RTC外设的控制参数,时钟分频等,并往电脑的超级终端打印出相应的调试信息。
通过串口测试打印出秒数据:
void RTCTest(void)
{
unsigned long RTCCounterValue;
while(1)
{
RTCCounterValue = RTC_GetCounter();
printf("已过去%d秒\r\n",RTCCounterValue);
Delay();
}
}
以上工程代码已上传至我的网盘,有需要的可以自行下载,重要的是分析理解硬件整个工作过程以及代码思想。
链接:https://pan.baidu.com/s/15X2uQAGZlp3U2MX_Vxpntw
提取码:s7p8 。
下一篇: 在SRAM中调试程序