RTC (Real Time Clock)即实时时钟。它可以提供时钟、日历的功能;并且可以使用外部电池供电,在极低的功耗下保持计数,使得断电之后还能够计算时间,所以名为实时时钟。
STM32f103的RTC,本质上是一个32位的计数器,在断电后,由电池供电还能保持计数;要使用时间时,需要将计数器的值换算成日期和时间。
此外,stm32的RTC还具备后备存储区,可以利用电池供电,保存10个16bit的数据。
我们在之前的串口中断工程上修改,以便于打印数值查看。
使用cubemx打开串口中断的工程,然后另存为RTC工程,增加如下设置,启用RTC,勾选时钟源、日历、以及RTC OUT:
(注意这里选择RTC OUT输出时,固定是PC13引脚,如果我们之前的工程中设置了PC13引脚,要先设为reset模式,再选RTC OUT)
设置RTC的初始时间:
接着,设置RTC的时钟,切换到clock configuration 选项卡,如下设置,选择外部的低速时钟:
这里选择外部时钟是因为,内部的RC振荡器精度较低,计时不准确;而如果使用主时钟(高速时钟)分频后作为时钟源功耗较大;一般RTC要求掉电还能运行,需要低功耗。
设置完成后,就可以生成工程代码了。
Keil中打开工程,在rtc.c文件中,我们可以看到RTC的初始时间是通过如下方式设置的,
有需要的时候,我们可以仿照这部分代码重新设置时间:
在main.c中添加如下代码,然后编译、下载,运行结果可见右下角的串口打印信息:
这个程序先定义了两个结构体,用来作为保存日期和时间的临时变量;然后通过重定向后的串口打印输出。每秒钟打印一次,可以看到输出时间和日期能自动进位、更新。
通过上面的配置,我们已经可以设置RTC开始运行,但是,这个程序还很不实用;每次掉电、上电重启都会重新设置为初始时间;这完全不能发挥RTC的特点。
要想RTC在掉电后仍能继续计时、再次上电后还能保持准确的时间,需要软硬件两方面的支持。
首先,硬件上,VBAT引脚上必须连接电池,一般我们使用3V的纽扣电池,以使得断电后电池能为RTC供电,继续计时。
其次,软件上,需要修改HAL库代码,这里详细说说软件改写这一步。
Cubemx(5.10)生成的RTC HAL库代码,有个明显的缺陷,主要体现在日期的存储和时间的存储是分开的;日期的存储是在一个普通的RAM变量(就是HAL库中DateToUpdate这个全局变量)中,它每次在累计到满一天时,就会将日期存储(DateToUpdate)变量加1,然后将rtc的计数器清0;这样使用在不掉电时是没有什么问题的,但是一旦掉电,存储日期的变量(DateToUpdate)不能保持,会被置为初始值,而时间每次都是从rtc计数器中读取,这就导致掉电重启后,日期变成了2000/01/01,而时间还在正常继续累加。
正因为这个每次日期进位的清rtc计数器操作,使得原本连续的rtc计数变得不连续,因而仅仅从rtc计数器中无法恢复时间。
有些网友的办法是,将日期值存在后备存储器中,这样掉电后再读取时可以恢复,但是如果掉电时间超过了一天,读回的日期会是前一天存储的,也不能保证完全正确。
比较彻底的解决办法是不用HAL库的时间设置和时间获取函数,把这套函数完全重写。主要的两个函数如下,就是设置时间和获取时间的函数,把日期和时间看作一个整体。
设置时间时,由日期和时间换算成秒,再写入rtc计数器:
获取时间时,读取rtc计数器,再由秒数换算成年月日时分秒:
实际上,rtc计数器有32位,每秒增1的话可以计时130多年不溢出,足够一般的使用了。
另外,还需要在MX_RTC_Init()函数中添加代码,如下:
这一段是读取后备存储区中的内容,然后对比是否与特定的RTC_unique_ID一致,如果不一致,则设置一个当前的时间初值,并将RTC_unique_ID写入后备存储区;如果一致,则说明初值已经被设置过,直接返回,不再执行HAL库函数中设置RTC时间的操作。
这样就设置完了,之后可以调用获取时间的函数了:
运行结果如下,可以看到中间断电了26分钟,有电池为VBAT供电,再次给系统加电时,时间和日期都能正确获取。
好了,这一节的内容基本讲完了,主要讲了RTC相关的知识,要想掉电后还能正确恢复时间,HAL库函数有缺陷,需要大改;此外,RTC的后备存储器,可以利用电池供电存储一些掉电需要保存的数据。