江协科技STM32 12-2 BKP备份寄存器&RTC实时时钟

这一节我们要讲的主要内容是RTC实时时钟,实时时钟本质上是一个定时器,但是这个定时器是专门用来产生年月日时分秒,这种日期和时间信息的。所以学会了STM32的RTC就可以在STM32内部拥有一个独立运行的钟表。想要记录或读取日期和时间,就可以通过操作RTC来实现。

RTC这个外设比较特殊,它和备份寄存器(BKP)、电源控制(PWR)这两章关联性比较强,因此在RTC这一章中,就把BKP和RTC放在一起讲。

我们首先就先介绍备份寄存器(BKP),其实备份寄存器(BKP)和SPI章节中学过的Flash存储器类似,都是用来存储数据的,只是Flash的数据是真正的掉电不丢失;而BKP的数据是需要VBAT引脚接上备用电池来维持的,只要VBAT有电池供电,即使STM32主电源断电,BKP的值也可以维持原状。

首先我们来看一下时间戳的知识点。Unix 时间戳(Unix Timestamp)定义为从UTC/GMT的1970年1月1日0时0分0秒开始到现在所经过的秒数,不考虑闰秒。在计算机的底层我们使用秒计数器来计时,需要给人类观看时,就转换为年月日时分秒这样的格式。时间戳存储在一个秒计数器中,秒计数器为32位/64位的整型变量,计算机为了存储这样一个永不进位的秒数,这个数据变量类型还是要定义大一些,这个变量类型在不同系统中定义是不一样的。我们本节STM32中的RTC,其核心的计时部分是一个32位的可编程计数器,说明我们这款STM32,它的时间戳是32位的数据类型。世界上所有时区的秒计数器相同,不同时区通过添加偏移来得到当地时间。

GMT(Greenwich Mean Time)格林尼治标准时间是一种以地球自转为基础的时间计量系统。它将地球自转一周的时间间隔等分为24小时,以此确定计时标准。

UTC(Universal Time Coordinated)协调世界时是一种以原子钟为基础的时间计量系统。它规定铯133原子基态的两个超精细能级间在零磁场下跃迁辐射9,192,631,770周所持续的时间为1秒。当原子钟计时一天的时间与地球自转一周的时间相差超过0.9秒时,UTC会执行闰秒来保证其计时与地球自转的协调一致

接下来我们学习时间戳中秒计数器和日期时间如何进行相互转换,这时候我们需要用到time.h模块,C语言的time.h模块提供了时间获取和时间戳转换的相关函数,可以方便地进行秒计数器、日期时间和字符串之间的转换,在time.h里主要有表格内的这些主要函数

时间戳转换关系如下图所示

接下来我们学习BKP和RTC的外设部分,我们首先学习BKP的相关知识点。BKP全称Backup Registers,翻译过来就是备份寄存器,BKP的用途就是可用于存储用户应用程序数据。其特性就是当VDD(2.0~3.6V)电源被切断,他们仍然由VBAT(1.8~3.6V)维持供电。如果VDD断电,VBAT也没电,那BKP里的数据就会清零,因为BKP本质上是RAM存储器,没有掉电不丢失的能力。当系统在待机模式下被唤醒,或系统复位或电源复位时,他们也不会被复位;TAMPER引脚产生的侵入事件将所有备份寄存器内容清除,TEMPER是一个接到STM32外部的引脚,其位置就是VBAT旁边的2号引脚,其与PC13、RTC共用,这个TAMPER引脚是一个安全保障设计,比如如果做一个安全系数非常高的设备,设备需要有防拆功能,BKP里也存储了一些敏感数据,那就可以使能这个TEMPER引脚的侵入检测功能;设计者把下面两个RTC功能也放在了BKP中: 1.引脚输出RTC校准时钟、RTC闹钟脉冲或者秒脉冲,RTC的引脚也是在PC13这个位置 2.存储RTC时钟校准寄存器。最后看一下BKP中,用户数据的存储容量,在中容量和小容量设备里,BKP是20个字节,在大容量和互联型设备里,BKP是84个字节。可以看出来BKP的容量其实非常小,一般只能用来存储少量的参数。BKP的简介我们就介绍到这里。

下面看一下BKP的基本结构,图中橙色部分我们可以叫做后备区域,BKP处于后备区域,但后备区域不只有BKP,还有RTC的相关电路。STM32后备区域的特性就是当VDD主电源掉电时,后备区域仍然可以由VBAT的备用电池供电,当VDD主电源上电时,后备区域供电会由VBAT切换到VDD,也就是主电源有电时,VBAT不会用到,这样可以节省电池电量。BKP是位于后备区域的,BKP里主要有数据寄存器、控制寄存器、状态寄存器和RTC时钟校准寄存器。其中数据寄存器是主要部分,用来存储数据,每个数据寄存器都是16位的,也就是一个数据寄存器可以存2个字节,对于中容量和小容量的设备,里面有DR1、DR2一直到DR10,总共10个数据寄存器,那一个寄存器两个字节,所以容量是20个字节。对于大容量和互联型设备,里面除了DR1到DR10还有DR11、DR12一直到DR42,总共42个数据寄存器,容量是84个字节。侵入检测可以从PC13位置的TAMPER引脚引入一个检测信号,当TAMPER产生上升沿或者下降沿时,清除BKP所有的内容,以保证安全,时钟输出可以把RTC的相关时钟从PC13位置的RTC引脚输出出去,供外部使用。其中,输出校准时钟时,再配合校准寄存器,可以对RTC的误差进行校准。以上就是BKP外设的结构和功能。

接下来我们就继续学习以下RTC外设,RTC英文全称 Real Time Clock 中文翻译为实时时钟,在STM32中,RTC是一个独立的定时器,可为系统提供时钟和日历的功能,RTC实时时钟,一般就指提供年月日时分秒这种日期时间信息的计时装置;RTC和时钟配置系统处于后备区域,系统复位时数据不清零,VDD(2.0~3.6V)断电后可借助VBAT(1.8~3.6V)供电继续走时;其内部设有32位的可编程计数器,可对应Unix时间戳的秒计数器。在读取时间时,我们先得到这个计数器中的秒数,然后使用time.h模块里的localtime函数就能立刻知道年月日时分秒的信息了,在写入时间时,我们先填充年月日时分秒信息到struct tm结构体,然后用mktime函数得到秒数,再写入到这个32位计数器即可。RTC外设中配有20位的可编程预分频器,可适配不同频率的输入时钟,由于32位的秒计数器显然1秒要自增一次,所以驱动计数器的时钟,需要是一个1Hz的信号,但是实际提供给RTC模块的时钟,也就是RTCCLK一般频率都比较高。所以显然我们需要在RTCCLK和计数器时钟输入之间加入一个分频器,给RTCCLK降一降频率,保证分频器输出给计数器的频率为1Hz。那为了适配各种频率的RTCCLK,就在其中间加入了一个20位的分频器,可以选择对输入时钟进行1-2^20这么大范围的分频,这样就可以适配不同频率的输入时钟,这就是这个可编程分频器的作用;可选择三种RTC时钟源:1. HSE时钟除以128(通常为8MHz/128) 2. LSE振荡器时钟(通常为32.768KHz) 3. LSI振荡器时钟(40KHz) 这三个时钟可以选择其中一个介入到RTCCLK。

在时钟树中,高速时钟一般供内部程序运行和主要外设使用;低速时钟一般供RTC、看门狗这些东西使用,红色所圈出来的地方最右侧箭头通往RTC,就是RTCCLK,RTCCLK有三个来源,第一个是OSC引脚接的HSE,外部高速晶振,这个晶振是主晶振,一般用的8MHz,8MHz进来,通过128分频,可以产生RTCCLK信号,128分频的原因是8MHz的主晶振太快了,如果不提前分频,直接给RTCCLK,后续即使再通过RTC的20位分频器也分不到1Hz这么低的频率。中间这一路的时钟来源是LSE,外部低速晶振,我们在OSC32这两个引脚接上外部低速晶振,这个晶振产生的时钟可以直接提供给RTCCLK,这个OSC32的晶振是内部RTC的专用时钟,这个晶振的值也不是随便选的,通常跟RTC有关的晶振,都是统一的数值,就是32.768KHz,选择这个数值的原因是32KHz这个值附近的频率是这个晶振工艺比较合适的频率,另一方面是32768是一个2的次方数,2^15 = 32768 ,所以32.768KHz即32768Hz,经过一个15位分频器的自然溢出,就能很方便地得到1Hz的频率。自然溢出的意思就是设计一个15位的计数器,这个计数器不用设置计数目标,直接从0计到最大值,就是计到32767,计满后自然溢出,这个溢出信号就是1Hz。所以,目前在RTC电路中,基本都是清一色的32.768KHz的晶振。最后第三路时钟源来自LSI,内部低速RC振荡器,LSI固定是40kHz,如果选择LSI当作RTCCLK,后续再经过40K的分频,就能得到1Hz的计数时钟了。当然内部的RC振荡器一般精准度没有外部晶振高,所以LSI给RTCCLK可以当作一个备选方案。另外LSI还可以提供给看门狗,这个之后我们讲看门狗的时候再说。

这三路时钟中我们最常用的就是中间这一路外部32.768KHz的晶振,提供RTCCLK的时钟。不仅因为中间这一路32.768KHz的晶振本身就是专供RTC使用的,其余的时钟其实各自都有各自的主要任务,另外一个更重要的原因就是只有中间这一路的时钟可以通过VBAT备用电池供电,上下两路时钟,在主电源断电后,是停止运行的。所以要想实现RTC主电源掉电继续走时的功能,必须选择中间这一路的RTC专用时钟。

接下来我们看一下RTC的框图,看一下RTC外设具体是怎么设计的。先整体上划分一下,左边的一块是核心的分频和计数计时部分,右边这一块是中断输出使能和NVIC部分,最上面一块是APB1总线读写部分,最下面一块是和PWR关联的部分,意思就是RTC的闹钟可以唤醒设备,推出待机模式。在图中,有灰色填充的部分都处于后备区域,这些电路在主电源掉电后,可以使用备用电池维持工作,其他未被填充的部分就是待机时不供电,有关睡眠、停机、待机这些低功耗相关的内容,我们下节学PWR的时候再细讲。

我们依次详细看一下。首先看分频和计数计时部分,这一块的输入时钟是RTCCLK,RTCCLK的来源需要在RCC里进行配置。因为可选的三路时钟频率各不相同,而且都远大于我们所需要的1Hz的秒计数频率,所以RTCCLK进来,需要首先经过RTC预分频器进行分频,这个分频器由两个寄存器组成,上面这个是重装载寄存器RTC_PRL,下面这个RTC_DIV,手册里叫做余数寄存器,但实际上这一块跟我们之前定时器时基单元里的计数器CNT和重装值ARR是一样的作用。分频器其实就是一个计数器,计几个数溢出一次,那就是几分频,所以对于可编程的分频器来说,需要有两个计数器,RTC_DIV寄存器用来不断地计数,另一个RTC_PRL寄存器,我们写入一个计数目标值,用来配置是几分频。那么PRL中就是计数目标,我们写入6,那就是7分频,写入9,那就是10分频;下面这个DIV,就是每来一个时钟计一个数的用途了,当然这个DIV计数器是一个自减计数器,每来一个输入时钟,DIV的值自减一次,自减到0时,再来一个输入时钟,DIV输出一个脉冲,产生溢出信号,同时DIV从PRL获取重装值,回到重装值继续自减。分频输出后的时钟频率是1Hz,提供给后续的秒计数器。然后看一下计数计时部分,32位可编程计数器RTC_CNT就是计时最核心的部分,我们可以把这个计数器看作是Unix时间戳的秒计数器,这样借助time.h的函数就可以很方便地得到年月日时分秒了,在其下面还设计有一个闹钟寄存器RTC_ALR,这个ALR也是一个32位的寄存器,和上面这个CNT是等宽的,它的作用顾名思义就是设置闹钟,我们可以在ALR写一个秒数,设定闹钟,当CNT的值跟ALR设定的闹钟值一样时,这时就会产生RTC_Alarm闹钟信号,通往右边的中断系统,在中断函数里,你可以执行相应的操作,同时这个闹钟还兼具一个功能,就是下面这里的闹钟信号可以让STM32退出待机模式。这个功能就可以对应一些用途,比如你设计一个数据采集设备,需要在环节非常恶劣的地方工作,比如海底、高原、深井这些地方,然后要求是每天中午12点采集一次环节数据,其他时间为了节省电量,避免频繁换电池,芯片都必须处于待机模式,这样的话我们就可以用这个RTC自带的闹钟功能。另外这个闹钟值是一个定值,只能响一次,所以你想实现周期性的闹钟,那在每次闹钟响之后,都需要再重新设置一下下一个闹钟时间。继续往右看就是中断部分了,在左边这里有三个信号可以触发中断,第一个是RTC_Second,秒中断,它的来源就是CNT的输入时钟,如果开启这个中断,那么程序就会每秒进一次RTC中断;第二个是RTC_Overflow,溢出中断,它的来源是CNT的右边,意思就是CNT的32位计数器计满溢出了会触发一次中断,所以这个中断一般不会触发;第三个RTC_Alarm,闹钟中断,刚才说过,当计数器和闹钟值相等时,触发中断,同时,闹钟信号可以把设备从待机模式唤醒。中断信号到右边这里就是中断标志位和中断输出控制,F结尾的是对应的中断标志位,IE结尾的是中断使能,最后三个信号通过一个或门汇聚到NVIC中断控制器。最上面这部分APB1总线和APB1接口就是我们程序读写寄存器的地方了,读写寄存器可以通过APB1总线来完成,另外也可以看出RTC是APB1总线上的设备。最后,最下面这一块,推出待机模式还有一个WKUP引脚,闹钟信号和WKUP引脚都可以唤醒设备。到这里这个RTC外设框图就已经全部了解清楚了。

接下来看一下下面的基本结构图,再总结一下以上内容,最左边是RTCCLK时钟来源,这一块需要在RCC里配置,3个时钟,选择一个,当作RTCCLK,之后RTCCLK先通过预分频器,对时钟进行分频,余数寄存器是一个自减寄存器,存储当前的计数值,重装寄存器是计数目标,决定分频值。分频之后得到1Hz的秒计数信号,通向32位计数器,1s自增一次,下面还有一个32位的闹钟值可以设定闹钟,右边有三个信号可以触发中断,分别是秒信号、计数器溢出信号和闹钟信号,三个信号先通过中断输出控制进行中断使能,使能的中断才能通向NVIC,然后向CPU申请中断。在程序中,我们配置这个数据选择器,可以选择时钟来源;配置重装寄存器,可以选择分频系数;配置32位计数器,可以进行日期时间的读写,需要闹钟的话,配置32位闹钟值即可;需要中断的话,先允许中断,再配置NVIC,最后写对应的中断函数即可,这就是RTC外设的主要内容。

最后,我们再看一些这个RTC的一些操作注意事项

1.执行以下操作将使能对BKP和RTC的访问:设置RCC_APB1ENR的PWREN和BKPEN,使能PWR和BKP时钟;设置PWR_CR的DBP,使能对BKP和RTC的访问。正常的外设开启了时钟就能使用了,但是BKP和RTC这两个外设开启稍微复杂些,首先要设置RCC_APB1ENR,这个实际上就是开启APB1外设的时钟,要同时开启PWR和BKP的时钟,对于RTC来说,并没有单独开启时钟的选项。然后我们还要设置PWR_CR的DBP位,来使能对BKP和RTC的访问

  1. 若在读取RTC寄存器时,RTC的APB1接口曾经处于禁止状态,则软件首先必须等待RTC_CRL寄存器中的RSF位(寄存器同步标志)被硬件置1。这一步对应代码里的一个库函数,就是RTC等待同步,一般在刚上电的时候调用一下这个函数就行了。

  2. 必须设置RTC_CRL寄存器中的CNF位,使RTC进入配置模式后,才能写入RTC_PRL、RTC_CNT、RTC_ALR寄存器。就是RTC会有一个进入配置模式的标志位,把这一位置1,才能设置时间,其实这个操作在库函数中,每个写寄存器的函数都会自动加上这个操作,所以就不用再单独调用代码进入配置模式了。

  3. 对RTC任何寄存器的写操作,都必须在前一次写操作结束后进行。可以通过查询RTC_CR寄存器中的RTOFF状态位,判断RTC寄存器是否处于更新中。仅当RTOFF状态位是1时,才可以写入RTC寄存器

2和3的注意事项都是因为读写数据时使用的APB1总线是在PCLK1的时钟频率下运行的,但是RTC外设内部的工作时钟是RTCCLK,PCLK1的频率远大于RTCCLK,因此任何读写操作都需要等待一会。

下面我们进入到代码编写的部分。

首先介绍一下备份寄存器BKP的库函数,其中,BKP_DeInit函数用于恢复缺省配置在BKP的外设中是有一个用途的,就是手动清空BKP所有的数据寄存器,因为如果有备用电池的话,BKP的数据主电源掉电不清零、上电复位也不清零,它就没清零的时候,如果我们确实想要清零,就可以使用这个函数,这样所有BKP的数据都会变0;BKP_TamperPinLevelConfig和BKP_TamperPinCmd用于配置TAMPER侵入检测功能,前者可以配置TAMPER引脚的有效电平,就是高电平触发还是低电平触发,后者就是配置是否开启侵入检测功能,如果需要侵入检测的话,那就先配置TAMPER有效电平,再使能侵入检测功能就行了;BKP_ITConfig,中断配置,就是配置是否开启中断;BKP_RTCOutputConfig这是配置时钟输出功能,可以选择在RTC引脚上输出时钟信号,输出RTC校准时钟、RTC闹钟脉冲或者秒脉冲,该配置需要通过 BKP 模块的寄存器来实现,因此函数被归在了BKP的库文件之中;BKP_SetRTCCalibrationValue,用于设置RTC校准值,其实就是写入RTC校准寄存器,校准值的设置也需要写入 BKP 模块的相关寄存器,因此也被归在了BKP的库文件之中。以上这些函数就是我们在上面说的BKP附加的小功能。之后的这几个函数才是经常使用的:BKP_WriteBackupRegister,写备份寄存器,其第一个参数指定要写在哪个DR里,第二个参数填你要写入的数据;BKP_ReadBackupRegister,读备份寄存器。参数指定要读哪个DR,返回值就是DR里存的值。 此外,我们还需要特别关注PWR库函数中的PWR_BackupAccessCmd函数,即备份寄存器访问使能,该函数中的内容就是设置PWR_CR寄存器里的DBP位,我们来调用这个函数满足使用RTC和BKP外设时的注意事项1。

下面对于RTC实时时钟编程,我们总结其初始化步骤如下:

1.开启PWR时钟和BKP时钟,使能BKP和RTC的访问

2.启动RTC的时钟,我们计划使用LES作为系统时钟,所以使用RCC模块里的函数,开启LSE的时钟

3.配置RTCCLK这个数据选择器,指定LSE为RTCCLK,这一步的函数也是在RCC模块里的

4.调用注意事项中提到的等待函数,分别为等待同步和等待上一次操作完成

5.配置预分频器,给PRL重装寄存器一个合适的分频值,以确保输出给计数器的频率是1Hz

6.配置CNT的值,给这个RTC一个初始时间

如果需要闹钟的话,可以配置闹钟值;需要中断的话可以配置中断部分

因为RTC比较简单,所以库函数并没有使用结构体来配置,RTC也没有RTC_Cmd这样的函数,开启时钟就能自动运行了,不需要最后再启动一下的。

在RCC库函数中,存在着一些和RTC时钟相关的函数。其中RCC_LSEConfig用于配置LSE外部低速时钟,启动LSE时钟就调用这个函数;RCC_LSICmd函数用于配置LSI内部低速时钟,如果出现了外部时钟不起振的情况,也可以使用这个内部时钟来进行实验;RCC_RTCCLKConfig,RTCCLK配置,这个函数用来选择RTCCLK的时钟源,实际上就是配置简化结构图中的数据选择器;RCC_RTCCLKCmd,启动RTCCLK,在调用上一个函数选择时钟之后,还需要调用一下这个Cmd函数,使能一下;另外还需要用到RCC_GetFlagStatus函数获取标志位,因为LSE时钟不是你让它启动它就能立刻启动的,调用启动时钟的函数之后,我们还需要等待一下标志位,等RCC的标志位LSERDY置1之后,这个时钟才算启动完成,工作稳定。有关RCC时钟部分的函数就这么多。

接下来我们继续看RTC库函数中的函数:RTC_ITConfig用于配置中断输出;RTC_EnterConfigMode,进入配置模式,就是置CRL的CNF为1,进入配置模式,其对应注意事项中的第三条;RTC_ExitConfigMode,退出配置模式,就是把CNF位清零;RTC_GetCounter,获取CNT计数器的值,显然,读取时钟就靠这个函数;RTC_SetCounter,写入CNT计数器的值,显然,设置时间,就靠这个函数;RTC_SetPrescaler,写入预分频器,这个值会写入到预分频器的PRL重装寄存器中,用来配置预分频器的分频系数;RTC_SetAlarm,写入闹钟值;RTC_GetDivider,读取预分频器中的DIV余数寄存器,余数寄存器是一个自减寄存器,获取余数寄存器的值,一般是为了得到更细致的时间,因为CNT计数间隔最短就是1s,如果需要更细致的时间,比如分秒、厘秒、毫秒,那就得靠这个DIV余数寄存器来实现;RTC_WaitForLastTask,等待上次操作完成,对应注意事项中的第四条,等待前一次写操作结束;RTC_WaitForSynchro,等待同步,对应注意事项中的第二条。

在RTC显示实时时钟代码中,我如何保证在系统复位后,保证时间信息仍然不被重置呢?我可以借助BKP中存储内容在单片机供电断开时仍然能靠电池保存的特性,在初始化代码中加入判断:如果BKP中任一一个数据寄存器中内容不等于约定好的数字,说明现在是单片机电源掉电、电池电源掉电之后重新启动,因此需要进行初始化,并且在BKP对应寄存器中写入该数据;如果其中的内容等于数字了,说明电池并未掉电,则跳过初始化。

相关推荐
✎ ﹏梦醒͜ღ҉繁华落℘11 天前
单片机基础知识---stm32单片机的优先级
stm32·单片机·mongodb
u1521096484911 天前
S.S.Audio PRO A2音频隔离器
嵌入式硬件·音视频·实时音视频·视频编解码·视频
CNNACN电商经济11 天前
纸价波动加速中小产能出清,包装印刷板块龙头份额提升与议价能力重估
科技·生活
国产化创客11 天前
ESP32 CameraWebServer 原生摄像头项目全解析
物联网·开源·嵌入式·实时音视频·智能硬件
牛根生同志12 天前
SPI数据收发的时候 TXE与RXNE标志位置位的时机
stm32·spi·transfer
绿算技术12 天前
Mooncake 与绿算ForinnBase GroundPool如何联手打破推理僵局?
科技·算法·架构
nanoscientific12 天前
在芬顿耦合微纳米气泡系统中最大化利用界面处的Fe²⁺以实现有机污染物降解。
科技·微纳米气泡
goldenrolan12 天前
学习型红外控制系统稳定性挂测工装专项总结
软件测试·python·stm32·嵌入式·红外
蓝速科技12 天前
蓝速科技 AI 数字人部署与交互实战指南
人工智能·科技·交互
CC城子12 天前
STM32H7_FDCAN 驱动笔记
stm32·can·canfd