作者: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所示。
![](https://news.xinpengboligang.com/upload/keji/6b29ec12ba81e8915c836c5848576e92.jpeg)
圖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. 結構框圖
![](https://news.xinpengboligang.com/upload/keji/98af25a2c82f9966cfdfde96afb4d184.jpeg)
圖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的標志位
![](https://news.xinpengboligang.com/upload/keji/c142d7082b22a8f9c5d20ab605b5d5d8.jpeg)
圖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所示。
![](https://news.xinpengboligang.com/upload/keji/c5866c7b2237a4c88f7d25823d95c323.jpeg)
圖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);
}
}