一、RTC實(shí)時(shí)時(shí)鐘特征與原理
查看STM32中文手冊(cè) 16 實(shí)時(shí)時(shí)鐘(RTC)(308頁)
RTC (Real Time Clock):實(shí)時(shí)時(shí)鐘
實(shí)時(shí)時(shí)鐘是一個(gè)獨(dú)立的定時(shí)器。RTC模塊擁有一組連續(xù)計(jì)數(shù)的計(jì)數(shù)器,在相應(yīng)軟件配置下,可提供時(shí)鐘日歷的功能。修改計(jì)數(shù)器的值可以重新設(shè)置系統(tǒng)當(dāng)前的時(shí)間和日期。
RTC模塊和時(shí)鐘配置系統(tǒng)(RCC_BDCR寄存器)處于后備區(qū)域,即在系統(tǒng)復(fù)位或從待機(jī)模式喚醒后, RTC的設(shè)置和時(shí)間維持不變。
系統(tǒng)復(fù)位后,對(duì)后備寄存器和RTC的訪問被禁止,這是為了防止對(duì)后備區(qū)域(BKP)的意外寫操作。執(zhí)行以下操作將使能對(duì)后備寄存器和RTC的訪問:
● 設(shè)置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能電源和后備接口時(shí)鐘
● 設(shè)置寄存器PWR_CR的DBP位,使能對(duì)后備寄存器和RTC的訪問。
RTC特征
可編程的預(yù)分頻系數(shù):分頻系數(shù)最高為220。
● 32位的可編程計(jì)數(shù)器,可用于較長(zhǎng)時(shí)間段的測(cè)量。
● 2個(gè)分離的時(shí)鐘:用于APB1接口的PCLK1和RTC時(shí)鐘(RTC時(shí)鐘的頻率必須小于PCLK1時(shí)鐘
頻率的四分之一以上)。
● 可以選擇以下三種RTC的時(shí)鐘源:
— HSE時(shí)鐘除以128;
— LSE振蕩器時(shí)鐘;
— LSI振蕩器時(shí)鐘(詳見6.2.8節(jié)RTC時(shí)鐘)。
● 2個(gè)獨(dú)立的復(fù)位類型:
— APB1接口由系統(tǒng)復(fù)位;
— RTC核心(預(yù)分頻器、鬧鐘、計(jì)數(shù)器和分頻器)只能由后備域復(fù)位(詳見6.1.3節(jié))。
● 3個(gè)專門的可屏蔽中斷:
— 鬧鐘中斷,用來產(chǎn)生一個(gè)軟件可編程的鬧鐘中斷。
— 秒中斷,用來產(chǎn)生一個(gè)可編程的周期性中斷信號(hào)(最長(zhǎng)可達(dá)1秒)。
— 溢出中斷,指示內(nèi)部可編程計(jì)數(shù)器溢出并回轉(zhuǎn)為0的狀態(tài)。
二、RTC由兩部分組成
**APB1接口:**用來和APB1總線相連。通過APB1接口可以訪問RTC的相關(guān)寄存器(預(yù)分頻值,計(jì)數(shù)器值,鬧鐘值)。
**RTC核心:**由一組可編程計(jì)數(shù)器組成。分兩個(gè)主要模塊。
第一個(gè)是RTC預(yù)分頻模塊,它可以編程產(chǎn)生最長(zhǎng)1秒的RTC時(shí)間基TR_CLK。如果設(shè)置了秒中斷允許位,可以產(chǎn)生秒中斷。
第二個(gè)是32位的可編程計(jì)數(shù)器,可被初始化為當(dāng)前時(shí)間。系統(tǒng)時(shí)間按TR_CLK周期累加并與存儲(chǔ)在RTC_ALR寄存器中的可編程時(shí)間相比,當(dāng)匹配時(shí)候如果設(shè)置了鬧鐘中斷允許位,可以產(chǎn)生鬧鐘中斷。
RTC內(nèi)核完全獨(dú)立于APB1接口,軟件通過APB1接口對(duì)RTC相關(guān)寄存器訪問。但是相關(guān)寄存器只在RTC APB1時(shí)鐘進(jìn)行重新同步的RTC時(shí)鐘的上升沿被更新。所以軟件必須先等待寄存器同步標(biāo)志位(RTC_CRL的RSF位)被硬件置1才讀。
三、RTC時(shí)鐘源
首先講一下時(shí)鐘源:
三種不同的時(shí)鐘源可被用來驅(qū)動(dòng)系統(tǒng)時(shí)鐘(SYSCLK):
● HSI振蕩器時(shí)鐘
● HSE振蕩器時(shí)鐘
● PLL時(shí)鐘
這些設(shè)備有以下2種二級(jí)時(shí)鐘源:
● 40kHz低速內(nèi)部RC,可以用于驅(qū)動(dòng)獨(dú)立看門狗和通過程序選擇驅(qū)動(dòng)RTC。RTC用于從停機(jī)/待機(jī)模式下自動(dòng)喚醒系統(tǒng)。
● 32.768kHz低速外部晶體也可用來通過程序選擇驅(qū)動(dòng)RTC(RTCCLK)。
參看:STM32時(shí)鐘系統(tǒng)
當(dāng)不被使用時(shí),任一個(gè)時(shí)鐘源都可被獨(dú)立地啟動(dòng)或關(guān)閉,由此優(yōu)化系統(tǒng)功耗。
用戶可通過多個(gè)預(yù)分頻器配置AHB、高速APB(APB2)和低速APB(APB1)域的頻率。AHB和APB2域的最大頻率是72MHz。APB1域的最大允許頻率是36MHz。SDIO接口的時(shí)鐘頻率固定為HCLK/2。
RCC通過AHB時(shí)鐘(HCLK)8分頻后作為Cortex系統(tǒng)定時(shí)器(SysTick)的外部時(shí)鐘。通過對(duì)SysTick控制與狀態(tài)寄存器的設(shè)置,可選擇上述時(shí)鐘或Cortex(HCLK)時(shí)鐘作為SysTick時(shí)鐘。ADC時(shí)鐘由高速APB2時(shí)鐘經(jīng)2、 4、 6或8分頻后獲得。
定時(shí)器時(shí)鐘頻率分配由硬件按以下2種情況自動(dòng)設(shè)置:
如果相應(yīng)的APB預(yù)分頻系數(shù)是1,定時(shí)器的時(shí)鐘頻率與所在APB總線頻率一致。
否則,定時(shí)器的時(shí)鐘頻率被設(shè)為與其相連的APB總線頻率的2倍。
如上圖,有五個(gè)時(shí)鐘源,為HSI、HSE、LSI、LSE、PLL。
接下來我們一一看一下:
HSE時(shí)鐘
**高速外部時(shí)鐘信號(hào)(HSE)**由以下兩種時(shí)鐘源產(chǎn)生:
● HSE外部晶體/陶瓷諧振器
● HSE用戶外部時(shí)鐘
為了減少時(shí)鐘輸出的失真和縮短啟動(dòng)穩(wěn)定時(shí)間,晶體/陶瓷諧振器和負(fù)載電容器必須盡可能地靠
近振蕩器引腳。負(fù)載電容值必須根據(jù)所選擇的振蕩器來調(diào)整。
外部時(shí)鐘源(HSE旁路)
在這個(gè)模式里,必須提供外部時(shí)鐘。它的頻率最高可達(dá)25MHz。用戶可通過設(shè)置在時(shí)鐘控制寄存器中的HSEBYP和HSEON位來選擇這一模式。外部時(shí)鐘信號(hào)(50%占空比的方波、正弦波或三角波)必須連到SOC_IN引腳,同時(shí)保證OSC_OUT引腳懸空。見圖9。
外部晶體/陶瓷諧振器(HSE晶體)
**4~16Mz外部振蕩器可為系統(tǒng)提供更為精確的主時(shí)鐘。**相關(guān)的硬件配置可參考圖9,進(jìn)一步信息可參考數(shù)據(jù)手冊(cè)的電氣特性部分。
在時(shí)鐘控制寄存器RCC_CR中的HSERDY位用來指示高速外部振蕩器是否穩(wěn)定。在啟動(dòng)時(shí),直到這一位被硬件置’1’,時(shí)鐘才被釋放出來。如果在時(shí)鐘中斷寄存器RCC_CIR中允許產(chǎn)生中斷,將會(huì)產(chǎn)生相應(yīng)中斷。
HSE晶體可以通過設(shè)置時(shí)鐘控制寄存器里RCC_CR中的HSEON位被啟動(dòng)和關(guān)閉。
HSI時(shí)鐘
HSI時(shí)鐘信號(hào)由內(nèi)部8MHz的RC振蕩器產(chǎn)生,可直接作為系統(tǒng)時(shí)鐘或在2分頻后作為PLL輸入。
HSI RC振蕩器能夠在不需要任何外部器件的條件下提供系統(tǒng)時(shí)鐘。它的啟動(dòng)時(shí)間比HSE晶體振蕩器短。然而,即使在校準(zhǔn)之后它的時(shí)鐘頻率精度仍較差。
校準(zhǔn)
制造工藝決定了不同芯片的RC振蕩器頻率會(huì)不同,這就是為什么每個(gè)芯片的HSI時(shí)鐘頻率在出廠前已經(jīng)被ST校準(zhǔn)到1%(25°C)的原因。系統(tǒng)復(fù)位時(shí),工廠校準(zhǔn)值被裝載到時(shí)鐘控制寄存器的HSICAL[7:0]位。
如果用戶的應(yīng)用基于不同的電壓或環(huán)境溫度,這將會(huì)影響RC振蕩器的精度。可以通過時(shí)鐘控制寄存器里的HSITRIM[4:0]位來調(diào)整HSI頻率。
時(shí)鐘控制寄存器中的HSIRDY位用來指示HSI RC振蕩器是否穩(wěn)定。在時(shí)鐘啟動(dòng)過程中,直到這一位被硬件置’1’, HSI RC輸出時(shí)鐘才被釋放。HSI RC可由時(shí)鐘控制寄存器中的HSION位來啟動(dòng)和關(guān)閉。
如果HSE晶體振蕩器失效, HSI時(shí)鐘會(huì)被作為備用時(shí)鐘源。
PLL
內(nèi)部PLL可以用來倍頻HSI RC的輸出時(shí)鐘或HSE晶體輸出時(shí)鐘。
PLL的設(shè)置(選擇HIS振蕩器除2或HSE振蕩器為PLL的輸入時(shí)鐘,和選擇倍頻因子)必須在其被激活前完成。一旦PLL被激活,這些參數(shù)就不能被改動(dòng)。
如果PLL中斷在時(shí)鐘中斷寄存器里被允許,當(dāng)PLL準(zhǔn)備就緒時(shí),可產(chǎn)生中斷申請(qǐng)。
如果需要在應(yīng)用中使用USB接口, PLL必須被設(shè)置為輸出48或72MHZ時(shí)鐘,用于提供48MHz的USBCLK時(shí)鐘。
LSE時(shí)鐘
**LSE晶體是一個(gè)32.768kHz的低速外部晶體或陶瓷諧振器。**它為實(shí)時(shí)時(shí)鐘或者其他定時(shí)功能提供一個(gè)低功耗且精確的時(shí)鐘源。
LSE晶體通過在備份域控制寄存器(RCC_BDCR)里的LSEON位啟動(dòng)和關(guān)閉。
在備份域控制寄存器(RCC_BDCR)里的LSERDY指示LSE晶體振蕩是否穩(wěn)定。在啟動(dòng)階段,直到這個(gè)位被硬件置’1’后, LSE時(shí)鐘信號(hào)才被釋放出來。如果在時(shí)鐘中斷寄存器里被允許,可產(chǎn)生中斷申請(qǐng)。
外部時(shí)鐘源(LSE旁路)
在這個(gè)模式里必須提供一個(gè)32.768kHz頻率的外部時(shí)鐘源。你可以通過設(shè)置在備份域控制寄存器(RCC_BDCR)里的LSEBYP和LSEON位來選擇這個(gè)模式。具有50%占空比的外部時(shí)鐘信號(hào)(方波、正弦波或三角波)必須連到OSC32_IN引腳,同時(shí)保證OSC32_OUT引腳懸空,見圖9。
LSI時(shí)鐘
LSI RC擔(dān)當(dāng)一個(gè)低功耗時(shí)鐘源的角色,它可以在停機(jī)和待機(jī)模式下保持運(yùn)行,為獨(dú)立看門狗和自動(dòng)喚醒單元提供時(shí)鐘。**LSI時(shí)鐘頻率大約40kHz(在30kHz和60kHz之間)。**進(jìn)一步信息請(qǐng)參考數(shù)據(jù)手冊(cè)中有關(guān)電氣特性部分。
LSI RC可以通過控制/狀態(tài)寄存器(RCC_CSR)里的LSION位來啟動(dòng)或關(guān)閉。
在控制/狀態(tài)寄存器(RCC_CSR)里的LSIRDY位指示低速內(nèi)部振蕩器是否穩(wěn)定。在啟動(dòng)階段,直到這個(gè)位被硬件設(shè)置為’1’后,此時(shí)鐘才被釋放。如果在時(shí)鐘中斷寄存器(RCC_CIR)里被允許,將產(chǎn)生LSI中斷申請(qǐng)。
注意:只有大容量和互聯(lián)型產(chǎn)品可以進(jìn)行LSI校準(zhǔn)
LSI校準(zhǔn)
可以通過校準(zhǔn)內(nèi)部低速振蕩器LSI來補(bǔ)償其頻率偏移,從而獲得精度可接受的RTC時(shí)間基數(shù),以及獨(dú)立看門狗(IWDG)的超時(shí)時(shí)間(當(dāng)這些外設(shè)以LSI為時(shí)鐘源)。
**校準(zhǔn)可以通過使用TIM5的輸入時(shí)鐘(TIM5_CLK)測(cè)量LSI時(shí)鐘頻率實(shí)現(xiàn)。**測(cè)量以HSE的精度為保證,軟件可以通過調(diào)整RTC的20位預(yù)分頻器來獲得精確的RTC時(shí)鐘基數(shù),以及通過計(jì)算得到精確的獨(dú)立看門狗(IWDG)的超時(shí)時(shí)間。
LSI校準(zhǔn)步驟如下:
打開TIM5,設(shè)置通道4為輸入捕獲模式;
設(shè)置AFIO_MAPR的TIM5_CH4_IREMAP位為’1’,在內(nèi)部把LSI連接到TIM5的通道4;
通過TIM5的捕獲/比較4事件或者中斷來測(cè)量LSI時(shí)鐘頻率;
根據(jù)測(cè)量結(jié)果和期望的RTC時(shí)間基數(shù)和獨(dú)立看門狗的超時(shí)時(shí)間,設(shè)置20位預(yù)分頻器。
四、RTC時(shí)鐘
**通 過 設(shè) 置 備 份 域 控 制 寄 存 器 (RCC_BDCR) 里 的 RTCSEL[1:0] 位 , RTCCLK 時(shí)鐘源可以由HSE/128、LSE或LSI時(shí)鐘提供。**除非備份域復(fù)位,此選擇不能被改變。
LSE時(shí)鐘在備份域里,但HSE和LSI時(shí)鐘不是。因此:
● 如果LSE被選為RTC時(shí)鐘:
— 只要VBAT維持供電,盡管VDD供電被切斷, RTC仍繼續(xù)工作。
● 如果LSI被選為自動(dòng)喚醒單元(AWU)時(shí)鐘:
— 如果VDD供電被切斷, AWU狀態(tài)不能被保證。有關(guān)LSI校準(zhǔn),詳見6.2.5節(jié)LSI時(shí)鐘。
● 如果HSE時(shí)鐘128分頻后作為RTC時(shí)鐘:
— 如果VDD供電被切斷或內(nèi)部電壓調(diào)壓器被關(guān)閉(1.8V域的供電被切斷),則RTC狀態(tài)不確定。
— 必須設(shè)置電源控制寄存器(見4.4.1節(jié))的DPB位(取消后備區(qū)域的寫保護(hù))為’1’。
五、RTC寄存器
上面都是從STM32中文手冊(cè)里摘取的。大概了解一下RTC和時(shí)鐘。
不過講的有點(diǎn)扯,里面有多好寄存器,不知道是干啥的。接下來重點(diǎn)看一下這些寄存器。
RTC控制寄存器高位(RTC_CRH)
RTC控制寄存器低位(RTC_CRL)
①修改CRH/CRL寄存器,必須先判斷RSF位,確定已經(jīng)同步。
②修改CNT,ALR,PRL的時(shí)候,必須先配置CNF位進(jìn)入配置模式,修改完之后,設(shè)置CNF位為0退出配置模式
③同時(shí)在對(duì)RTC相關(guān)寄存器寫操作之前,必須判斷上一次寫操作已經(jīng)結(jié)束,也就是判斷RTOFF位是否置位。
RTC預(yù)分頻裝載寄存器(RTC_PRLH/RTC_PRLL)
預(yù)分頻裝載寄存器用來保存RTC預(yù)分頻器的周期計(jì)數(shù)值。它們受RTC_CR寄存器的RTOFF位保護(hù),僅當(dāng)RTOFF值為’1’時(shí)允許進(jìn)行寫操作。
配置RTC寄存器
必須設(shè)置RTC_CRL 寄 存 器 中 的CNF位 , 使RTC進(jìn)入配置模式后 , 才能寫 入RTC_PRL、RTC_CNT、 RTC_ALR寄存器。
另外,對(duì)RTC任何寄存器的寫操作,都必須在前一次寫操作結(jié)束后進(jìn)行。可以通過查詢RTC_CR寄存器中的RTOFF狀態(tài)位,判斷RTC寄存器是否處于更新中。僅當(dāng)RTOFF狀態(tài)位是’1’時(shí),才可以寫入RTC寄存器。
配置過程:
查詢RTOFF位,直到RTOFF的值變?yōu)椤?’
置CNF值為1,進(jìn)入配置模式
對(duì)一個(gè)或多個(gè)RTC寄存器進(jìn)行寫操作
清除CNF標(biāo)志位,退出配置模式
查詢RTOFF,直至RTOFF位變?yōu)椤?’以確認(rèn)寫操作已經(jīng)完成。
僅當(dāng)CNF標(biāo)志位被清除時(shí),寫操作才能進(jìn)行,這個(gè)過程至少需要3個(gè)RTCCLK周期。
讀RTC寄存器
RTC核完全獨(dú)立于RTC APB1接口。
軟件通過APB1接口訪問RTC的預(yù)分頻值、 計(jì)數(shù)器值和鬧鐘值。但是,相關(guān)的可讀寄存器只在與RTC APB1時(shí)鐘進(jìn)行重新同步的RTC時(shí)鐘的上升沿被更新。RTC標(biāo)志也是如此的。
這意味著,如果APB1接口曾經(jīng)被關(guān)閉,而讀操作又是在剛剛重新開啟APB1之后,則在第一次的內(nèi)部寄存器更新之前,從APB1上讀出的RTC寄存器數(shù)值可能被破壞了(通常讀到0)。下述幾種
情況下能夠發(fā)生這種情形:
● 發(fā)生系統(tǒng)復(fù)位或電源復(fù)位
● 系統(tǒng)剛從待機(jī)模式喚醒(參見第4.3節(jié):低功耗模式)。
● 系統(tǒng)剛從停機(jī)模式喚醒(參見第4.3節(jié):低功耗模式)。
所有以上情況中, APB1接口被禁止時(shí)(復(fù)位、無時(shí)鐘或斷電)RTC核仍保持運(yùn)行狀態(tài)。
因此,若在讀取RTC寄存器時(shí), RTC的APB1接口曾經(jīng)處于禁止?fàn)顟B(tài),則軟件首先必須等待RTC_CRL寄存器中的RSF位(寄存器同步標(biāo)志)被硬件置’1’。
注:RTC的 APB1接口不受WFI和WFE等低功耗模式的影響
六、RTC相關(guān)庫(kù)函數(shù)講解
庫(kù)函數(shù)所在文件:stm32f10x_rtc.c / stm32f10x_rtc.h
RTC時(shí)鐘源和時(shí)鐘操作函數(shù):
void RCC_RTCCLKConfig(uint32_t CLKSource);//時(shí)鐘源選擇 void RCC_RTCCLKCmd(FunctionalState NewState)//時(shí)鐘使能RTC配置函數(shù)(預(yù)分頻,計(jì)數(shù)值:
void RTC_SetPrescaler(uint32_t PrescalerValue);//預(yù)分頻配置:PRLH/PRLLvoid RTC_SetCounter(uint32_t CounterValue);//設(shè)置計(jì)數(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);//等待時(shí)鐘同步: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時(shí)鐘RCC_LSEConfig();//開啟LSE,RTC選擇LSE作為時(shí)鐘源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) 使能電源時(shí)鐘和備份區(qū)域時(shí)鐘。 前面已經(jīng)介紹了,我們要訪問 RTC 和備份區(qū)域就必須先使能電源時(shí)鐘和備份區(qū)域時(shí)鐘。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
2) 取消備份區(qū)寫保護(hù)。
要向備份區(qū)域?qū)懭霐?shù)據(jù),就要先取消備份區(qū)域?qū)懕Wo(hù)(寫保護(hù)在每次硬復(fù)位之后被使能),否則是無法向備份區(qū)域?qū)懭霐?shù)據(jù)的。我們需要用到向備份區(qū)域?qū)懭胍粋€(gè)字節(jié),來標(biāo)記時(shí)鐘已經(jīng)配置過了,這樣避免每次復(fù)位之后重新配置時(shí)鐘。取消備份區(qū)域?qū)懕Wo(hù)的庫(kù)函數(shù)實(shí)現(xiàn)方法是:
PWR_BackupAccessCmd(ENABLE); //使能 RTC 和后備寄存器訪問
3) 復(fù)位備份區(qū)域,開啟外部低速振蕩器。
在取消備份區(qū)域?qū)懕Wo(hù)之后,我們可以先對(duì)這個(gè)區(qū)域復(fù)位,以清除前面的設(shè)置,當(dāng)然這個(gè)操作不要每次都執(zhí)行,因?yàn)閭浞輩^(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 時(shí)鐘,并使能。
這里我們將通過 RCC_BDCR 的 RTCSEL 來選擇選擇外部 LSI 作為 RTC 的時(shí)鐘。然后通過RTCEN 位使能 RTC 時(shí)鐘。庫(kù)函數(shù)中,選擇 RTC 時(shí)鐘的函數(shù)是:
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //選擇 LSE 作為 RTC 時(shí)鐘
對(duì)于 RTC 時(shí)鐘的選擇,還有 RCC_RTCCLKSource_LSI 和RCC_RTCCLKSource_HSE_Div128 這兩個(gè),顧名思義,前者為 LSI,后者為 HSE 的 128 分頻,這在時(shí)鐘系統(tǒng)章節(jié)有講解過。
使能 RTC 時(shí)鐘的函數(shù)是:
RCC_RTCCLKCmd(ENABLE); //使能 RTC 時(shí)鐘
5) 設(shè)置 RTC 的分頻,以及配置 RTC 時(shí)鐘。
在開啟了 RTC 時(shí)鐘之后,我們要做的就是設(shè)置 RTC 時(shí)鐘的分頻數(shù),通過 RTC_PRLH 和RTC_PRLL 來設(shè)置,然后等待 RTC 寄存器操作完成,并同步之后,設(shè)置秒鐘中斷。然后設(shè)置RTC 的允許配置位(RTC_CRH 的 CNF 位),設(shè)置時(shí)間(其實(shí)就是設(shè)置 RTC_CNTH 和 RTC_CNTL兩個(gè)寄存器)。下面我們一一這些步驟用到的庫(kù)函數(shù):在進(jìn)行 RTC 配置之前首先要打開允許配置位(CNF),庫(kù)函數(shù)是:
RTC_EnterConfigMode();/// 允許配置
在配置完成之后,千萬別忘記更新配置同時(shí)退出配置模式,函數(shù)是:
RTC_ExitConfigMode();//退出配置模式, 更新配置
設(shè)置 RTC 時(shí)鐘分頻數(shù), 庫(kù)函數(shù)是:
void RTC_SetPrescaler(uint32_t PrescalerValue);
這個(gè)函數(shù)只有一個(gè)入口參數(shù),就是 RTC 時(shí)鐘的分頻數(shù),很好理解。
然后是設(shè)置秒中斷允許, RTC 使能中斷的函數(shù)是:
void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);
這個(gè)函數(shù)的第一個(gè)參數(shù)是設(shè)置秒中斷類型,這些通過宏定義定義的。對(duì)于使能秒中斷方法是:
RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能 RTC 秒中斷
八、RTC程序
這篇文章復(fù)制粘貼了這么多,感覺不到一絲有用的東西。算了,還是看一下,程序是怎么寫的吧。
RTC_Init
//實(shí)時(shí)時(shí)鐘配置//初始化 RTC 時(shí)鐘,同時(shí)檢測(cè)時(shí)鐘是否工作正常//BKP-》DR1 用于保存是否第一次配置的設(shè)置//返回 0:正常//其他:錯(cuò)誤代碼u8 RTC_Init(void){u8 temp=0;//檢查是不是第一次配置時(shí)鐘RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR |RCC_APB1Periph_BKP, ENABLE); //①使能 PWR 和 BKP 外設(shè)時(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;//初始化時(shí)鐘失敗,晶振有問題RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //設(shè)置 RTC 時(shí)鐘//(RTCCLK),選擇 LSE 作為 RTC 時(shí)鐘RCC_RTCCLKCmd(ENABLE); //使能 RTC 時(shí)鐘RTC_WaitForLastTask(); //等待最近一次對(duì) RTC 寄存器的寫操作完成RTC_WaitForSynchro(); //等待 RTC 寄存器同步RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能 RTC 秒中斷RTC_WaitForLastTask(); //等待最近一次對(duì) RTC 寄存器的寫操作完成RTC_EnterConfigMode(); // 允許配置RTC_SetPrescaler(32767); //設(shè)置 RTC 預(yù)分頻的值RTC_WaitForLastTask(); //等待最近一次對(duì) RTC 寄存器的寫操作完成RTC_Set(2015,1,14,17,42,55); //設(shè)置時(shí)間RTC_ExitConfigMode(); //退出配置模式BKP_WriteBackupRegister(BKP_DR1, 0X5050); //向指定的后備寄存器中//寫入用戶程序數(shù)據(jù) 0x5050}else//系統(tǒng)繼續(xù)計(jì)時(shí){RTC_WaitForSynchro(); //等待最近一次對(duì) RTC 寄存器的寫操作完成RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能 RTC 秒中斷RTC_WaitForLastTask(); //等待最近一次對(duì) RTC 寄存器的寫操作完成}RTC_NVIC_Config(); //RCT 中斷分組設(shè)置RTC_Get(); //更新時(shí)間return 0; //ok}RTC_Set
//設(shè)置時(shí)鐘//把輸入的時(shí)鐘轉(zhuǎn)換為秒鐘//以 1970 年 1 月 1 日為基準(zhǔn)//1970~2099 年為合法年份//返回值:0,成功;其他:錯(cuò)誤代碼。//月份數(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í)秒鐘數(shù)seccount+=(u32)min*60; //分鐘秒鐘數(shù)seccount+=sec; //最后的秒鐘加上去RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR |RCC_APB1Periph_BKP, ENABLE); //使能 PWR 和 BKP 外設(shè)時(shí)鐘PWR_BackupAccessCmd(ENABLE); //使能 RTC 和后備寄存器訪問RTC_SetCounter(seccount); //設(shè)置 RTC 計(jì)數(shù)器的值RTC_WaitForLastTask(); //等待最近一次對(duì) RTC 寄存器的寫操作完成return 0;}RTC_Get
//得到當(dāng)前的時(shí)間,結(jié)果保存在 calendar 結(jié)構(gòu)體里面//返回值:0,成功;其他:錯(cuò)誤代碼.u8 RTC_Get(void){ static u16 daycnt=0;u32 timecount=0;u32 temp=0;u16 temp1=0;timecount=RTC-》CNTH; //得到計(jì)數(shù)器中的值(秒鐘數(shù))timecount《《=16;timecount+=RTC-》CNTL;temp=timecount/86400; //得到天數(shù)(秒鐘數(shù)對(duì)應(yīng)的)if(daycnt!=temp) //超過一天了{(lán)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) //超過了一個(gè)月{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; //小時(shí)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)先級(jí)1位,從優(yōu)先級(jí)3位NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;//先占優(yōu)先級(jí)0位,從優(yōu)先級(jí)4位NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能該通道中斷NVIC_Init(&NVIC_InitStructure);//根據(jù)NVIC_InitStruct中指定的參數(shù)初始化外設(shè)NVIC寄存器}RTC_IRQHandler
//RTC 時(shí)鐘中斷//每秒觸發(fā)一次void RTC_IRQHandler(void){if (RTC_GetITStatus(RTC_IT_SEC) != RESET) //秒鐘中斷{RTC_Get(); //更新時(shí)間}if(RTC_GetITStatus(RTC_IT_ALR)!= RESET) //鬧鐘中斷{RTC_ClearITPendingBit(RTC_IT_ALR); //清鬧鐘中斷RTC_Get(); //更新時(shí)間printf(“Alarm Time:%d-%d-%d %d:%d:%d ”,calendar.w_year,calendar.w_month,calendar.w_date,calendar.hour,calendar.min,calendar.sec);//輸出鬧鈴時(shí)間}RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW); //清鬧鐘中斷RTC_WaitForLastTask();}九、項(xiàng)目代碼
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)志必須跟測(cè)試程序一致否則時(shí)間被復(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)運(yùn)行前使能中斷//RTC_ITConfig(RTC_IT_ALR, ENABLE);// 系統(tǒng)鬧鐘中斷/* Wait until last write operation on RTC registers has finished *///RTC_WaitForLastTask();// 不能在系統(tǒng)運(yùn)行前使能中斷
/* 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)初始化未完成前進(jìn)入中斷程序RTC_ClearFlag(RTC_IT_SEC);
/* Enable one second interrupe *///RTC_ITConfig(RTC_IT_SEC,ENABLE);// 不能在系統(tǒng)運(yùn)行前使能中斷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時(shí)鐘源
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
-
時(shí)鐘
+關(guān)注
關(guān)注
11文章
1746瀏覽量
131799 -
定時(shí)器
+關(guān)注
關(guān)注
23文章
3255瀏覽量
115368 -
RTC
+關(guān)注
關(guān)注
2文章
542瀏覽量
67028 -
可編程計(jì)數(shù)器
+關(guān)注
關(guān)注
0文章
3瀏覽量
5619
原文標(biāo)題:STM32開發(fā) -- RTC詳解
文章出處:【微信號(hào):mcu168,微信公眾號(hào):硬件攻城獅】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論