RTC框圖
實時時鐘(Real-time clock: RTC)是一個獨立的計時器。RTC提供一組連續執行的計數器,可以與合適的軟體一起使用,以提供時鐘日曆功能。可以寫入計數器值以設定系統的當前時間/日期。
可以選擇以下三種作為RTC時鐘源:
- HSE時鐘進行128分頻
- LSE振盪器時鐘
- LSI振盪器時鐘
有關時鐘的更多資訊,參考STM32時鐘樹
RTC核心部分由2個部分構成:
- RTC分頻器由
RTC_PRL
、RTC_DIV
構成:RTC_PRL
是預分頻裝載暫存器,用來儲存RTC預分頻器的週期計數值。RTC_DIV
是預分頻器計數暫存器(只讀)。在TR_CLK的每個週期裡,RTC預分頻器中計數器的值都會被重新設定為RTC_PRL暫存器的值。使用者可透過讀取RTC_DIV暫存器,以獲得預分頻計數器的當前值,而不停止分頻計數器的工作,從而獲得精確的時間測量。當RTC_DIV的值等於RTC_PRL的值,可生成最長為1s的時間基準TR_CLK
- RTC32位可程式設計計數器由
RTC_CNT
、RTC_ALR
構成RTC_CNT
是32位計數暫存器,存放RTC當前計數值,計數的速率取決於TR_CLK
。分為兩個16位暫存器RTC_CNTH
和RTC_CNTL
RTC_ALR
是鬧鐘(alarm)暫存器,當可程式設計計數器(RTC_CNT)的值與RTC_ALR中的32位值相等時,觸發一個鬧鐘事件,並且產生RTC鬧鐘中斷。
系統復位後,對後備暫存器和RTC的訪問被禁止,這是為了防止對後備區域(BKP)的意外寫操作。執行以下操作使能對(Backup)後備暫存器和RTC的訪問:
- 透過設定RCC_APB1ENR暫存器的PWREN位和BKPEN位,來開啟POWER和BACKUP的時鐘
- 透過設定電源控制暫存器(PWR_CR)的DBP位,允許對RTC和Backup暫存器的訪問
PWR
電源控制(Power control:PWR),電源框圖如下。STM32的工作電壓(VDD)為2.0~3.6V。透過內建的電壓調節器提供所需的1.8V電源。 當主電源VDD掉電後,可透過VBAT腳為實時時鐘(RTC)和備份暫存器提供電源。
備份域(Backup domain)有以下幾個部分構成:
- 頻率為32K的LSE振盪器
- BKP(備份)暫存器
- RCC_BDCR(備份域控制)暫存器
- RTC
BKP
備份暫存器(Backup Register)是42個16位的暫存器,可用來儲存84個位元組的使用者應用程式資料。他們處在備份域裡,當VDD電源被切斷,他們仍然由VBAT維持供電,資料不會丟失。當系統在待機模式下被喚醒,或系統復位或電源復位時,他們也不會被複位。BKP可用於RTC校準功能。
RTC顯示實時時間
透過串列埠配置修改將RTC計數暫存器修改為當前時間,開啟RTC秒中斷(Second Interrupt),1S產生一次RTC中斷,並透過串列埠助手實時顯示時間。備份暫存器在這個例程中的作用就是檢查RTC是否被配置了。由於使用的最小系統板VBAT引腳沒有接電池,所以需要單獨供電,斷電後BKP的內容會被清空。
整體流程圖
透過串列埠修改RTC外設暫存器的值設為當前時間,配置完成後微控制器向串列埠助手傳送資料,使用串列埠助手顯示當前時間。BKP在本例程中的作用就是用於檢測RTC是否被配置,透過向BKP_DR1暫存器寫入一個任意的值,來判斷RTC是否被配置。整體流程圖如下:
void MyRTC_Init(void)
{
NVIC_Configuration();
if(BKP_ReadBackupRegister(BKP_DR1) != 0x1234)
{
printf("\r\n\n RTC not yet configured....");
RTC_Configuration(); //配置RTC
printf("\r\n RTC configured....");
Time_Adjust();
BKP_WriteBackupRegister(BKP_DR1, 0x1234);
}
else
{
/* 檢查電源復位標誌位是否被設定 */
if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET)
{
printf("\r\n\n Power On Reset occurred....");
}
/* 檢查引腳復位標誌位是否被設定 */
else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET)
{
printf("\r\n\n External Reset occurred....");
}
printf("\r\n No need to configure RTC....");
/* 等待RTC同步 */
RTC_WaitForSynchro();
/* 使能RTC秒中斷 */
RTC_ITConfig(RTC_IT_SEC, ENABLE);
/* 等待RTC暫存器上的最後一次寫入操作完成 */
RTC_WaitForLastTask();
}
/* 清除復位標誌位 */
RCC_ClearFlag();
}
配置RTC
系統復位後,執行以下操作才能實現對(Backup)備份暫存器和RTC的訪問:
- 開啟PWR、BKP時鐘
- 允許訪問BACKUP暫存器、RTC外設
{ signal: [
{ name: "RTCCLK", wave: "p..........", period: 2 },
{ name: "RTC_DIV", wave: "=.=.=.=.=.=.=.=.=.=.=.", data: ["0x00", "0x03", "0x02", "0x01", "0x00","0x03", "0x02", "0x01", "0x00", "0x03", "0x02"] },
{ name: "TR_CLK", wave: "0.1.0.....1.0.....1.0.."},
{ name: "RTC_Second", wave: "0.1.0.....1.0.....1.0.."},
{ name: "RTC_CNT", wave: "=...=.......=.......=...", data: ["FFFFFFFD", "FFFFFFFFE", "FFFFFFFFF", "00000000"] },
{ name: "RTC_Overflow", wave:"0.................1.0.."}
],
head:{
text:'RTC_PRL=0x03,計數溢位的波形圖'
}
}
- RTCCLK的時鐘是來自LSE振盪器的時鐘,\(f_{LSE}\)=32.728KHz。由參考手冊可得\(f_{TR\_CLK}=\frac{f_{RTCCLK}}{RTC\_PRL[19:0]+1}\),時間基準TR_CLK最大可計時1S,即RTC_Second的時鐘週期最長可為1S。要獲得1S的時間基準,RTC_PRL暫存器應該寫入32767。
- RTC_DIV存放分頻計數器當前值,當減為0時,在TR_CLK的上升沿時發生重灌載(reload),RTC_DIV的值被重灌載為RTC_PRL的值
- RTC_CNT暫存器的值在每個TR_CLK的時鐘週期下,值都會遞增。RTC_CNT暫存器用於存放系統的當前時間
- RTC_Overflow:當32位的RTC_CNT暫存器計數滿了之後,在下個TR_CLK上升沿時,產生一次溢位訊號
RTC配置程式程式碼:
void RTC_Configuration(void)
{
/* 開啟PWR、BKP時鐘 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
/* 允許訪問Backup和RTC暫存器 */
PWR_BackupAccessCmd(ENABLE);
/* 復位BKP */
BKP_DeInit();
/* 使能LSE */
RCC_LSEConfig(RCC_LSE_ON);
/* 等待LSE準備好 */
while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)
{
}
/* 選擇LSE做為RTC時鐘源 */
RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
/* 使能RTC時鐘 */
RCC_RTCCLKCmd(ENABLE);
/* 等待RTC暫存器同步 */
RTC_WaitForSynchro();
/* RTC寫暫存器操作必須要這段語句,等待上次對RTC暫存器的寫操作完成 */
RTC_WaitForLastTask();
/* 每秒產生一次中斷 */
RTC_ITConfig(RTC_IT_SEC, ENABLE);
/* 等待上次對RTC暫存器的寫操作完成 */
RTC_WaitForLastTask();
/* 設定預分頻 */
RTC_SetPrescaler(32767); //RTC時鐘週期 = (32.768KHz) / (32767 + 1) = 1Hz = 1s
/* 等待上次對RTC暫存器的寫操作完成 */
RTC_WaitForLastTask();
}
串列埠修改RTC
透過串列埠設定RTC計數暫存器的值,printf()
向串列埠助手傳送資訊,USART_Scanf()
函式接收來自串列埠助手的訊息。USART的資料幀使用8位有效資料。
串列埠修改RTC程式程式碼:
/**
* @brief 調整時間
* @param none
* @retval none
*/
void Time_Adjust(void)
{
/* 等待上次對RTC暫存器的寫操作完成 */
RTC_WaitForLastTask();
/* 設定RTC計數器的值 */
RTC_SetCounter(Time_Regulate());
/* 等待上次對RTC暫存器的寫操作完成 */
RTC_WaitForLastTask();
}
/**
* @brief 透過串列埠設定時間
* @param none
* @retval 用秒錶示的時間
*/
uint32_t Time_Regulate(void)
{
uint32_t Tmp_HH = 0xFF, Tmp_MM = 0xFF, Tmp_SS = 0xFF;
printf("\r\n==============Time Settings=====================================");
printf("\r\n Please Set Hours");
while(Tmp_HH == 0xFF)
{
Tmp_HH = USART_Scanf(23);
}
printf(": %d", Tmp_HH);
printf("\r\n Please Set Minutes");
while(Tmp_MM == 0xFF)
{
Tmp_MM = USART_Scanf(59);
}
printf(": %d", Tmp_MM);
printf("\r\n Please Set Seconds");
while(Tmp_SS == 0xFF)
{
Tmp_SS = USART_Scanf(59);
}
printf(": %d", Tmp_SS);
/* 將輸入的值轉換為Counter CNT */
return (Tmp_HH * 60 * 60 + Tmp_MM * 60 + Tmp_SS);
}
/**
* @brief 獲取來自串列埠的資料
* @param value 允許串列埠輸入的最大值
* @retval 轉換後的串列埠的資料
*/
uint8_t USART_Scanf(uint32_t value)
{
uint32_t index = 0;
uint8_t rev[2] = {0, 0};
while(index < 2)
{
/* 等待RXNE = 1 */
while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET)
{
}
rev[index++] = USART_ReceiveData(USART1);
if(rev[index - 1] < 0x30 || rev[index - 1] > 0x39)
{
printf("\n\rPlease enter valid number between 0 and 9");
index--;
}
}
index = ((rev[0] - 0x30) * 10) + (rev[1] - 0x30); //獲取輸入的值
/* 檢驗 */
if(index > value)
{
printf("\n\rPlease enter valid number between 0 and %d", value);
return 0xFF;
}
return index;
}
顯示實時時間
/**
* @brief 將時間按照指定格式顯示
* @param value RTC計數暫存器的值
* @retval none
*/
void Time_Display(uint32_t value)
{
/* 當前時間為23:59:59時,復位RTC */
if(RTC_GetCounter() == 0x0001517F)
{
RTC_SetCounter(0x00);
/* Waits until last write operation on RTC registers has finished. */
RTC_WaitForLastTask();
}
uint32_t Tmp_HH = 0x00, Tmp_MM = 0x00, Tmp_SS = 0x00;
Tmp_HH = value / 3600; //小時
Tmp_MM = (value % 3600) / 60; //分鐘
Tmp_SS = (value % 3600) % 60; //秒
printf("Time: %0.2d:%0.2d:%0.2d\r", Tmp_HH, Tmp_MM, Tmp_SS);
}
/**
* @brief 顯示實時時間
* @param none
* @retval none
*/
void Time_Show(void)
{
while(1)
{
if(time_display == 1)
{
Time_Display(RTC_GetCounter());
time_display = 0; //透過RTC中斷來置1
}
}
}
RTC中斷處理函式
void RTC_IRQHandler(void)
{
if(RTC_GetITStatus(RTC_IT_SEC) != RESET)
{
time_display = 1;
RTC_ClearITPendingBit(RTC_IT_SEC);
/* Waits until last write operation on RTC registers has finished. */
RTC_WaitForLastTask();
}
}
主函式
int main(void)
{
COM_Init(); //串列埠初始化,取樣資料
MyRTC_Init(); //初始化RTC
Time_Show(); //在無限迴圈中顯示時間
}
演示結果
使用串列埠助手手動調節RTC來顯示當前時間
完整例程
9-1 RTC