BKP备份寄存器&RTC实时时钟
VDDA和VSSA是内部模拟部分的电路
VDD和VSS_1、2、3是内部数字电路的供电。系统以VDD开头的电源都是主电源。在正常使用STM32时,全部需要接到3.3v电源上。
- VBAT备用电池供电引脚,如使用STM32内部的BKP和RTC,引脚必须接备用电池,用来维持BKP和RTC,在VDD主电源掉电后的供电,这里备用电池只有一跟正极的引脚,接电池时,电池正极接VBAT,电池负极和主电源负极接在一起,供地。
- TAMPER引脚产生的侵入事件将所有备份寄存器内容清除,TAMPER是一个接到stm32外部的引脚,这个TAMPER是一个安全保障设计,如果你做一个安全系数非常高的设备,设备需要有防拆功能,然后BKP里也存储了一些敏感数据,这些数据不能被别人窃取或者篡改,那你就可以使用这个TAMPER引脚的侵入检测功能。
设计电路时,TAMPER引脚可以先加一个默认的上拉或者下拉电阻,然后引一根线到你的设备外壳的防拆开关或触点,别人一拆开你的设备触发开关,就会在TAMPER引脚产生上升沿或者下降沿,这样STM32就检测到侵入事件了,这时BKP的数据会自动清零,并且申请中断,你在中断里还可以继续保护设备,比如清除其他存储器数据,然后设备锁死,这样来保障设备的安全,另外主电源断电后,侵入检测仍然有效,这样即使设备关机也能防拆。 - RTC引脚输出RTC校准时钟、RTC闹钟脉冲或者秒脉冲,其中外部用设备测量RTC校准时钟,可以对内部RTC微小的误差进行校准。
闹钟脉冲或者秒脉冲可以输出出来,为别的设备提供这些信号,这是RTC时钟输出的功能。因为PC13、temple和RTC这三个引脚共用一个端口,所以这三个功能同一时间只能使用一个。 - 存储RTC时钟校准寄存器,这个可以配合上面这个校准时钟输出的功能,结合一些测量方法,可以对RTC进行校准,那这两个功能实际上就是RTC的配置,设计者目前就是把这两个RTC的功能放在BKP里了。
- 我们使用的C8T6是中容量设备,所以可以看出BKP的容量其实非常小,一般只能用来存储少量的参数。
BKP基本结构
BKP的基本结构,这个图中橙色部分我们可以叫做后备区域,BKP处于后备区域,但后备区域不只有BKP,还有RTC的相关电路也位于后备区域。
STM32后备区域的特性就是当VDD主电源掉电时,后备区域仍然可以由VBAT的备用电池供电,当VDD主电源上电时,后备区域供电会切换到VDD,主电源有电时VBAT不会用的,这样可以节省电池电量。
- 然后BKP是位于后备区域的,BKP里主要有数据寄存器、控制寄存器、状态寄存器和RTC时钟校准寄存器,其中数据寄存器是主要部分,用来存储数据的,每个数据寄存器都是16位的,也就是一个数据寄存器可以存两个字节,那对于中容量和小容量的设备,里面有DR1、DR2一直到DR10总共十个数据寄存器,那一个寄存器存两个字节,所以容量是20个字节。总共是42个数据寄存器,容量是84个字节。
- BKP功能,这里的侵入检测,可以从PC13位置的TAMPER引脚引入一个检测信号,当TAMPER产生上升沿或者下降沿时,清除BKP所有的内容以保证安全。
- 时钟输出,可以把RTC的相关时钟,从PC13位置的RTC引脚输出数据供外部使用,其中输出较准时钟时,再配合这个校准寄存器,可以对RTC的误差进行校准。
RTC介绍
20位的可编程预分频器,可适配不同频率的输入时钟,保证分频器输出给计数器的频率为1Hz,计时才正确。
RTCCLK频率比较高,需要加一个分频器,来降频。加了一个20位的可编程预分频器,可以对选择的输入时钟进行1~2^20的范围分频,这可适配不同频率的输入时钟。
H开头是高速,L开头是低速。E结尾是外部,I结尾是内部。高速时钟一般供内部程序运行和主要外设使用,低速时钟一般供RTC,看门狗使用。
RTCCLK有三个来源,第一个是OSC引脚接的HSE外部高速晶振,这个晶振是主晶振,我们一般都用的8MHZ,通过128分频可以产生RTCCLK信号,因为这个8MHz的晶振太快了,如果不提前分频,直接给RTCCLK,后续即使再通过RTC的20位分频器,也分不到1Hz这么低的频率。
中间是,时钟来源是LSE,外部低速晶振,我们在oc32这两个引脚,接上外部低速晶振,这个晶振产生的时钟,可以直接提供给RTCCLK,这个osc32的晶振,是内部RTC的专用时钟,通常跟RTC有关的晶振都是统一的数值,就是32.768KHz,一方面是32.768KHz这个值附近的频率,是晶振工艺比较合适的频率,另一方面是2的15次方等于32768,所以32.768KHz,经过一个15位分频器的自然溢出,就能很方便地得到1Hz的频率。
第三路,时钟源,来自于LSI,内部低速rc振荡器,LSI固定是40KHz,如果选择LSI当做RTCCLK,后续再经过40k的分频,就能得到1Hz的计数时钟了,当然内部的RC振荡器,一般精度没有外部晶振高,所以LSI给RTCCLK,l可以当做一个备选方案,另外LSI还可以提供给看门狗。
总结:
最常用的就是中间这一路,外部32.768KHz的晶振,提供RTCCLK的时钟。
第一个原因就是中间这一路,32.768KHz的晶振本身就是专供RTC使用的,上下这两路其实是有各自的任务,上面这一路主要作为系统主时钟,下面这一路主要作为看门狗时钟,他们只是顺带可以备选当做RTC的时钟。
另外一个原因,只有中间这一路的时钟可以通过VBAT备用电池供电,上下两路时钟在主电源断电后是停止运行的,所以要想实现RTC主电源掉电继续走时的功能,必须得选择中间这一路的RTC专用时钟,如果选择的是上下两路时钟,主电源断电后时钟就暂停了,这显然会导致走时出错。
RTC框图
左边这一块是核心的分频和计数计时部分,右边这一块是中断输出使能和NVIC部分,上面这一块是APB1总线读写部分,下面这块是PWR关联的部分。
灰色填充的部分,都处于后备区域,这些电路在主电源掉电后,可以使用备用电池维持工作,另外这里还写了,这些模块在待机时都会继续维持供电,其他未被填充的部分就是待机时不供电。
-
首先看分频和计数器计数部分,这一块的输入时钟是RTCCLK,RTCCLK的来源需要在所以RTCCLK进来,首先需要经过RTC预分频器进行分频,这个分频器由两个计算器组成,上面这个是重装载寄存器RTC_PRL,下面这个RTC_DIV手册里叫做余数寄存器,实际上是计数器的作用。
分频器其实就是一个计数器,记几个数溢出一次就几分频,所以对于可编程的分频器来说,需要有两个寄存器,一个寄存器用来不断的计数,另一个寄存器我们写入一个计数目标值,用来配置是几分频。
那在这里上面这个PRL就是计数目标,我们写入六那就是七分频,写九那就是十分频,因为计数指包含了零。然后下面这个DIV就是每来一个时钟计一个数的用途了,当然这个DIV计数器啊是一个自减计数器,每来一个输入时钟,DIV的值自减一次,自减到0时再来一个输入时钟,DIV输出一个脉冲产生溢出信号,同时DIV从PRL获取重装值,回到重装值继续自减。
计数计时部分,32位可编程计数器,RTC_CNT就是计时最核心的部分,把这个计数器看作是Unix时间戳的秒计数器,这样借助time.h的函数,就可以很方便地得到年月日时分秒了。
-
这个RTC还设计的有一个闹钟寄存器RTC_ALR,这个ALR也是一个32位的寄存器,作用是设置闹钟,我们可以在ALR写一个秒数,设定闹钟,当CNT的值跟ALR设定的闹钟值一样时,也是这里的等号啊,如果两个值相等就代表闹钟响了,这时就会产生RTC_Alarm闹钟信号,通往右边的中断系统,在中断函数里你可以执行相应的操作。
同时闹钟还兼具一个功能,闹钟信号可以让STM32退出待机模式,比如你设计一个数据采集设备,需要在环境非常恶劣的地方工作,比如海底高原深井这些地方,然后要求是每天中午12点采集一次环境数据,其他时间为了节省电量避免频繁换电池,芯片都必须处于待机模式,这样的话我们就可以用这个RTC自带的闹钟功能,定一个中午12点的闹钟,闹钟一响,芯片唤醒,采集数据完成后继续待机,另外这个闹钟值是一个定值,只能响一次,所以如果你想实现周期性的闹钟,大家每次闹钟响之后都需要再重新设置下一个闹钟时间,就是这个闹钟和闹钟唤醒的一个用途。
中断部分的,在左边这里有三个信号可以触发中断:
RTC_Second秒中断,它的来源就是cnt的输入时钟,如果开启这个中断,那么程序就会每秒进一次RTC中断。
RTC_Overflow溢出中断,它的来源是CNT的右边,意思就是CNT的32位计数器计满溢出了,会触发一次中断,所以这个中断一般不会触发。这个CNT定义的是无符号数,到2106年才会溢出,所以这个中断在2106年会触发一次,你可以让芯片罢工,然后提示当前设备过老,请及时更换,但在2106年之后这个stm32的RTC就不太好用了。
RTC_Alarm闹钟中断,当计数器和闹钟值相等时触发中断,同时闹钟信号可以把设备从待机模式唤醒。
中断信号到右边这里,这一块是中断标志位和中段输出控制,这些F结尾的是对应的中断标志位,IE结尾(Interrupt ENABLE)的是中断使能,最后三个信号通过一个或门,汇聚到NVIC中断控制器。
APB1总线和APB1接口,就是程序读写寄存器的地方,读写寄存器可以通过APB1总线来完成。另外,RTC是APB1总线上的设备,
最后下面,退出待机模式,有一个WKUP(weak up)引脚,闹钟信号,和WKUP引脚都可以唤醒设备,WKUP引脚,引脚图PA0的位置它兼具唤醒的功能。
RTC基本结构
RTC的核心部分如图所示:
最左边是RTCCLK时钟来源,这块需要在RCC里配置,三个时钟选择一个当做RTCCLK,之后RTCCLK先通过预分频器对时钟进行分频,余数寄存器是一个自减计数器,存储当前的计数值,重装寄存器是计数目标,决定分频值,分频之后,得到1Hz的秒计数信号通向32位计数器,一秒自增一次,下面还有个32位的闹钟值,可以设定闹钟,如果不需要闹钟的话,下面这一块可以不用管。
然后右边有三个信号可以触发中断,分别是秒信号、计数器溢出信号和闹钟信号,三个信号先通过中断输出控制进行中断使能,使能的中断才能通向NVIC,然后向CPU申请中断。
在程序中,配置数据选择器,可以选择时钟来源,配置重装寄存器,可以选择分频系数,配置32位计数器,可以进行日期时间的读写,需要闹钟的话,配置32位闹钟值即可,需要中断的话,先允许中断,再配置NVIC,最后写对应的中断函数即可,这是RTC外设的主要内容。
硬件电路
在最小系统电路上,外部电路还要额外加两部分,第一部分就是备用电池,第二部分就是外部低速晶振,首先备用电池供电部分,我这里给了两个参考电路,第一个是简单连接,使用一个3v的电池,负极和系统共地,正极直接接到stm32 的VBAT引脚。
参考来源是数据手册,在5.1.6供电方案这里就给出来这个图,图上画的是直接接一个1.8~3.6v的电池到VBAT。另外,在内部是有一个供电开关的,当VDD有电时,开关拨到下面,后备电路由VDD供电,当VDD没电时,开关拨到上面,后备电路由VBAT供电。
-
第二种方案是推荐连接,这种连接方法是电池通过二极管D1向VBAT供电,另外主电源的3.3V,也通过二极管D2向VBAT供电,最后VBAT再加一个0.1uf的电源滤波电容。
供电方案的参考来源是stm32的参考手册,在这个4.1.2电池备份区域其中手册里有几个建议,所以综合这两条建议,我们可以设计出右边的推荐连接。
电池和主电源都加一个二极管,防止电流倒灌,VBAT加一个0.1uf的电源滤波电容,0.1uf就是100nf,如果没有备用电池,就3.3V的主电源供电,如果接了备用电池,3.3v没电时,就是备用电池供电,这是根据参考手册设计的推荐电路。
如果你只是进行实验,那使用左边的简单连接就行了,如果你要画板子设计产品,那还是推荐使用右边的连接,这样更保险。
外部低速晶振部分,这是一个典型的晶振电路了,这里X1是一个32.768KHz的RTC晶振,这个晶振不分正负极,两端分别接在OSC32 这两个引脚上,然后这两端再分别接一个启动电容到GND,这个电路的设计,参考来源还是stm32的数据手册。
在5.3.6外部时钟源特性这里有参考电路,使用一个晶体或陶瓷谐振器产生的低速外部时钟,CL1和CL2上面这里写了,对于CL1和CL2,建议使用高质量的5pF~15pF之间的瓷介电容器,所以在这里给出的晶振电路是这样的,起振电容给的是10pF。
纽扣电池
上图片,是备用电池,我们一般可以选择这样的3v纽扣电池,型号是CR2032,另外注意这个纽扣电池印字的这一面是正极,,然后32.768KHz的晶振。
我们可以选择这样的一个金属壳柱状体的晶振,这个晶振也是比较常见,这是32.768KHz的晶振,晶振的全称是石英晶体振荡器,所以我们常说的石英钟,名称就来源于这样一个元件。
然后是这个是我们的最小系统板,这个板子自带的有RTC晶振电路,这里这个黑色的元件写的有32.768k,这个也是一种样式的RTC晶振,然后旁边这个金属壳柱状体是8MHz的外部高速晶振。
不过我们这个板子没有自带备用电池,VBAT引脚直接通过右上角的这个端口引出来了,如果需要备用电池的话,可以接,以上就是RTC的硬件电路部分。
RTC操作注意事项
-
如果你要使用BKP或者RTC,都要先执行这两步,第一步开启PWR和BKP的时钟,第二步使用PWR使能BKP和RTC的访问。
-
这一步对应代码里的一个库函数就是RTC等待同步,一般在刚上电的时候调用一下这个函数就可以。要求我们在APB1总线刚开机时,要等一下RTCCLK,只要RTCCLK来一个上升沿,RTC把它的寄存器的值同步到APB1总线上,这样之后读取的值就都是没问题的了,这是设计细节的一个问题,只需要在初始化时调用一个等待同步的函数就行了。
-
这一条其实比较简单,就是RTC会有一个进入配置模式的标志位(CNF位),把这一位置1才能设置时间,其实这个操作在库函数中,每个写寄存器的函数,它都自动帮我们加上了这个操作,所以我们就不用再单独调用函数进入配置模式了。
-
这个操作也是调用一个等待的函数就行了,就写入之前要等待一下,如果上一次的写入还没完成,你就别急着写下一次了,或者说每次写入之后,你要等待RTOFF为1,只有RTOFF为1才表示写完成。
为什么要有这个操作呢,其实还是因为这里的PCLK1和RTCCLK时钟频率不一样,你用PCLK1的频率写入之后,这个值还不能立刻更新到RTC的寄存器里,因为RTC寄存器是由RTCCLK驱动的,所以PCLK1写完之后,得等一下RTCCLK的时钟,RTCCLK来个上升沿,值更新到RTC寄存器里,整个写作过程才算结束了。在代码里也就是调用一个等待函数的事。
手册: