你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

MiniPro STM32H750 开发指南_V1.1-RTC实时时钟实验

[复制链接]
STMCU小助手 发布时间:2022-10-7 18:40
RTC实时时钟实验
4 p) W" I1 b7 \7 u9 E7 w8 t, m; V本章,我们将介绍STM32H750的内部实时时钟(RTC)。我们将使用LCD模块来显示日期和时间,实现一个简单的实时时钟,并可以设置闹铃,另外还将介绍BKP的使用。; D6 U* J& q  l, p8 z4 N
3 G) t6 e+ r! i# X3 _- p, Z6 `! O
27.1 RTC时钟简介4 m5 k4 W8 }9 {% k2 i8 L
STM32H750的实时时钟(RTC)相对于STM32F1来说,改进了不少,带了日历功能了。RTC是一个独立的 BCD 定时器/计数器。RTC 提供一个日历时钟(包含年月日时分秒信息)、两个可编程闹钟(ALARM A和ALARM B)中断,以及一个具有中断功能的周期性可编程唤醒标志。RTC 还包含用于管理低功耗模式的自动唤醒单元。7 |% t. ^/ E* F
两个32位寄存器(TR和DR)包含二进码十进数格式 (BCD) 的秒、分钟、小时(12或24小时制)、星期、日期、月份和年份。此外,还可提供二进制格式的亚秒值。* K8 ~9 @, t; q9 d  F: ^
STM32H750的RTC可以自动将月份的天数补偿为 28、29(闰年)、30 和 31 天。并且还可以进行夏令时补偿。
# u- Z  z! _* j: xRTC模块和时钟配置是在后备区域,即在系统复位或从待机模式唤醒后RTC的设置和时间维持不变,只要后备区域供电正常,那么RTC将可以一直运行。但是在系统复位后,会自动禁止访问后备寄存器和RTC,以防止对后备区域(BKP)的意外写操作。所以在要设置时间之前, 先要取消备份区域(BKP)写保护。" }' g% I1 @4 c' v: A) {
27.1.1 RTC框图6 X8 Y5 \- N% r: j% r
下面先来学习RTC框图,通过学习RTC框图会有一个很好的整体掌握,同时对之后的编程也会有一个清晰的思路。RTC的框图,如图27.1.1所示:
# q* ?+ J+ S# }0 `/ y) d9 T
( D9 k/ U8 c; { e5f57546f60a4eb59cf451870079b99e.png ! _$ g& F5 n% t6 z* A9 L5 O- O
) F/ @" K. P  t5 ~/ w% Q3 j1 o
图27.1.1 RTC框图; s/ x8 B- e- Z
我们把RTC框图分成以下几个部分讲解:) d$ ?+ A- `6 r  t+ k8 S$ o. t
①时钟源
5 x# _9 M9 ~$ @5 tSTM32H750的RTC时钟源(RTCCLK)通过时钟控制器,可以从LSE时钟、LSI时钟以及HSE时钟三者中选择其一(通过设置RCC_BDCR寄存器选择)。一般我们选择LSE,即外部32.768Khz晶振作为时钟源(RTCCLK)。外部晶振具有精度高的优点。LSI是STM32芯片内部的低速RC 振荡器,频率约32 kHz,缺点是精度较低,可能会有温漂,所以一般不建议使用。比如当没有外部低速晶振(32.768Khz)的时候,分频后的HSE可以作为备选使用的时钟源。+ g, F* Y* E* a! m7 f
②预分频器
1 \6 l1 H1 s' K8 [9 ?: h  \9 E预分配器(RTC_PRER)分为2个部分:一个通过RTC_PRER寄存器的PREDIV_A位配置的7位异步预分频器。另一个通过RTC_PRER寄存器的PREDIV_S位配置的15位同步预分频器。
4 O6 J; _4 N9 z经过7位异步预分频器出来的时钟ck_apre可作为RTC_SSR亚秒递减计数器(RTC_SSR)的时钟,ck_apre时钟频率的计算公式如下:% _* |5 G; n& f6 X

, w5 `% `; T9 x) V) K当RTC_SSR寄存器递减到0的时候,会使用PREDIV_S的值重新装载PREDIV_S。而PREDIV_S一般为255,这样,我们得到亚秒时间的精度是:1/256秒,即3.9ms左右,有了这个亚秒寄存器RTC_SSR,就可以得到更加精确的时间数据。
: x. E2 e  Y- L" x, L经过15位同步预分频器出来的时钟ck_spre可以用于更新日历,也可以用作16位唤醒自动重载定时器的时基,ck_apre时钟频率的计算公式如下:" ], m! h- z' Z  S7 V
. c6 {% r& m; @( f
PREDIV_A和PREDIV_S分别为RTC的异步和同步分频器,使用两个预分频器时,我们推荐设置7位异步预分频器(PREDIV_A)的值较大,以最大程度降低功耗。例如:本实验我们的外部低速晶振的频率32.768KHZ经过7位异步预分频器后,再经过15位同步预分频器,要得到1HZ频率的时钟用于更新日历。通过计算知道,32.768KHZ的时钟要经过32768分频,才能得到1Hz的ck_spre。于是我们只需要设置:PREDIV_A=0X7F,即128分频;PREDIV_S=0XFF,即256分频,即可得到1Hz的Fck_spre,PREDIV_A的值我们也是往尽量大的原则,以最大程度降低功耗。5 H9 Y# `; @* [9 ?8 j8 c
③时间和日期相关寄存器# C/ E: i) q. z( C6 o# P& X0 r" g' e
该部分包括三个影子寄存器,RTC_SSR(亚秒)、RTC_TR(时间)、RTC_DR(日期)。实时时钟一般表示为:时/分/秒/亚秒。RTC_TR寄存器用于存储时/分/秒时间数据,可读可写(即可设置或者获取时间)。RTC_DR寄存器用于存储日期数据,包括年/月/日/星期,可读可写(即可设置或者获取日期)。RTC_SSR寄存器用于存储亚秒级的时间,这样我们可以获取更加精确的时间数据。
4 S$ Z& v2 K6 j$ j) x: {这三个影子寄存器可以通过与 PCLK4(APB4 时钟)同步的影子寄存器来访问,这些时间和日期寄存器也可以直接访问,这样可避免等待同步的持续时间。6 Q3 C+ H4 L& ^* _
每隔2个RTCCLK周期,当前日历值便会复制到影子寄存器,并置位RTC_ISR寄存器的RSF位。我们可以读取RTC_TR和RTC_DR来得到当前时间和日期信息,不过需要注意的是:时间和日期都是以BCD码的格式存储的,读出来要转换一下,才可以得到十进制的数据。! Y& J& E0 ^% d, Z3 X0 d! c: p0 @
④可编程闹钟
$ `. _' ]4 m1 z2 L$ dSTM32H750提供两个可编程闹钟:闹钟A(ALARM_A)和闹钟B(ALARM_B)。通过RTC_CR寄存器的ALRAE和ALRBE位置1来使能闹钟。当亚秒、秒、分、小时、日期分别与闹钟寄存器RTC_ALRMASSR/RTC_ALRMAR和RTC_ALRMBSSR/RTC_ALRMBR中的值匹配时,则可以产生闹钟(需要适当配置)。本章我们将利用闹钟A产生闹铃,即设置RTC_ALRMASSR和RTC_ALRMAR即可。
) j- o7 c  ?, t3 Y⑤周期性自动唤醒
2 l) R4 B: |  Z; f+ F  K/ RSTM32H750的RTC不带秒钟中断了,但是多了一个周期性自动唤醒功能。周期性唤醒功能,由一个16位可编程自动重载递减计数器(RTC_WUTR)生成,可用于周期性中断/唤醒。
" }; N- d! ?! |+ L% z2 C我们可以通过RTC_CR寄存器中的WUTE位设置使能此唤醒功能。/ R5 n6 B8 [  e8 E
唤醒定时器的时钟输入可以是:2、4、8或16分频的RTC时钟(RTCCLK),也可以是ck_spre时钟(一般为1Hz)。
; r- l* U- V! Y- {当选择RTCCLK(假定LSE是:32.768 kHz)作为输入时钟时,可配置的唤醒中断周期介于 122us(因为RTCCLK/2时,RTC_WUTR不能设置为0)和32 s之间,分辨率最低为:61us。
# v/ s! ^- x6 ?当选择ck_spre(1Hz)作为输入时钟时,可得到的唤醒时间为 1s到36h左右,分辨率为1 秒。并且这个1s~36h的可编程时间范围分为两部分:
2 x2 K: X# K6 V0 R. X6 L5 H: x当WUCKSEL[2:1]=10时为:1s到18h。" s% t9 {- @# a& V$ U  T1 O( @
当WUCKSEL[2:1]=11时约为:18h到36h。
: J8 w3 m. {5 l1 |在后一种情况下,会将2^16添加到16位计数器当前值(即扩展到17位,相当于最高位用WUCKSEL [1]代替)。
5 O- ]  Q3 S* P+ u, l4 J初始化完成后,定时器开始递减计数。在低功耗模式下使能唤醒功能时,递减计数保持有效。此外,当计数器计数到0时,RTC_ISR寄存器的WUTF标志会置1,并且唤醒寄存器会使用其重载值(RTC_WUTR寄存器值)动重载,之后必须用软件清零WUTF标志。
1 e2 G0 H2 v/ l. w, `9 b+ s通过将 RTC_CR寄存器中的WUTIE位置1来使能周期性唤醒中断时,可以使STM32H750退出低功耗模式。系统复位以及低功耗模式(睡眠、停机和待机)对唤醒定时器没有任何影响,它仍然可以正常工作,故唤醒定时器,可以用于周期性唤醒STM32H750。1 }, t9 |$ h. ]% r6 T
1 S( d9 K8 v" Q0 i
27.1.2 RTC寄存器# B8 j4 g. t% z
接下来,我们介绍本实验我们要用到的RTC寄存器。
/ A, F. ^3 H- C( {- e* A( Y6 |1 v( ~RTC时间寄存器(RTC_TR)
4 e0 y9 H0 X* A4 YRTC时间寄存器描述如图27.1.2.1所示:0 y- C0 M6 E1 X0 A; ^) ?/ j

3 F! H7 f" ?) T ff97d198c4c34715a5a5**de2b951f.png
1 R6 r: \" N9 Q2 f: {- E3 t; d1 o( U- |2 B/ u) m$ h  m
图27.1.2.1 RTC_TR寄存器; N8 j- _+ ~+ q, S8 i9 i) g. Z1 \
该寄存器是RTC的时间寄存器,可读可写,对该寄存器写,可以设置时间,对该寄存器读,可以获取当前的时间,此外该寄存器受到寄存器写保护,通过RTC写保护寄存器(RTC_WPR)设置,后面会讲解到RTC_WPR寄存器。需要注意的是:本寄存器存储的数据都是BCD格式的,读取之后需要进行转换,方可得到十进制的时分秒等数据。# |' Y* }) J. [8 E! j
RTC日期寄存器(RTC_DR)2 K$ N6 |+ b/ C8 k
RTC日期寄存器描述如图27.1.2.2所示:
; Z# g8 s- W6 A/ c8 G. w0 g9 x" {; s! X; p% c9 @
f68fc5f5df2446c69a57927d6532c74e.png . c7 e+ G% G( ]' m. a- y

/ R$ ?4 g( l( H& P- ^图27.1.2.2 RTC_DR寄存器
* B6 A6 ~0 [+ X& Z  b7 V该寄存器是RTC的日期寄存器,可读可写,对该寄存器写,可以设置日期,对该寄存器读,可以获取当前的日期,同样该寄存器也受到寄存器写保护,存储的数据也都是BCD格式的。
( ]) @4 g4 u' @4 M7 cRTC亚秒寄存器(RTC_SSR)
2 s, B5 t6 U$ v3 d2 y+ MRTC亚秒寄存器描述如图27.1.2.3所示:
4 K/ [2 r; k9 h  z: {
3 o2 P  I7 U) t, d6 \: x5 _* Y8 s bbba1c4c2835499f95620d0faa5fa4e9.png
4 Z  l, \, r% r
0 }3 E/ }/ b# e- m图27.1.2.3 RTC_SSR寄存器! ~0 X) b. Y* }, j' D3 ^: j, j5 q
该寄存器可用于获取更加精确的RTC时间。不过,在本章没有用到,如果需要精确时间的地方,大家可以使用该寄存器。
8 r. |- B+ a5 H/ W* F0 ]0 BRTC控制寄存器(RTC_CR)! f" e5 B6 M2 Z. ^
RTC控制寄存器描述如图27.1.2.4所示:  \2 a1 H- N/ I

! T( z2 W$ Z0 `' C8 ^ 1f6d36f306004d0093bb059a37473959.png % \) d) L6 t/ {$ x% A( e& W, G

3 K) j2 |! @. y% A( m. c图27.1.2.4 RTC_CR寄存器% ^- ~2 D# F! Y; o3 b
该寄存器重点介绍几个要用到的位:WUTIE是唤醒定时器中断使能位,ALRAIE是闹钟A中断使能位,本章用到这两个使能位,都设置为1即可。WUTE和ALRAE分别是唤醒定时器和闹钟A使能位,同样都设置为1,开启。FMT为小时格式选择位,我们设置为0,选择24小时制。WUCKSEL[2:0],用于唤醒时钟选择,这个前面已经有介绍了,我们这里就不多说了。$ L* `  x* P8 F4 J. N
RTC初始化和状态寄存器(RTC_ISR)4 `- A) g6 u: t* S# g& y
RTC初始化和状态寄存器描述如图27.1.2.5所示:
8 L- k- I0 S7 {" ?& x0 h# H. t* p
6 @  M& Z! R+ i* J5 P) }' _ 916f0a6ea61646cb86ed315bdca6197a.png   Z8 p# s# P& E2 b) O( i: U
; j% t9 ^) h3 ~# X. F! @- E
图27.1.2.5 RTC_ISR寄存器
9 ?- k  `! p+ O% g) K该寄存器中,WUTF、ALRBF和ALRAF,分别是唤醒定时器、闹钟B和闹钟A的中断标志位,当对应事件产生时,这些标志位被置1,如果设置了中断,则会进入中断服务函数,这些位通过软件写0清除。* z9 i% ~! j  k' B
INIT为初始化模式控制位,要初始化RTC时,必须先设置INIT=1。, @- U: w$ ^1 M
INITF为初始化标志位,当设置INIT为1以后,要等待INITF为1,才可以更新时间、日期和预分频寄存器等。
3 w0 [3 b8 M8 N1 J3 cRSF位为寄存器同步标志,仅在该位为1时,表示日历影子寄存器已同步,可以正确读取RTC_TR/RTC_TR寄存器的值了。
5 U8 O7 O* G. |WUTWF、ALRBWF和ALRAWF分别是唤醒定时器、闹钟B和闹钟A的写标志,只有在这些位为1的时候,才可以更新对应的内容。比如:要设置闹钟A的ALRMAR和ALRMASSR,则必须先等待ALRAWF为1,才可以设置。
' v+ C& G& j( [8 ?3 j! sRTC预分频寄存器(RTC_PRER)
0 N8 v: c0 A8 Y/ D2 ZRTC预分频寄存器描述如图27.1.2.6所示:
+ B; M. {9 M3 g2 t$ c7 a$ l6 h) u
1 Z+ o& R+ p% w$ _2 M2 y. h 6239ceb109b54a9792ebd14059f52580.png
( W: Y! b8 L* G; |* d8 ?7 R' L8 ?+ F* M
图27.1.2.6 RTC_PRER寄存器
& w% d. ]. H( Y% z该寄存器用于RTC的分频,我们在之前也有讲过,这里就不多说了。该寄存器的配置,必须在初始化模式(INITF=1)下,才可以进行。* H  P" k. H. J! q- K) Z( o
RTC唤醒寄存器(RTC_WUTR)$ a$ a1 y9 `7 f8 \7 S1 e$ E
RTC唤醒寄存器描述如图27.1.2.7所示:  O3 v1 M6 s2 k' K* x5 x

9 Y, d+ n( D3 U5 _4 X bef38fa8b83a45b5ab2dcedb50c7b8ee.png
: Y$ S( \$ o  \- i# A
7 d5 ~/ G+ {4 i' S/ G图27.1.2.7 RTC_WUTR寄存器# n1 Y( ?9 L2 h
该寄存器用于设置自动唤醒重装载值,可用于设置唤醒周期。该寄存器的配置,必须等待RTC_ISR的WUTWF为1才可以进行。
+ ~" Y; b2 W2 o. m$ F4 C" \RTC闹钟A寄存器(RTC_ALRMAR)6 J: ]% {; Z, B- I
RTC闹钟A寄存器描述如图27.1.2.8所示:0 B! g% G4 e) \  @; w! z9 ^  t) A

* `/ a2 a. y5 Y4 M- w% O5 u a16a45f85ccb45579617c3c0b0668a5b.png ; q; b  b$ ]+ N  L! I( z, C, c- J! J
4 |; K+ Y& U2 F  S  A
图27.1.2.8 RTC_WUTR寄存器! `0 c' Q' `1 O1 J  P/ x
该寄存器用于设置闹铃A,当WDSEL选择1时,使用星期制闹铃,本章我们选择星期制闹铃。该寄存器的配置,必须等待RTC_ISR的ALRAWF为1才可以进行。另外,还有RTC_ALRMASSR寄存器,该寄存器我们这里就不再介绍了,大家参考手册。' j) r0 s8 r( G3 a& G( s
RTC写保护寄存器(RTC_WPR)
) n6 m! O; f. w) ZRTC写保护寄存器:RTC_WPR,该寄存器比较简单,低八位有效。上电后,所有RTC寄存器都受到写保护(RTC_ISR[13:8]、RTC_TAFCR和RTC_BKPxR除外),必须依次写入:0XCA、0X53两关键字到RTC_WPR寄存器,才可以解锁。写一个错误的关键字将再次激活RTC的寄存器写保护。) u: A* E& K+ F- N
RTC备份寄存器(RTC_BKPxR)
* b2 G; e" g% O; J" M+ z: XRTC备份寄存器描述如图27.1.2.9所示:
- i# o5 [0 ?$ M; Z" k( k; O. e; R, q* R
4c413bdf804147df9e1840a6f6d6e44d.png
/ l1 x) H; u$ L, u, {* p) ~6 C7 Q  H" i+ ~# `* e  }% `, D' ]
图27.1.2.9 RTC_BKPxR寄存器. ^$ A6 w5 }. }. Z! a1 S7 K+ f
该寄存器组总共有32个,每个寄存器是32位的,可以存储128个字节的用户数据,这些寄存器在备份域中实现,可在VDD电源关闭时通过VBAT保持上电状态。备份寄存器不会在系统复位或电源复位时复位,也不会在MCU从待机模式唤醒时复位。
( I+ k2 W, _: N" J1 l复位后,对RTC和RTC备份寄存器的写访问被禁止,执行以下操作可以使能对RTC及RTC备份寄存器的写访问:
: Y! V! M1 a5 E, m# T1)电源控制寄存器(PWR_CR)的DBP位来使能对RTC及RTC备份寄存器的访问。5 `3 A% T0 Q5 b
2)往RTC_WPR写入0XCA、0X53解锁序列(先写0XCA,再写0X53)。+ {$ d! X  N9 L1 n" ?( Z; a
我们可以用BKP来存储一些重要的数据,相当于一个EEPROM,不过这个EEPROM并不是真正的EEPROM,而是需要电池来维持它的数据。3 f# p2 a/ b0 ?2 p0 I, ^7 O- `
备份区域控制(RCC_BDCR)7 c9 ~# U% X$ z/ p. i
备份区域控制寄存器描述如图27.1.2.10所示:
7 a6 _1 w" ]: p" A' U
! e4 f0 _0 j; r: t  A 2e9c125ec0a946d2a813d6d1d47d7469.png
$ d7 M  [  {0 k4 T! [/ D. N) s# ?, Y( a
图27.1.2.10 RCC_BDCR寄存器" Y* d( M- X; M, J. B% X  C5 Z
RTC的时钟源选择及使能设置都是通过这个寄存器来实现的,所以我们在RTC操作之前先要通过这个寄存器选择RTC的时钟源,然后才能开始其他的操作。
' m( ]  s9 C# k
  J# B1 g& j8 b3 O, y) o* x, P; K0 H27.2 硬件设计
' ~7 \- ~$ i# |6 P1.例程功能( A+ w( {& K* [4 G! W+ I9 i
本实验通过LCD显示RTC时间,并可以通过usmart设置RTC时间,从而调节时间,或设置RTC闹钟,还可以写入或者读取RTC后备区域SRAM。LED1每两秒闪烁一次,表示进入WAKE UP中断。LED0闪烁,提示程序运行。
7 O/ g: W+ V1 Q. i/ k. ~2.硬件资源  B- b5 U' E  X" `* q
1)RGB灯% o7 ]2 o! S! |! ]' h/ X: \
RED : LED0 - PB47 B/ P: z4 ^- \
GREEN : LED1 - PE6
) Z; Y' u6 R: |9 R# M) {2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面)
, p' E: k* F& x7 ]; T5 L, J6 |! K3)RTC(实时时钟)1 n% i2 ~. y7 F: C/ p
4)正点原子2.8/3.5/4.3/7寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)
6 b% k( @, n; u! f3.原理图& J8 s( x7 Z9 F
RTC属于STM32H750内部资源,通过软件设置好就可以了。不过RTC不能断电,否则数据就丢失了,我们如果想让时间在断电后还可以继续走,那么必须确保开发板的电池有电。
' c; ^. Q; s2 C& o9 l
2 Z& t% e6 Q& I  g3 @9 v27.3 程序设计5 B4 e- d' [: [: t
27.3.1 RTC的HAL库驱动

4 M4 e& z, p) T" VRTC在HAL库中的驱动代码在stm32h7xx_hal_rtc.c文件(及其头文件)中。下面介绍几个重要的RTC函数,其他没有介绍的请看源码。
+ j% V, t8 Y: V( ~9 a4.HAL_RTC_Init函数) I  v$ y) V. h* ~, e6 C
RTC的初始化函数,其声明如下:- O% B3 Y3 \9 C% `
HAL_StatusTypeDef HAL_RTC_Init(RTC_HandleTypeDef *hrtc);
5 e1 x7 l+ d, b- `% O函数描述:
; Y2 K7 o/ f7 O% _$ U% D' v4 P用于初始化RTC。
( I2 V! K' Q9 u# u4 L$ G函数形参:
& R- y8 S$ Q# S0 c. l! r形参1是RTC_HandleTypeDef结构体类型指针变量,其定义如下:. U% j( c& q+ R7 c
  1. typedef struct
    6 R2 e# ?- M. z. n
  2. {; y- `5 \* V6 o6 S0 q
  3.   RTC_TypeDef                    *Instance;         /* 寄存器基地址 */
    : }) N: v* h* w1 C
  4.   RTC_InitTypeDef               Init;               /* RTC配置结构体 */9 A4 `9 G9 |. _2 P
  5.   HAL_LockTypeDef               Lock;               /* RTC锁定对象 */# [7 c/ w" S* o& ]
  6.   __IO HAL_RTCStateTypeDef    State;              /* RTC设备访问状态 */( q% h6 i1 L4 t, n7 q6 q+ R$ Z5 `) G
  7. }RTC_HandleTypeDef;  W. C3 t( m  N! j" O3 M
  8. 1)Instance:指向RTC寄存器基地址。' a6 F' `& [+ C, {/ t/ x9 M5 W
  9. 2)Init:是真正的RTC初始化结构体,其结构体类型RTC_InitTypeDef定义如下:" |6 c/ n9 R. W0 V
  10. typedef struct. I0 C3 L$ t; V/ s/ d- f1 W
  11. {$ L! X0 B# }! x+ A) ]. Q% h6 \
  12.   uint32_t HourFormat;               /* 小时格式 */
    $ j2 b/ E: a- b, G' J- h3 V
  13.   uint32_t AsynchPrediv;             /* 异步预分频系数 */
    9 ~1 Q! _& G% C3 [
  14.   uint32_t SynchPrediv;              /* 同步预分频系数 */  * o# w+ a: P$ P, i
  15.   uint32_t OutPut;                    /* 选择连接到RTC_ALARM输出的标志 */   ! W7 {' G! N: V4 g1 B0 k
  16. uint32_t OutPutRemap;              /* 指定RTC输出的映射 */
    " w: r' `. k  `
  17.   uint32_t OutPutPolarity;          /* 设置RTC_ALARM的输出极性 */% k2 ~5 N- F' c5 H" I5 S% q
  18.   uint32_t OutPutType;               /* 设置RTC_ALARM的输出类型为开漏输出还是推挽输出 */  4 b! R) k$ j6 g5 G' p6 W8 {( V8 k4 t
  19. }RTC_InitTypeDef;
复制代码

) t) P$ i1 [" MHourFormat用来设置小时格式,可以是12小时制或者24小时制,这两个选项的宏定义分别为RTC_HOURFORMAT_12和RTC_HOURFORMAT_24。8 X0 [/ V& T$ w' I, m; S9 b. j2 O
AsynchPrediv用来设置RTC的异步预分频系数,也就是设置RTC_PRER寄存器的PREDIV_A相关位,因为异步预分频系数是7位,所以最大值为0x7F,不能超过这个值。0 W# k+ n* j% F4 G: ]  X0 `$ ]
SynchPrediv用来设置RTC的同步预分频系数,也就是设置RTC_PRER寄存器的PREDIV_S相关位,因为同步预分频系数也是15位,所以最大值为0x7FFF,不能超过这个值。) S& X: C5 @6 x" H" C
OutPut用来选择要连接到RTC_ALARM输出的标志,取值为:RTC_OUTPUT_DISABLE(禁止输出),RTC_OUTPUT_ALARMA(使能闹钟A输出),RTC_OUTPUT_ALARMB(使能闹钟B输出)和RTC_OUTPUT_WAKEUP(使能唤醒输出)。6 S% s; H; Q" b; v
OutPutRemap用于设置指定RTC输出的映射,即配置RTC_OR寄存器。
# P0 k3 F! _7 H$ sOutPutPolarity用来设置RTC_ALARM的输出极性,与Output成员变量配合使用,取值为RTC_OUTPUT_POLARITY_HIGH(高电平)或RTC_OUTPUT_POLARITY_LOW(低电平)。
& Y9 q6 w) Z, n0 M8 WOutPutType用来设置RTC_ALARM的输出类型为开漏输出(RTC_OUTPUT_TYPE_ OPENDRAIN)还是推挽输出(RTC_OUTPUT_TYPE_PUSHPULL),与成员变量OutPut和OutPutPolarity配合使用。( m4 \0 t2 O, Z0 l
3)Lock:用于配置锁状态。, ~! ~  I8 {* \# B
4)State:RTC设备访问状态。
' A% t8 I# A3 L2 X/ \" _- {函数返回值:$ ^7 t' h; R: |5 _: {& |, a
HAL_StatusTypeDef枚举类型的值。, O9 f. F: H3 Q6 m
2. HAL_RTC_SetTime函数
2 K5 ?+ ]$ b6 U+ Q( VHAL_RTC_SetTime是设置RTC的时间函数。其声明如下:
5 G& l# W3 P+ n  r) b1 R# ~HAL_StatusTypeDef HAL_RTC_SetTime(RTC_HandleTypeDef *hrtc,
. I4 a/ U! A7 _! d, P4 mRTC_TimeTypeDef *sTime, uint32_t Format);* ?' `$ w+ d" d; V
函数描述:
0 |8 j9 H% o1 C9 v4 X5 E$ y, k该函数用于设置RTC的时间,即设置时间寄存器RTC_TR的相关位的值。
5 E! O: @" k5 C  \函数形参:& _' H  ?- F# |
形参1是RTC_HandleTypeDef结构体类型指针变量,即RTC的句柄。
* E0 ~1 y: E/ ~- u4 f" L形参2是RTC_TimeTypeDef结构体类型指针变量,定义如下:1 k2 i+ M# I+ {1 ~4 h# ^+ i9 o8 e
! N% C5 s! Q" Q
  1. typedef struct+ R) T  s+ U% h" j  W# e
  2. {* t; e7 ]! t. b  Q/ Y* l) p
  3.   uint8_t Hours;
    6 R( j( F* Q* Q: H0 |& E
  4.   uint8_t Minutes;
      \1 T, Y( `7 u! M: s- H
  5.   uint8_t Seconds;" j- d9 G2 ?' S: P
  6.   uint8_t TimeFormat;8 X$ r; q* u7 H2 z7 d% [7 L" [! M4 z
  7.   uint32_t SubSeconds;9 B6 V0 B+ J; S* i7 n: c+ E) ~
  8.   uint32_t SecondFraction;
    ) ?5 l+ A4 A* \) z5 e. C
  9.   uint32_t DayLightSaving;$ h" Y9 k* S- x# ?# U% f# w
  10.   uint32_t StoreOperation; $ o- R( t0 z3 C: w5 o7 I
  11. }RTC_TimeTypeDef;
复制代码
, a- L8 A( L$ i6 D
前面四个成员变量就比较好理解了,分别用来设置RTC时间参数的小时,分钟,秒钟,以及AM/PM符号,大家参考前面讲解的RTC_TR的位描述即可。SubSeconds用来读取保存亚秒寄存器 RTC_SSR的值,SecondFraction用来读取保存同步预分频系数 的值,也就是RTC_PRER的位0~14,DayLightSaving用来设置日历时间增加1小时,减少1小时,还是不变。StoreOperation用户可对此变量设置以记录是否已对夏令时进行更改。
2 ?) @0 _8 |  D2 r& J  p形参3是uint32_t类型变量,用来设置输入的时间格式为BIN格式还是BCD格式,可选值为RTC_FORMAT_BIN或者RTC_FORMAT_BCD。
# ?* Y+ m7 A" h4 j函数返回值:- c8 x& \$ I4 K! k+ }
HAL_StatusTypeDef枚举类型的值。4 t% m9 Y1 P+ o' L( K7 N* `
3. HAL_RTC_SetDate函数' H4 Y% K$ ~4 F( D; i( i
HAL_RTC_SetDate是设置RTC的日期函数。其声明如下:  W* H' e' m8 a8 i3 m
HAL_StatusTypeDef HAL_RTC_SetDate(RTC_HandleTypeDef *hrtc,) t2 i- p5 E$ B- K: _5 d0 @& r; ^- d  D
RTC_DateTypeDef *sDate, uint32_t Format);6 }& L* v% G  W8 ?2 Z
函数描述:
0 v5 q0 Q7 z+ E# X1 R2 \8 m% e该函数用于设置RTC的日期,即设置日期寄存器RTC_DR的相关位的值。' [7 q, {2 z# Q9 D7 K
函数形参:4 w' i! r" I9 t" d5 X: C4 A
形参1是RTC_HandleTypeDef结构体类型指针变量,即RTC的句柄。" _  G# O6 F% }; G3 N5 Y, N' @! z
形参2是RTC_DateTypeDef结构体类型指针变量,定义如下:; o+ W' K: o5 a
* f& E: K7 ]0 a* R' C: V7 f
  1. typedef struct
    + h! G4 N6 t4 R# z+ L0 f' P
  2. {0 R* a& [# M; u, K
  3.   uint8_t WeekDay;          /* 星期 */
    / D& I7 x- Z! y, Q6 X0 u
  4.   uint8_t Month;            /* 月份 */& X: J4 h1 C0 x' ]
  5.   uint8_t Date;             /* 日期 */
    $ Z, s) k/ N9 L1 H4 l
  6.   uint8_t Year;             /* 年份 */% _2 H: }+ N0 T) f" |
  7. }RTC_DateTypeDef;
复制代码
' E5 T8 A" b$ m$ l
结构体一共四个成员变量,这四个成员变量分别对应星期、月份、日期和年份,对应的是RTC_DR寄存器。
% l0 N$ ?1 U7 p" M7 O6 s形参3是uint32_t类型变量,用来设置输入的时间格式为BIN格式还是BCD格式,可选值为RTC_FORMAT_BIN或者RTC_FORMAT_BCD。% D3 q1 F8 C) ~/ F2 c$ }* c
函数返回值:
& N+ U1 k$ @( oHAL_StatusTypeDef枚举类型的值。4 |& }; e/ l1 p" c/ @
4. HAL_RTC_GetTime函数
( p- }0 ^. U6 F5 X- b8 \) y' O+ u+ pHAL_RTC_GetTime是获取当前RTC时间函数。其声明如下:
) L4 o! [7 k4 u# A8 f- iHAL_StatusTypeDef HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc,( r) T/ ]7 l6 ]  \
RTC_TimeTypeDef *sTime, uint32_t Format);1 ]1 s9 E, k! b/ {9 b
函数描述:2 q# i$ y+ s& j) T; p% [4 @  A
该函数用于获取当前RTC时间,即读时间寄存器RTC_TR的相关位的值。- f: z5 c2 F" f! r+ }5 x) h/ T
函数形参:- |4 o6 w& j" g- m0 m% U% X
形参1是RTC_HandleTypeDef结构体类型指针变量,即RTC的句柄。3 k, R4 B; x; o- B. v& c
形参2是RTC_TimeTypeDef结构体类型指针变量,对应的是RTC_TR寄存器。8 X9 P- S& s0 |+ ]
形参3是uint32_t类型变量,用来设置获取的时间格式为BIN格式还是BCD格式,可选值为RTC_FORMAT_BIN或者RTC_FORMAT_BCD。* v+ }. W9 v* h. M8 ~" Z( U
函数返回值:
( H0 p' p$ f- `% E8 U8 rHAL_StatusTypeDef枚举类型的值。6 ~" ~( L$ m" K  R4 l
5. HAL_RTC_SetDate函数& f: ]# l% q3 N+ w6 G5 g
HAL_RTC_SetDate是获取当前RTC日期函数。其声明如下:& |9 U  |8 C; F& E0 O$ W
HAL_StatusTypeDef HAL_RTC_SetDate (RTC_HandleTypeDef *hrtc,
! I- d' J9 ?: t, YRTC_DateTypeDef *sDate, uint32_t Format);4 i( y" n3 R# c6 r! S
函数描述:
2 a* ~0 H7 o' d; A2 v该函数用于获取当前RTC日期,即读时间寄存器RTC_DR的相关位的值。$ a$ g- n2 g# r7 L- D" X5 g
函数形参:) z7 _- H# A0 i8 `! f9 }& i
形参1是RTC_HandleTypeDef结构体类型指针变量,即RTC的句柄。9 k1 `# o) e& ~8 N5 Q7 o
形参2是RTC_DateTypeDef结构体类型指针变量,对应的是RTC_DR寄存器。
. g& ?; m2 q/ E" ]9 n: S形参3是uint32_t类型变量,用来设置获取的时间格式为BIN格式还是BCD格式,可选值为RTC_FORMAT_BIN或者RTC_FORMAT_BCD。
4 k/ \- k; ]3 L5 V: Y; ]函数返回值:
  |/ \4 R) A1 Y  THAL_StatusTypeDef枚举类型的值。
8 ]' T3 {' S6 N3 ?$ q' D6. HAL_RTC_SetAlarm_IT函数$ k5 N) n9 e# }# {; v; H
HAL_RTC_SetAlarm_IT是设置闹钟并开启闹钟中断的函数。其声明如下:& J  J. S1 w  Y* b
HAL_StatusTypeDef HAL_RTC_SetAlarm_IT(RTC_HandleTypeDef *hrtc,0 u$ k* `% f# b6 X& s
RTC_AlarmTypeDef *sAlarm, uint32_t Format);1 o) I. `* G' w* y9 A4 Y: W  j
函数描述:# K$ y2 q/ i: J) {
该函数用于设置闹钟并开启闹钟中断。
" h, e" s( I; o, B/ O函数形参:) h/ s# e! E( G7 d( e$ q
形参1是RTC_HandleTypeDef结构体类型指针变量,即RTC的句柄。6 Q3 G- Z# T( U( h
形参2是RTC_AlarmTypeDef结构体类型指针变量。
( M6 |% b- a" r& I8 W形参3是uint32_t类型变量,用来设置获取的时间格式为BIN格式还是BCD格式,可选值为RTC_FORMAT_BIN或者RTC_FORMAT_BCD。1 R/ K; B9 E4 g8 g6 h; ?+ L. Y
重点介绍RTC_AlarmTypeDef 结构体指针类型,结构体定义如下:6 q7 H0 |* c4 T9 H1 d

( \/ I% ~! W, @; K0 I- E4 r0 b
  1. typedef struct7 D9 h" n, ^' G
  2. {
    1 p9 h" v, T" o" _
  3.   RTC_TimeTypeDef AlarmTime;  5 [4 }2 \" e; g& n
  4.   uint32_t AlarmMask;      ( L; E: W* v4 M0 |* H- ^, l
  5.   uint32_t AlarmSubSecondMask;, G1 c2 [8 V; P, G* h2 i
  6.   uint32_t AlarmDateWeekDaySel;9 F! C1 R4 I* ~$ q, [! W( S
  7.   uint8_t AlarmDateWeekDay;                                   
    # }. @, w' E! t+ r' Z
  8.   uint32_t Alarm;                             
    7 `" b5 y$ m. R
  9. }RTC_AlarmTypeDef;
复制代码
' y1 m* |/ i3 s  C
AlarmTime用来设置闹钟时间,是RTC_TimeTypeDef结构体类型,该结构体前面我们已经讲解过各个成员变量含义。
! e$ H/ k: F) E$ I# u5 i5 ZAlarmMask用来设置闹钟时间掩码,也就是在我们第一个参数设置的时间中(包括后面参数RTC_AlarmDateWeekDay设置的星期几/哪一天),哪些是无关的。 比如我们设置闹钟时间为每天的10点10分10秒,那么我们可以选择值RTC_AlarmMask_DateWeekDay,也就是我们不关心是星期几/每月哪一天。这里我们选择为RTC_AlarmMask_None,也就是精确匹配时间,所有的时分秒以及星期几/(或者每月哪一天)都要精确匹配。
8 O9 i( U5 r% WAlarmSubSecondMask和AlarmMask作用类似,只不过该变量是用来设置亚秒的。
  ^  Z, r) B) s3 Q9 ]5 m/ C" Z- z  ^6 AAlarmDateWeekDaySel用来选择是闹钟是按日期还是按星期。比如我们选择RTC_AlarmDateWeekDaySel_WeekDay那么闹钟就是按星期。如果我们选择RTC_AlarmDateWeekDaySel_Date那么闹钟就是按日期。这与后面第四个参数是有关联的,我们在后面第四个参数讲解。
6 O( a8 z2 W3 z" @" ]' L8 ^AlarmDateWeekDay用来设置闹钟的日期或者星期几。比如我们第三个参数RTC_AlarmDateWeekDaySel设置了值为RTC_AlarmDateWeekDaySel_WeekDay,也就是按星期,那么参数RTC_AlarmDateWeekDay的取值范围就为星期一到星期天,也就是RTC_Weekday_Monday到RTC_Weekday_Sunday。如果第三个参数RTC_AlarmDateWeekDaySel设置值为RTC_AlarmDateWeekDaySel_Date,那么它的取值范围就为日期值,0~31。1 n' C* k8 b# O& i3 \) U1 q
Alarm用来设置是闹钟A还是闹钟B,这个很好理解。+ p' r8 i2 z& r& z* c
函数返回值:
5 W: m* \/ Z9 V2 DHAL_StatusTypeDef枚举类型的值。+ W8 A4 Y. Q0 N$ c- y
RTC配置步骤
2 K$ s" v8 z; F8 k* O6 t8 S3 }1)使能RTC时钟,并使能RTC及RTC后备寄存器写访问
( k' V1 d* {  j: d$ A- Z复位后,后备区域存在写保护,RTC属于后备区域,所以我们需要取消后备区域写保护。同时,我们需要使能RTC时钟,通过RCC_APB4ENR寄存器中的RTCAPBEN位设置。而取消后备区域写保护则是通过PWR_CR1寄存器中的DBP位去设置。HAL库设置方法为:
( d9 F0 T* r' Z( W__HAL_RCC_RTC_CLK_ENABLE(); /* 使能RTC时钟 /6 ]. _, m. q3 }; ^" O' Q
HAL_PWR_EnableBkUpAccess(); / 取消备份区域写保护 */
- i1 g$ y( P. S; t3 T7 ^2)开启外部低速振荡器LSE,选择RTC时钟,并使能
! [( S7 Q: |  F! Z* c调用HAL_RCC_OscConfig函数配置开启LSE。. M+ n. `* S. [9 }4 ]
调用HAL_RCCEx_PeriphCLKConfig函数选择RTC时钟源。
1 V8 d% @& m! p* G' b7 n* k使能RTC函数为:__HAL_RCC_RTC_ENABLE。$ m' ^) z  Q2 ]; H, _0 u
3)初始化RTC,设置RTC的分频,以及配置RTC参数1 B/ c" _1 N2 l+ O; b1 N6 e
在HAL中,通过HAL_RTC_Init函数配置RTC分频系数,以及RTC的工作参数。/ U" x4 t' d7 i' C6 f! p
注意:该函数会调用:HAL_RTC_MspInit函数来完成对RTC的底层初始化,包括:RTC时钟使能、时钟源选择等。
! t! e$ U6 f) {2 u; D  z4)设置RTC的时间$ |0 C$ e2 h% i7 [# k
调用HAL_RTC_SetTime函数设置RTC时间,该函数实际设置时间寄存器RTC_TR的相关位的值。, b- N7 F. k+ m0 W9 n
5)设置RTC的日期% ~) x' E* M% C$ m7 V0 B  j
调用HAL_RTC_SetDate函数设置RTC的日期,该函数实际设置日期寄存器RTC_DR的相关位的值。
  t0 \6 {6 A6 {% k* X3 {6 @: _/ x6)获取RTC当前日期和时间
. m1 I7 R9 ~$ V0 K( N: ]2 z调用HAL_RTC_GetTime函数获取当前RTC时间,该函数实际读取RTC_TR寄存器,然后将值存放到相应的结构体中。- Y$ C: T0 W9 z/ H
调用HAL_RTC_GetDate函数获取当前RTC日期,该函数实际读取RTC_DR寄存器,然后将值存放到相应的结构体中。7 a6 \5 Z, |1 e
通过以上6个步骤,我们就完成了对RTC的配置,RTC即可正常工作,而且这些操作不是每次上电都必须执行的,可以视情况而定。当然,我们还可以唤醒中断、闹钟等,这些将在后面介绍。
! f, t( `% U0 |9 y0 \8 e9 F3 g$ c- w" L' e; z) P. w0 V
27.3.2 程序流程图
6 U8 ~9 W- ~) o+ z9 z% ]+ d
# T* `2 F' s" r( M c017d17481694bde8477506a1d9ee6e6.png + X: @- g$ }; j4 \) v  ?! _

/ \/ J) Z- {: T9 `图27.3.2.1 RTC实时时钟实验程序流程图
7 ^5 o0 K  k8 L6 ~( Z, k! [: g  [$ K  `) Z
27.3.3 程序解析( w- Z6 V% l( m1 f8 [: m
1.RTC驱动代码. ^3 z4 K7 z% x# I* |/ f0 L
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。RTC驱动源码包括两个文件:rtc.c和rtc.h。
; c1 b( k7 J4 ?0 F7 P5 W5 @( ?由于篇幅所限,rtc.c中的代码,我们不全部贴出了,只针对几个重要的函数进行介绍。
: N( Q# }8 Y# L- {rtc.h头文件只有函数的声明,下面我们直接介绍rtc.c的程序,先看RTC的初始化函数,其定义如下:
4 k& y1 J# S1 Z) o
  1. /**
    + A6 T; F; _. [" a+ o
  2. * @brief            RTC初始化) G0 R7 Y  r: h( a/ e: Z- B8 T& a
  3. *   @note
    " ]; D5 T8 G) }9 Q/ ^% D
  4. *                      默认尝试使用LSE,当LSE启动失败后,切换为LSI., L$ @: p& N# {* E* K
  5. *                      通过BKP寄存器0的值,可以判断RTC使用的是LSE/LSI:4 B2 q7 }- ~5 v- t
  6. *                      当BKP0==0X5050时,使用的是LSE
    1 p1 p' V& y4 Z, e1 g
  7. *                      当BKP0==0X5051时,使用的是LSI: B- H0 Z# Y" s/ R  o' _5 j4 g+ D, Z
  8. *                      注意:切换LSI/LSE将导致时间/日期丢失,切换后需重新设置.5 C3 m7 @5 [! G: }% g
  9. *
    8 V3 Y6 t5 D: E# [3 y# q4 e& o
  10. * @param             无% H+ z6 ^, `5 V$ M% d" x
  11. * @retval           0,成功
    & q5 V* c) K  t) K! v
  12. *                     1,进入初始化模式失败
    1 c0 r/ C# [% B$ [
  13. */+ i- r- ^" b3 B) h% l
  14. uint8_t rtc_init(void), W" e4 a, u6 d0 a
  15. {" }9 R0 j( \; P9 ^9 L  P; Q
  16.     /* 检查是不是第一次配置时钟 */
    * b* W8 z# n. l, e2 y/ _4 w) i
  17.     uint16_t bkpflag = 0;/ b8 k6 I3 f9 @5 C6 K# Q
  18. : r+ Z# q+ _0 t7 m$ V# H
  19.     g_rtc_handle.Instance = RTC;  e  d* v0 P. j( E% L
  20.     g_rtc_handle.Init.HourFormat = RTC_HOURFORMAT_24;        /* RTC设置为24小时格式 */# M3 H% U" I7 {) e7 J4 c) b9 g( |
  21.     g_rtc_handle.Init.AsynchPrediv = 0X7F;                 /* RTC异步分频系数(1~0X7F) */7 B2 u& k1 J3 T
  22.     g_rtc_handle.Init.SynchPrediv = 0XFF;                 /* RTC同步分频系数(0~7FFF) */
    - ~. L2 b$ a5 K; ~  w. Y, R+ v
  23.     g_rtc_handle.Init.OutPut = RTC_OUTPUT_DISABLE;     4 a, r5 n6 F7 k; ]0 W$ X  ^( q
  24.     g_rtc_handle.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
    % @+ `0 t% Y+ s7 d* X4 _/ ^6 ]0 _
  25.     g_rtc_handle.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
    8 }2 V; j- t( ~0 o" c6 I  C* b5 s( H7 J
  26. + `/ u2 f9 n2 n8 F# v2 H2 Z" ^
  27.     bkpflag = rtc_read_bkr(0);  /* 读取BKP0的值 */
    3 U& n4 D( [9 n

  28. + ?# U1 d8 T$ o  e! B
  29.     if (HAL_RTC_Init(&g_rtc_handle) != HAL_OK)% S, B' _7 w, M* c( w
  30.     {2 |; k6 a  s- ~0 s& c+ [5 k( ?+ ^' ?
  31.         return 1;0 S% G% J2 j# r$ k
  32.     }
    ( [' t* P) j3 q! Y9 \8 x# T) C0 E7 h

  33.   ?, T/ C0 K% n
  34.     if ((bkpflag != 0X5050) && (bkpflag != 0x5051))                /* 之前未初始化过 重新配置 */! o% a( w: s8 c
  35.     {, F4 a3 Y+ w* k7 `3 s
  36.         rtc_set_time(23, 59, 56, RTC_HOURFORMAT12_AM);        /* 设置时间*/
    ; j) l: ~+ Q3 K9 s# h6 D
  37.         rtc_set_date(20, 1, 13, 7);                             /* 设置日期 */8 o# t2 B- P5 H( p
  38.     }
    ' m6 z2 r0 A" X0 \
  39. 4 _9 L1 f7 {$ n1 n
  40.     return 0;
    ; a- z. y5 V2 z
  41. }
复制代码
+ v8 M# D% r: f. H
该函数用来初始化RTC配置以及日期和时钟,但是只在第一次的时候设置时间,以后如果重新上电/复位都不会再进行时间设置了(前提是备份电池有电)。在第一次配置的时候,我们是按照上面介绍的RTC初始化步骤调用函数HAL_RTC_Init来实现的。
5 T( b' j6 S2 e; j5 E' M. A我们通过读取BKP寄存器0的值来判断是否需要进行时间的设置,对BKP寄存器0的写操作是在HAL_RTC_MspInit回调函数中实现,下面会讲。第一次未对RTC进行初始化BKP寄存器0的值非0x5050非0x5051,当进行RTC初始化时,BKP寄存器0的值就是0x5050或0x5051,所以以上代码操作确保时间只会设置一次,复位时不会重新设置时间。电池正常供电时,我们设置的时间不会因复位或者断电而丢失。
4 g+ h$ ]: {* J& I4 e% O: c读取后备寄存器的函数其实还是调用HAL库提供的函数接口,写后备寄存器函数同样也是。这两个函数如下:. k1 ^. v& x! ~0 n
uint32_t HAL_RTCEx_BKUPRead(RTC_HandleTypeDef *hrtc, uint32_t BackupRegister);
4 n. e+ A% J+ [' k: dvoid HAL_RTCEx_BKUPWrite(RTC_HandleTypeDef *hrtc, uint32_t BackupRegister,5 n1 i, a7 i1 Y" r: a+ G
uint32_t Data);
9 {' @1 X# E1 w- I& n: {这两个函数的使用方法就非常简单,分别用来读和写BKR寄存器的值。这里我们只是略微点到为止,详看例程源码。
1 y6 l2 K1 G( w. G  P这里设置时间和日期,分别是通过rtc_set_time和rtc_set_date函数来实现的,这两个函数实际就是调用库函数里面的HAL_RTC_SetTime函数和HAL_RTC_SetDate函数来实现,这里我们之所以要写两个这样的函数,目的是为了我们的USMART来调用,方便直接通过USMART来设置时间和日期。rtc_set_time和rtc_set_date实现十分简单,这里讲解了,详看源码。0 j, o8 j* Q1 ~" p/ r' w
接下来,我们用HAL_RTC_MspInit函数来编写RTC时钟配置等代码,其定义如下:
: j+ r- u( V( m/ x$ b* j1 L( U5 D1 B
  1. void HAL_RTC_MspInit(RTC_HandleTypeDef* hrtc)
    % j  k! [+ m* a- Y9 c0 d- \
  2. {( x( {5 ?% a, Y
  3.     uint16_t retry = 200;# f+ S8 I% }1 T5 A

  4. 3 K/ L5 _! T$ A1 v7 A
  5.     RCC_OscInitTypeDef rcc_osc_init_handle;
    ) o5 @$ V( p, r) f& i( h  U- K$ L! T
  6.     RCC_PeriphCLKInitTypeDef rcc_periphclk_init_handle;
    3 Q6 F2 l. W2 C8 c' @, T
  7. 7 M" m0 r9 h. W3 T' }
  8.     __HAL_RCC_RTC_CLK_ENABLE();           /* 使能RTC时钟 */
    . t  ?7 r' K$ S
  9.     HAL_PWR_EnableBkUpAccess();           /* 取消备份区域写保护 */! W4 ~7 I1 K, F2 S
  10.     __HAL_RCC_RTC_ENABLE();                    /* 使能RTC */
    5 f" S$ }2 A9 M9 k
  11. / y# q$ u+ H* |3 r0 @
  12.     /* 使用寄存器的方式去检测LSE是否可以正常工作 */
    ! n0 x. m9 g& J. w
  13.     RCC->BDCR |= 1 << 0;        /* 开启外部低速振荡器LSE */  @, t1 W2 J: ]9 ?  j

  14. 8 p) k8 _( z! I5 X; q
  15.     while (retry && ((RCC->BDCR & 0X02) == 0))  /* 等待LSE准备好 */
    " v: i0 h7 A2 R  W) `
  16.     {
    0 A# d9 Z( R8 Q  e8 Z
  17.         retry--;# @, I8 M. l! {1 C5 y  `( B* S+ O8 F1 K
  18.         delay_ms(5);
    " N# H, \' k& @4 o
  19.     }
    $ B% C5 Q8 J$ J% c$ l

  20. 0 s. H! i  _* \- t1 T7 Z3 Z% j
  21.     if (retry == 0)                /* LSE起振失败,使用LSI */
    ( c; ?2 U; p- _9 R
  22.     {   /* 选择要配置的振荡器 */  i! v# H5 R# V; C# M
  23.         rcc_osc_init_handle.OscillatorType = RCC_OSCILLATORTYPE_LSI;  5 e( @0 A1 b6 o9 W- ?
  24.         rcc_osc_init_handle.LSEState = RCC_LSI_ON;                          /* LSI状态:开启 */' T( K; i8 y9 M6 W2 p7 t" f2 ~8 |" z+ Z
  25.         rcc_osc_init_handle.PLL.PLLState = RCC_PLL_NONE;        /* PLL无配置 */
    + u1 f$ B$ `) @
  26.         HAL_RCC_OscConfig(&rcc_osc_init_handle);                      2 U) g) N( j4 O# a

  27. 3 A9 e# a4 S% Q/ J
  28. /* 选择要配置的外设 RTC */& C7 ^0 A# n3 m6 L7 x) e
  29.         rcc_periphclk_init_handle.PeriphClockSelection = RCC_PERIPHCLK_RTC;   3 Q" w. L* h  _9 C1 N
  30. /* RTC时钟源选择 LSI */, E  S% Z, z6 {* Y$ r
  31.         rcc_periphclk_init_handle.RTCClockSelection = RCC_RTCCLKSOURCE_LSI;   
    7 Y/ O( {; M) S# \, j( o
  32.         HAL_RCCEx_PeriphCLKConfig(&rcc_periphclk_init_handle);
    / V; _6 f8 [) c
  33.         rtc_write_bkr(0, 0X5051);
    6 D7 F) {" q  \+ u0 V8 G; ?
  34.     }" G) |! y  F% H# d3 e" W
  35.     else
    9 Z6 o3 W0 a1 w
  36.     {
    ; c3 Q! e$ B! K& v
  37.         rcc_osc_init_handle.OscillatorType = RCC_OSCILLATORTYPE_LSE;   5 x0 Q' v1 e) M2 ^. l4 M6 ?
  38.         rcc_osc_init_handle.PLL.PLLState = RCC_PLL_NONE;          /* PLL不配置 */
    6 \  ?  x, }7 I' R5 j
  39.         rcc_osc_init_handle.LSEState = RCC_LSE_ON;                /* LSE状态:开启 */
    ! X, v  }7 o* V2 H6 A9 Y( B
  40.         HAL_RCC_OscConfig(&rcc_osc_init_handle);                        ) N- L* p  o2 \$ i8 v

  41. 2 e8 P* r7 I% S& Q" D
  42.         rcc_periphclk_init_handle.PeriphClockSelection = RCC_PERIPHCLK_RTC;
    2 o3 \# n( D  h# [) \* F
  43.         rcc_periphclk_init_handle.RTCClockSelection = RCC_RTCCLKSOURCE_LSE; / M+ b2 n' _. W8 z" }" F
  44.         HAL_RCCEx_PeriphCLKConfig(&rcc_periphclk_init_handle);              
    1 S- q* {5 G- r# X) h+ Z
  45.         rtc_write_bkr(0, 0X5050);: j. `7 r$ ]$ Z+ |
  46.     }
    1 u" }: ]% _' o. A2 R; b
  47. }+ H& E: `* \9 j  g6 s6 l; J5 [
  48. 接着,我们介绍一下rtc_set_alarma函数,其定义如下:
    ) }' u& T4 U# s5 F5 l
  49. /**
    - W4 c0 d$ J5 ^, C3 \/ H# g1 S
  50. * @breif              设置闹钟时间(按星期闹铃,24小时制)
    9 b' @3 g! n  E& J
  51. * @param               week        : 星期几(1~7) ' c5 Y: T5 m5 b/ j. P
  52. * @param               hour,min,sec: 小时,分钟,秒钟( c* p; X- z1 w) I5 f
  53. * @retval              无3 A. f( _1 H; Z4 M) k! z0 S
  54. */
    3 p, y* V! I6 {# V2 w& U2 C
  55. void rtc_set_alarma(uint8_t week, uint8_t hour, uint8_t min, uint8_t sec)
    , }# H; T& I% y" B/ [* E
  56. {
    . P( L  D: A$ `4 H* M1 P
  57.     RTC_AlarmTypeDef rtc_alarm_handle;
    " u! {% S. a/ @

  58. 5 e9 ]5 T6 x/ B0 {( T: D# p! m
  59.     rtc_alarm_handle.AlarmTime.Hours = hour;                        /* 小时 */3 V* K' j0 H2 O5 K& B5 R
  60.     rtc_alarm_handle.AlarmTime.Minutes = min;                   /* 分钟 */* `: F" B# @9 v( j5 X
  61.     rtc_alarm_handle.AlarmTime.Seconds = sec;                   /* 秒 */5 d9 o/ D. W. F' d- u: c
  62.     rtc_alarm_handle.AlarmTime.SubSeconds = 0;  n$ G% N7 @% k+ R& ^  v
  63.     rtc_alarm_handle.AlarmTime.TimeFormat = RTC_HOURFORMAT12_AM;
    : z" p: Y: I8 e* w

  64. ; p0 N1 X. h/ H, S& d; H, t
  65.     rtc_alarm_handle.AlarmMask = RTC_ALARMMASK_NONE;         /* 精确匹配星期,时分秒 */; W7 u6 g2 [* p
  66.     rtc_alarm_handle.AlarmSubSecondMask = RTC_ALARMSUBSECONDMASK_NONE;
    & @, p4 Q: N4 J1 s
  67.     /* 按星期 */
    : W  y& g: t$ m, X2 I5 O
  68. rtc_alarm_handle.AlarmDateWeekDaySel=RTC_ALARMDATEWEEKDAYSEL_WEEKDAY;2 ~& l7 E# y% O8 C; ?( }( s1 x
  69.     rtc_alarm_handle.AlarmDateWeekDay = week;                       /* 星期 */8 F1 I$ p0 y4 J8 \$ d( J
  70.     rtc_alarm_handle.Alarm = RTC_ALARM_A;                            /* 闹钟A */" `* n* A8 V; @- Z
  71.     HAL_RTC_SetAlarm_IT(&g_rtc_handle, &rtc_alarm_handle, RTC_FORMAT_BIN);8 M7 l/ V7 P* f. d9 G' Q
  72. ! E+ N( X) a' E6 K7 \1 H
  73.     HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 1, 2);   /* 抢占优先级1,子优先级2 */
    9 N8 _* {2 a( F! N! [$ \
  74.     HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);* j0 a7 f+ ?1 v6 ]/ c2 G  j) b
  75. }
复制代码

1 ]% m! W2 G& B4 E- k- F# E该函数用于设置闹钟A,也就是设置ALRMAR和ALRMASSR寄存器的值,来设置闹钟时间,这里使用HAL库的HAL_RTC_SetAlarm_IT函数来初始化闹钟相关寄存器,前面已经介绍过这个函数,请回顾。0 U  R$ e- u- w5 Q& R" R

! N9 p7 G# [9 ]0 m1 h调用函数rtc_set_wakeup设置闹钟A的参数之后,最后,开启闹钟A中断(连接在外部中断线17),并设置中断分组。当RTC的时间和闹钟A设置的时间完全匹配时,将产生闹钟中断。
3 n: h+ h% K. W; K8 U9 m接着,我们介绍一下rtc_set_wakeup函数,该函数代码如下:
8 o/ ]' N6 Z: \' |2 ]# k+ ~# P8 v( t% R4 Q- G- Y$ a$ r
  1. /**+ z! |( P$ s+ S
  2. * @breif             周期性唤醒定时器设置
    9 B% c# Z) ~) f+ S% w# |. u
  3. * @param               wksel! j7 |( [) j7 W. S' D" c" o
  4. *   @arg               RTC_WAKEUPCLOCK_RTCCLK_DIV16              ((uint32_t)0x00000000)8 X' q6 U$ N( k0 l0 `6 |6 U2 I
  5. *   @arg               RTC_WAKEUPCLOCK_RTCCLK_DIV8               ((uint32_t)0x00000001)
    . R; E: @7 V( Q- w9 u" |0 i: y' k
  6. *   @arg               RTC_WAKEUPCLOCK_RTCCLK_DIV4                ((uint32_t)0x00000002)0 d& _, Z5 T! G" q& R
  7. *   @arg               RTC_WAKEUPCLOCK_RTCCLK_DIV2               ((uint32_t)0x00000003)" \$ b2 r. \) \6 B7 y/ m9 E7 @
  8. *   @arg               RTC_WAKEUPCLOCK_CK_SPRE_16BITS            ((uint32_t)0x00000004)& i3 ^2 [5 i* l* k: e, p- \0 w
  9. *   @arg               RTC_WAKEUPCLOCK_CK_SPRE_17BITS            ((uint32_t)0x00000006)
    ! l  q3 l/ \/ i7 R- [
  10. * @note                000,RTC/16;001,RTC/8;010,RTC/4;011,RTC/2;
    # Y- q5 r3 {% N( S
  11. * @note                注意:RTC就是RTC的时钟频率,即RTCCLK!
    : N2 O; @8 g1 J
  12. * @param               cnt: 自动重装载值.减到0,产生中断.
    + h7 X8 g  M& ?$ W6 W1 x
  13. * @retval              无
    ( r0 a. V% K% F8 B/ o$ `" @% ^# c$ M* `
  14. */! y3 M+ u! D, @0 B+ `
  15. void rtc_set_wakeup(uint8_t wksel, uint16_t cnt)1 C+ R2 `9 z" F' U( d
  16. { 6 }& G8 n; F) k
  17. /* 清除RTC WAKE UP的标志 */
    ( v, e1 C2 k& s! K
  18.     __HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&g_rtc_handle, RTC_FLAG_WUTF);  
    0 Z4 m6 H6 E/ C4 i8 M/ t" D4 t$ C
  19.     HAL_RTCEx_SetWakeUpTimer_IT(&g_rtc_handle, cnt, wksel);/* 设置重装载值和时钟 */
    5 y; m4 d2 h. Y3 v
  20.     HAL_NVIC_SetPriority(RTC_WKUP_IRQn, 2, 2);  /* 抢占优先级2,子优先级2 */
    2 ?! @3 f! B/ L* C2 W1 I8 ^
  21.     HAL_NVIC_EnableIRQ(RTC_WKUP_IRQn);
    0 O1 [$ p0 C9 {9 n9 X/ q
  22. }8 }4 M: D. n$ L. h2 [# ?
  23. 该函数用于设置RTC周期性唤醒定时器,实现周期性唤醒中断,连接在外部中断线19。该函数调用的是HAL库函数HAL_RTCEx_SetWakeUpTimer_IT实现的。
    * g. c% s8 d" d; F( Q% v! x: J8 u
  24. 有了中断设置函数,就必定有中断服务函数,同时HAL库会开放中断处理回调函数。接下来看这两个中断的中断服务函数和中断处理回调函数,代码如下:& V0 O& |; y5 g8 v
  25. /**
    ! c) e  K0 [: [1 H% s) [
  26. * @breif             RTC闹钟中断服务函数
      g5 J1 e/ H( t0 X, Y! O& Q/ ?
  27. * @param              无! \3 @* P/ p3 t  }
  28. * @retval              无
    ! h5 r+ g$ N+ y( d, e' x1 j/ `
  29. */, n( K! A1 t+ r
  30. void RTC_Alarm_IRQHandler(void)
    . L2 T" J7 ?( K7 f* G
  31. {
    0 i" H) l2 n# ^, e; n( J. d
  32.     HAL_RTC_AlarmIRQHandler(&g_rtc_handle);: b! O9 h) z/ r8 @2 N+ @3 _7 _& M' b
  33. }0 K+ E" I3 \2 B0 G+ ~0 Z

  34. ' W% J& B6 A0 l- C- ^2 @5 o' @5 F
  35. /**
    * r) m1 Y- h; x% P- N
  36. * @breif             RTC闹钟A中断处理回调函数
    0 q) V  R" o* x4 Y
  37. * @param              hrtc:RTC句柄; F0 j2 k9 E; O. b, Q4 M
  38. * @retval             无
    1 T- o! i6 k6 U+ ~0 \8 V
  39. */
    / x/ ^. ^( F7 d" {/ r: l
  40. void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
    * s/ z0 f7 ]6 j. U7 F; V& M
  41. {" Q( q5 A, L+ ?3 D$ k3 W# S1 r
  42.     printf("ALARM A!\r\n");9 d: n0 l- C: \/ i6 V0 L
  43. }% n, c! ~3 O  ^9 \
  44.   A: p& H' C4 s! |; _+ E! h
  45. /**1 G# z$ r0 p2 w% i& A" ^4 k
  46. * @breif             RTC WAKE UP中断服务函数
    1 d& k- u* J0 b5 V& O
  47. * @param              无
    ) B8 F* ?: g* J% D6 f9 ?6 O
  48. * @retval            无% \$ B2 u5 f7 g5 z
  49. */8 g7 }6 m* m$ H) k  R/ D( ]- t
  50. void RTC_WKUP_IRQHandler(void)
    - t; O5 l. C8 C7 q
  51. {2 A  L, H. l8 ^5 s6 `! {) K% R
  52.     HAL_RTCEx_WakeUpTimerIRQHandler(&g_rtc_handle); $ p% t, u# R' i5 ]$ s7 R
  53. }( S( F1 i# k" i2 h

  54. % m% m" b1 m+ a  t$ F
  55. /**
    " T1 |. h4 b' ^# ], Q1 ]/ W
  56. * @breif             RTC WAKE UP中断处理处理回调函数
    & @$ \4 l4 K4 O- a: i6 N
  57. * @param              hrtc:RTC句柄
    9 G( V8 O" ?( D1 M+ I
  58. * @retval             无+ ]& P1 o3 P0 \% f  c( z2 b
  59. */& w9 a9 f* x# x; ^9 S5 a
  60. void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
    " o, d; e2 ]0 P; X& a# Z
  61. {' ^* l% C; ?, |5 N& w3 d# e
  62.     LED1_TOGGLE();
    # W4 M$ d+ M) M1 X9 H! O
  63. }
      K' _; t% E/ `, U( O
  64. 其中,RTC_Alarm_IRQHandler函数用于闹钟中断,其中断控制逻辑写在中断回调函数HAL_RTC_AlarmAEventCallback中,每当闹钟A闹铃时,会从串口打印一个字符串“ALARM A!”。RTC_WKUP_IRQHandler函数用于RTC自动唤醒定时器中断, 其中断控制逻辑写在中断回调函数HAL_RTCEx_WakeUpTimerEventCallback中,可以通过观察LED1的状态来查看RTC自动唤醒中断的情况。
    0 V/ |% A  _+ m& B9 U: s# V
  65. rtc.c的其他程序,这里就不再介绍了,请大家直接看源码。  L* L+ K) R* d: Y$ _+ f
  66. 2. main.c代码
    7 g( P& Z/ y$ i% L. [1 g. W
  67. 在main.c里面编写如下代码: 3 h* C: W' N6 ~0 R$ N5 }% Y; N
  68. int main(void)& Q% n$ z2 V  J
  69. {! p5 a  M+ [5 H7 T8 R, b
  70.     uint8_t hour, min, sec, ampm;) k% G$ {$ I9 F* O0 [1 j# s
  71.     uint8_t year, month, date, week;6 Y' A6 E- ]+ W4 y. W1 g$ a
  72.     uint8_t tbuf[40];
    " \' |$ B( t+ L1 g; |, d
  73.     uint8_t t = 0;7 C8 a" G1 ^  [: c4 A8 P# G( O* b& x
  74.     sys_cache_enable();                            /* 打开L1-Cache */
    7 b: v) G# k: u0 t
  75.     HAL_Init();                                      /* 初始化HAL库 */1 _, g5 |' a  g& i
  76.     sys_stm32_clock_init(240, 2, 2, 4);         /* 设置时钟, 480Mhz */  Z0 b& _! n/ v! n9 x# ]! u
  77.     delay_init(480);                                /* 延时初始化 */7 o& q1 q2 P8 `3 B7 P
  78.     usart_init(115200);                            /* 串口初始化为115200 */
    9 y2 R9 j0 [/ c6 H2 e1 g7 D
  79.     usmart_dev.init(240);                          /* 初始化USMART */+ C8 G+ B8 A0 z6 y+ f
  80.     led_init();                                      /* 初始化LED */" i5 ]# b# j6 F0 d9 R# b
  81.     mpu_memory_protection();                      /* 保护相关存储区域 */
    ! ?4 k5 J0 D4 x2 f% `. w
  82.     lcd_init();                                      /* 初始化LCD */2 q6 k- ?& x# x. t  |$ \7 H+ A2 V
  83. rtc_init();                                      /* 初始化RTC */8 {) S/ |9 K2 Z4 i! R! Z+ s( R
  84. /* 配置WAKE UP中断,1秒钟中断一次 */
    9 k* g2 ^8 F* _7 s5 i0 F3 T
  85.     rtc_set_wakeup(RTC_WAKEUPCLOCK_CK_SPRE_16BITS, 0);  & M% d. t6 U! F& j7 C3 I) q
  86.     lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);. n5 S% O/ l4 Z; J
  87.     lcd_show_string(30, 70, 200, 16, 16, "RTC TEST", RED);* _. M3 ^% L) n) a$ u' y" t8 Z+ F
  88.     lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);$ K& X# R% }( d
  89.     while (1)
    ) @4 {0 |1 {+ J  B; i5 W# K, @8 t
  90.     {1 n4 x* f6 a" }
  91.         t++;
    % N# b# B0 O4 p" n/ u& \. J
  92.         if ((t % 10) == 0)  /* 每100ms更新一次显示数据 */% ]8 D+ p' ?, ^2 V
  93.         {
    ' _! }, i8 ^1 r& T/ ~+ Z+ b
  94.             rtc_get_time(&hour, &min, &sec, &m);/ y2 c& c8 A- C. O0 Y
  95.             sprintf((char *)tbuf, "Time:%02d:%02d:%02d", hour, min, sec);7 J$ l& Z* ^  T. ~
  96.             lcd_show_string(30, 140, 210, 16, 16, (char*)tbuf, RED);& E  [' g- s4 g& x6 B
  97.             rtc_get_date(&year, &month, &date, &week);
    4 a% d: N: |/ [- S- F
  98.             sprintf((char *)tbuf, "Date:20%02d-%02d-%02d", year, month, date);# l0 W; |$ o! A( T
  99.             lcd_show_string(30, 160, 210, 16, 16, (char*)tbuf, RED);/ Q/ K7 s/ ~3 k8 T7 o
  100.             sprintf((char *)tbuf, "Week:%d", week);
    9 u; s! Y" W& c5 J
  101.             lcd_show_string(30, 180, 210, 16, 16, (char*)tbuf, RED);
    & l# L: n9 f9 T) x
  102.         }
    ( q0 P: a+ |6 }4 y$ S$ H: [- i
  103.         if ((t % 20) == 0)3 s, j( y, h& Y6 p
  104.         {
    7 N; Z& i) r" L! C# k. Y: R
  105.             LED0_TOGGLE();  /* 每200ms,翻转一次LED0 */
    ' w+ i3 c  \$ Z/ d! s% u0 u
  106.         }        
    ' O; }! H) T% Z
  107.         delay_ms(10);$ x/ g3 w: A5 X. T
  108.     }
    2 M0 P! {* d* ^4 T4 F; r' o& K
  109. }: a$ N' \& E: Z! D$ G
复制代码
' O9 v& @" H/ L. R: f. w% l0 Q0 D; _
我们通过RTC_Set_WakeUp(RTC_WAKEUPCLOCK_CK_SPRE_16BITS,0)设置RTC周期性自动唤醒周期为1秒钟。然后,在无限循环中每100ms读取RTC的时间和日期(一次),并显示在LCD上面。每200ms,翻转一次LED0。. u! p% C, U; N4 B/ q7 c# N
为方便RTC相关函数的调用验证,在usmart_config.c里面,修改了usmart_nametab如下:
5 V! w2 w" C1 p4 Z: d6 h8 L/* 函数名列表初始化(用户自己添加)8 J9 H8 m, L0 P
: J9 t# M& ?) P5 o
用户直接在这里输入要执行的函数名及其查找串
, w: b' F5 b( {0 ~* n; H
  1. */6 K  f+ ^; H4 T: |' T! z6 T
  2. struct _m_usmart_nametab usmart_nametab[] =# r2 x! s  n2 J: T
  3. {
    3 M2 t. ?) e- N1 l, r9 \+ F# u
  4. #if USMART_USE_WRFUNS == 1      /* 如果使能了读写操作 */) L5 s) K: {) A$ A5 w- ^
  5.     (void *)read_addr, "uint32_t read_addr(uint32_t addr)",
    . l& U' V# S. v$ j3 z( H1 T
  6.     (void *)write_addr, "void write_addr(uint32_t addr,uint32_t val)",
    ; E6 \9 m! S. @! z0 ]- ~' ^
  7. #endif3 S  _* j- N6 u0 E9 p% C4 B  D
  8.     (void *)delay_ms, "void delay_ms(uint16_t nms)",
    ) L4 `& D; `0 \5 S" _3 W3 D5 Z+ K! K9 P
  9.     (void *)delay_us, "void delay_us(uint32_t nus)",
    4 |* y: {5 Y  |7 ?
  10.   y8 C" O. s6 p7 ?
  11.     (void *)rtc_read_bkr, "uint32_t rtc_read_bkr(uint32_t bkrx)",
    ' b+ z9 S$ I2 ^0 g- p8 N5 k+ e
  12.     (void *)rtc_write_bkr, "void rtc_write_bkr(uint32_t bkrx, uint32_t data)",
    - F* {1 p( |  u6 J9 g5 g* B+ J/ N
  13. (void *)rtc_set_time, "uint8_t rtc_set_time(uint8_t hour, uint8_t min,
    1 Q' h7 w* p7 R, B# k3 l* N) p
  14. uint8_t sec, uint8_t ampm)"," A+ B1 @9 L$ ?2 l7 u! s, x
  15. (void *)rtc_set_date, "uint8_t rtc_set_date(uint8_t year, uint8_t month, ' I6 K0 K+ X- V% L5 ~' C. @$ ]4 t  z
  16. uint8_t date, uint8_t week)",
    / f& s2 o, c# N' l
  17. - e+ h0 i1 ^3 ^; `) i# r
  18.     (void *)rtc_set_wakeup, "void rtc_set_wakeup(uint8_t wksel, uint16_t cnt)",# a7 x  e2 ?$ @
  19. (void *)rtc_get_week, "uint8_t rtc_get_week(uint16_t year, uint8_t month,
    9 i. d: \$ }' q- _' O6 q" R5 D
  20. uint8_t day)",# y! j; i- U$ |" i( u+ z/ M6 w2 P
  21. (void *)rtc_set_alarma, "void rtc_set_alarma(uint8_t week, uint8_t hour, ) C( `# O# O9 B3 x2 [2 y7 r
  22. uint8_t min, uint8_t sec)",
    5 B$ M& X% j% c8 C7 ^
  23. };
复制代码

( L8 t- _$ [0 {% }1 e) c将RTC的一些相关函数加入了usmart,这样通过串口就可以直接设置RTC时间、日期、闹钟A和周期性唤醒等操作。 至此,RTC实时时钟的软件设计就完成了,接下来就让我们来检验一下,我们的程序是否正确。  `% r9 R6 `+ W* W) d
27.4 下载验证) q2 ]" i" h, s
将程序下载到开发板后,可以看到LED0不停的闪烁,提示程序已经在运行了,同时LED1每两秒闪烁一次,说明周期性唤醒中断工作正常。然后,可以看到LCD模块开始显示时间,实际显示效果如图27.4.1所示:
( T8 _6 d! {1 V$ t2 c3 N* H0 i$ I- a
9e909b88237147b891fcdb03fcd808c2.png
  z+ S# d! {2 k0 C
2 h# ?6 g6 E  s/ d  \图27.4.1 RTC实验测试图
' g+ d$ T9 D3 @+ ^如果时间和日期不正确,可以利用上一章介绍的usmart工具,通过串口来设置,并且可以设置闹钟时间等,如图27.4.2所示:! |2 J0 G1 j6 A5 ]0 Y% r: _" Y

# d7 l$ I5 E: u/ V; _ 4b7ae6d2425b4cee93ff7a03b3b88a7b.png
/ s& x1 |' Z5 e6 d
9 [5 y- O9 \% M' D图27.4.2 通过USMART设置时间和日期并测试闹钟A
- p( q! a, G  j0 ^按照图中编号6、7、8顺序,设置闹钟A、设置日期、设置时间。然后等待我们设置的时间到来后,串口打印ALARM A!这个字符串,证明我们的闹钟A程序正常运行了!
: e3 j1 F$ g) H4 l/ e- v4 ^; A————————————————2 F# G7 Z& n& n! w
版权声明:正点原子4 M0 K7 q% d0 z5 x& e' G" A$ ]

& `  O) R* V/ t' G1 B# f- D
2 T) X6 G; O3 |) D- w- n/ n1 K4 J
赞 收藏 评论0 发布时间:2022-10-7 18:40

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版