RTC實時時鐘簡介
RTC外設(Real Time Clock)實質是一個掉電后還繼續運行的定時器。從定時器的角度,相對于通用定時器Timer外設,它十分簡單,只有很純粹的計時和觸發中斷的功能,但具備掉電還能繼續運行的特殊功能,可以應用在特定場景。這里所說的掉電是指主電源VDD斷開的情況,因此為了RTC外設掉電繼續運行,必須接上鋰電池通過VBAT引腳供電。當主電源VDD有效時,由VDD給RTC外設供電;而當VDD掉電后,由VBAT給RTC外設供電。但無論由什么電源供電,RTC中的數據都保存在屬于RTC的備份域中,若主電源VDD和VBAT都掉電,那么備份域中保存的所有數據將丟失。
從RTC的定時器特性來說,它是一個32位的計數器,只能向上計數。在相應軟件配置下,可提供時鐘日歷的功能,修改計數器的值可以重新設置系統當前的時間和日期。它使用的時鐘源有三種,分別為高速外部時鐘的128分頻(HSE/128)、低速內部時鐘LSI以及低速外部時鐘LSE。在主電源VDD掉電的情況下,HSE和LSI這兩個時鐘來源都會受到影響,沒法保證RTC正常工作,因此RTC一般使用低速外部時鐘LSE供電。在主電源VDD有效的情況下(待機),RTC還可以配置鬧鐘事件使CKS32退出待機模式。
RTC框圖結果分析
RTC由兩個主要部分組成,參見下圖。第一部分(背景灰色區域)用來和APB1總線相連,屬于備份域,在VDD掉電時可在VBAT的驅動下繼續運行。這部分僅包括RTC的分頻器,計數器和鬧鐘控制器。若VDD電源有效,RTC可以觸發RTC_Second(秒中斷)、RTC_Overflow(溢出事件)和RTC_Alarm(鬧鐘中斷)。此單元還包含一組16位寄存器,可通過APB1總線對其進行讀寫操作。APB1接口由APB1總線時鐘驅動,用來與APB1總線連接。
另一部分(RTC核心)由一組可編程計數器組成,分成兩個主要模塊。第一個模塊是RTC的預分頻模塊,它可編程產生最長為1秒的RTC時間基準TR_CLK。RTC的預分頻模塊包含了一個20位的可編程分頻器(RTC 預分頻器)。如果在RTC_CR寄存器中設置了相應的允許位,則在每個TR_CLK周期中RTC產生一個中斷(秒中斷)。第二個模塊是一個32位的可編程計數器,可被初始化為當前的系統時間,按秒鐘計算,可以記錄4294967296秒,約合136年左右,作為一般應用已經足夠。RTC還有一個鬧鐘寄存器RTC_ALR,用于產生鬧鐘。系統時間按TR_CLK周期累加并與存儲在RTC_ALR寄存器中的可編程時間相比較,如果RTC_CR控制寄存器中設置了相應允許位,比較匹配時將產生一個鬧鐘中斷。
圖1 簡化的RTC框圖
由于備份域的存在,使得RTC內核具有了完全獨立于APB1接口的特性,也因此對RTC寄存器的訪問要遵守一定的規則。系統復位后,默認禁止訪問后備寄存器和RTC,防止對后備區域BKP的意外寫操作。需要執行以下操作使能才可以對后備寄存器和RTC的訪問:(1)設置RCC_APB1ENR寄存器的PWREN和BKPEN位來使能電源和后備接口時鐘。(2)設置PWR_CR寄存器的DBP位使能對后備寄存器和RTC的訪問。設置后備寄存器為可訪問后,在第一次通過APB1接口訪問RTC時,因為時鐘頻率的差異,所以必須等待APB1與RTC外設同步,確保被讀取出來的RTC寄存器值是正確的。如果內核要對RTC寄存器進行任何的寫操作,在內核發出寫指令后,RTC模塊在3個RTCCLK時鐘之后才開始正式的寫RTC寄存器操作。由于RTCCLK的頻率比內核主頻低得多,所以每次操作后必須要檢查RTC關閉操作標志位RTOFF,當這個標志被置1時,寫操作才正式完成。當然,以上的操作都具有庫函數來快速實現。
RTC控制相關庫函數
標準庫對RTC控制提供了完善的函數,使用它們可以方便地進行控制,本小節對這些內容進行講解。RTC相關的庫函數在文件cks32f10x_rtc.c和cks32f10x_rtc.h文件中。
1、等待時鐘同步和操作完成
RTC區域的時鐘比APB時鐘慢,訪問前需要進行時鐘同步,只要調用庫函數 RTC_WaitForSynchro即可,而如果修改了RTC的寄存器,又需要調用RTC_WaitForLastTask函數確保數據已寫入。這兩個庫函數主要通過while循環檢測RTC控制寄存器的RSF和RTOFF位實現等待功能。
/**
* @brief 等待RTC寄存器與APB時鐘同步(RTC_CNT, RTC_ALR and RTC_PRL)
* @note 在APB時鐘復位或停止后,在對RTC寄存器的任何操作前,必須調用本函數
* @param None
* @retval None
*/
void RTC_WaitForSynchro(void)
{
RTC->CRL &= (uint16_t)~RTC_FLAG_RSF;//清除RSF寄存器位
while ((RTC->CRL & RTC_FLAG_RSF) == (uint16_t)RESET);//等待RSF寄存器位為SET
}
/**
* @brief 等待上一次對RTC寄存器的操作完成
* @note 修改RTC寄存器后,必須調用本函數
* @param None
* @retval None
*/
void RTC_WaitForLastTask(void)
{
while ((RTC->CRL & RTC_FLAG_RTOFF) == (uint16_t)RESET); //等待至 RTOFF 寄存器位為SET
}
2、使能備份域及RTC訪問
默認情況下RTC 所屬的備份域禁止訪問,可用庫函數PWR_BackupAccessCmd使能訪問。
/**
* @brief 使能對RTC和Backup寄存器的訪問
* @param ENABLE 或 DISABLE
* @retval None
*/
void PWR_BackupAccessCmd(FunctionalState NewState)
{
*(__IO uint32_t *) CR_DBP_BB = (uint32_t)NewState;
}
該函數通過PWR_CR寄存器的DBP位使能訪問,使能后才可以訪問RTC相關的寄存器,然而若希望修改RTC的寄存器,還需要進一步調用RTC_EnterConfigMode使能RTC控制寄存器的CNF位使能寄存器配置。
/**
* @brief 進入RTC配置模式
* @param None
* @retval None
*/
void RTC_EnterConfigMode(void)
{
RTC->CRL |= RTC_CRL_CNF;//設置CNF位進入配置模式
}
3、設置RTC時鐘分頻
選擇RTC使用的時鐘后,可以使用庫函數RTC_SetPrescaler進行分頻,把函數參數PrescalerValue寫入到RTC的PRLH和PRLL寄存器,一般會把RTC時鐘分頻得到1Hz時鐘。
/**
* @brief 設置RTC分頻配置
* @param PrescalerValue:RTC分頻值
* @retval None
*/
void RTC_SetPrescaler(uint32_t PrescalerValue)
{
RTC_EnterConfigMode();
RTC->PRLH = (PrescalerValue & PRLH_MSB_MASK) >> 16; //設置RTC分頻值的高八位
RTC->PRLL = (PrescalerValue & RTC_LSB_MASK);//設置RTC分頻值的低八位
RTC_ExitConfigMode();
}
4、設置RTC計數器
RTC外設中最重要的就是計數器以及鬧鐘寄存器了,它們可以使用RTC_SetCounter、RTC_GetCounter以及RTC_SetAlarm庫函數操作。利用RTC_SetCounter可以向RTC的計數器寫入新數值,通常這些數值被設置為時間戳以更新時間。RTC_GetCounter函數則用于在RTC正常運行時獲取當前計數器的值以獲取當前時間。RTC_SetAlarm函數用于配置鬧鐘時間,當計數器的值與鬧鐘寄存器的值相等時,可產生鬧鐘事件或中斷,該事件可以把睡眠、停止和待機模式的芯片喚醒。
/**
* @brief 設置RTC計數器的值
* @param CounterValue:要設置的RTC計數值
* @retval None
*/
void RTC_SetCounter(uint32_t CounterValue)
{
RTC_EnterConfigMode();
RTC->CNTH = CounterValue >> 16;//設置RTC計數值的高八位
RTC->CNTL = (CounterValue & RTC_LSB_MASK); //設置RTC計數值的低八位
RTC_ExitConfigMode();
}
/**
* @brief 獲取RTC計數器的值
* @param None
* @retval 返回RTC計數器的值
*/
uint32_t RTC_GetCounter(void)
{
uint16_t tmp = 0;
tmp = RTC->CNTL;
return (((uint32_t)RTC->CNTH << 16 ) | tmp) ;
}
/**
* @brief 設置RTC鬧鐘的值
* @param AlarmValue:要設置的RTC鬧鐘值
* @retval None
*/
void RTC_SetAlarm(uint32_t AlarmValue)
{
RTC_EnterConfigMode();
RTC->ALRH = AlarmValue >> 16;//設置RTC鬧鐘的高八位
RTC->ALRL = (AlarmValue & RTC_LSB_MASK); //設置RTC鬧鐘的低八位
RTC_ExitConfigMode();
}
UNIX時間戳
在使用RTC外設前,還需要引入UNIX時間戳的概念。如果從現在起,把計數器RTC_CNT的計數值置0,然后每秒加1,RTC_CNT什么時候會溢出呢?由于RTC_CNT是32位寄存器,可存儲的最大值為(232-1),這樣計時的話,在232秒后溢出,N=232/365/24/60/60≈136年,即它將在今后136年時溢出。
假如某個時刻讀取到計數器的數值為X=60*60*24*2,即兩天時間的秒數,而假設又知道計數器是在2011年1月1日的0時0分0秒置0的,那么就可以根據計數器的這個相對時間數值,計算得這個X時刻是2011年1月3日的0時0分0秒了。而計數器則會在(2011+136)年左右溢出,也就是說到了(2011+136)年時,如果我們還在使用這個計數器提供時間的話就會出現問題。在這個例子中,定時器被置0的這個時間被稱為計時元年,相對計時元年經過的秒數稱為時間戳,也就是計數器中的值。
大多數操作系統都是利用時間戳和計時元年來計算當前時間的,而這個時間戳和計時元年大家都取了同一個標準——UNIX時間戳和UNIX計時元年。UNIX計時元年被設置為格林威治時間1970年1月1日0時0分0秒,大概是為了紀念UNIX的誕生的時代吧,而UNIX時間戳即為當前時間相對于UNIX計時元年經過的秒數。因為UNIX時間戳主要用來表示當前時間或者和電腦有關的日志時間(如文件創立時間,log發生時間等),考慮到所有電腦文件不可能在1970年前創立,所以用UNIX時間戳很少用來表示1970前的時間。
在這個計時系統中,使用的是有符號的32位整型變量來保存UNIX時間戳的,即實際可用計數位數比我們上面例子中的少了一位,少了這一位,UNIX 計時元年也相對提前了一半,這個計時方法在2038年1月19日03時14分07秒將會發生溢出,這個時間離我們并不遠,在設計預期壽命較長的設備需要注意。
小結
本章內容介紹了CKS32F107系列RTC實時時鐘外設的硬件結構和工作原理,并結合相關寄存器講解了與RTC控制相關的外設庫函數使用方法,最后介紹了UNIX時間戳的概念。從上述內容可知,RTC外設是個連續計數的計數器,利用它提供的時間戳,可通過程序轉換輸出實時時鐘和日歷的功能,修改計數器的值則可以重新設置系統當前的時間和日期。由于它的時鐘配置系統(RCC_BDCR寄存器)是在備份域,在系統復位或從待機模式喚醒后RTC的設置維持不變,而且使用備份域電源可以讓RTC計時器在主電源關掉的情況下仍然運行,保證時間的正確。有了這些基礎,下一節將詳細介紹如何利用RTC的計時功能實現一個簡單的萬年歷效果。
-
寄存器
+關注
關注
31文章
5421瀏覽量
123306 -
實時時鐘
+關注
關注
4文章
301瀏覽量
66797 -
定時器
+關注
關注
23文章
3287瀏覽量
117197 -
RTC
+關注
關注
2文章
607瀏覽量
68283
原文標題:MCU微課堂|CKS32F107xx RTC(一)
文章出處:【微信號:中科芯MCU,微信公眾號:中科芯MCU】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
一文看懂rtc實時時鐘和單片機時鐘的區別
淺談RTC實時時鐘特征與原理
stm32f4 RTC實時時鐘解析

STM32CubeMX系列|RTC實時時鐘

評論