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位),會產生一個侵入檢測事件。侵入檢測事件將所有數據備份寄存器內容清除。

  1. 也就是第一個是使能tamper(PC13)引腳作為時鍾脈沖輸出
  2. 第二個是使能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_TimeTypeDefRTC_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(); } __HAL_RTC_SECOND_ENABLE_IT(&hrtc,RTC_IT_SEC); //開啟RTC時鍾秒中斷 } }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM