一、RTC實時時鐘特征與原理
查看STM32中文手冊 16 實時時鐘(RTC)(308頁)
RTC (Real Time Clock):實時時鐘
實時時鐘是一個獨立的定時器。RTC模塊擁有一組連續(xù)計數(shù)的計數(shù)器,在相應(yīng)軟件配置下,可提供時鐘日歷的功能。修改計數(shù)器的值可以重新設(shè)置系統(tǒng)當(dāng)前的時間和日期。
RTC模塊和時鐘配置系統(tǒng)(RCC_BDCR寄存器)處于后備區(qū)域,即在系統(tǒng)復(fù)位或從待機模式喚醒后, RTC的設(shè)置和時間維持不變。
系統(tǒng)復(fù)位后,對后備寄存器和RTC的訪問被禁止,這是為了防止對后備區(qū)域(BKP)的意外寫操作。執(zhí)行以下操作將使能對后備寄存器和RTC的訪問:
● 設(shè)置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能電源和后備接口時鐘
● 設(shè)置寄存器PWR_CR的DBP位,使能對后備寄存器和RTC的訪問。
RTC特征
可編程的預(yù)分頻系數(shù):分頻系數(shù)最高為220。
● 32位的可編程計數(shù)器,可用于較長時間段的測量。
● 2個分離的時鐘:用于APB1接口的PCLK1和RTC時鐘(RTC時鐘的頻率必須小于PCLK1時鐘
頻率的四分之一以上)。
● 可以選擇以下三種RTC的時鐘源:
— HSE時鐘除以128;
— LSE振蕩器時鐘;
— LSI振蕩器時鐘(詳見6.2.8節(jié)RTC時鐘)。
● 2個獨立的復(fù)位類型:
— APB1接口由系統(tǒng)復(fù)位;
— RTC核心(預(yù)分頻器、鬧鐘、計數(shù)器和分頻器)只能由后備域復(fù)位(詳見6.1.3節(jié))。
● 3個專門的可屏蔽中斷:
— 鬧鐘中斷,用來產(chǎn)生一個軟件可編程的鬧鐘中斷。
— 秒中斷,用來產(chǎn)生一個可編程的周期性中斷信號(最長可達1秒)。
— 溢出中斷,指示內(nèi)部可編程計數(shù)器溢出并回轉(zhuǎn)為0的狀態(tài)。
二、RTC由兩部分組成
**APB1接口:**用來和APB1總線相連。通過APB1接口可以訪問RTC的相關(guān)寄存器(預(yù)分頻值,計數(shù)器值,鬧鐘值)。
**RTC核心:**由一組可編程計數(shù)器組成。分兩個主要模塊。
第一個是RTC預(yù)分頻模塊,它可以編程產(chǎn)生最長1秒的RTC時間基TR_CLK。如果設(shè)置了秒中斷允許位,可以產(chǎn)生秒中斷。
第二個是32位的可編程計數(shù)器,可被初始化為當(dāng)前時間。系統(tǒng)時間按TR_CLK周期累加并與存儲在RTC_ALR寄存器中的可編程時間相比,當(dāng)匹配時候如果設(shè)置了鬧鐘中斷允許位,可以產(chǎn)生鬧鐘中斷。
RTC內(nèi)核完全獨立于APB1接口,軟件通過APB1接口對RTC相關(guān)寄存器訪問。但是相關(guān)寄存器只在RTC APB1時鐘進行重新同步的RTC時鐘的上升沿被更新。所以軟件必須先等待寄存器同步標(biāo)志位(RTC_CRL的RSF位)被硬件置1才讀。
三、RTC時鐘源
首先講一下時鐘源:
三種不同的時鐘源可被用來驅(qū)動系統(tǒng)時鐘(SYSCLK):
● HSI振蕩器時鐘
● HSE振蕩器時鐘
● PLL時鐘
這些設(shè)備有以下2種二級時鐘源:
● 40kHz低速內(nèi)部RC,可以用于驅(qū)動獨立看門狗和通過程序選擇驅(qū)動RTC。RTC用于從停機/待機模式下自動喚醒系統(tǒng)。
● 32.768kHz低速外部晶體也可用來通過程序選擇驅(qū)動RTC(RTCCLK)。
參看:STM32時鐘系統(tǒng)
當(dāng)不被使用時,任一個時鐘源都可被獨立地啟動或關(guān)閉,由此優(yōu)化系統(tǒng)功耗。
用戶可通過多個預(yù)分頻器配置AHB、高速APB(APB2)和低速APB(APB1)域的頻率。AHB和APB2域的最大頻率是72MHz。APB1域的最大允許頻率是36MHz。SDIO接口的時鐘頻率固定為HCLK/2。
RCC通過AHB時鐘(HCLK)8分頻后作為Cortex系統(tǒng)定時器(SysTick)的外部時鐘。通過對SysTick控制與狀態(tài)寄存器的設(shè)置,可選擇上述時鐘或Cortex(HCLK)時鐘作為SysTick時鐘。ADC時鐘由高速APB2時鐘經(jīng)2、 4、 6或8分頻后獲得。
定時器時鐘頻率分配由硬件按以下2種情況自動設(shè)置:
如果相應(yīng)的APB預(yù)分頻系數(shù)是1,定時器的時鐘頻率與所在APB總線頻率一致。
否則,定時器的時鐘頻率被設(shè)為與其相連的APB總線頻率的2倍。
如上圖,有五個時鐘源,為HSI、HSE、LSI、LSE、PLL。
接下來我們一一看一下:
HSE時鐘
**高速外部時鐘信號(HSE)**由以下兩種時鐘源產(chǎn)生:
● HSE外部晶體/陶瓷諧振器
● HSE用戶外部時鐘
為了減少時鐘輸出的失真和縮短啟動穩(wěn)定時間,晶體/陶瓷諧振器和負載電容器必須盡可能地靠
近振蕩器引腳。負載電容值必須根據(jù)所選擇的振蕩器來調(diào)整。
外部時鐘源(HSE旁路)
在這個模式里,必須提供外部時鐘。它的頻率最高可達25MHz。用戶可通過設(shè)置在時鐘控制寄存器中的HSEBYP和HSEON位來選擇這一模式。外部時鐘信號(50%占空比的方波、正弦波或三角波)必須連到SOC_IN引腳,同時保證OSC_OUT引腳懸空。見圖9。
外部晶體/陶瓷諧振器(HSE晶體)
**4~16Mz外部振蕩器可為系統(tǒng)提供更為精確的主時鐘。**相關(guān)的硬件配置可參考圖9,進一步信息可參考數(shù)據(jù)手冊的電氣特性部分。
在時鐘控制寄存器RCC_CR中的HSERDY位用來指示高速外部振蕩器是否穩(wěn)定。在啟動時,直到這一位被硬件置’1’,時鐘才被釋放出來。如果在時鐘中斷寄存器RCC_CIR中允許產(chǎn)生中斷,將會產(chǎn)生相應(yīng)中斷。
HSE晶體可以通過設(shè)置時鐘控制寄存器里RCC_CR中的HSEON位被啟動和關(guān)閉。
HSI時鐘
HSI時鐘信號由內(nèi)部8MHz的RC振蕩器產(chǎn)生,可直接作為系統(tǒng)時鐘或在2分頻后作為PLL輸入。
HSI RC振蕩器能夠在不需要任何外部器件的條件下提供系統(tǒng)時鐘。它的啟動時間比HSE晶體振蕩器短。然而,即使在校準(zhǔn)之后它的時鐘頻率精度仍較差。
校準(zhǔn)
制造工藝決定了不同芯片的RC振蕩器頻率會不同,這就是為什么每個芯片的HSI時鐘頻率在出廠前已經(jīng)被ST校準(zhǔn)到1%(25°C)的原因。系統(tǒng)復(fù)位時,工廠校準(zhǔn)值被裝載到時鐘控制寄存器的HSICAL[7:0]位。
如果用戶的應(yīng)用基于不同的電壓或環(huán)境溫度,這將會影響RC振蕩器的精度??梢酝ㄟ^時鐘控制寄存器里的HSITRIM[4:0]位來調(diào)整HSI頻率。
時鐘控制寄存器中的HSIRDY位用來指示HSI RC振蕩器是否穩(wěn)定。在時鐘啟動過程中,直到這一位被硬件置’1’, HSI RC輸出時鐘才被釋放。HSI RC可由時鐘控制寄存器中的HSION位來啟動和關(guān)閉。
如果HSE晶體振蕩器失效, HSI時鐘會被作為備用時鐘源。
PLL
內(nèi)部PLL可以用來倍頻HSI RC的輸出時鐘或HSE晶體輸出時鐘。
PLL的設(shè)置(選擇HIS振蕩器除2或HSE振蕩器為PLL的輸入時鐘,和選擇倍頻因子)必須在其被激活前完成。一旦PLL被激活,這些參數(shù)就不能被改動。
如果PLL中斷在時鐘中斷寄存器里被允許,當(dāng)PLL準(zhǔn)備就緒時,可產(chǎn)生中斷申請。
如果需要在應(yīng)用中使用USB接口, PLL必須被設(shè)置為輸出48或72MHZ時鐘,用于提供48MHz的USBCLK時鐘。
LSE時鐘
**LSE晶體是一個32.768kHz的低速外部晶體或陶瓷諧振器。**它為實時時鐘或者其他定時功能提供一個低功耗且精確的時鐘源。
LSE晶體通過在備份域控制寄存器(RCC_BDCR)里的LSEON位啟動和關(guān)閉。
在備份域控制寄存器(RCC_BDCR)里的LSERDY指示LSE晶體振蕩是否穩(wěn)定。在啟動階段,直到這個位被硬件置’1’后, LSE時鐘信號才被釋放出來。如果在時鐘中斷寄存器里被允許,可產(chǎn)生中斷申請。
外部時鐘源(LSE旁路)
在這個模式里必須提供一個32.768kHz頻率的外部時鐘源。你可以通過設(shè)置在備份域控制寄存器(RCC_BDCR)里的LSEBYP和LSEON位來選擇這個模式。具有50%占空比的外部時鐘信號(方波、正弦波或三角波)必須連到OSC32_IN引腳,同時保證OSC32_OUT引腳懸空,見圖9。
LSI時鐘
LSI RC擔(dān)當(dāng)一個低功耗時鐘源的角色,它可以在停機和待機模式下保持運行,為獨立看門狗和自動喚醒單元提供時鐘。**LSI時鐘頻率大約40kHz(在30kHz和60kHz之間)。**進一步信息請參考數(shù)據(jù)手冊中有關(guān)電氣特性部分。
LSI RC可以通過控制/狀態(tài)寄存器(RCC_CSR)里的LSION位來啟動或關(guān)閉。
在控制/狀態(tài)寄存器(RCC_CSR)里的LSIRDY位指示低速內(nèi)部振蕩器是否穩(wěn)定。在啟動階段,直到這個位被硬件設(shè)置為’1’后,此時鐘才被釋放。如果在時鐘中斷寄存器(RCC_CIR)里被允許,將產(chǎn)生LSI中斷申請。
注意:只有大容量和互聯(lián)型產(chǎn)品可以進行LSI校準(zhǔn)
LSI校準(zhǔn)
可以通過校準(zhǔn)內(nèi)部低速振蕩器LSI來補償其頻率偏移,從而獲得精度可接受的RTC時間基數(shù),以及獨立看門狗(IWDG)的超時時間(當(dāng)這些外設(shè)以LSI為時鐘源)。
**校準(zhǔn)可以通過使用TIM5的輸入時鐘(TIM5_CLK)測量LSI時鐘頻率實現(xiàn)。**測量以HSE的精度為保證,軟件可以通過調(diào)整RTC的20位預(yù)分頻器來獲得精確的RTC時鐘基數(shù),以及通過計算得到精確的獨立看門狗(IWDG)的超時時間。
LSI校準(zhǔn)步驟如下:
打開TIM5,設(shè)置通道4為輸入捕獲模式;
設(shè)置AFIO_MAPR的TIM5_CH4_IREMAP位為’1’,在內(nèi)部把LSI連接到TIM5的通道4;
通過TIM5的捕獲/比較4事件或者中斷來測量LSI時鐘頻率;
根據(jù)測量結(jié)果和期望的RTC時間基數(shù)和獨立看門狗的超時時間,設(shè)置20位預(yù)分頻器。
四、RTC時鐘
**通 過 設(shè) 置 備 份 域 控 制 寄 存 器 (RCC_BDCR) 里 的 RTCSEL[1:0] 位 , RTCCLK 時鐘源可以由HSE/128、LSE或LSI時鐘提供。**除非備份域復(fù)位,此選擇不能被改變。
LSE時鐘在備份域里,但HSE和LSI時鐘不是。因此:
● 如果LSE被選為RTC時鐘:
— 只要VBAT維持供電,盡管VDD供電被切斷, RTC仍繼續(xù)工作。
● 如果LSI被選為自動喚醒單元(AWU)時鐘:
— 如果VDD供電被切斷, AWU狀態(tài)不能被保證。有關(guān)LSI校準(zhǔn),詳見6.2.5節(jié)LSI時鐘。
● 如果HSE時鐘128分頻后作為RTC時鐘:
— 如果VDD供電被切斷或內(nèi)部電壓調(diào)壓器被關(guān)閉(1.8V域的供電被切斷),則RTC狀態(tài)不確定。
— 必須設(shè)置電源控制寄存器(見4.4.1節(jié))的DPB位(取消后備區(qū)域的寫保護)為’1’。
五、RTC寄存器
上面都是從STM32中文手冊里摘取的。大概了解一下RTC和時鐘。
不過講的有點扯,里面有多好寄存器,不知道是干啥的。接下來重點看一下這些寄存器。
RTC控制寄存器高位(RTC_CRH)
RTC控制寄存器低位(RTC_CRL)
①修改CRH/CRL寄存器,必須先判斷RSF位,確定已經(jīng)同步。
②修改CNT,ALR,PRL的時候,必須先配置CNF位進入配置模式,修改完之后,設(shè)置CNF位為0退出配置模式
③同時在對RTC相關(guān)寄存器寫操作之前,必須判斷上一次寫操作已經(jīng)結(jié)束,也就是判斷RTOFF位是否置位。
RTC預(yù)分頻裝載寄存器(RTC_PRLH/RTC_PRLL)
預(yù)分頻裝載寄存器用來保存RTC預(yù)分頻器的周期計數(shù)值。它們受RTC_CR寄存器的RTOFF位保護,僅當(dāng)RTOFF值為’1’時允許進行寫操作。
配置RTC寄存器
必須設(shè)置RTC_CRL 寄 存 器 中 的CNF位 , 使RTC進入配置模式后 , 才能寫 入RTC_PRL、RTC_CNT、 RTC_ALR寄存器。
另外,對RTC任何寄存器的寫操作,都必須在前一次寫操作結(jié)束后進行??梢酝ㄟ^查詢RTC_CR寄存器中的RTOFF狀態(tài)位,判斷RTC寄存器是否處于更新中。僅當(dāng)RTOFF狀態(tài)位是’1’時,才可以寫入RTC寄存器。
配置過程:
查詢RTOFF位,直到RTOFF的值變?yōu)椤?’
置CNF值為1,進入配置模式
對一個或多個RTC寄存器進行寫操作
清除CNF標(biāo)志位,退出配置模式
查詢RTOFF,直至RTOFF位變?yōu)椤?’以確認(rèn)寫操作已經(jīng)完成。
僅當(dāng)CNF標(biāo)志位被清除時,寫操作才能進行,這個過程至少需要3個RTCCLK周期。
讀RTC寄存器
RTC核完全獨立于RTC APB1接口。
軟件通過APB1接口訪問RTC的預(yù)分頻值、 計數(shù)器值和鬧鐘值。但是,相關(guān)的可讀寄存器只在與RTC APB1時鐘進行重新同步的RTC時鐘的上升沿被更新。RTC標(biāo)志也是如此的。
這意味著,如果APB1接口曾經(jīng)被關(guān)閉,而讀操作又是在剛剛重新開啟APB1之后,則在第一次的內(nèi)部寄存器更新之前,從APB1上讀出的RTC寄存器數(shù)值可能被破壞了(通常讀到0)。下述幾種
情況下能夠發(fā)生這種情形:
● 發(fā)生系統(tǒng)復(fù)位或電源復(fù)位
● 系統(tǒng)剛從待機模式喚醒(參見第4.3節(jié):低功耗模式)。
● 系統(tǒng)剛從停機模式喚醒(參見第4.3節(jié):低功耗模式)。
所有以上情況中, APB1接口被禁止時(復(fù)位、無時鐘或斷電)RTC核仍保持運行狀態(tài)。
因此,若在讀取RTC寄存器時, RTC的APB1接口曾經(jīng)處于禁止?fàn)顟B(tài),則軟件首先必須等待RTC_CRL寄存器中的RSF位(寄存器同步標(biāo)志)被硬件置’1’。
注:RTC的 APB1接口不受WFI和WFE等低功耗模式的影響
六、RTC相關(guān)庫函數(shù)講解
庫函數(shù)所在文件:stm32f10x_rtc.c / stm32f10x_rtc.h
RTC時鐘源和時鐘操作函數(shù):
void RCC_RTCCLKConfig(uint32_t CLKSource);//時鐘源選擇 void RCC_RTCCLKCmd(FunctionalState NewState)//時鐘使能RTC配置函數(shù)(預(yù)分頻,計數(shù)值:
void RTC_SetPrescaler(uint32_t PrescalerValue);//預(yù)分頻配置:PRLH/PRLLvoid RTC_SetCounter(uint32_t CounterValue);//設(shè)置計數(shù)器值:CNTH/CNTLvoid RTC_SetAlarm(uint32_t AlarmValue);//鬧鐘設(shè)置:ALRH/ALRLRTC中斷設(shè)置函數(shù):
void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);//CRH
RTC允許配置和退出配置函數(shù):
void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);//CRH
同步函數(shù):
void RTC_WaitForLastTask(void);//等待上次操作完成:CRL位RTOFFvoid RTC_WaitForSynchro(void);//等待時鐘同步:CRL位RSF相關(guān)狀態(tài)位獲取清除函數(shù):
FlagStatus RTC_GetFlagStatus(uint16_t RTC_FLAG);void RTC_ClearFlag(uint16_t RTC_FLAG);ITStatus RTC_GetITStatus(uint16_t RTC_IT);void RTC_ClearITPendingBit(uint16_t RTC_IT);其他相關(guān)函數(shù)(BKP等)
PWR_BackupAccessCmd();//BKP后備區(qū)域訪問使能RCC_APB1PeriphClockCmd();//使能PWR和BKP時鐘RCC_LSEConfig();//開啟LSE,RTC選擇LSE作為時鐘源PWR_BackupAccessCmd();//BKP后備區(qū)域訪問使能uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);//讀BKP寄存器void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);//寫B(tài)KP 七、RTC配置一般步驟 1) 使能電源時鐘和備份區(qū)域時鐘。 前面已經(jīng)介紹了,我們要訪問 RTC 和備份區(qū)域就必須先使能電源時鐘和備份區(qū)域時鐘。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
2) 取消備份區(qū)寫保護。
要向備份區(qū)域?qū)懭霐?shù)據(jù),就要先取消備份區(qū)域?qū)懕Wo(寫保護在每次硬復(fù)位之后被使能),否則是無法向備份區(qū)域?qū)懭霐?shù)據(jù)的。我們需要用到向備份區(qū)域?qū)懭胍粋€字節(jié),來標(biāo)記時鐘已經(jīng)配置過了,這樣避免每次復(fù)位之后重新配置時鐘。取消備份區(qū)域?qū)懕Wo的庫函數(shù)實現(xiàn)方法是:
PWR_BackupAccessCmd(ENABLE); //使能 RTC 和后備寄存器訪問
3) 復(fù)位備份區(qū)域,開啟外部低速振蕩器。
在取消備份區(qū)域?qū)懕Wo之后,我們可以先對這個區(qū)域復(fù)位,以清除前面的設(shè)置,當(dāng)然這個操作不要每次都執(zhí)行,因為備份區(qū)域的復(fù)位將導(dǎo)致之前存在的數(shù)據(jù)丟失,所以要不要復(fù)位,要看情況而定。然后我們使能外部低速振蕩器,注意這里一般要先判斷 RCC_BDCR 的 LSERDY位來確定低速振蕩器已經(jīng)就緒了才開始下面的操作。
備份區(qū)域復(fù)位的函數(shù)是:
BKP_DeInit();//復(fù)位備份區(qū)域
開啟外部低速振蕩器的函數(shù)是:
RCC_LSEConfig(RCC_LSE_ON);// 開啟外部低速振蕩器
4) 選擇 RTC 時鐘,并使能。
這里我們將通過 RCC_BDCR 的 RTCSEL 來選擇選擇外部 LSI 作為 RTC 的時鐘。然后通過RTCEN 位使能 RTC 時鐘。庫函數(shù)中,選擇 RTC 時鐘的函數(shù)是:
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //選擇 LSE 作為 RTC 時鐘
對于 RTC 時鐘的選擇,還有 RCC_RTCCLKSource_LSI 和RCC_RTCCLKSource_HSE_Div128 這兩個,顧名思義,前者為 LSI,后者為 HSE 的 128 分頻,這在時鐘系統(tǒng)章節(jié)有講解過。
使能 RTC 時鐘的函數(shù)是:
RCC_RTCCLKCmd(ENABLE); //使能 RTC 時鐘
5) 設(shè)置 RTC 的分頻,以及配置 RTC 時鐘。
在開啟了 RTC 時鐘之后,我們要做的就是設(shè)置 RTC 時鐘的分頻數(shù),通過 RTC_PRLH 和RTC_PRLL 來設(shè)置,然后等待 RTC 寄存器操作完成,并同步之后,設(shè)置秒鐘中斷。然后設(shè)置RTC 的允許配置位(RTC_CRH 的 CNF 位),設(shè)置時間(其實就是設(shè)置 RTC_CNTH 和 RTC_CNTL兩個寄存器)。下面我們一一這些步驟用到的庫函數(shù):在進行 RTC 配置之前首先要打開允許配置位(CNF),庫函數(shù)是:
RTC_EnterConfigMode();/// 允許配置
在配置完成之后,千萬別忘記更新配置同時退出配置模式,函數(shù)是:
RTC_ExitConfigMode();//退出配置模式, 更新配置
設(shè)置 RTC 時鐘分頻數(shù), 庫函數(shù)是:
void RTC_SetPrescaler(uint32_t PrescalerValue);
這個函數(shù)只有一個入口參數(shù),就是 RTC 時鐘的分頻數(shù),很好理解。
然后是設(shè)置秒中斷允許, RTC 使能中斷的函數(shù)是:
void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);
這個函數(shù)的第一個參數(shù)是設(shè)置秒中斷類型,這些通過宏定義定義的。對于使能秒中斷方法是:
RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能 RTC 秒中斷
八、RTC程序
這篇文章復(fù)制粘貼了這么多,感覺不到一絲有用的東西。算了,還是看一下,程序是怎么寫的吧。
RTC_Init
//實時時鐘配置//初始化 RTC 時鐘,同時檢測時鐘是否工作正常//BKP-》DR1 用于保存是否第一次配置的設(shè)置//返回 0:正常//其他:錯誤代碼u8 RTC_Init(void){u8 temp=0;//檢查是不是第一次配置時鐘RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR |RCC_APB1Periph_BKP, ENABLE); //①使能 PWR 和 BKP 外設(shè)時鐘PWR_BackupAccessCmd(ENABLE); //②使能后備寄存器訪問if (BKP_ReadBackupRegister(BKP_DR1) != 0x5050) //從指定的后備寄存器中//讀出數(shù)據(jù):讀出了與寫入的指定數(shù)據(jù)不相乎{BKP_DeInit(); //③復(fù)位備份區(qū)域RCC_LSEConfig(RCC_LSE_ON); //設(shè)置外部低速晶振(LSE)while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&&temp《250)//檢查指定的 RCC 標(biāo)志位設(shè)置與否,等待低速晶振就緒{temp++;delay_ms(10);}if(temp》=250)return 1;//初始化時鐘失敗,晶振有問題RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //設(shè)置 RTC 時鐘//(RTCCLK),選擇 LSE 作為 RTC 時鐘RCC_RTCCLKCmd(ENABLE); //使能 RTC 時鐘RTC_WaitForLastTask(); //等待最近一次對 RTC 寄存器的寫操作完成RTC_WaitForSynchro(); //等待 RTC 寄存器同步RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能 RTC 秒中斷RTC_WaitForLastTask(); //等待最近一次對 RTC 寄存器的寫操作完成RTC_EnterConfigMode(); // 允許配置RTC_SetPrescaler(32767); //設(shè)置 RTC 預(yù)分頻的值RTC_WaitForLastTask(); //等待最近一次對 RTC 寄存器的寫操作完成RTC_Set(2015,1,14,17,42,55); //設(shè)置時間RTC_ExitConfigMode(); //退出配置模式BKP_WriteBackupRegister(BKP_DR1, 0X5050); //向指定的后備寄存器中//寫入用戶程序數(shù)據(jù) 0x5050}else//系統(tǒng)繼續(xù)計時{RTC_WaitForSynchro(); //等待最近一次對 RTC 寄存器的寫操作完成RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能 RTC 秒中斷RTC_WaitForLastTask(); //等待最近一次對 RTC 寄存器的寫操作完成}RTC_NVIC_Config(); //RCT 中斷分組設(shè)置RTC_Get(); //更新時間return 0; //ok}RTC_Set
//設(shè)置時鐘//把輸入的時鐘轉(zhuǎn)換為秒鐘//以 1970 年 1 月 1 日為基準(zhǔn)//1970~2099 年為合法年份//返回值:0,成功;其他:錯誤代碼。//月份數(shù)據(jù)表u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正數(shù)據(jù)表//平年的月份日期表const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31};u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec){u16 t;u32 seccount=0;if(syear《1970||syear》2099)return 1;for(t=1970;t《syear;t++) //把所有年份的秒鐘相加{ if(Is_Leap_Year(t))seccount+=31622400;//閏年的秒鐘數(shù)else seccount+=31536000; //平年的秒鐘數(shù)}smon-=1;for(t=0;t《smon;t++) //把前面月份的秒鐘數(shù)相加{ seccount+=(u32)mon_table[t]*86400; //月份秒鐘數(shù)相加if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//閏年 2 月份增加一天的秒鐘數(shù)}seccount+=(u32)(sday-1)*86400; //把前面日期的秒鐘數(shù)相加seccount+=(u32)hour*3600; //小時秒鐘數(shù)seccount+=(u32)min*60; //分鐘秒鐘數(shù)seccount+=sec; //最后的秒鐘加上去RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR |RCC_APB1Periph_BKP, ENABLE); //使能 PWR 和 BKP 外設(shè)時鐘PWR_BackupAccessCmd(ENABLE); //使能 RTC 和后備寄存器訪問RTC_SetCounter(seccount); //設(shè)置 RTC 計數(shù)器的值RTC_WaitForLastTask(); //等待最近一次對 RTC 寄存器的寫操作完成return 0;}RTC_Get
//得到當(dāng)前的時間,結(jié)果保存在 calendar 結(jié)構(gòu)體里面//返回值:0,成功;其他:錯誤代碼.u8 RTC_Get(void){ static u16 daycnt=0;u32 timecount=0;u32 temp=0;u16 temp1=0;timecount=RTC-》CNTH; //得到計數(shù)器中的值(秒鐘數(shù))timecount《《=16;timecount+=RTC-》CNTL;temp=timecount/86400; //得到天數(shù)(秒鐘數(shù)對應(yīng)的)if(daycnt!=temp) //超過一天了{daycnt=temp;temp1=1970; //從 1970 年開始while(temp》=365){if(Is_Leap_Year(temp1)) //是閏年{if(temp》=366)temp-=366; //閏年的秒鐘數(shù)else break;}else temp-=365; //平年temp1++;}calendar.w_year=temp1; //得到年份temp1=0;while(temp》=28) //超過了一個月{if(Is_Leap_Year(calendar.w_year)&&temp1==1)//當(dāng)年是不是閏年/2 月份{if(temp》=29)temp-=29;//閏年的秒鐘數(shù)else break;}else{ if(temp》=mon_table[temp1])temp-=mon_table[temp1];//平年else break;}temp1++;}calendar.w_month=temp1+1; //得到月份calendar.w_date=temp+1; //得到日期}temp=timecount%86400; //得到秒鐘數(shù)calendar.hour=temp/3600; //小時calendar.min=(temp%3600)/60; //分鐘calendar.sec=(temp%3600)%60; //秒鐘calendar.week=RTC_Get_Week(calendar.w_year,calendar.w_month,calendar.w_date);//獲取星期return 0;}RTC_NVIC_Config
static void RTC_NVIC_Config(void){NVIC_InitTypeDef NVIC_InitStructure;NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;//RTC全局中斷NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//先占優(yōu)先級1位,從優(yōu)先級3位NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;//先占優(yōu)先級0位,從優(yōu)先級4位NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能該通道中斷NVIC_Init(&NVIC_InitStructure);//根據(jù)NVIC_InitStruct中指定的參數(shù)初始化外設(shè)NVIC寄存器}RTC_IRQHandler
//RTC 時鐘中斷//每秒觸發(fā)一次void RTC_IRQHandler(void){if (RTC_GetITStatus(RTC_IT_SEC) != RESET) //秒鐘中斷{RTC_Get(); //更新時間}if(RTC_GetITStatus(RTC_IT_ALR)!= RESET) //鬧鐘中斷{RTC_ClearITPendingBit(RTC_IT_ALR); //清鬧鐘中斷RTC_Get(); //更新時間printf(“Alarm Time:%d-%d-%d %d:%d:%d ”,calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec);//輸出鬧鈴時間}RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW); //清鬧鐘中斷RTC_WaitForLastTask();}九、項目代碼
void BSP_RTC_Init(void){u32 i = 0;#if(INFO_OUT_RTC_INIT_EN 》 0)u8tmpBuf[60]=“”;#endif
/* Clear reset flags */RCC_ClearFlag();
// 這里標(biāo)志必須跟測試程序一致否則時間被復(fù)位成默認(rèn)if (BKP_ReadBackupRegister(BKP_DR1) != RTC_SAVE_FLAG){RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);/* Allow access to BKP Domain */PWR_BackupAccessCmd(ENABLE);
/* Backup data register value is not correct or not yet programmed (whenthe first time the program is executed) */
/* RTC Configuration */BSP_RTC_Config();
#if(DEF_RTCINFO_OUTPUTEN 》 0)if(dbgInfoSwt & DBG_INFO_RTC)myPrintf(“[RTC]: RTC finish configured.。。。 ”);#endif
/* Set default time */SYS_RTC.year=Default_year;SYS_RTC.month=Default_month;SYS_RTC.day=Default_day;SYS_RTC.hour=Default_hour;SYS_RTC.minute =Default_minute;SYS_RTC.second =Default_second;
/* Adjust time by values entred by the user on the hyperterminal */BSP_RTC_Set_Current(&SYS_RTC);
BKP_WriteBackupRegister(BKP_DR1, RTC_SAVE_FLAG);}else{/* Enable PWR and BKP clocks */RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
/* Allow access to BKP Domain */PWR_BackupAccessCmd(ENABLE);
/* Wait for RTC registers synchronization */RTC_WaitForSynchro();
/* Wait until last write operation on RTC registers has finished */RTC_WaitForLastTask();
/* Enable the RTC Second *///RTC_ITConfig(RTC_IT_SEC, ENABLE);// 不能在系統(tǒng)運行前使能中斷//RTC_ITConfig(RTC_IT_ALR, ENABLE);// 系統(tǒng)鬧鐘中斷/* Wait until last write operation on RTC registers has finished *///RTC_WaitForLastTask();// 不能在系統(tǒng)運行前使能中斷
/* Initialize Date structure */SYS_RTC.year = BKP_ReadBackupRegister(BKP_DR4);SYS_RTC.month= BKP_ReadBackupRegister(BKP_DR3);SYS_RTC.day = BKP_ReadBackupRegister(BKP_DR2);
if(RTC_GetCounter() / 86399 != 0){for(i = 0; i 《 (RTC_GetCounter() / 86399); i++){BSP_Date_Update(&SYS_RTC);}
/* Wait until last write operation on RTC registers has finished */RTC_WaitForLastTask();RTC_SetCounter(RTC_GetCounter() % 86399);/* Wait until last write operation on RTC registers has finished */RTC_WaitForLastTask();
BKP_WriteBackupRegister(BKP_DR4, SYS_RTC.year);BKP_WriteBackupRegister(BKP_DR3, SYS_RTC.month);BKP_WriteBackupRegister(BKP_DR2, SYS_RTC.day);}}/* Clear the RTC Second Interrupt pending bit */RTC_ClearITPendingBit(RTC_IT_SEC);// 防止系統(tǒng)初始化未完成前進入中斷程序RTC_ClearFlag(RTC_IT_SEC);
/* Enable one second interrupe *///RTC_ITConfig(RTC_IT_SEC,ENABLE);// 不能在系統(tǒng)運行前使能中斷rtcInitFinish=1;// 設(shè)置初始化完成標(biāo)志}
void BSP_RTC_Config(void){//u32 counter = 0;uint32_t tmp = 0;RCC_ClocksTypeDef RCC_Clocks;TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;TIM_ICInitTypeDef TIM_ICInitStructure;
/* Enable PWR and BKP clocks */RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);/* Allow access to BKP Domain */PWR_BackupAccessCmd(ENABLE);/* Reset Backup Domain */BKP_DeInit();RCC_LSICmd(ENABLE); //啟用LSIwhile (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET){}RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);RCC_RTCCLKCmd(ENABLE); // Enable RTC ClockRTC_WaitForSynchro();RTC_WaitForLastTask();RTC_SetPrescaler(40000); // RTC period = RTCCLK/RTC_PR = (4 KHz)/(4000+1) LSIRTC_WaitForLastTask();BKP_TamperPinCmd(DISABLE);BKP_RTCOutputConfig(BKP_RTCOutputSource_Second);RCC_GetClocksFreq(&RCC_Clocks);RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);GPIO_PinRemapConfig(GPIO_Remap_TIM5CH4_LSI, ENABLE);TIM_TimeBaseStructure.TIM_Prescaler = 0;TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;TIM_TimeBaseStructure.TIM_Period = 0xFFFF;TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure);TIM_ICInitStructure.TIM_Channel = TIM_Channel_4;TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;TIM_ICInitStructure.TIM_ICFilter = 0;TIM_ICInit(TIM5, &TIM_ICInitStructure);OperationComplete = 0;TIM_Cmd(TIM5, ENABLE);TIM5-》SR = 0;//TIM_ITConfig(TIM5, TIM_IT_CC4, ENABLE);
while (OperationComplete != 2){if (TIM_GetFlagStatus(TIM5, TIM_FLAG_CC4) == SET){tmpCC4[IncrementVar_OperationComplete()] = (uint16_t)(TIM5-》CCR4);TIM_ClearFlag(TIM5, TIM_FLAG_CC4);if (GetVar_OperationComplete() 》= 2){tmp = (uint16_t)(tmpCC4[1] - tmpCC4[0] + 1);SetVar_PeriodValue(tmp);}}}if (PeriodValue != 0){#if defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || defined (STM32F10X_HD_VL)LsiFreq = (uint32_t)((uint32_t)(RCC_Clocks.PCLK1_Frequency) / (uint32_t)PeriodValue);#elseLsiFreq = (uint32_t)((uint32_t)(RCC_Clocks.PCLK1_Frequency * 2) / (uint32_t)PeriodValue);#endif}RTC_SetPrescaler(LsiFreq - 1);RTC_WaitForLastTask();
TIM_DeInit( TIM5 );}十、HSE作為RTC時鐘源
void RTC_Configuration(void){RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);PWR_BackupAccessCmd(ENABLE);/* Reset Backup Domain */BKP_DeInit();//使用外部高速晶振8M/128 = 62.5KRCC_RTCCLKConfig(RCC_RTCCLKSource_HSE_Div128);//允許RTCRCC_RTCCLKCmd(ENABLE);//等待RTC寄存器同步RTC_WaitForSynchro();
RTC_WaitForLastTask();//允許RTC的秒中斷(還有鬧鐘中斷和溢出中斷可設(shè)置)RTC_ITConfig(RTC_IT_SEC, ENABLE);
RTC_WaitForLastTask();//62500晶振預(yù)分頻值是62500,不過一般來說晶振都不那么準(zhǔn)RTC_SetPrescaler(62498); //如果需要校準(zhǔn)晶振,可修改此分頻值RTC_WaitForLastTask();//清除標(biāo)志RCC_ClearFlag();}編輯:jq
-
時鐘
+關(guān)注
關(guān)注
10文章
1714瀏覽量
131274 -
定時器
+關(guān)注
關(guān)注
23文章
3231瀏覽量
114326 -
RTC
+關(guān)注
關(guān)注
2文章
522瀏覽量
66227 -
可編程計數(shù)器
+關(guān)注
關(guān)注
0文章
3瀏覽量
5601
原文標(biāo)題:STM32開發(fā) -- RTC詳解
文章出處:【微信號:mcu168,微信公眾號:硬件攻城獅】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論