自學STM32 - RTC與備份區

2024年2月6日 22点热度 0人点赞

作者:junziyang


(註:如非特別聲明,以下筆記內容均針對STM32F103ZET6而言。不同型號,細節可能存在差別)

學習RTC和備份區的原理用。最後通過一個實例演示利用RTC的鬧鐘功能實現自動報時。

一、RTC實時時鐘

RTC全稱為Real-Time Clock,即實時時鐘,是芯片內部一個獨立的計時器。類似於電腦上的時鐘,在合適軟件的支持下,可以為嵌入式系統提供時間、日歷和鬧鐘等功能。例如,顯示x年x月x日xx時xx分xx秒,利用鬧鐘中斷定時喚醒等。RTC位於備份區,即使系統斷電關機,仍可由電池供電,維持RTC的計時。

RTC的計數器為32bit,可由LSI/LSE或HSE的128分頻來提供時鐘。如果以秒為單位,理論上來說單向最大計數時長可達136年。RTC可以產生3個中斷,即鬧鐘中斷,秒中斷和溢出中斷。RTC鬧鐘與外部中斷線EXTI 17相連,且在NVIC中有專門的鬧鐘中斷向量RTC_Alarm_RIQn(編號41)與之對應。通過鬧鐘中斷,可以對程序運行進行日程和時間管理,比如節假日自動關機等。

1.1 RTC的功能與原理

1. RTC寄存器復位值表

任何外設的功能配置都是通過配置相關寄存器實現的,RTC也不例外。為便於接下來對RTC原理和功能的學習,可以先瀏覽一下RTC寄存器的地址映射與復位值表,如圖1所示。

圖1 RTC寄存器地址映射與復位值表

RTC共占用10個32bit寄存器,兩兩配對,分為5組:

  • CRH/CRL為控制寄存器,分管中斷使能和狀態位;
  • DIVH/DIVL為預分頻寄存器,用來對時鐘源RTCCLK進行分頻,得到RTC的基礎時鐘TR_CLK(通常為1Hz);
  • PRLH/PRLL為預分頻重載寄存器,用來存儲預分頻系數,每個TR_CLK周期向分頻計數器重新裝載;
  • CNTH/CNTL為計數寄存器,從初始值開始在TR_CLK控制下遞增;
  • ALRH/ALRL為鬧鐘寄存器,用來設置鬧鐘,當CNT寄存器與ALR寄存器中的值相等時,產生鬧鐘事件,如果CRH寄存器的ALRIE=1,則會產生鬧鐘中斷。

2. 結構框圖

圖2. RTC原理框圖

RTC的原理框圖如圖2所示。主要由兩個單元構成:APB1接口(橙色部分)和RTC核心(綠色部分)。APB1接口與一組16bit的RTC的寄存器相連(參見圖1),共享APB1總線的時鐘(PCLK1),這樣通過APB1總線可以對這些寄存器進行讀寫操作從而實現對寄存器的配置。APB1接口在待機狀態是不供電的。

RTC核心單元又可分為兩個模塊:RTC預分頻器和計數器。左側的RTC prescaler主要是對RTCCLK進行分頻,以產生RTC的基礎時鐘TR_CLK。其中包含兩組功能寄存器(參見圖2),RTC_DIV(共32bit)用來對RTCLK分頻,而RTC_PRL(共20bit)用來存儲預分頻系數。每個TR_CLK周期向分頻計數器裝載1次。

右側的模塊中是一個計數器,也包括2組功能寄存器,RTC_CNT(共32bit)用來計數,RTC_ALR(共32bit)用來設置鬧鐘。通常將RTC_CNT初始化為當前時間(對表),然後它會按TR_CLK時鐘遞增計數。如果不初始化,默認的時間起點是1969年12月31日23:59:58(STM32F103ZET6實測,可以用c語言的time和localtime函數獲取該時間)。RTC核心部分在備份區中,如果安裝了電池,在待機模式甚至主電源斷開情況下該區域都是有供電的。因此RTC初始化以後,隻要不主動關閉或電池耗盡,可以持續工作。

在待機模式下APB1接口以及右側的RTC_CR寄存器是不供電的。RTC的3個中斷中,溢出中斷(RTC_Overflow)和秒中斷(RTC_Second)在待機模式下無效。而鬧鐘中斷(RTC_Alarm)有獨立的中斷向量,在待機狀態下可以觸發中斷並將MCU從待機狀態喚醒。

3. RTC設置

系統復位或電源復位後,除RTC_PRL/DIV/CNT/ALR不會復位外,RTC_CR寄存器與備份區外的所有寄存器一起被復位。這幾個受保護的RTC寄存器,隻有在對備份區進行專門復位(RCC_APB1RSTR寄存器BKPRST位置1)時才會被清除。

與其他外設一樣,RTC的設置也是通過配置相關寄存器實現的。但由於這些寄存器位於“保護區”,讀寫操作略有不同。

1) 讀取RTC寄存器

RTC核心與RTC APB1接口是完全獨立的。RTC寄存器的更新受控於自己內部的時鐘,在TR-CLK的每個上升沿被更新,即使外部時鐘關閉的情況下,也是如此。而軟件讀取RTC的可讀寄存器需要通過APB1接口。如果APB1的時鐘被關閉,再次重新開啟後,RTC的時鐘要與之重新同步。如果APB1時鐘重新開啟後馬上就去讀RTC寄存器(例如更新時間顯示),由於時鐘不同步,第一次讀取的數據有可能是不正確的(通常會讀到0)。例如,系統復位或電源復位後,或者MCU剛從停止模式(Stop mode)或待機模式(Standby mode)被喚醒後,馬上去讀RTC寄存器就有可能會出現這種情況。

為了避免這種情況,在APB1接口重啟後,第一次讀RTC寄存器前,要等待硬件將RTC_CRL中的RSF(Register Synchronized Flag)位置1。RSF=1表明,TR_CLK與PCLK1同步完成。

普通睡眠模式不會受此影響,因為這種情況下APB1接口時鐘不會關閉。

2) 配置RTC寄存器

RTC位於備份區,系統復位後備份區默認是寫保護的。如果需要設置或修改RTC,需要執行如下操作:

  • 置RCC_APB1ENR寄存器的PWREN=1和BKPEN=1,開啟電源接口和備份區接口的時鐘。
  • 置電源控制寄存器PWR_CR中的DBP=1,使能對備份區和RTC的訪問。

RTC寄存器必須在前一次寫入結束後才能進行下一次寫入。執行完上述操作,釋放寫入權限後,還必須確認沒有正在寫入RTC寄存器的操作才可以執行新的寫入。確認的方法是:讀取RTOFF(RTC operation OFF),寫操作結束後,硬件會將該位置1。

由於RTC_PRL/CNT/ALR寄存器是在TR_CLK同步下不斷更新的,要寫入這些寄存器,必須先主動停止這些寄存器的自動更新。方法是:在確認RTOFF=1的情況下,將CNF(Configuration Flag)位置1。可以認為CNF=1後,這些寄存器被暫時與TR_CLK斷開,進入配置模式(Configuration mode)。配置完畢再置CNF=0,退出配置模式,配置才會生效。為了保險期間,最後可以再確認寫一下RTOFF=1。概況起來,寫入這些寄存器的步驟如下:

  • 查詢並等待RTOFF=1;
  • 設置CNF=1;
  • 寫入RTC寄存器;
  • 清除CNF,即寫入CNF=0;
  • 查詢並等待RTOFF=1。

由於內外時鐘的不同頻率,CNF的寫入至少需要等待3個RTCCLK周期。

3) RTC的標志位

圖3. ALRF與計數器、RTC時鐘以及秒時鐘的時序關系

RTC相關狀態標志位由RTC_CRL寄存器管理,除了前面提到過的RTOFF(寫入結束)、CNF(配置模式)和RSF(寄存器同步)外,還有SECF(秒標志)、OWF(溢出標志)和ALRF(鬧鐘標志)3個標志位:

  • SECF(Second Flag)置位發生在RTC計數器更新前,提前量為1個RTCCLK周期,計數器在每個TR_CLK周期都會更新。因TR_CLK通常設為1秒,所以稱為秒標志。
  • OWF(Overflow Flag)置位發生在計數器數器溢出前,提前量為1個RTCCLK周期。RTC的計數器是32bit遞增的,達到最大數後會自動回到0,這一事件稱為計數器溢出。
  • ALRF(Alarm Flag)置位發生在計數器數值達到ALR 1前,提前量為1個RTCCLK。圖3所示為PR=0003(即TR_CLK為RTCCLK4分頻),ALR=0004(即第5秒)時,RTC時鐘、秒時鐘、計數器以及ALRF置位的時序關系。OWF的置位情況與此類似,隻是發生在計數器溢出前。

1.2 RTC相關寄存器

RTC共有10個寄存器,按功能分為5組,可以按半字或字訪問。

1. RTC_CRH/L

RTC_CRH和RTC_CRL為RTC控制寄存器(RTC Control Register High/Low )。二者均占32bit。

RTC_CRH用來管理RTC相關的中斷,隻有3個功能位:

  • OWIE[2]:Overflow interrupt enable,溢出中斷使能。可讀寫。1-開啟溢出中斷;0-屏蔽溢出中斷。
  • ALRIE[1]:Alarm interrupt enable,鬧鐘中斷使能。可讀寫。1-開啟鬧鐘中斷;0-屏蔽鬧鐘中斷。
  • SECIE[0]:Second interrupt enable,秒中斷使能。可讀寫。1-開啟秒中斷;0-屏蔽秒中斷。

RTC_CRL用來管理RTC相關的一些標志位。共有6個功能位:

  • RTOFF[5]:RTC Operation OFF,RTC寫入結束標志。隻讀。1-寫入結束;0-寫入進行中。RTC寄存器不允許同時寫入,上一次寫入完成,硬件會將此位置1。寫入前檢查RTOFF是否為1,以避免寫入失敗。
  • CNF[4]:Configuration flag,配置模式設置和標志位。可讀寫。1-進入配置模式;0-退出配置模式(開始更新寄存器)。配置RTC_PRL/CNT/ALR三個寄存器前要先向該位寫入1,進入配置模式。配置結束再向該位寫入0,更新寄存器使配置生效。通過讀取該寄存器也可以判斷是否處於配置模式。
  • RSF[3]:Registers syncronized flag,寄存器同步標志位。1-寄存器已同步;0-寄存器未同步。每次RTC_CNT和RTC_DIV寄存器被更新後,硬件將此位置1。如果APB1復位或APB1時鐘關閉,重啟後,必須軟件清除該位,待硬件置1後再去讀取RTC_PRL/CNT/ALR寄存器,否則會由於時鐘不同步導致讀取錯誤。
  • OWF[2]:Overflow flag,溢出標志位。可讀寫。硬件設置,軟件清除。1-計數器溢出;0-計數器未溢出。
  • ALRF[1]:Alarm flag,鬧鐘標志位。可讀寫。硬件設置,軟件清除。1-有鬧鐘事件;0-無鬧鐘事件。
  • SECF[0]:Second flag,秒標志位。可讀寫。硬件設置,軟件清除。每個TR_CLK周期設置1次。

說明:

  • RTOFF=0時,所有RTC寄存器不可寫入。
  • 所有掛起的標志位都需要主動軟件復位。
  • APB1時鐘關閉後,OWF/ALRF/SECF/RSF位不會被更新,這些位隻能由硬件設置且隻能由軟件清除。
  • 若ALRF=1且ALRIE=1,RTC全局中斷使能。如果EXTI控制器也使能了EXTI-17,則RTC全局中斷和鬧鐘中斷均被開啟。
  • 若ALRF=1,如果EXTI-17開啟的是中斷模式,則RTC鬧鐘中斷開啟。如果EXTI-17開啟的是事件模式,該線上會產生事件脈沖(不會產生RTC鬧鐘中斷)。

2. RTC_PRLH/L

RTC的本地時鐘TR_CLK是通過對RTCCLK進行分頻得到的。分頻器的工作原理是,通過一個計數器,每隔N個輸入時鐘脈沖,產生1個輸出時鐘脈沖,即實現了對時鐘源的N分頻。分頻器中用一個寄存器來存儲分頻系數,每次計數器計數歸零,自動將分頻系數寄存器中的值重新裝載到計數器,開始下一個周期的計數。

RTC_PRLH/L為RTC預分頻裝載寄存器(RTC Prescaler Load Register High/Low),是用來存儲分頻系數的,每個RTCCLK周期遞減,歸零時產生TR_CLK時鐘脈沖。在該脈沖觸發下,RTC_PRL將其中存儲的分頻系數重新裝載到計數器,開始下一個TR_CLK周期的計數(參見圖3)。

RTC_PRL寄存器由兩個32位寄存器組成。RTC_PRLH僅使用低4位,存儲PRL[19-16]。RTC_PRLL使用低16位,存儲PRL[15-0]。RTCCLK的頻率一般為32.768kHz(215),將RPLL設為0x7FFF,即可得到1s的周期。設置PRLH主要是為了應對更高的RTC時鐘頻率,例如HSE的128分頻為562.5kHz,得到一秒則需要用到PRLH(0x89544)。

TR_CLK的與RTCCLK的關系為:

該寄存器受RTOFF位的保護

3. RTC_DIVH/L

RTC_DIVH/L為RTC預分頻器計數寄存器(RTC Prescaler Divider Register High/Low),用來存儲預分頻計數器當前值。有了該寄存器,可以在不幹擾計數器工作的情況下,通過獲取當前計數值實現更精確的時間度量。例如,TR_CLK通常為1秒,為32768個RTCCLK周期,通過讀取RTC_DIV中的值,可以實現比秒更短的時間分辨,比如秒表上的1/100s。

此寄存器為隻讀。RTC_PRL或CNT寄存器修改後,硬件會重新裝載該寄存器的值。

4. RTC_CNTH/L

RTC_CNTH/L是RTC計數寄存器(RTC Counter Register High/Low),是用來計數的。由兩個32位寄存器組成,每個寄存器僅用低16位,每個TR_CLK周期數值遞增1。可連續計數范圍0-232(約136年)。該寄存器受RTOFF位的保護

5. RTC_ALRH/L

RTC_ALRH/L為RTC鬧鐘寄存器(RTC Alarm Register High/Low),用來設置鬧鐘。當此寄存器中的值與CNT寄存器中的值相等時,會設置ALRF位,產生鬧鐘中斷/事件。

該寄存器受RTOFF位的保護

1.3 HAL庫RTC函數

1. RTC配置步驟

在HAL庫中,RTC的API在stm32f1xx_hal_rtc.h中聲明,在stm32f1xx_hal_rtc.c中定義。配置RTC基本步驟如下:

  • 使能RTC區訪問:RCC_APB1ENR中的PWREN=1和BKPEN=1,PWR_CR中的DBP=1;
  • 準備配置RTC:清除RSF並待其硬件復位,確保寄存器已同步;每次寫入前都要確認RTOFF=1,即沒有進行中的寫入;
  • 配置RTC預分頻系數。
  • 設置日期和時間:將日期和時間折算為秒,寫入RTC_CNT寄存器。CNT寄存器僅是一個計數器,時間和日期需要在程序中約定一個起點,然後根據計數器的值進行折算。時間起點的約定可以是任意的。計數寄存器中並不會存儲年月日等信息,隻是相對於參考時間起點的計數次數(秒數),具體的日期和時間需要在程序中折算。
  • 設置鬧鐘:寫入RTC_ALRH/L寄存器。
  • 使能所需中斷。

2. RTC初始化/復位函數

  • HAL_RTC_Init(); RTC初始化。根據句柄中參數初始化RTC。包括清除中斷標志位,設置備份區控制寄存器(BKP->RTCCR,見BKP相關部分),設置RPL寄存器。該函數還可以自動根據PCLK1的頻率計算RTC預分頻系數,使得TR_CLK時鐘周期為1s(設置方法:hrtc->Init.AsynchPrediv = RTC_AUTO_1_SECOND)。句柄中年月日等參數會被初始化為2000年1月1日。
  • HAL_RTC_MspInit(); MCU相關外設初始化。在HAL_RTC_Init()中先調用該函數,然後才會進行參數設置。需要用戶編寫,一般用來使能RTC區訪問,開啟RTC中斷等。
  • HAL_RTC_DeInit(); 復位RTC所有寄存器。
  • HAL_RTC_MspDeInit(); 在HAL_RTC_DeInit()中,最後調用該函數來復位MCU相關外設。這個函數需要用戶編寫。執行HAL_RTC_MspInit()中相反的操作

3. 事件和日期設置函數

  • HAL_RTC_SetTime(); 設置CNT寄存器。將按時分秒輸入的時間折算為計數器值,寫入CNTH/L寄存器。函數中,先調用RTC_EnterInitMode()關閉寫保護,寫入完畢後,調用RTC_ExitInitMode()開啟寫保護。這兩個私有函數實際設置的是CNF位,並等待RTOFF=1。如果CNT寄存器設置成功,該函數還會管理鬧鐘設置,先讀取hrtc中的ALRH/L的值,如果鬧鐘已過期(ALR中的值小於新設置的值),則會增加24h的計數次數,並更新到ALRH/L。
  • HAL_RTC_GetTime(); 獲取RTC當前時間。讀取CNT的值,折算為時分秒並更新到時間結構體的對應字段中。
  • HAL_RTC_SetDate(); 設置日期。日期按年月日存儲於hrtc句柄的DateToUpdate字段。這個新設的日期的00:00:00會被作為計數器的新起點。DateToUpdate的意思就是上一次的更新日期,查詢這個字段可以確定計時起點。私有函數RTC_DateUpdate可以根據CNT中的天數自動更新句柄中的Year/Mongth/Date/WeekDay字段。日歷的計算方法挺有意思,考慮了閏年(leep year)等因素(詳見stm32f1xx_hal_rtc.c)。
  • HAL_RTC_GetDate(); 查詢日期。返回句柄中存儲的年月日等字段的值。

4. 鬧鐘設置函數

  • HAL_RTC_SetAlarm(); 設置鬧鐘。按24小時制的時分秒提供參數,換算成秒數,寫入ALRH/L寄存器。如果所設時間小於當前時間,則自動增加24小時。Stm32F1系列隻能設置1個鬧鐘。
  • HAL_RTC_SetAlarm_IT(); 時鐘鬧鐘中斷,即設置鬧鐘的同時開啟鬧鐘中斷。
  • HAL_RTC_DeactivateAlarm(); 關閉鬧鐘。復位ALRH/L寄存器(0xFFFF)。
  • HAL_RTC_GetAlarm(); 查詢當前設置的鬧鐘。
  • HAL_RTC_AlarmIRQHandler(); 鬧鐘中斷請求處理函數。調用需要用戶定義的回調函數HAL_RTC_AlarmAEventCallback處理中斷。
  • HAL_RTC_PollForAlarmAEvent(); 輪詢等待鬧鐘事件。while循環,直至ALRAF=1或超時。
  • HAL_RTC_AlarmAEventCallback(); 鬧鐘中斷回調函數。這是一個weak函數,需要用戶編寫代碼。

5. 狀態控制函數

  • HAL_RTC_GetState(); 查詢RTC狀態。返回狀態機 hrtc->State。
  • HAL_RTC_WaitForSynchro(); 等待RTC寄存器同步

二、 BKP寄存器

2.1 BKP簡介

備份區(BacKuP domain)中除了RTC,還有一組寄存器,用來備份應用程序中的用戶數據。由於關機或待機後備份區可以由電池供電,因此把一些重要數據存儲到備份區的寄存器中,MCU復位或喚醒後可以讀取這些數據,執行一些初始化的工作。例如,MCU從停機被喚醒後,默認會使用HSI時鐘,往往會造成一些問題。可以設置一個睡眠方式標志,停機前寫入備份區寄存器,待被重新激活後,可以讀取這個數據,判斷是否從停機狀態恢復,以便重新初始化時鐘。(好像讀PWR_CR的PDDS位也可以)。

2.2 BKP寄存器

STM32系列大容量產品在備份區設置了42個16bit的寄存器(32bit寄存器僅開放低16位),可以存儲84個字節的數據。由於備份區在復位後默認是寫保護的,與前述設置RTC一樣,必須先去掉寫保護(RCC_APB1ENR:PWREN=1,BKPEN=1;PWR_CR:DBP=1)才可以寫入BKP寄存器。

除了42個數據寄存器,BKP還有3個專用寄存器,來管理RTC的校準、防侵入引腳(TAMPER)的設置,以及BKP狀態和中斷。這個專用寄存器的地址映射即復位值如圖4所示。

圖4. BKP寄存器地址映射與復位值表

1. BKP_RTCCR寄存器

BKP_RTCCR是RTC校準寄存器(RCC Clock Calibration Register ),用來管理RTC脈沖輸出和校正。該寄存器共有4個功能位:

  • ASOS[9]:Alarm or second output selection,0 - 輸出RTC鬧鐘脈沖;1 - 輸出RTC秒脈沖。需要先開啟ASOE=1。 備份區復位時此位被復位。
  • ASOE[8]:Alarm or second output enable,0 - 禁止信號輸出;1 - 使能信號輸出。該位必須在TAMPER引腳使能前,即TPE=0時設置。備份區復位時此位被復位。
  • CCO[7]:Calibration clock output,0 - 無效;1 - 在TAMPER引腳輸出RTC時鐘的64分頻。測量此頻率,可用來校準時鐘。該位必須在TAMPER引腳使能前,即TPE=0時設置。VDD關閉時此位復位。
  • CAL[6:0]:Calibration value,RTC時鐘(TR_CLK)校準值。如果需要精確計數,需要選擇32.768kHz的LSE晶振時鐘。但晶振的頻率會受溫度的影響而發生漂移,ST提供了這種軟件校準的方法。CAL值的意義為:每2^20(1048576)個時鐘脈沖忽略的脈沖個數。以每百萬時鐘計,調整步長為10^6/2^20 ppm,調整范圍為0-121ppm。折算成頻率為4,即將可校正[32768,32772]Hz范圍內的晶振頻率。這種方法隻能調慢不能調快,顯然是不符合實際情況的。一種解決方案是,人為調低RTC_PRLH/L中的預分頻系數,這樣相當於調高了RTC時鐘源的頻率。例如,不是設置為0x7FFF(32768),而是設置為0x7FFE(32766),這樣相當於把CAL的校正頻率范圍拉到了[32766-32770]。詳細的RTC時鐘校正方法可查閱《AN2604 STM32F101xx and STM32F103xx RTC calibration》

2. BKP_CR寄存器

BKP_CR是備份區控制寄存器(Backup control register),用來管理防侵入Tamper引腳。該寄存器隻有兩個功能位:

  • TPAL[1]:Tamper pin active level,TAMPER引腳生效電平。開啟TPE後,TAMPER引腳可以觸發數據備份寄存器的復位。此位設置起作用的復位電平。0 - 高電平復位所有數據備份寄存器;1 - 低電平復位所有數據備份寄存器。
  • TPE[0]:Tamper pin enable,TAMPER引腳使能。0 - TAMPER引腳作為普通I/O;1 - TAMPER引腳使能(AFIO)。TPAL和TPE一起置1沒問題,但最好不要一起置0。建議TPE先置0,再復位TPAL。防止產生虛假Tamper事件。

3. BKP_CSR寄存器

BKP_CSR是備份區控制/狀態寄存器(Backup control/status register),用來管理TAMPER引腳上的中斷/事件及標志位。該寄存器共5個功能位:

  • TIF[9]:Tamper interrupt flag,入侵中斷標志位。0 - 無侵入中斷;1 - 出現侵入中斷。TPIE=1且發生侵入事件時,由硬件置1。寫入CTI=0或TPIE=0可清除。
  • TEF[8]:Tamper event flag,入侵事件標志位。0 - 無侵入事件;1 - 發生侵入事件;Tamper事件會復位所有數據備份寄存器。在TEF=1時,寫入備份寄存器的數據不會被保存。侵入事件發生時由硬件置1。寫入CTE=0可清除。
  • TPIE[2]:TAMPER pin interrupt enable,防入侵引腳中斷使能。0 - 禁用;1-使能。如果系統處於低功耗模式,Tamper中斷不會將其喚醒。此位僅在系統復位或待機喚醒後才會被復位。
  • CTI[1]:Clear tamper interrupt,清除TAMPER中斷。0 - 無效;1 - 清除Taper中斷且置TIF=0。此寄存器為隻寫寄存器。
  • CTE[0]:Clear tamper event,清除TAMPER事件。0 - 無效;1 - 清除Taper事件且置TEIF=0。此寄存器為隻寫寄存器。

2.3 HAL庫BKP函數

不同型號的MCU,BKP不盡相同。因此HAL庫將相關函數納入擴展API。在stm32f1xx_hal_rtc_ex.h中聲明,在stm32f1xx_hal_rtc_ex.c中定義。

1. TAMPER管理函數

  • HAL_RTCEx_SetTamper(); 使能TPE設置TPAL。TPE隻有在CCO=0且ASOE=0時才可使能TPE。
  • HAL_RTCEx_SetTamper_IT(); 使能TPE設置TPAL並設置TPIE=1。同樣,TPE隻有在CCO=0且ASOE=0時才可使能TPE。
  • HAL_RTCEx_DeactivateTamper(); 復位TPE,清除中斷/事件及標志位
  • HAL_RTCEx_TamperIRQHandler();
  • HAL_RTCEx_Tamper1EventCallback();
  • HAL_RTCEx_PollForTamper1Event();

2. 秒中斷/事件管理函數

  • HAL_RTCEx_SetSecond_IT();
  • HAL_RTCEx_DeactivateSecond();
  • HAL_RTCEx_RTCIRQHandler();
  • HAL_RTCEx_RTCEventCallback();
  • HAL_RTCEx_RTCEventErrorCallback();

3. 擴展控制函數

  • HAL_RTCEx_BKUPWrite();
  • HAL_RTCEx_BKUPRead();
  • HAL_RTCEx_SetSmoothCalib();

三、實例

利用RTC鬧鐘,每半小時顯示當前時間並啟動蜂鳴器報時。

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    rtc.c
  * @brief   This file provides code for the configuration
  *          of the RTC instances.
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "rtc.h"
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
RTC_HandleTypeDef hrtc;
/* RTC init function */
void MX_RTC_Init(void){
  /* USER CODE BEGIN RTC_Init 0 */
	RTC_AlarmTypeDef sAlarm = {0};
  /* USER CODE END RTC_Init 0 */
  RTC_TimeTypeDef sTime = {0};
  RTC_DateTypeDef DateToUpdate = {0};
  /* USER CODE BEGIN RTC_Init 1 */
  /* USER CODE END RTC_Init 1 */
  /** Initialize RTC Only
  */
  hrtc.Instance = RTC;
  hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
  hrtc.Init.OutPut = RTC_OUTPUTSOURCE_ALARM;
  if (HAL_RTC_Init(&hrtc) != HAL_OK)  {
    Error_Handler();
  }
  /* USER CODE BEGIN Check_RTC_BKUP */
  /* USER CODE END Check_RTC_BKUP */
  /** Initialize RTC and set the Time and Date
  */
  sTime.Hours = 0x0;
  sTime.Minutes = 0x0;
  sTime.Seconds = 0x0;
  if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)  {
    Error_Handler();
  }
  DateToUpdate.WeekDay = RTC_WEEKDAY_MONDAY;
  DateToUpdate.Month = RTC_MONTH_JANUARY;
  DateToUpdate.Date = 0x1;
  DateToUpdate.Year = 0x0;
  if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BCD) != HAL_OK)  {
    Error_Handler();
  }
  /* USER CODE BEGIN RTC_Init 2 */
  sTime.Hours  = 0;
  sTime.Minutes = 30;
  sAlarm.AlarmTime = sTime;
  HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN); //Alarm in 30min later
  /* USER CODE END RTC_Init 2 */
}
void HAL_RTC_MspInit(RTC_HandleTypeDef* rtcHandle){
  if(rtcHandle->Instance==RTC)  {
  /* USER CODE BEGIN RTC_MspInit 0 */
  /* USER CODE END RTC_MspInit 0 */
    HAL_PWR_EnableBkUpAccess();
    /* Enable BKP CLK enable for backup registers */
    __HAL_RCC_BKP_CLK_ENABLE();
    /* RTC clock enable */
    __HAL_RCC_RTC_ENABLE();
    /* RTC interrupt Init */
    HAL_NVIC_SetPriority(RTC_IRQn, 2, 0);
    HAL_NVIC_EnableIRQ(RTC_IRQn);
    HAL_NVIC_SetPriority(RTC_Alarm_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
  /* USER CODE BEGIN RTC_MspInit 1 */
  /* USER CODE END RTC_MspInit 1 */
  }
}
void HAL_RTC_MspDeInit(RTC_HandleTypeDef* rtcHandle){
  if(rtcHandle->Instance==RTC)  {
  /* USER CODE BEGIN RTC_MspDeInit 0 */
  /* USER CODE END RTC_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_RTC_DISABLE();
    /* RTC interrupt Deinit */
    HAL_NVIC_DisableIRQ(RTC_IRQn);
    HAL_NVIC_DisableIRQ(RTC_Alarm_IRQn);
  /* USER CODE BEGIN RTC_MspDeInit 1 */
  /* USER CODE END RTC_MspDeInit 1 */
  }
}
/* USER CODE BEGIN 1 */
/*----------------------------------------------------------------------
 * @brief  Display the current RTC date and time
 * @param  None
 * @retval None
 -----------------------------------------------------------------------*/
void HQ_RTC_DisplayTime(){
	RTC_TimeTypeDef sTime = {0};
	RTC_DateTypeDef sDate = {0};
	HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
	HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
	printf("20d-d-d d:d:d \r\n",
			sDate.Year,sDate.Month,sDate.Date,
			sTime.Hours,sTime.Minutes,sTime.Seconds);
}
/*----------------------------------------------------------------------
 * @brief  Update the RTC Alarm
 * @param  hrtc  handle to RTC
 *         interval  Time interval of the next alarm in minutes
 * @retval None
 -----------------------------------------------------------------------*/
void HQ_RTC_UpdateAlarm(RTC_HandleTypeDef *hrtc,uint8_t interval){
	uint16_t high1 = 0U, low = 0U;
	uint32_t AlarmCounter = 0U,tickstart = 0U;
	// 01. RTC_ReadAlarmCounter
	high1 = READ_REG(hrtc->Instance->ALRH & RTC_CNTH_RTC_CNT);
	low   = READ_REG(hrtc->Instance->ALRL & RTC_CNTL_RTC_CNT);
	AlarmCounter = (((uint32_t) high1 << 16U) | low);
	// 02. Update the value
	AlarmCounter  = ((uint32_t) interval)*60U;
	// 03. RTC_WriteAlarmCounter
	while ((hrtc->Instance->CRL & RTC_CRL_RTOFF) == (uint32_t)RESET);//Wait till RTC is in INIT state
	__HAL_RTC_WRITEPROTECTION_DISABLE(hrtc);/* Disable the write protection for RTC registers */
	WRITE_REG(hrtc->Instance->ALRH, (AlarmCounter >> 16U));/* Set RTC COUNTER MSB word */
	WRITE_REG(hrtc->Instance->ALRL, (AlarmCounter & RTC_ALRL_RTC_ALR));/* Set RTC COUNTER LSB word */
	__HAL_RTC_WRITEPROTECTION_ENABLE(hrtc);/* Enable the write protection for RTC registers */
	tickstart = HAL_GetTick();
	while ((hrtc->Instance->CRL & RTC_CRL_RTOFF) == (uint32_t)RESET){ //Wait till RTC is in INIT state
		if ((HAL_GetTick() - tickstart) >  RTC_TIMEOUT_VALUE){
			Error_Handler();
		}
	}
}
/* USER CODE END 1 */

鬧鐘回調函數

/*----------------------------------------------------------------------
 * @brief  Alarm event callback
 * @param  hrtc: RTC handle
 * @retval None
 -----------------------------------------------------------------------*/
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc) {
	uint8_t n = 0u;
	HQ_RTC_UpdateAlarm(hrtc, 30);
	printf("Current Time is:");
	HQ_RTC_DisplayTime();
	if((PWR->CSR & (0x1 << 1)) != 0) { //Wakeup from Stop mode
		HQ_SystemClock_Config(); //Restore system clock
	}
	for (n = 0; n < 4; n  ) { //Ring the bell
		BEEP = !BEEP;
		delay_ms(300);
	}
}

#文章首發挑戰賽#

#精品長文創作季#