1. 程式人生 > >STM32開發 -- RTC詳解

STM32開發 -- RTC詳解

RTC實時時鐘部分,之前也是有講到過的。

接下來看一下STM32裡RTC該怎麼配置

一、RTC實時時鐘特徵與原理

檢視STM32中文手冊 16 實時時鐘(RTC)(308頁)

RTC (Real Time Clock):實時時鐘

實時時鐘是一個獨立的定時器。 RTC模組擁有一組連續計數的計數器,在相應軟體配置下,可提供時鐘日曆的功能。修改計數器的值可以重新設定系統當前的時間和日期。
RTC模組和時鐘配置系統(RCC_BDCR暫存器)處於後備區域,即在系統復位或從待機模式喚醒後, RTC的設定和時間維持不變。
系統復位後,對後備暫存器和RTC的訪問被禁止,這是為了防止對後備區域(BKP)的意外寫操作。執行以下操作將使能對後備暫存器和RTC的訪問:
● 設定暫存器RCC_APB1ENR的PWREN和BKPEN位,使能電源和後備介面時鐘
● 設定暫存器PWR_CR的DBP位,使能對後備暫存器和RTC的訪問。

RTC特徵

可程式設計的預分頻係數:分頻係數最高為220。
● 32位的可程式設計計數器,可用於較長時間段的測量。
● 2個分離的時鐘:用於APB1介面的PCLK1和RTC時鐘(RTC時鐘的頻率必須小於PCLK1時鐘
頻率的四分之一以上)。
● 可以選擇以下三種RTC的時鐘源:
─ HSE時鐘除以128;
─ LSE振盪器時鐘;
─ LSI振盪器時鐘(詳見6.2.8節RTC時鐘)。
● 2個獨立的復位型別:
─ APB1介面由系統復位;
─ RTC核心(預分頻器、鬧鐘、計數器和分頻器)只能由後備域復位(詳見6.1.3節)。
● 3個專門的可遮蔽中斷:
─ 鬧鐘中斷,用來產生一個軟體可程式設計的鬧鐘中斷。
─ 秒中斷,用來產生一個可程式設計的週期性中斷訊號(最長可達1秒)。
─ 溢位中斷,指示內部可程式設計計數器溢位並回轉為0的狀態。

二、RTC由兩部分組成

APB1介面:用來和APB1匯流排相連。通過APB1介面可以訪問RTC的相關暫存器(預分頻值,計數器值,鬧鐘值)。
RTC核心:由一組可程式設計計數器組成。分兩個主要模組。
第一個是RTC預分頻模組,它可以程式設計產生最長1秒的RTC時間基TR_CLK。如果設定了秒中斷允許位,可以產生秒中斷。
第二個是32位的可程式設計計數器,可被初始化為當前時間。系統時間按TR_CLK週期累加並與儲存在RTC_ALR暫存器中的可程式設計時間相比,當匹配時候如果設定了鬧鐘中斷允許位,可以產生鬧鐘中斷。
這裡寫圖片描述

RTC核心完全獨立於APB1介面,軟體通過APB1介面對RTC相關暫存器訪問。但是相關暫存器只在RTC APB1時鐘進行重新同步的RTC時鐘的上升沿被更新。所以軟體必須先等待暫存器同步標誌位(RTC_CRL的RSF位)被硬體置1才讀。

三、RTC時鐘源

首先講一下時鐘源:

三種不同的時鐘源可被用來驅動系統時鐘(SYSCLK):
● HSI振盪器時鐘
● HSE振盪器時鐘
● PLL時鐘
這些裝置有以下2種二級時鐘源:
● 40kHz低速內部RC,可以用於驅動獨立看門狗和通過程式選擇驅動RTC。 RTC用於從停機/待機模式下自動喚醒系統。
● 32.768kHz低速外部晶體也可用來通過程式選擇驅動RTC(RTCCLK)。


這裡寫圖片描述
當不被使用時,任一個時鐘源都可被獨立地啟動或關閉,由此優化系統功耗。
使用者可通過多個預分頻器配置AHB、高速APB(APB2)和低速APB(APB1)域的頻率。 AHB和APB2域的最大頻率是72MHz。 APB1域的最大允許頻率是36MHz。 SDIO介面的時鐘頻率固定為HCLK/2。
RCC通過AHB時鐘(HCLK)8分頻後作為Cortex系統定時器(SysTick)的外部時鐘。通過對SysTick控制與狀態暫存器的設定,可選擇上述時鐘或Cortex(HCLK)時鐘作為SysTick時鐘。 ADC時鐘由高速APB2時鐘經2、 4、 6或8分頻後獲得。
定時器時鐘頻率分配由硬體按以下2種情況自動設定:
1. 如果相應的APB預分頻係數是1,定時器的時鐘頻率與所在APB匯流排頻率一致。
2. 否則,定時器的時鐘頻率被設為與其相連的APB匯流排頻率的2倍。

如上圖,有五個時鐘源,為HSI、HSE、LSI、LSE、PLL。
接下來我們一一看一下:

HSE時鐘

這裡寫圖片描述
高速外部時鐘訊號(HSE)由以下兩種時鐘源產生:
● HSE外部晶體/陶瓷諧振器
● HSE使用者外部時鐘
為了減少時鐘輸出的失真和縮短啟動穩定時間,晶體/陶瓷諧振器和負載電容器必須儘可能地靠
近振盪器引腳。負載電容值必須根據所選擇的振盪器來調整。
這裡寫圖片描述
外部時鐘源(HSE旁路)
在這個模式裡,必須提供外部時鐘。它的頻率最高可達25MHz。使用者可通過設定在時鐘控制暫存器中的HSEBYP和HSEON位來選擇這一模式。外部時鐘訊號(50%佔空比的方波、正弦波或三角波)必須連到SOC_IN引腳,同時保證OSC_OUT引腳懸空。見圖9。
外部晶體/陶瓷諧振器(HSE晶體)
4~16Mz外部振盪器可為系統提供更為精確的主時鐘。相關的硬體配置可參考圖9,進一步資訊可參考資料手冊的電氣特性部分。
在時鐘控制暫存器RCC_CR中的HSERDY位用來指示高速外部振盪器是否穩定。在啟動時,直到這一位被硬體置’1’,時鐘才被釋放出來。如果在時鐘中斷暫存器RCC_CIR中允許產生中斷,將會產生相應中斷。
HSE晶體可以通過設定時鐘控制暫存器裡RCC_CR中的HSEON位被啟動和關閉。

HSI時鐘

這裡寫圖片描述
HSI時鐘訊號由內部8MHz的RC振盪器產生,可直接作為系統時鐘或在2分頻後作為PLL輸入。
HSI RC振盪器能夠在不需要任何外部器件的條件下提供系統時鐘。它的啟動時間比HSE晶體振盪器短。然而,即使在校準之後它的時鐘頻率精度仍較差
校準
製造工藝決定了不同晶片的RC振盪器頻率會不同,這就是為什麼每個晶片的HSI時鐘頻率在出廠前已經被ST校準到1%(25°C)的原因。系統復位時,工廠校準值被裝載到時鐘控制暫存器的HSICAL[7:0]位。
如果使用者的應用基於不同的電壓或環境溫度,這將會影響RC振盪器的精度。可以通過時鐘控制暫存器裡的HSITRIM[4:0]位來調整HSI頻率。
時鐘控制暫存器中的HSIRDY位用來指示HSI RC振盪器是否穩定。在時鐘啟動過程中,直到這一位被硬體置’1’, HSI RC輸出時鐘才被釋放。 HSI RC可由時鐘控制暫存器中的HSION位來啟動和關閉。
如果HSE晶體振盪器失效, HSI時鐘會被作為備用時鐘源。

PLL

這裡寫圖片描述
內部PLL可以用來倍頻HSI RC的輸出時鐘或HSE晶體輸出時鐘。
PLL的設定(選擇HIS振盪器除2或HSE振盪器為PLL的輸入時鐘,和選擇倍頻因子)必須在其被啟用前完成。一旦PLL被啟用,這些引數就不能被改動。
如果PLL中斷在時鐘中斷暫存器裡被允許,當PLL準備就緒時,可產生中斷申請。
如果需要在應用中使用USB介面, PLL必須被設定為輸出48或72MHZ時鐘,用於提供48MHz的USBCLK時鐘。

LSE時鐘

這裡寫圖片描述
LSE晶體是一個32.768kHz的低速外部晶體或陶瓷諧振器。它為實時時鐘或者其他定時功能提供一個低功耗且精確的時鐘源。
LSE晶體通過在備份域控制暫存器(RCC_BDCR)裡的LSEON位啟動和關閉。
在備份域控制暫存器(RCC_BDCR)裡的LSERDY指示LSE晶體振盪是否穩定。在啟動階段,直到這個位被硬體置’1’後, LSE時鐘訊號才被釋放出來。如果在時鐘中斷暫存器裡被允許,可產生中斷申請。
外部時鐘源(LSE旁路)
在這個模式裡必須提供一個32.768kHz頻率的外部時鐘源。你可以通過設定在備份域控制暫存器(RCC_BDCR)裡的LSEBYP和LSEON位來選擇這個模式。具有50%佔空比的外部時鐘訊號(方波、正弦波或三角波)必須連到OSC32_IN引腳,同時保證OSC32_OUT引腳懸空,見圖9。

LSI時鐘

這裡寫圖片描述
LSI RC擔當一個低功耗時鐘源的角色,它可以在停機和待機模式下保持執行,為獨立看門狗和自動喚醒單元提供時鐘。 LSI時鐘頻率大約40kHz(在30kHz和60kHz之間)。進一步資訊請參考資料手冊中有關電氣特性部分。
LSI RC可以通過控制/狀態暫存器(RCC_CSR)裡的LSION位來啟動或關閉。
在控制/狀態暫存器(RCC_CSR)裡的LSIRDY位指示低速內部振盪器是否穩定。在啟動階段,直到這個位被硬體設定為’1’後,此時鐘才被釋放。如果在時鐘中斷暫存器(RCC_CIR)裡被允許,將產生LSI中斷申請。
注意: 只有大容量和互聯型產品可以進行LSI校準
LSI校準
可以通過校準內部低速振盪器LSI來補償其頻率偏移,從而獲得精度可接受的RTC時間基數,以及獨立看門狗(IWDG)的超時時間(當這些外設以LSI為時鐘源)。
校準可以通過使用TIM5的輸入時鐘(TIM5_CLK)測量LSI時鐘頻率實現。測量以HSE的精度為保證,軟體可以通過調整RTC的20位預分頻器來獲得精確的RTC時鐘基數,以及通過計算得到精確的獨立看門狗(IWDG)的超時時間。
LSI校準步驟如下:
1. 開啟TIM5,設定通道4為輸入捕獲模式;
2. 設定AFIO_MAPR的TIM5_CH4_IREMAP位為’1’,在內部把LSI連線到TIM5的通道4;
3. 通過TIM5的捕獲/比較4事件或者中斷來測量LSI時鐘頻率;
4. 根據測量結果和期望的RTC時間基數和獨立看門狗的超時時間,設定20位預分頻器。

四、RTC時鐘

通 過 設 置 備 份 域 控 制 寄 存 器 (RCC_BDCR) 裡 的 RTCSEL[1:0] 位 , RTCCLK 時鐘源可以由HSE/128、LSE或LSI時鐘提供。除非備份域復位,此選擇不能被改變。
LSE時鐘在備份域裡,但HSE和LSI時鐘不是。因此:
● 如果LSE被選為RTC時鐘:
─ 只要VBAT維持供電,儘管VDD供電被切斷, RTC仍繼續工作。
● 如果LSI被選為自動喚醒單元(AWU)時鐘:
─ 如果VDD供電被切斷, AWU狀態不能被保證。有關LSI校準,詳見6.2.5節LSI時鐘。
● 如果HSE時鐘128分頻後作為RTC時鐘:
─ 如果VDD供電被切斷或內部電壓調壓器被關閉(1.8V域的供電被切斷),則RTC狀態不確定。
─ 必須設定電源控制暫存器(見4.4.1節)的DPB位(取消後備區域的防寫)為’1’。

五、RTC暫存器

上面都是從STM32中文手冊裡摘取的。大概瞭解一下RTC和時鐘。
不過講的有點扯,裡面有多好暫存器,不知道是幹啥的。接下來重點看一下這些暫存器。

RTC控制暫存器高位(RTC_CRH)

這裡寫圖片描述

RTC控制暫存器低位(RTC_CRL)

這裡寫圖片描述
①修改CRH/CRL暫存器,必須先判斷RSF位,確定已經同步。
②修改CNT,ALR,PRL的時候,必須先配置CNF位進入配置模式,修改完之後,設定CNF位為0退出配置模式
③同時在對RTC相關暫存器寫操作之前,必須判斷上一次寫操作已經結束,也就是判斷RTOFF位是否置位。

RTC預分頻裝載暫存器(RTC_PRLH/RTC_PRLL)

預分頻裝載暫存器用來儲存RTC預分頻器的週期計數值。它們受RTC_CR暫存器的RTOFF位保護,僅當RTOFF值為’1’時允許進行寫操作。
這裡寫圖片描述

RTC預分頻器餘數暫存器(RTC_DIVH / RTC_DIVL)

這裡寫圖片描述

RTC計數器暫存器 (RTC_CNTH / RTC_CNTL)

這裡寫圖片描述

RTC鬧鐘暫存器(RTC_ALRH/RTC_ALRL)

這裡寫圖片描述

配置RTC暫存器

必須設定RTC_CRL 寄 存 器 中 的CNF位 , 使RTC進入配置模式後 , 才能寫 入RTC_PRL、RTC_CNT、 RTC_ALR暫存器。
另外,對RTC任何暫存器的寫操作,都必須在前一次寫操作結束後進行。可以通過查詢RTC_CR暫存器中的RTOFF狀態位,判斷RTC暫存器是否處於更新中。僅當RTOFF狀態位是’1’時,才可以寫入RTC暫存器。
配置過程:
1. 查詢RTOFF位,直到RTOFF的值變為’1’
2. 置CNF值為1,進入配置模式
3. 對一個或多個RTC暫存器進行寫操作
4. 清除CNF標誌位,退出配置模式
5. 查詢RTOFF,直至RTOFF位變為’1’以確認寫操作已經完成。
僅當CNF標誌位被清除時,寫操作才能進行,這個過程至少需要3個RTCCLK週期。

讀RTC暫存器

RTC核完全獨立於RTC APB1介面。
軟體通過APB1介面訪問RTC的預分頻值、 計數器值和鬧鐘值。但是,相關的可讀暫存器只在與RTC APB1時鐘進行重新同步的RTC時鐘的上升沿被更新。 RTC標誌也是如此的。
這意味著,如果APB1介面曾經被關閉,而讀操作又是在剛剛重新開啟APB1之後,則在第一次的內部暫存器更新之前,從APB1上讀出的RTC暫存器數值可能被破壞了(通常讀到0)。下述幾種
情況下能夠發生這種情形:
● 發生系統復位或電源復位
● 系統剛從待機模式喚醒(參見第4.3節: 低功耗模式)。
● 系統剛從停機模式喚醒(參見第4.3節: 低功耗模式)。
所有以上情況中, APB1介面被禁止時(復位、無時鐘或斷電)RTC核仍保持執行狀態。
因此,若在讀取RTC暫存器時, RTC的APB1介面曾經處於禁止狀態,則軟體首先必須等待RTC_CRL暫存器中的RSF位(暫存器同步標誌)被硬體置’1’。
注: RTC的 APB1介面不受WFI和WFE等低功耗模式的影響

六、RTC相關庫函式講解

庫函式所在檔案:stm32f10x_rtc.c / stm32f10x_rtc.h

RTC時鐘源和時鐘操作函式:

 void RCC_RTCCLKConfig(uint32_t  CLKSource);//時鐘源選擇
 void RCC_RTCCLKCmd(FunctionalState NewState)//時鐘使能

RTC配置函式(預分頻,計數值:

void RTC_SetPrescaler(uint32_t PrescalerValue);//預分頻配置:PRLH/PRLL
void RTC_SetCounter(uint32_t CounterValue);//設定計數器值:CNTH/CNTL
void RTC_SetAlarm(uint32_t AlarmValue);//鬧鐘設定:ALRH/ALRL

RTC中斷設定函式:

 void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);//CRH

RTC允許配置和退出配置函式:

void RTC_EnterConfigMode(void);//允許RTC配置 :CRL位 CNF
void RTC_ExitConfigMode(void);//退出配置模式:CRL位 CNF

同步函式:

void RTC_WaitForLastTask(void);//等待上次操作完成:CRL位RTOFF
void RTC_WaitForSynchro(void);//等待時鐘同步:CRL位RSF

相關狀態位獲取清除函式:

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);

其他相關函式(BKP等)

PWR_BackupAccessCmd();//BKP後備區域訪問使能
RCC_APB1PeriphClockCmd();//使能PWR和BKP時鐘
RCC_LSEConfig();//開啟LSE,RTC選擇LSE作為時鐘源
PWR_BackupAccessCmd();//BKP後備區域訪問使能
uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR);//讀BKP暫存器
void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data);//寫BKP 

七、RTC配置一般步驟

1) 使能電源時鐘和備份區域時鐘。

前面已經介紹了,我們要訪問 RTC 和備份區域就必須先使能電源時鐘和備份區域時鐘。

RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);

2) 取消備份區防寫。

要向備份區域寫入資料,就要先取消備份區域防寫(防寫在每次硬復位之後被使能),否則是無法向備份區域寫入資料的。我們需要用到向備份區域寫入一個位元組,來標記時鐘已經配置過了,這樣避免每次復位之後重新配置時鐘。 取消備份區域防寫的庫函式實現方法是:

PWR_BackupAccessCmd(ENABLE); //使能 RTC 和後備暫存器訪問

3) 復位備份區域,開啟外部低速振盪器。

在取消備份區域防寫之後,我們可以先對這個區域復位,以清除前面的設定,當然這個操作不要每次都執行,因為備份區域的復位將導致之前存在的資料丟失,所以要不要復位,要看情況而定。然後我們使能外部低速振盪器,注意這裡一般要先判斷 RCC_BDCR 的 LSERDY位來確定低速振盪器已經就緒了才開始下面的操作。
備份區域復位的函式是:

BKP_DeInit();//復位備份區域

開啟外部低速振盪器的函式是:

RCC_LSEConfig(RCC_LSE_ON);// 開啟外部低速振盪器

4) 選擇 RTC 時鐘,並使能。

這裡我們將通過 RCC_BDCR 的 RTCSEL 來選擇選擇外部 LSI 作為 RTC 的時鐘。然後通過RTCEN 位使能 RTC 時鐘。庫函式中,選擇 RTC 時鐘的函式是:

RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //選擇 LSE 作為 RTC 時鐘

對於 RTC 時鐘的選擇,還有 RCC_RTCCLKSource_LSIRCC_RTCCLKSource_HSE_Div128 這兩個,顧名思義,前者為 LSI,後者為 HSE 的 128 分頻,這在時鐘系統章節有講解過。
使能 RTC 時鐘的函式是:

RCC_RTCCLKCmd(ENABLE); //使能 RTC 時鐘

5) 設定 RTC 的分頻,以及配置 RTC 時鐘。

在開啟了 RTC 時鐘之後,我們要做的就是設定 RTC 時鐘的分頻數,通過 RTC_PRLH 和RTC_PRLL 來設定,然後等待 RTC 暫存器操作完成,並同步之後,設定秒鐘中斷。然後設定RTC 的允許配置位(RTC_CRH 的 CNF 位),設定時間(其實就是設定 RTC_CNTH 和 RTC_CNTL兩個暫存器)。 下面我們一一這些步驟用到的庫函式:在進行 RTC 配置之前首先要開啟允許配置位(CNF),庫函式是:

RTC_EnterConfigMode();/// 允許配置

在配置完成之後,千萬別忘記更新配置同時退出配置模式,函式是:

RTC_ExitConfigMode();//退出配置模式, 更新配置

設定 RTC 時鐘分頻數, 庫函式是:

void RTC_SetPrescaler(uint32_t PrescalerValue);

這個函式只有一個入口引數,就是 RTC 時鐘的分頻數,很好理解。
然後是設定秒中斷允許, RTC 使能中斷的函式是:

void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState);

這個函式的第一個引數是設定秒中斷型別,這些通過巨集定義定義的。 對於使能秒中斷方法是:

RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能 RTC 秒中斷

八、RTC程式

這篇文章複製貼上了這麼多,感覺不到一絲有用的東西。算了,還是看一下,程式是怎麼寫的吧。

RTC_Init

//實時時鐘配置
//初始化 RTC 時鐘,同時檢測時鐘是否工作正常
//BKP->DR1 用於儲存是否第一次配置的設定
//返回 0:正常
//其他:錯誤程式碼
u8 RTC_Init(void)
{
u8 temp=0;
//檢查是不是第一次配置時鐘
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR |
RCC_APB1Periph_BKP, ENABLE); //①使能 PWR 和 BKP 外設時鐘
PWR_BackupAccessCmd(ENABLE); //②使能後備暫存器訪問
if (BKP_ReadBackupRegister(BKP_DR1) != 0x5050) //從指定的後備暫存器中
//讀出資料:讀出了與寫入的指定資料不相乎
{
BKP_DeInit(); //③復位備份區域
RCC_LSEConfig(RCC_LSE_ON); //設定外部低速晶振(LSE)
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET&&temp<250)
//檢查指定的 RCC 標誌位設定與否,等待低速晶振就緒
{
temp++;
delay_ms(10);
}
if(temp>=250)return 1;//初始化時鐘失敗,晶振有問題
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //設定 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); //設定 RTC 預分頻的值
RTC_WaitForLastTask(); //等待最近一次對 RTC 暫存器的寫操作完成
RTC_Set(2015,1,14,17,42,55); //設定時間
RTC_ExitConfigMode(); //退出配置模式
BKP_WriteBackupRegister(BKP_DR1, 0X5050); //向指定的後備暫存器中
//寫入使用者程式資料 0x5050
}
else//系統繼續計時
{
RTC_WaitForSynchro(); //等待最近一次對 RTC 暫存器的寫操作完成
RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能 RTC 秒中斷
RTC_WaitForLastTask(); //等待最近一次對 RTC 暫存器的寫操作完成
}
RTC_NVIC_Config(); //RCT 中斷分組設定
RTC_Get(); //更新時間
return 0; //ok
}

RTC_Set

//設定時鐘
//把輸入的時鐘轉換為秒鐘
//以 1970 年 1 月 1 日為基準
//1970~2099 年為合法年份
//返回值:0,成功;其他:錯誤程式碼.
//月份資料表
u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正資料表
//平年的月份日期表
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;//閏年的秒鐘數
else seccount+=31536000; //平年的秒鐘數
}
smon-=1;
for(t=0;t<smon;t++) //把前面月份的秒鐘數相加
{ seccount+=(u32)mon_table[t]*86400; //月份秒鐘數相加
if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//閏年 2 月份增加一天的秒鐘數
}
seccount+=(u32)(sday-1)*86400; //把前面日期的秒鐘數相加
seccount+=(u32)hour*3600; //小時秒鐘數
seccount+=(u32)min*60; //分鐘秒鐘數
seccount+=sec; //最後的秒鐘加上去
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR |
RCC_APB1Periph_BKP, ENABLE); //使能 PWR 和 BKP 外設時鐘
PWR_BackupAccessCmd(ENABLE); //使能 RTC 和後備暫存器訪問
RTC_SetCounter(seccount); //設定 RTC 計數器的值
RTC_WaitForLastTask(); //等待最近一次對 RTC 暫存器的寫操作完成
return 0;
}

RTC_Get

//得到當前的時間,結果儲存在 calendar 結構體裡面
//返回值:0,成功;其他:錯誤程式碼.
u8 RTC_Get(void)
{ static u16 daycnt=0;
u32 timecount=0;
u32 temp=0;
u16 temp1=0;
timecount=RTC->CNTH; //得到計數器中的值(秒鐘數)
timecount<<=16;
timecount+=RTC->CNTL;
temp=timecount/86400; //得到天數(秒鐘數對應的)
if(daycnt!=temp) //超過一天了
{
daycnt=temp;
temp1=1970; //從 1970 年開始
while(temp>=365)
{
if(Is_Leap_Year(temp1)) //是閏年
{
if(temp>=366)temp-=366; //閏年的秒鐘數
else break;
}
else temp-=365; //平年
temp1++;
}
calendar.w_year=temp1; //得到年份
temp1=0;
while(temp>=28) //超過了一個月
{
if(Is_Leap_Year(calendar.w_year)&&temp1==1)//當年是不是閏年/2 月份
{
if(temp>=29)temp-=29;//閏年的秒鐘數
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; //得到秒鐘數
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;   //先佔優先順序1位,從優先順序3位
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;  //先佔優先順序0位,從優先順序4位
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;     //使能該通道中斷
    NVIC_Init(&NVIC_InitStructure);     //根據NVIC_InitStruct中指定的引數初始化外設NVIC暫存器
}

RTC_IRQHandler

//RTC 時鐘中斷
//每秒觸發一次
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\n",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)
    u8  tmpBuf[60]="";
    #endif

    /* Clear reset flags */
    RCC_ClearFlag();

    // 這裡標誌必須跟測試程式一致否則時間被複位成預設
    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 (when
        the 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....\r\n");
        #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);                             // 不能在系統執行前使能中斷 
        //RTC_ITConfig(RTC_IT_ALR, ENABLE);                             // 系統鬧鐘中斷
        /* Wait until last write operation on RTC registers has finished */
        //RTC_WaitForLastTask();                                                    // 不能在系統執行前使能中斷 

        /* 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);                                      // 防止系統初始化未完成前進入中斷程式
    RTC_ClearFlag(RTC_IT_SEC);

    /* Enable  one second interrupe */  
    //RTC_ITConfig(RTC_IT_SEC,  ENABLE);                                    // 不能在系統執行前使能中斷
    rtcInitFinish   =1;     // 設定初始化完成標誌
}
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); //啟用LSI 
    while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET)
    {}
    RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);
    RCC_RTCCLKCmd(ENABLE);  //  Enable RTC Clock 
    RTC_WaitForSynchro();
    RTC_WaitForLastTask();
    RTC_SetPrescaler(40000); // RTC period = RTCCLK/RTC_PR = (4 KHz)/(4000+1) LSI
    RTC_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);
#else
        LsiFreq = (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.5K
RCC_RTCCLKConfig(RCC_RTCCLKSource_HSE_Div128);  
//允許RTC
RCC_RTCCLKCmd(ENABLE);
//等待RTC暫存器同步
RTC_WaitForSynchro();

RTC_WaitForLastTask();
//允許RTC的秒中斷(還有鬧鐘中斷和溢位中斷可設定)
RTC_ITConfig(RTC_IT_SEC, ENABLE);

RTC_WaitForLastTask();
//62500晶振預分頻值是62500,不過一般來說晶振都不那麼準
RTC_SetPrescaler(62498); //如果需要校準晶振,可修改此分頻值
RTC_WaitForLastTask();
//清除標誌
RCC_ClearFlag(); 
}

相關推薦

STM32開發 -- RTC

RTC實時時鐘部分,之前也是有講到過的。 接下來看一下STM32裡RTC該怎麼配置 一、RTC實時時鐘特徵與原理 檢視STM32中文手冊 16 實時時鐘(RTC)(308頁) RTC (Real Time Clock):實時時鐘 實時時鐘是

Javascript設計模式與開發實踐(二:策略模式) http://www.jianshu.com/p/ef53781f6ef2

的人 思想 ram gis pan pro msg have 改變 上一章我們介紹了單例模式及JavaScript惰性單例模式應用這一次我主要介紹策略模式策略模式是定義一系列的算法,把它們一個個封裝起來,並且讓他們可以互相替換。比方說在現實中很多時候也有很多途徑到達同一個

Android MVC開發框架

div ada 2-2 展示 gpo lose 回調接口 cycle recycler 1、目錄根據需要自行添加   adapter    用於RecyclerView、ListView 等各種適配器  fragment    存放fragment   model    

JAVA 圖形界面開發基礎

.so 積木 並且 init 中間 ram stat 字符 tle /*文章中用到的代碼只是一部分,需要源碼的可通過郵箱聯系我 [email protected]*/ 與C的win32一樣,JAVA也有自己的圖形界面開發,將在此篇博客中對基礎部分進行講解。 1.Java提供

基於DKHadoop的智慧政務服務平臺開發案例

正在 平臺開發 ado 大數據技術 大量數據 數據信息 alt 大數據平臺 不可 基於DKHadoop的智慧政務服務平臺開發案例詳解大數據技術的應用與發展正在讓我們的生活經歷一場深刻的“變革”,而且這種變革幾乎讓所有人都感覺非常舒服,自然而然的就完成了這樣的一個變化。最根本

大白BC26 opencpu開發視訊-王旭輝-專題視訊課程

大白BC26 opencpu開發視訊詳解—490人已學習 課程介紹         BC26 opencpu開發視訊詳解。對BC26 opencpu開發過程中遇到的問題進行詳細說明:開發所需要的工具檔案

Android開發Handler的記憶體洩露

原文:https://blog.csdn.net/carson_ho/article/details/52693211 前言 記憶體洩露在Android開發中非常常見 記憶體洩露的定義:本該被回收的物件不能被回收而停留在堆記憶體中

JavaWeb開發Servlet及Servlet容器

由於 servlet開發 遊戲 metadata 移動互 -o 每一個 web開發 port 自JavaEE誕生伊始,Servlet容器和Servlet技術,就構成了JavaEE應用的核心,配合其它組件,它們完善了Java企業級開發的全套解決方案。小到一個靜態博客網站,大到

Flutter完整開發實戰(五、 深入探索)

作為系列文章的第五篇,本篇主要探索下 Flutter 中的一些有趣原理,幫助我們更好的去理解和開發。 前文: 一、Dart語言和Flutter基礎 二、 快速開發實戰篇 三、 打包與填坑篇 四、Redux、主題、國際化) 一、WidgetsFlutterBin

Cocos2d-x 3 X手遊開發例項

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

LCD1602液晶開發技術

  1602液晶它也可以被叫做1602字元型液晶,這個是一種只用來顯示字母、數字、符號等的點陣型液晶模組。1602裡面儲存器有三種:CGROM、CGRAM、DDRAM。 DDRAM(Display Data RAM)就是顯示資料RAM,用來寄存待顯示的字元程式碼。共80個位元組

STM32啟動檔案

在<<STM32不完全手冊裡面>>,用的是STM32F103RBT6,所有的例程都採用了一個叫STM32F10x.s的啟動檔案,裡面定義了STM32的堆疊大小以及各種中斷的名字及入口函式名稱,還有啟動相關的彙編程式碼。STM32F10x.s是MDK提供的啟動程式碼,從其

vue外掛開發流程-從開發到釋出至npm(二)

  前記:上一篇 https://www.cnblogs.com/adouwt/p/9211003.html,(這裡感謝部落格園的網友,給我點贊推薦了) 說到了一個完整的vue外掛開發、釋出的流程,總結下來就講了這麼一個事,如何注入vue, 如果封裝vue外掛,如何測試vue外掛,以及如何釋出vue外掛到np

vue外掛開發流程-從開發到釋出至npm(一)

 vue的外掛開發 1.本地開發   1.1 初始化本地開發專案        1.2 test.js 的內容 ,這是外掛的入口檔案      test.js的程式碼如下: import testPanel from './panel.vue

iOS之藍芽開發—CoreBluetooth

CoreBluetooth的API是基於BLE4.0的標準的。這個框架涵蓋了BLE標準的所有細節。僅僅只有新的iOS裝置和Mac是和BLE標準相容.在CoreBluetooth框架中,有兩個主要的角色:周邊和中央(Peripheral and Central) ,整個框架

Badboy 指令碼開發技術

Badboy 是一款不錯 web 自動化測試工具,單獨使用他可以實現一些簡單的自動化測試。 案例:使用 Badboy 實現 web 自動化測試 【測試專案】: 招生系統 【URL】: http://127.0.0.1:8090/recruit.students/login/view 【使用者名稱】: admi

STM32庫函式----(外部中斷/事件控制器 EXTI)

1.void EXTI_DeInit  (void) 函式解釋:將EXTI外設暫存器重置為默註釋。RCC_APB2PeriphResetCmd引數中沒有EXTI外設的的巨集,該外設重置採取的是直接向暫存器賦預設值的操作。 例子:EXTI_DeInit ( );  

STM32庫函式----(通用輸入/輸出GPIO)

初始化和配置相關函式 1.void  GPIO_DeInit  (GPIO_TypeDef* GPIOx) 函式解釋:gpio的反初始化函式,該函式的作用是把GPIO相關的暫存器配置成上電覆位後的預設狀態,在第一次初始化前或者不在使用某一介面後,可以呼叫該函式。 引數

小程式雲開發demo

1、新建小程式——選擇建立雲開發快速啟動模板 2、多了雲開發按鈕 3、隨便命名: 使用者管理、資料庫、儲存管理、雲函式。用上面第二步中的用demo對應解釋。 4、使用者管理: 獲取到的使用者資訊會儲存到使用者管理中 5、資料庫: 點選“”

《C#網站開發案例:基於Ajax.CSS.JavaScript.XML技術》宮生文等.掃描版.pdf

書籍簡介: 《C#網站開發案例詳解:基於Ajax.CSS.JavaScript.XML技術》宮生文等.掃描版.pdf從.net頁面與ajax的應用講起,通過實用、簡潔的例項來介紹ajax技術在web頁面中的使用。以使讀者在使用c#+ajax技術開發web應用程式前,有紮實的語言基礎。