目錄
一、RTC簡介
?二、主要特性
三、功能描述
3.1 讀RTC寄存器? ? ????
?3.2 配置RTC寄存器
四、BKP簡介
五、RTC_Init()
1.?函數BKP_ReadBackupRegister
?2.RCC_LSEConfig設置外部低速晶振(LSE)
?3.RTC基本結構? ?
5.RTC_Init()實現
6.time.h
一、RTC簡介
? ? ? ?RTC和時鐘配置系統處于后備區域,系統復位時數據不清零,VDD(2.0~3.6V)斷電后可借助VBAT(1.8~3.6V)供電繼續走時
???????? 實時時鐘是一個獨立的定時器,RTC模塊擁有一組連續計數的計數器,在相應軟件的配置下,可以實現提供時鐘日歷的功能。
????????系統復位后,對后備寄存器和RTC的訪問被禁止,這是為了防止對后備區域(BKP)的意外寫操作。
????????執行以下操作將使能對后備寄存器和RTC的訪問:
● 設置寄存器RCC_APB1ENR的PWREN和BKPEN位,使能電源和后備接口時鐘
????????APB1外設時鐘使能寄存器(RCC_APB1ENR)
● 設置寄存器PWR_CR的DBP位,使能對后備寄存器和RTC的訪問。
/*開啟時鐘*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); //開啟PWR的時鐘RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE); //開啟BKP的時鐘/*備份寄存器訪問使能*/PWR_BackupAccessCmd(ENABLE); //使用PWR開啟對備份寄存器的訪問
?二、主要特性
????????可編程的預分頻系數:分頻系數最高為2^20。 32位的可編程計數器,可用于較長時間段的測量。 2個分離的時鐘:用于APB1接口的PCLK1和RTC時鐘(RTC時鐘的頻率必須小于PCLK1時鐘頻率的四分之一以上)。
STM32——RTC實時時鐘-CSDN博客https://blog.csdn.net/NRWHF/article/details/132377472
????????可以選擇以下三種RTC的時鐘源:
?????????HSE時鐘除以128; LSE振蕩器時鐘;LSI振蕩器時鐘。
可選擇三種RTC時鐘源(PTCCLK):
HSE時鐘除以128(通常為8MHz/128)
LSE振蕩器時鐘(通常為32.768KHz)【經過15位分頻器自然溢出得到1hz頻率】
LSI振蕩器時鐘(40KHz)
RTC 復位和主電源掉電后,數據不丟失是BKP來實現的
注意:整個stm32有四個時鐘源
HSE =高速外部時鐘信號
HSI = 高速內部時鐘信號
LSl=低速內部時鐘信號【低速時鐘供RTC和看門狗】
LSE =低速外部時鐘信號【低速時鐘供RTC和看門狗】
????????2個獨立的復位類型:APB1接口由系統復位; RTC核心(預分頻器、鬧鐘、計數器和分頻器)只能由后備域復位。?3個專門的可屏蔽中斷,鬧鐘中斷,用來產生一個軟件可編程的鬧鐘中斷。秒中斷,用來產生一個可編程的周期性中斷信號(最長可達1秒)。 溢出中斷,指示內部可編程計數器溢出并回轉為0的狀態。
三、功能描述
????????RTC由兩個主要部分組成(參見下圖)。第一部分(APB1接口)用來和APB1總線相連。此單元還包含一組16位寄存器,可通過APB1總線對其進行讀寫操作。APB1接口由APB1總線時鐘驅動,用來與APB1總線接口。
????????另一部分(RTC核心)由一組可編程計數器組成,分成兩個主要模塊。第一個模塊是RTC的預分頻模塊,它可編程產生最長為1秒的RTC時間基準TR_CLK。RTC的預分頻模塊包含了一個20位的可編程分頻器(RTC預分頻器)。第二個模塊是一個32位的可編程計數器,可被初始化為當前的系統時間。
3.1 讀RTC寄存器? ? ????
????????若在讀取RTC寄存器時,RTC的APB1接口曾經處于禁止狀態,則軟件首先必須等待RTC_CRL寄存器中的RSF位(寄存器同步標志)被硬件置’1’。
?????????RTC的功能由這個控制寄存器控制。當前一個寫操作還未完成時(RTOFF=0時),不能寫RTC_CR寄存器。
?3.2 配置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周期。
四、BKP簡介
????????備份寄存器是42個16位的寄存器,可用來存儲84個字節的用戶應用程序數據。他們處在備份域里,當VDD電源被切斷,他們仍然由VBAT維持供電。當系統在待機模式下被喚醒,或系統復位或電源復位時,他們也不會被復位。
????????BKP可用于存儲用戶應用程序數據。當VDD(2.0~3.6V)電源被切斷,他們仍然由VBAT(1.8~3.6V)維持供電。當系統在待機模式下被喚醒,或系統復位或電源復位時,他們也不會被復位。
????????復位后,對備份寄存器和RTC的訪問被禁止,并且備份域被保護以防止可能存在的意外的寫操作。執行以下操作可以使能對備份寄存器和RTC的訪問。 ● 通過設置寄存器RCC_APB1ENR的PWREN和BKPEN位來打開電源和后備接口的時鐘 ● 電源控制寄存器(PWR_CR)的DBP位來使能對后備寄存器和RTC的訪問。(配置代碼上文已經給出)
五、RTC_Init()
1.?函數BKP_ReadBackupRegister
?2.RCC_LSEConfig設置外部低速晶振(LSE)
?3.RTC基本結構? ?
??????????RTC和時鐘配置系統處于后備區域,系統復位時數據不清零,VDD(2.0~3.6V)斷電后可借助VBAT(1.8~3.6V)供電繼續走時
?????????執行以下操作將使能對BKP和RTC的訪問:
?? ?????????設置RCC_APB1ENR的PWREN和BKPEN,使能PWR和BKP時鐘
?? ?????????設置PWR_CR的DBP,使能對BKP和RTC的訪問
????????若在讀取RTC寄存器時,RTC的APB1接口曾經處于禁止狀態,則軟件首先必須等待RTC_CRL寄存器中的RSF位(寄存器同步標志)被硬件置1;
????????必須設置RTC_CRL寄存器中的CNF位,使RTC進入配置模式后,才能寫入RTC_PRL、RTC_CNT、RTC_ALR寄存器;
????????對RTC任何寄存器的寫操作,都必須在前一次寫操作結束后進行。可以通過查詢RTC_CR寄存器中的RTOFF狀態位,判斷RTC寄存器是否處于更新中。僅當RTOFF狀態位是1時,才可以寫入RTC寄存器
RTC_WaitForSynchro()
//等待RTC寄存器(RTC_CNT, RTC_ALR and RTC_PRL)與RTC的APB時鐘同步
RTC_WaitForLastTask()
//等待最近一次對RTC寄存器的寫操作完成
?查閱固件函數庫用戶手冊可知:對于RTC的配置基本需要在調用函數前先調用RTC_WaitForLastTask(),等待標志位RTOFF被設置。如下RTC_SetPrescaler函數
4.RCC_GetFlagStatus函數
RCC_LSEConfig(RCC_LSE_ON); //開啟LSE時鐘
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET); //等待LSE準備就緒
?RCC_FLAG:Table 383. 給出了所有可以被函數RCC_ GetFlagStatus檢查的標志位列表
5.RTC_Init()實現
注意:? ? ? ??
????????如果不加if判斷會導致每次復位時間都會重置,因為每次復位后都會調用初始化函數,我們在初始化函數中有寫入MyRTC_SetTime函數,所以每次都會調用該函數進行復位變成uint16_t MyRTC_Time[] = {2023, 1, 1, 23, 59, 55};?//定義全局的時間數組,數組內容分別為年、月、日、時、分、秒
? ? ? ? 這樣我們需要加入判斷,當系統主電源斷電,備用電池沒有斷電的時候,不進行初始化。通過對BKP的判斷實現該代碼:
????????0xA5A5全憑借個人喜好,你可以設置任意值:
?? ?if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)?? ??? ??? ?//通過寫入備份寄存器的標志位,判斷RTC是否是第一次配置
? ? ? ? 當第一次上電時,或者系統完全斷電的時候,BKP_DR1默認為0(只要不完全斷電,這玩意就不會被復位),if判斷語句成立,進行初始化。最后再寫入A5A5,這就說明我們已經進行了初始化即已經上電了,避免重復初始化,這樣就不會進入if語句不進行初始化,就不會每次時間都回到你設置的值。
? ? ? ? 如果已經初始化了,我們就不用初始化了,在else中僅僅寫上兩行等待代碼即可。
void MyRTC_Init(void)
{/*開啟時鐘*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); //開啟PWR的時鐘RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE); //開啟BKP的時鐘/*備份寄存器訪問使能*/PWR_BackupAccessCmd(ENABLE); //使用PWR開啟對備份寄存器的訪問if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5) //通過寫入備份寄存器的標志位,判斷RTC是否是第一次配置//if成立則執行第一次的RTC配置{RCC_LSEConfig(RCC_LSE_ON); //開啟LSE時鐘while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET); //等待LSE準備就緒RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //選擇RTCCLK來源為LSERCC_RTCCLKCmd(ENABLE); //RTCCLK使能RTC_WaitForSynchro(); //等待同步RTC_WaitForLastTask(); //等待上一次操作完成RTC_SetPrescaler(32768 - 1); //設置RTC預分頻器,預分頻后的計數頻率為1HzRTC_WaitForLastTask(); //等待上一次操作完成MyRTC_SetTime(); //設置時間,調用此函數,全局數組里時間值刷新到RTC硬件電路BKP_WriteBackupRegister(BKP_DR1, 0xA5A5); //在備份寄存器寫入自己規定的標志位,用于判斷RTC是不是第一次執行配置}else //RTC不是第一次配置{RTC_WaitForSynchro(); //等待同步RTC_WaitForLastTask(); //等待上一次操作完成}
}
?????????如果LSE無法起振導致程序卡死在初始化函數中,可將初始化函數替換為下述代碼,使用LSI當作RTCCLK,但是LSI無法由備用電源供電,故主電源掉電時,RTC走時會暫停。
void MyRTC_Init(void)
{RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP, ENABLE);PWR_BackupAccessCmd(ENABLE);if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5){RCC_LSICmd(ENABLE);while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) != SET);RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);RCC_RTCCLKCmd(ENABLE);RTC_WaitForSynchro();RTC_WaitForLastTask();RTC_SetPrescaler(40000 - 1);//保證1Hz的頻率,1s計次RTC_WaitForLastTask();MyRTC_SetTime();BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);}else{RCC_LSICmd(ENABLE); //即使不是第一次配置,也需要再次開啟LSI時鐘while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) != SET);RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);RCC_RTCCLKCmd(ENABLE);RTC_WaitForSynchro();RTC_WaitForLastTask();}
}
/**
? * 函 ? ?數:RTC設置時間
? * 參 ? ?數:無
? * 返 回 值:無
? * 說 ? ?明:調用此函數后,全局數組里時間值將刷新到RTC硬件電路
? */
uint16_t MyRTC_Time[] = {2023, 1, 1, 23, 59, 55};//定義全局的時間數組,數組內容分別為年、月、日、時、分、秒void MyRTC_SetTime(void)
{time_t time_cnt; //定義秒計數器數據類型struct tm time_date; //定義日期時間數據類型time_date.tm_year = MyRTC_Time[0] - 1900; //將數組的時間賦值給日期時間結構體time_date.tm_mon = MyRTC_Time[1] - 1;time_date.tm_mday = MyRTC_Time[2];time_date.tm_hour = MyRTC_Time[3];time_date.tm_min = MyRTC_Time[4];time_date.tm_sec = MyRTC_Time[5];
//從1900年1月1日0點到2023年1月1日0點一共經過了多少秒//通過mktime1轉換為s
//C 庫函數 time_t mktime(struct tm *timeptr) 把 timeptr 所指向的結構轉換為自 1970 年 1 月 1 日以來持續時間的秒數time_cnt = mktime(&time_date) - 8 * 60 * 60; //調用mktime函數,將日期時間轉換為秒計數器格式//- 8 * 60 * 60為東八區的時區調整RTC_SetCounter(time_cnt); //將秒計數器寫入到RTC的CNT中RTC_WaitForLastTask(); //等待上一次操作完成
}
?
?/**
? * 函 ? ?數:RTC讀取時間
? * 參 ? ?數:無
? * 返 回 值:無
? * 說 ? ?明:調用此函數后,RTC硬件電路里時間值將刷新到全局數組
? */
void MyRTC_ReadTime(void)
{time_t time_cnt; //定義秒計數器數據類型struct tm time_date; //定義日期時間數據類型time_cnt = RTC_GetCounter() + 8 * 60 * 60; //讀取RTC的CNT,獲取當前的秒計數器//+ 8 * 60 * 60為東八區的時區調整time_date = *localtime(&time_cnt); //使用localtime函數,將秒計數器轉換為日期時間格式MyRTC_Time[0] = time_date.tm_year + 1900; //將日期時間結構體賦值給數組的時間MyRTC_Time[1] = time_date.tm_mon + 1;MyRTC_Time[2] = time_date.tm_mday;MyRTC_Time[3] = time_date.tm_hour;MyRTC_Time[4] = time_date.tm_min;MyRTC_Time[5] = time_date.tm_sec;
}
6.time.h
C 標準庫 – | 菜鳥教程 (runoob.com)https://www.runoob.com/cprogramming/c-standard-library-time-h.html
struct tm *localtime(const time_t *timer) timer 的值被分解為 tm 結構,并用本地時區表示。這里默認為0時區 |
time_t mktime(struct tm *timeptr) 把 timeptr 所指向的結構轉換為一個依據本地時區的 time_t 值。這里同樣默認為0時區 |
?注意:
為什么結構tm中的tm_year成員相對于1900年而不是1970年的macosx上的C? - VoidCChttp://cn.voidcc.com/question/p-brsuhefn-va.html
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<time.h>
int main()
{struct tm Timer;Timer.tm_hour = 13;Timer.tm_mon = 11;Timer.tm_wday = 4;Timer.tm_mday = 23;Timer.tm_year = 2023;Timer.tm_min = 24;Timer.tm_sec = 50;printf("%d:%d:%d\n", Timer.tm_hour, Timer.tm_min, Timer.tm_sec);time_t TIME = time(NULL);printf("%lld\n%lld\n", TIME, time(&TIME));const time_t Time_CNT = 1047861430;Timer = *localtime(&Time_CNT);printf("%d-%d-%d %d:%d:%d\n", Timer.tm_year+1900,Timer.tm_mon+1,Timer.tm_mday,Timer.tm_hour, Timer.tm_min, Timer.tm_sec);char* Timer_ENG;Timer_ENG = ctime(&Time_CNT);printf("%s\n", Timer_ENG);time_t Time_COUNT;Time_COUNT = mktime(&Timer);printf("%lld\n", Time_COUNT);return 0;
}