STM32】HAL庫 STM32CubeMX教程十三---RTC時鐘
前言:
本系列教程將 對應外設原理,HAL庫與STM32CubeMX結合在一起講解,使您可以更快速的學會各個模組的使用
所用工具:
1、晶片: STM32F407ZET6/ STM32F103ZET6
2、STM32CubeMx軟體
3、IDE: MDK-Keil軟體
4、STM32F1xx/STM32F4xxHAL庫
知識概括:
通過本篇部落格您將學到:
RTC時鐘原理
STM32CubeMX建立RTC例程
HAL庫定時器RTC函式庫
PS: 這裡的RTC講解,我們只將原理,不講暫存器,如果要看RTC的暫存器,請看這篇文章
【STM32】RTC實時時鐘,步驟超細詳解,一文看懂RTC
什麼是RTC
RTC (Real Time Clock)
RTC是個獨立的定時器。RTC模組擁有一個連續計數的計數器,在相應的軟體配置下,可以提供時鐘日曆的功能。修改計數器的值可以重新設定當前時間和日期 RTC還包含用於管理低功耗模式的自動喚醒單元。
在斷電情況下 RTC仍可以獨立執行 只要晶片的備用電源一直供電,RTC上的時間會一直走。
RTC實質是一個掉電後還繼續執行的定時器,從定時器的角度來看,相對於通用定時器TIM外設,它的功能十分簡單,只有計時功能(也可以觸發中斷)。但其高階指出也就在於掉電之後還可以正常執行。
兩個 32 位暫存器包含二進碼十進數格式 (BCD) 的秒、分鐘、小時( 12 或 24 小時制)、星期幾、日期、月份和年份。此外,還可提供二進位制格式的亞秒值。系統可以自動將月份的天數補償為 28、29(閏年)、30 和 31 天。
上電覆位後,所有RTC暫存器都會受到保護,以防止可能的非正常寫訪問。
無論器件狀態如何(執行模式、低功耗模式或處於復位狀態),只要電源電壓保持在工作範圍內,RTC使不會停止工作。
RCT特徵:
● 可程式設計的預分頻係數:分頻係數高為220。
● 32位的可程式設計計數器,可用於較長時間段的測量。
● 2個分離的時鐘:用於APB1介面的PCLK1和RTC時鐘(RTC時鐘的頻率必須小於PCLK1時鐘 頻率的四分之一以上)。
● 可以選擇以下三種RTC的時鐘源:
● HSE時鐘除以128;
● LSE振盪器時鐘;
● LSI振盪器時鐘
● 2個獨立的復位型別:
● APB1介面由系統復位;
● RTC核心(預分頻器、鬧鐘、計數器和分頻器)只能由後備域復位
● 3個專門的可遮蔽中斷:
● 1.鬧鐘中斷,用來產生一個軟體可程式設計的鬧鐘中斷。
● 2.秒中斷,用來產生一個可程式設計的週期性中斷訊號(長可達1秒)。
● 3.溢位中斷,指示內部可程式設計計數器溢位並回轉為0的狀態。
RTC時鐘源:
三種不同的時鐘源可被用來驅動系統時鐘(SYSCLK):
● HSI振盪器時鐘
● HSE振盪器時鐘
● PLL時鐘
這些裝置有以下2種二級時鐘源:
● 40kHz低速內部RC,可以用於驅動獨立看門狗和通過程式選擇驅動RTC。 RTC用於從停機/待機模式下自動喚醒系統。
● 32.768kHz低速外部晶體也可用來通過程式選擇驅動RTC(RTCCLK)。
RTC原理框圖
RTC時鐘的框圖還是比較簡單的,這裡我們把他分成 兩個部分
:
APB1 介面:用來和 APB1 匯流排相連。 此單元還包含一組 16 位暫存器,可通過 APB1 匯流排對其進行讀寫操作。APB1 介面由 APB1 總 線時鐘驅動,用來與 APB1 匯流排連線。
通過APB1介面可以訪問RTC的相關暫存器(預分頻值,計數器值,鬧鐘值)。
RTC 核心介面:由一組可程式設計計數器組成,分成 兩個主要模組
。
g)
第一個模組是 RTC 的 預分頻模組,它可程式設計產生 1 秒的 RTC 時間基準 TR_CLK。RTC 的預分頻模組包含了一個 20 位的可程式設計分頻器(RTC 預分頻器)。如果在 RTC_CR 暫存器中設定了相應的允許位,則在每個 TR_CLK 週期中 RTC 產生一箇中斷(秒中斷)。
第二個模組是一個 32 位的可程式設計計數器 (RTC_CNT),可被初始化為當前的系統時間,一個 32 位的時鐘計數器,按秒鐘計算,可以記 錄 4294967296 秒,約合 136 年左右,作為一般應用,這已經是足夠了的。
RTC具體流程:
RTCCLK經過RTC_DIV預分頻,RTC_PRL設定預分頻係數,然後得到TR_CLK時鐘訊號,我們一般設定其週期為1s,RTC_CNT計數器計數,假如1970設定為時間起點為0s,通過當前時間的秒數計算得到當前的時間。RTC_ALR是設定鬧鐘時間,RTC_CNT計數到RTC_ALR就會產生計數中斷,
- RTC_Second為秒中斷,用於重新整理時間,
- RTC_Overflow是溢位中斷。
- RTC Alarm 控制開關機
RTC時鐘選擇
使用HSE分頻時鐘或者LSI的時候,在主電源VDD掉電的情況下,這兩個時鐘來源都會受到影響,因此沒法保證RTC正常工作.所以RTC一般都時鐘低速外部時鐘LSE,頻率為實時時鐘模組中常用的32.768KHz,因為32768 = 2^15,分頻容易實現,所以被廣泛應用到RTC模組.(在主電源VDD有效的情況下(待機),RTC還可以配置鬧鐘事件使STM32退出待機模式).
RTC復位過程
除了RTC_PRL、RTC_ALR、RTC_CNT和RTC_DIV暫存器外,所有的系統暫存器都由系統復位或電源復位進行非同步復位。
RTC_PRL、RTC_ALR、RTC_CNT和RTC_DIV暫存器僅能通過備份域復位訊號復位。
系統復位後,禁止訪問後備暫存器和RCT,防止對後衛區域(BKP)的意外寫操作
RTC中斷
秒中斷:
這裡時鐘自帶一個秒中斷,每當計數加一的時候就會觸發一次秒中斷,。注意,這裡所說的秒中斷並非一定是一秒的時間,它是由RTC時鐘源和分頻值決定的“秒”的時間,當然也是可以做到1秒鐘中斷一次。我們通過往秒中斷裡寫更新時間的函式來達到時間同步的效果
鬧鐘中斷:
鬧鐘中斷就是設定一個預設定的值,計數每自加多少次觸發一次鬧鐘中斷
CubeMX配置RTC
工程建立
1設定RCC
- 設定高速外部時鐘HSE 選擇外部時鐘源
- 使能外部晶振LSE
RTC裝置因為其獨特的執行方式(即掉電依舊執行)使用HSE分頻時鐘或者LSI的時候,在主電源VDD掉電的情況下,這兩個時鐘來源都會受到影響,資源消耗太大,小小的鈕釦電池根本吃不消。沒法保證RTC正常工作.所以RTC一般都時鐘低速外部時鐘LSE
2.配置RTC
- Activate Clock Source 啟用時鐘源
- Activate calendar啟用日曆
這兩個都要點,作用也很明顯,先是使能時鐘源,再使能RTC日曆
- RTC_OUT: Not RTC_OUT
- Tamper: ×
第一個是是否使能 tamper(PC13)引腳上輸出校正的秒脈衝時鐘,
第二個: RTC入侵檢測校驗功能
RTC校驗功能,使能侵入檢測功能。RTC時鐘經64分頻輸出到侵入檢測引腳TAMPER上
當 TAMPER引腳上的訊號從 0變成1或者從 1變成 0(取決於備份控制暫存器BKP_CR的 TPAL位),會產生一個侵入檢測事件。侵入檢測事件將所有資料備份暫存器內容清除。
- 也就是第一個是使能tamper(PC13)引腳作為時鐘脈衝輸出
- 第二個是使能tamper(PC13)引腳作為入侵檢測功能
下面是兩個RTC的中斷:
- RTC全域性中斷RTC_IRQHandler()
- 鬧鐘中斷函式RTCAlarm_IRQHandler()
此處設定時間為2020/04/25 13:30:00
- Data Format: 日期格式
Binary data format 十六進位制
BCD data format BCD碼進位制
使用自動配置,初始化時間必須使用BCD data format,原因是庫函式存在bug,如果使用Binary data format,月份配置會出錯,比如說11月,配置時會賦值為RTC_MONTH_NOVEMBER,而此巨集定義值為0x11,也就是說其十進位制值為17
-
Hours: 小時
-
Minutes: 分鐘
-
Seconds: 秒
-
Week Day: 星期
-
Month 月份
-
Date: 日期
-
Year: 年份
3 使能串列埠
使能一下串列埠,因為傳送日期到上位機
4時鐘源設定
我的是 外部晶振為8MHz
- 1選擇外部時鐘HSE 8MHz
- 2PLL鎖相環倍頻9倍
- 3系統時鐘來源選擇為PLL
- 4設定APB1分頻器為 /2
- 5 使能CSS監視時鐘
- 6 設定RTC時鐘為LSE
32的時鐘樹框圖 如果不懂的話請看《【STM32】系統時鐘RCC詳解(超詳細,超全面)》
5專案檔案設定
- 1 設定專案名稱
- 2 設定儲存路徑
- 3 選擇所用IDE
6建立工程檔案
然後點選GENERATE CODE 建立工程
配置下載工具
新建的工程所有配置都是預設的 我們需要自行選擇下載模式,勾選上下載後復位執行
RTC_HAL庫函式
/*設定系統時間*/
HAL_StatusTypeDef HAL_RTC_SetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format)
/*讀取系統時間*/
HAL_StatusTypeDef HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format)
/*設定系統日期*/
HAL_StatusTypeDef HAL_RTC_SetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format)
/*讀取系統日期*/
HAL_StatusTypeDef HAL_RTC_GetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format)
/*啟動報警功能*/
HAL_StatusTypeDef HAL_RTC_SetAlarm(RTC_HandleTypeDef *hrtc, RTC_AlarmTypeDef *sAlarm, uint32_t Format)
/*設定報警中斷*/
HAL_StatusTypeDef HAL_RTC_SetAlarm_IT(RTC_HandleTypeDef *hrtc, RTC_AlarmTypeDef *sAlarm, uint32_t Format)
/*報警時間回撥函式*/
__weak void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc)
/*寫入後備儲存器*/
void HAL_RTCEx_BKUPWrite(RTC_HandleTypeDef *hrtc, uint32_t BackupRegister, uint32_t Data)
/*讀取後備儲存器*/
uint32_t HAL_RTCEx_BKUPRead(RTC_HandleTypeDef *hrtc, uint32_t BackupRegister
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
我們可以看到前面的四個函式,分別是
- 設定系統時間:HAL_RTC_SetTime();
- 讀取系統時間: HAL_RTC_GetTime();
- 設定系統日期: HAL_RTC_SetDate();
- 讀取系統日期: HAL_RTC_GetDate();
因為系統的時間和日期開始的時候已經設定過了,所以我們這裡只用兩個讀取函式
讀取系統時間函式
/*讀取系統時間*/
HAL_StatusTypeDef HAL_RTC_GetTime(RTC_HandleTypeDef *hrtc, RTC_TimeTypeDef *sTime, uint32_t Format)
- 1
- 2
功能: 獲取RTC時鐘的時間
引數:
-
*hrtc RTC結構體引數 例:&hi2c2
-
RTC_TimeTypeDef *sTime: 獲取RTC時間的結構體,
-
Format: 獲取時間的格式
RTC_FORMAT_BIN 使用16進位制
RTC_FORMAT_BCD 使用BCD進位制
讀取系統日期函式
/*讀取系統日期*/
HAL_StatusTypeDef HAL_RTC_GetDate(RTC_HandleTypeDef *hrtc, RTC_DateTypeDef *sDate, uint32_t Format)
- 1
- 2
功能: 獲取RTC時鐘的日期
引數:
-
*hrtc RTC結構體引數 例:&hi2c2
-
RTC_DateTypeDef *sTime: 獲取RTC日期的結構體,
-
Format: 獲取日期的格式
RTC_FORMAT_BIN 使用16進位制
RTC_FORMAT_BCD 使用BCD進位制
在stm32f1xx_hal_rtc.h標頭檔案中,可以找到RTC_TimeTypeDef
,RTC_DateTypeDef
這兩個結構體的成員變數。
/**
* @brief RTC Time structure definition
*/
typedef struct
{
uint8_t Hours; /*!< Specifies the RTC Time Hour.
This parameter must be a number between Min_Data = 0 and Max_Data = 23 */
uint8_t Minutes; /*!< Specifies the RTC Time Minutes.
This parameter must be a number between Min_Data = 0 and Max_Data = 59 */
uint8_t Seconds; /*!< Specifies the RTC Time Seconds.
This parameter must be a number between Min_Data = 0 and Max_Data = 59 */
} RTC_TimeTypeDef;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
/**
* @brief RTC Date structure definition
*/
typedef struct
{
uint8_t WeekDay; /*!< Specifies the RTC Date WeekDay (not necessary for HAL_RTC_SetDate).
This parameter can be a value of @ref RTC_WeekDay_Definitions */
uint8_t Month; /*!< Specifies the RTC Date Month (in BCD format).
This parameter can be a value of @ref RTC_Month_Date_Definitions */
uint8_t Date; /*!< Specifies the RTC Date.
This parameter must be a number between Min_Data = 1 and Max_Data = 31 */
uint8_t Year; /*!< Specifies the RTC Date Year.
This parameter must be a number between Min_Data = 0 and Max_Data = 99 */
} RTC_DateTypeDef;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
程式程式碼:
main.c
在main.c中重寫fputc函式,使得能夠使用printf函式
#include "stdio.h"
int fputc(int ch,FILE *f){
uint8_t temp[1]={ch};
HAL_UART_Transmit(&huart1,temp,1,2);
return ch;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
定義兩個結構體來獲取日期和時間:
RTC_DateTypeDef GetData; //獲取日期結構體
RTC_TimeTypeDef GetTime; //獲取時間結構體
- 1
- 2
- 3
在while迴圈中新增:
/* Get the RTC current Time */
HAL_RTC_GetTime(&hrtc, &GetTime, RTC_FORMAT_BIN);
/* Get the RTC current Date */
HAL_RTC_GetDate(&hrtc, &GetData, RTC_FORMAT_BIN);
/* Display date Format : yy/mm/dd */
printf("%02d/%02d/%02d\r\n",2000 + GetData.Year, GetData.Month, GetData.Date);
/* Display time Format : hh:mm:ss */
printf("%02d:%02d:%02d\r\n",GetTime.Hours, GetTime.Minutes, GetTime.Seconds);
printf("\r\n");
HAL_Delay(1000);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
程式中使用HAL_RTC_GetTime(),HAL_RTC_GetDate()讀取時間和日期,並儲存到結構體變數中,然後通過串列埠輸出讀取的時間和日期。
例程測試正常:
RTC掉電重置
但是呢,在hal庫中生成的程式碼,每次斷電就RTC時間會重置,每次上電都會重新初始化時間
因為HAL庫設定了一個BKP暫存器儲存一個標誌。每次微控制器啟動時都讀取這個標誌並判斷是不是預先設定的值:如度果不是就初始化RTC並設定時間,再設定標誌為預期值;如果是預期值就跳過初始化和時間設定,繼續執行後面的程式
所以這裡我們只需要每次上電執行RTC初始化之前,將標誌設定為預期值即可
在rtc.c中的RTC_Init修改為以下內容即可
void MX_RTC_Init(void)
{
/* USER CODE BEGIN RTC_Init 0 */
RTC_TimeTypeDef time; //時間結構體引數
RTC_DateTypeDef datebuff; //日期結構體引數
/* USER CODE END RTC_Init 0 */
RTC_TimeTypeDef sTime = {0};
RTC_DateTypeDef DateToUpdate = {0};
/* USER CODE BEGIN RTC_Init 1 */
__HAL_RCC_BKP_CLK_ENABLE(); //開啟後備區域時鐘
__HAL_RCC_PWR_CLK_ENABLE(); //開啟電源時鐘
/* USER CODE END RTC_Init 1 */
/**Initialize RTC Only
*/
hrtc.Instance = RTC;
hrtc.Init.AsynchPrediv = RTC_AUTO_1_SECOND;
hrtc.Init.OutPut = RTC_OUTPUTSOURCE_NONE;
if (HAL_RTC_Init(&hrtc) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN Check_RTC_BKUP */
if(HAL_RTCEx_BKUPRead(&hrtc,RTC_BKP_DR1)!= 0x5051)
{
/* USER CODE END Check_RTC_BKUP */
/**Initialize RTC and set the Time and Date
*/
sTime.Hours = 0x14;
sTime.Minutes = 0x30;
sTime.Seconds = 0x0;
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
{
Error_Handler();
}
DateToUpdate.WeekDay = RTC_WEEKDAY_SATURDAY;
DateToUpdate.Month = RTC_MONTH_APRIL;
DateToUpdate.Date = 0x25;
DateToUpdate.Year = 0x20;
if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BCD) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN RTC_Init 2 */
__HAL_RTC_SECOND_ENABLE_IT(&hrtc,RTC_IT_SEC); //開啟RTC時鐘秒中斷
datebuff = DateToUpdate; //把日期資料拷貝到自己定義的data中
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, 0x5051);//向指定的後備區域暫存器寫入資料
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR2, (uint16_t)datebuff.Year);
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR3, (uint16_t)datebuff.Month);
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR4, (uint16_t)datebuff.Date);
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR5, (uint16_t)datebuff.WeekDay);
}
else
{
datebuff.Year = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR2);
datebuff.Month = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR3);
datebuff.Date = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR4);
datebuff.WeekDay = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR5);
DateToUpdate = datebuff;
if (HAL_RTC_SetDate(&hrtc, &DateToUpdate, RTC_FORMAT_BIN) != HAL_OK)
{
Error_Handler(