第43章 RTC—實時時鍾
全套200集視頻教程和1000頁PDF教程請到秉火論壇下載:www.firebbs.cn
野火視頻教程優酷觀看網址:http://i.youku.com/firege
43.1 RTC簡介
RTC—real time clock,實時時鍾,主要包含日歷、鬧鍾和自動喚醒這三部分的功能,其中的日歷功能我們使用的最多。日歷包含兩個32bit的時間寄存器,可直接輸出時分秒,星期、月、日、年。比起F103系列的RTC只能輸出秒中斷,剩下的其他時間需要軟件來實現,429的RTC可謂是脫胎換骨,讓我們在軟件編程時大大降低了難度。RTC功能框圖分析
43.2 RTC功能框圖解析

1. 時鍾源
RTC 時鍾源—RTCCLK 可以從LSE、LSI和HSE_RTC這三者中得到。其中使用最多的是LSE,LSE由一個外部的32.768KHZ(6PF負載)的晶振提供,精度高,穩定,RTC首選。LSI是芯片內部的30KHZ晶體,精度較低,會有溫漂,一般不建議使用。HSE_RTC由HSE分頻得到,最高是4M,使用的也較少。
2. 預分頻器
預分頻器PRER由7位的異步預分頻器APRE和15位的同步預分頻器SPRE組成。異步預分頻器時鍾CK_APRE用於為二進制 RTC_SSR 亞秒遞減計數器提供時鍾,同步預分頻器時鍾CK_SPRE用於更新日歷。異步預分頻器時鍾fCK_APRE=fRTC_CLK/(PREDIV_A+1),同步預分頻器時鍾fCK_SPRE=fRTC_CLK/(PREDIV_S+1),)。使用兩個預分頻器時,推薦將異步預分頻器配置為較高的值,以最大程度降低功耗。一般我們會使用LSE生成1HZ的同步預分頻器時鍾
通常的情況下,我們會選擇LSE作為RTC的時鍾源,即fRTCCLK=fLSE=32.768KHZ。然后經過預分頻器PRER分頻生成1HZ的時鍾用於更新日歷。使用兩個預分頻器分頻的時候,為了最大程度的降低功耗,我們一般把同步預分頻器設置成較大的值,為了生成1HZ的同步預分頻器時鍾CK_SPRE,最常用的配置是PREDIV_A=127,PREDIV_S=255。計算公式為:fCK_SPRE=fRTCCLK/{(PREDIV_A+1)*(PREDIV_S+1)}= 32.768/{(127+1)*(255+1)}=1HZ。
3. 實時時鍾和日歷
我們知道,實時時鍾一般是這樣表示的:時/分/秒/亞秒,其中時分秒可直接從RTC 時間寄存器 (RTC_TR)中讀取,有關時間寄存器的說明具體見錯誤!未找到引用源。和錯誤!未找到引用源。。

圖 431RTC 時間寄存器(RTC_TR)
表格 431 時間寄存器位功能說明
| 位名稱 |
位說明 |
| PM |
AM/PM符號,0:AM/24小時制,1:PM |
| HT[1:0] |
小時的十位 |
| HU[3:0] |
小時的個位 |
| MNT[2:0] |
分鍾的十位 |
| MNU[3:0] |
分鍾的個位 |
| ST[2:0] |
秒的十位 |
| SU[3:0] |
秒的個位 |
亞秒由RTC 亞秒寄存器 (RTC_SSR)的值計算得到,公式為:亞秒值 = ( PREDIV_S – SS[15:0] ) / ( PREDIV_S + 1 ) ,SS[15:0]是同步預分頻器計數器的值,PREDIV_S是同步預分頻器的值。

圖 432RTC亞秒寄存器(RTC_SSR)
日期包含的年月日可直接從RTC 日期寄存器 (RTC_DR)中讀取。

圖 433 RTC日期寄存器(RTC_DR)
表格 432 RTC日期寄存器位功能說明
| 位名稱 |
位說明 |
| YT[1:0] |
年份的十位 |
| YU[3:0] |
年份的個位 |
| WDU[2:0] |
星期幾的個位,000:禁止,001:星期一,…,111:星期日 |
| MT |
月份的十位 |
| MU |
月份的個位 |
| DT[1:0] |
日期的十位 |
| DU[3:0] |
日期的個位 |
當應用程序讀取日歷寄存器時,默認是讀取影子寄存器的內容,每隔兩個 RTCCLK 周期,便將當前日歷值復制到影子寄存器。我們也可以通過將 RTC_CR 寄存器的BYPSHAD 控制位置 1 來直接訪問日歷寄存器,這樣可避免等待同步的持續時間。
RTC_CLK經過預分頻器后,有一個512HZ的CK_APRE和1個1HZ的CK_SPRE,這兩個時鍾可以成為校准的時鍾輸出RTC_CALIB,RTC_CALIB最終要輸出則需映射到RTC_AF1引腳,即PC13輸出,用來對外部提供時鍾。
4. 鬧鍾
RTC有兩個鬧鍾,鬧鍾A和鬧鍾B,,當RTC運行的時間跟預設的鬧鍾時間相同的時候,相應的標志位ALRAF(在RTC_ISR寄存器中)和ALRBF會置1。利用這個鬧鍾我們可以做一些備忘提醒功能。
如果使能了鬧鍾輸出(由RTC_CR的OSEL[0:1]位控制),則ALRAF和ALRBF會連接到鬧鍾輸出引腳RTC_ALARM,RTC_ALARM最終連接到RTC的外部引腳RTC_AF1(即PC13),輸出的極性由RTC_CR 寄存器的 POL 位配置,可以是高電平或者低電平。
5. 時間戳
時間戳即時間點的意思,就是某一個時刻的時間。時間戳復用功能 (RTC_TS) 可映射到 RTC_AF1 或 RTC_AF2,當發生外部的入侵事件時,即發生時間戳事件時, RTC_ISR 寄存器中的時間戳標志位 (TSF) 將置 1,日歷會保存到時間戳寄存器( RTC_TSSSR、 RTC_TSTR 和 RTC_TSDR)中。時間戳往往用來記錄危及時刻的時間,以供事后排查問題時查詢。
6. 入侵檢測
RTC自帶兩個入侵檢測引腳RTC_AF1(PC13)和RTC_AF2(PI8),這兩個輸入既可配置為邊沿檢測,也可配置為帶過濾的電平檢測。當發生入侵檢測時,備份寄存器將被復位。備份寄存器 (RTC_BKPxR) 包括20 個 32 位寄存器,用於存儲 80 字節的用戶應用數據。這些寄存器在備份域中實現,可在 VDD 電源關閉時通過 VBAT 保持上電狀態。備份寄存器不會在系統復位或電源復位時復位,也不會在器件從待機模式喚醒時復位。
43.3 RTC初始化結構體講解
標准庫函數對每個外設都建立了一個初始化結構體,比如RTC_InitTypeDef,結構體成員用於設置外設工作參數,並由外設初始化配置函數,比如RTC_Init()調用,這些配置好的參數將會設置外設相應的寄存器,達到配置外設工作環境的目的。
初始化結構體和初始化庫函數配合使用是標准庫精髓所在,理解了初始化結構體每個成員意義基本上就可以對該外設運用自如。初始化結構體定義在stm32f4xx_rtc.h頭文件中,初始化庫函數定義在stm32f4xx_rtc.c文件中,編程時我們可以結合這兩個文件內注釋使用。
RTC初始化結構體用來設置RTC小時的格式和RTC_CLK的分頻系數。
代碼 431 RTC初始化結構體
1 typedef struct {
2 uint32_t RTC_HourFormat; /* 指定RTC小時格式*/
3
4 uint32_t RTC_AsynchPrediv; /* 配置RTC_CLK的異步分頻因子 */
5
6 uint32_t RTC_SynchPrediv; /* 配置RTC_CLK的同步分頻因子 */
7 } RTC_InitTypeDef;
1) RTC_HourFormat:小時格式設置,有RTC_HourFormat_24和RTC_HourFormat_12兩種格式,一般我們選擇使用24小時制,具體由RTC_CR寄存器的FMT位配置。
2) RTC_AsynchPrediv:RTC_CLK異步分頻因子設置,7位有效,具體由RTC 預分頻器寄存器RTC_PRER的PREDIV_A[6:0]位配置。
3) RTC_SynchPrediv:RTC_CLK同步分頻因子設置,15位有效,具體由RTC 預分頻器寄存器RTC_PRER的PREDIV_S[14:0]位配置。
43.4 RTC時間結構體講解
RTC時間初始化結構體用來設置初始時間,配置的是RTC時間寄存器RTC_TR。
代碼 432 RTC時間結構體
1 typedef struct {
2 uint8_t RTC_Hours; /* 小時設置 */
3
4 uint8_t RTC_Minutes; /* 分鍾設置 */
5
6 uint8_t RTC_Seconds; /* 秒設置 */
7
8 uint8_t RTC_H12; /* AM/PM 符號設置 */
9 } RTC_TimeTypeDef;
1) RTC_Hours:小時設置,12小時制式時,取值范圍為0~11,24小時制式時,取值范圍為0~23。
2) RTC_Minutes:分鍾設置,取值范圍為0~59。
3) RTC_Seconds:秒鍾設置,取值范圍為0~59。
4) RTC_H12:AM/PM設置,可取值RTC_H12_AM和RTC_H12_PM,RTC_H12_AM時則是24小時制,RTC_H12_PM則是12小時制。
43.5 RTC日期結構體講解
RTC日期初始化結構體用來設置初始日期,配置的是RTC日期寄存器RTC_DR。
代碼 433 RTC 日期結構體
1 typedef struct {
2 uint8_t RTC_WeekDay; /* 星期幾設置 */
3
4 uint8_t RTC_Month; /* 月份設置 */
5
6 uint8_t RTC_Date; /* 日期設置 */
7
8 uint8_t RTC_Year; /* 年份設置 */
9 } RTC_DateTypeDef;
1) RTC_WeekDay:星期幾設置,取值范圍為1~7,對應星期一~星期日。
2) RTC_Month:月份設置,取值范圍為1~12。
3) RTC_Date:日期設置,取值范圍為1~31。
4) RTC_Year:年份設置,取值范圍為0~99。
43.6 RTC鬧鍾結構體講解
RTC鬧鍾結構體主要用來設置鬧鍾時間,設置的格式為[星期/日期]-[時]-[分]-[秒],共四個字段,每個字段都可以設置為有效或者無效,即可MASK。如果MASK掉[星期/日期]字段,則每天鬧鍾都會響。
代碼 434 RTC鬧鍾結構體
1 typedef struct {
2 RTC_TimeTypeDef RTC_AlarmTime; /* 設定RTC時間寄存器的值:時/分/秒 */
3
4 uint32_t RTC_AlarmMask; /* RTC 鬧鍾掩碼字段選擇 */
5
6 uint32_t RTC_AlarmDateWeekDaySel; /*鬧鍾的日期/星期選擇 */
7
8 uint8_t RTC_AlarmDateWeekDay; /* 指定鬧鍾的日期/星期
9 * 如果日期有效,則取值范圍為1~31
10 * 如果星期有效,則取值為1~7
11 */
12 } RTC_AlarmTypeDef;
1) RTC_AlarmTime:鬧鍾時間設置,配置的是RTC時間初始化結構體,主要配置小時的制式,有12小時或者是24小時,配套具體的時、分、秒。
2) RTC_AlarmMask:鬧鍾掩碼字段選擇,即選擇鬧鍾時間哪些字段無效,取值 可為:RTC_AlarmMask_None(全部有效)、RTC_AlarmMask_DateWeekDay(日期或者星期無效)、RTC_AlarmMask_Hours(小時無效)、RTC_AlarmMask_Minutes(分鍾無效)、RTC_AlarmMask_Seconds(秒鍾無效)、RTC_AlarmMask_All(全部無效)。比如我們選擇RTC_AlarmMask_DateWeekDay,那么就是當RTC的時間的小時等於鬧鍾時間小時字段時,每天的這個小時都會產生鬧鍾中斷。
3) RTC_AlarmDateWeekDaySel:鬧鍾日期或者星期選擇,可選擇RTC_AlarmDateWeekDaySel_WeekDay或者RTC_AlarmDateWeekDaySel_Date。要想這個配置有效,則RTC_AlarmMask不能配置為RTC_AlarmMask_DateWeekDay,否則會被MASK掉。
4) RTC_AlarmDateWeekDay:具體的日期或者星期幾,當RTC_AlarmDateWeekDaySel設置成RTC_AlarmDateWeekDaySel_WeekDay時,取值為1~7,對應星期一~星期日,當設置成RTC_AlarmMask_DateWeekDay時,取值為1~31。
43.7 RTC—日歷實驗
利用RTC的日歷功能制作一個日歷,顯示格式為:年-月-日-星期,時-分-秒。
43.7.1 硬件設計
該實驗用到了片內外設RTC,為了確保在VDD斷電的情況下時間可以保存且繼續運行,VBAT引腳外接了一個CR1220電池座,用來放CR1220電池給RTC供電。

圖 434 RTC 外接CR1220電池座子
43.7.2 軟件設計
1. 編程要點
1) 選擇RTC_CLK的時鍾源;
2) 配置RTC_CLK的分頻系數,包括異步和同步兩個;
3) 設置初始時間,包括日期;
4) 獲取時間和日期,並顯示;
2. 代碼分析
這里只講解核心的部分代碼,有些變量的設置,頭文件的包含等並沒有涉及到,完整的代碼請參考本章配套的工程。我們創建了兩個文件:bsp_rtc.c和bsp_rtc.h文件用來存RTC驅動程序及相關宏定義,中斷服務函數則放在stm32f4xx_it.h文件中。
宏定義
代碼 435 宏定義
1 // 時鍾源宏定義
2 #define RTC_CLOCK_SOURCE_LSE
3 //#define RTC_CLOCK_SOURCE_LSI
4
5 // 異步分頻因子
6 #define ASYHCHPREDIV 0X7F
7 // 同步分頻因子
8 #define SYHCHPREDIV 0XFF
9
10
11
12 // 時間宏定義
13 #define RTC_H12_AMorPM RTC_H12_AM
14 #define HOURS 1 // 0~23
15 #define MINUTES 1 // 0~59
16 #define SECONDS 1 // 0~59
17
18 // 日期宏定義
19 #define WEEKDAY 1 // 1~7
20 #define DATE 1 // 1~31
21 #define MONTH 1 // 1~12
22 #define YEAR 1 // 0~99
23
24 // 時間格式宏定義
25 #define RTC_Format_BINorBCD RTC_Format_BIN
26
27 // 備份域寄存器宏定義
28 #define RTC_BKP_DRX RTC_BKP_DR0
29 // 寫入到備份寄存器的數據宏定義
30 #define RTC_BKP_DATA 0X32F2
為了方便程序移植,我們把移植時需要修改的代碼部分都通過宏定義來實現。具體的配合注釋看代碼即可。
RTC時鍾配置函數
代碼 436 RTC配置函數
1 /**
2 * @brief RTC配置:選擇RTC時鍾源,設置RTC_CLK的分頻系數
3 * @param 無
4 * @retval 無
5 */
6 void RTC_CLK_Config(void)
7 {
8 RTC_InitTypeDef RTC_InitStructure;
9
10 /*使能 PWR 時鍾*/
11 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
12 /* PWR_CR:DBF置1,使能RTC、RTC備份寄存器和備份SRAM的訪問 */
13 PWR_BackupAccessCmd(ENABLE);
14
15 #if defined (RTC_CLOCK_SOURCE_LSI)
16 /* 使用LSI作為RTC時鍾源會有誤差
17 * 默認選擇LSE作為RTC的時鍾源
18 */
19 /* 使能LSI */
20 RCC_LSICmd(ENABLE);
21 /* 等待LSI穩定 */
22 while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET) {
23 }
24 /* 選擇LSI做為RTC的時鍾源 */
25 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSI);
26
27 #elif defined (RTC_CLOCK_SOURCE_LSE)
28
29 /* 使能LSE */
30 RCC_LSEConfig(RCC_LSE_ON);
31 /* 等待LSE穩定 */
32 while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) {
33 }
34 /* 選擇LSE做為RTC的時鍾源 */
35 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
36
37 #endif /* RTC_CLOCK_SOURCE_LSI */
38
39 /* 使能RTC時鍾 */
40 RCC_RTCCLKCmd(ENABLE);
41
42 /* 等待 RTC APB 寄存器同步 */
43 RTC_WaitForSynchro();
44
45 /*=====================初始化同步/異步預分頻器的值======================*/
46 /* 驅動日歷的時鍾ck_spare = LSE/[(255+1)*(127+1)] = 1HZ */
47
48 /* 設置異步預分頻器的值*/
49 RTC_InitStructure.RTC_AsynchPrediv = ASYNCHPREDIV;
50 /* 設置同步預分頻器的值 */
51 RTC_InitStructure.RTC_SynchPrediv = SYNCHPREDIV;
52 RTC_InitStructure.RTC_HourFormat = RTC_HourFormat_24;
53 /* 用RTC_InitStructure的內容初始化RTC寄存器 */
54 if (RTC_Init(&RTC_InitStructure) == ERROR) {
55 printf("\n\r RTC 時鍾初始化失敗 \r\n");
56 }
57 }
RTC配置函數主要實現兩個功能,一是選擇RTC_CLK的時鍾源,根據宏定義來決定使用LSE還是LSI作為RTC_CLK的時鍾源,這里我們選擇LSE;二是設置RTC_CLK的預分頻系數,包括異步和同步兩個,這里設置異步分頻因子為ASYNCHPREDIV(127),同步分頻因子為ASYNCHPREDIV(255),則產生的最終驅動日歷的時鍾CK_SPRE=32.768/(127+1)*(255+1)=1HZ,則每秒更新一次。
RTC時間初始化函數
代碼 437 RTC時間和日期設置函數
1 /**
2 * @brief 設置時間和日期
3 * @param 無
4 * @retval 無
5 */
6 void RTC_TimeAndDate_Set(void)
7 {
8 RTC_TimeTypeDef RTC_TimeStructure;
9 RTC_DateTypeDef RTC_DateStructure;
10
11 // 初始化時間
12 RTC_TimeStructure.RTC_H12 = RTC_H12_AMorPM;
13 RTC_TimeStructure.RTC_Hours = HOURS;
14 RTC_TimeStructure.RTC_Minutes = MINUTES;
15 RTC_TimeStructure.RTC_Seconds = SECONDS;
16 RTC_SetTime(RTC_Format_BINorBCD, &RTC_TimeStructure);
17 RTC_WriteBackupRegister(RTC_BKP_DRX, RTC_BKP_DATA);
18
19 // 初始化日期
20 RTC_DateStructure.RTC_WeekDay = WEEKDAY;
21 RTC_DateStructure.RTC_Date = DATE;
22 RTC_DateStructure.RTC_Month = MONTH;
23 RTC_DateStructure.RTC_Year = YEAR;
24 RTC_SetDate(RTC_Format_BINorBCD, &RTC_DateStructure);
25 RTC_WriteBackupRegister(RTC_BKP_DRX, RTC_BKP_DATA);
26 }
RTC時間和日期設置函數主要是設置時間和日期這兩個結構體,然后調相應的RTC_SetTime和RTC_SetDate函數把初始化好的時間寫到相應的寄存器,每當寫完之后都會在備份寄存器里面寫入一個數,以作標記,為的是程序開始運行的時候檢測RTC的時間是否已經配置過。
具體的時間、日期、備份寄存器和寫入備份寄存器的值都在頭文件的宏定義里面,要修改這些值只需修改頭文件的宏定義即可。
RTC時間顯示函數
代碼 438 RTC時間顯示函數
1 /**
2 * @brief 顯示時間和日期
3 * @param 無
4 * @retval 無
5 */
6 void RTC_TimeAndDate_Show(void)
7 {
8 uint8_t Rtctmp=0;
9 char LCDTemp[100];
10 RTC_TimeTypeDef RTC_TimeStructure;
11 RTC_DateTypeDef RTC_DateStructure;
12
13
14 while (1) {
15 // 獲取日歷
16 RTC_GetTime(RTC_Format_BIN, &RTC_TimeStructure);
17 RTC_GetDate(RTC_Format_BIN, &RTC_DateStructure);
18
19 // 每秒打印一次
20 if (Rtctmp != RTC_TimeStructure.RTC_Seconds) {
21
22 // 打印日期
23 printf("The Date : Y:20%0.2d - M:%0.2d - D:%0.2d - W:%0.2d\r\n",
24 RTC_DateStructure.RTC_Year,
25 RTC_DateStructure.RTC_Month,
26 RTC_DateStructure.RTC_Date,
27 RTC_DateStructure.RTC_WeekDay);
28
29 //液晶顯示日期
30 //先把要顯示的數據用sprintf函數轉換為字符串,然后才能用液晶顯示函數顯示
31 sprintf(LCDTemp,"The Date : Y:20%0.2d - M:%0.2d - D:%0.2d - W:%0.2d",
32 RTC_DateStructure.RTC_Year,
33 RTC_DateStructure.RTC_Month,
34 RTC_DateStructure.RTC_Date,
35 RTC_DateStructure.RTC_WeekDay);
36
37 LCD_DisplayStringLineEx(10,50,48,48,(uint8_t *)LCDTemp,0);
38
39 // 打印時間
40 printf("The Time : %0.2d:%0.2d:%0.2d \r\n\r\n",
41 RTC_TimeStructure.RTC_Hours,
42 RTC_TimeStructure.RTC_Minutes,
43 RTC_TimeStructure.RTC_Seconds);
44
45 //液晶顯示時間
46 sprintf(LCDTemp,"The Time : %0.2d:%0.2d:%0.2d",
47 RTC_TimeStructure.RTC_Hours,
48 RTC_TimeStructure.RTC_Minutes,
49 RTC_TimeStructure.RTC_Seconds);
50
51 LCD_DisplayStringLineEx(10,100,48,48,(uint8_t *)LCDTemp,0);
52
53 (void)RTC->DR;
54 }
55 Rtctmp = RTC_TimeStructure.RTC_Seconds;
56 }
57 }
RTC時間和日期顯示函數中,通過調用RTC_GetTime()和RTC_GetDate()這兩個庫函數,把時間和日期都讀取保存到時間和日期結構體中,然后以1s為頻率,把時間顯示出來。
在使用液晶顯示時間的時候,需要先調用標准的C庫函數sprintf()把數據轉換成字符串,然后才能調用液晶顯示函數來顯示,因為液晶顯示時處理的都是字符串。
主函數
代碼 439 main函數
1 /**
2 * @brief 主函數
3 * @param 無
4 * @retval 無
5 */
6 int main(void)
7 {
8 /* 初始化LED */
9 LED_GPIO_Config();
10
11 /* 初始化調試串口,一般為串口1 */
12 Debug_USART_Config();
13 printf("\n\r這是一個RTC日歷實驗 \r\n");
14
15 /*======================液晶初始化開始=========================*/
16 LCD_Init();
17 LCD_LayerInit();
18 LTDC_Cmd(ENABLE);
19
20 /*把背景層刷黑色*/
21 LCD_SetLayer(LCD_BACKGROUND_LAYER);
22 LCD_Clear(LCD_COLOR_BLACK);
23
24 /*初始化后默認使用前景層*/
25 LCD_SetLayer(LCD_FOREGROUND_LAYER);
26 /*默認設置不透明,該函數參數為不透明度,范圍 0-0xff ,0為全透明,0xff為不透明*/
27 LCD_SetTransparency(0xFF);
28 LCD_Clear(LCD_COLOR_BLACK);
29 /*經過LCD_SetLayer(LCD_FOREGROUND_LAYER)函數后,
30 以下液晶操作都在前景層刷新,除非重新調用過LCD_SetLayer函數設置背景層*/
31 LCD_SetColors(LCD_COLOR_RED,LCD_COLOR_BLACK);
32 /*=========================液晶初始化結束========================*/
33
34 /*
35 * 當我們配置過RTC時間之后就往備份寄存器0寫入一個數據做標記
36 * 所以每次程序重新運行的時候就通過檢測備份寄存器0的值來判斷
37 * RTC 是否已經配置過,如果配置過那就繼續運行,如果沒有配置過
38 * 就初始化RTC,配置RTC的時間。
39 */
40 if (RTC_ReadBackupRegister(RTC_BKP_DRX) != RTC_BKP_DATA) {
41 /* RTC配置:選擇時鍾源,設置RTC_CLK的分頻系數 */
42 RTC_CLK_Config();
43 /* 設置時間和日期 */
44 RTC_TimeAndDate_Set();
45 } else {
46 /* 檢查是否電源復位 */
47 if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET) {
48 printf("\r\n發生電源復位....\r\n");
49 }
50 /* 檢查是否外部復位 */
51 else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET) {
52 printf("\r\n發生外部復位....\r\n");
53 }
54
55 printf("\r\n不需要重新配置RTC....\r\n");
56
57 /* 使能 PWR 時鍾 */
58 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
59 /* PWR_CR:DBF置1,使能RTC、RTC備份寄存器和備份SRAM的訪問 */
60 PWR_BackupAccessCmd(ENABLE);
61 /* 等待 RTC APB 寄存器同步 */
62 RTC_WaitForSynchro();
63 }
64
65 /* 顯示時間和日期 */
66 RTC_TimeAndDate_Show();
67 }
主函數中,我們調用RTC_ReadBackupRegister()庫函數來讀取備份寄存器的值是否等於我們預設的那個值,因為當我們初始化完RTC的時間之后就往備份寄存器寫入一個數據做標記,所以每次程序重新運行的時候就通過檢測備份寄存器的值來判斷,RTC 是否已經配置過,如果配置過則判斷是電源復位還是外部引腳復位且繼續運行顯示時間,如果沒有配置過,就初始化RTC,配置RTC的時間,然后顯示。
如果想每次程序運行時都重新配置RTC,則用一個異於寫入的值來做判斷即可。
3. 下載驗證
把程序編譯好下載到開發板,通過電腦端口的串口調試助手或者液晶可以看到時間正常運行。當VDD不斷電的情況下,發生外部引腳復位,時間不會丟失。當VDD斷電或者發生外部引腳復位,VBT有電池供電時,時間不會丟失。當VDD斷電且VBAT也不供電的情況下,時間會丟失,然后根據程序預設的初始時間重新啟動。
43.8 RTC—鬧鍾實驗
在日歷實驗的基礎上,利用RTC的鬧鍾功能制作一個鬧鍾,在每天的[XX小時-XX分鍾-XX秒鍾]產生鬧鍾,然后蜂鳴器響。
43.8.1 硬件設計
硬件設計跟日歷實驗部分的硬件設計一樣。
43.8.2 軟件設計
鬧鍾實驗是在日歷實驗的基礎上添加,相同部分的代碼不再講解,這里只講解鬧鍾相關的代碼,更加具體的請參考鬧鍾實驗的工程源碼。
鬧鍾相關宏定義
代碼 4310 鬧鍾相關宏定義
1 // 鬧鍾相關宏定義
2 #define ALARM_HOURS 1 // 0~23
3 #define ALARM_MINUTES 1 // 0~59
4 #define ALARM_SECONDS 10 // 0~59
5
6 #define ALARM_MASK RTC_AlarmMask_DateWeekDay
7 #define ALARM_DATE_WEEKDAY_SEL RTC_AlarmDateWeekDaySel_WeekDay
8 #define ALARM_DATE_WEEKDAY 2
9 #define RTC_Alarm_X RTC_Alarm_A
為了方便程序移植,我們把需要頻繁修改的代碼用宏封裝起來。如果需要設置鬧鍾時間和鬧鍾的掩碼字段,只需要修改這些宏即可。這些宏對應的是RTC鬧鍾結構體的成員,想知道每個宏的具體含義可參考"RTC鬧鍾結構體講解"小節。
鬧鍾時間字段掩碼ALARM_MASK我們配置為MASK掉日期/星期,即忽略日期/星期,則鬧鍾時間只有時/分/秒有效,即每天到了這個時間鬧鍾都會響。掩碼還有其他取值,用戶可自行修改做實驗。
1. 編程要點
1) 初始化RTC,設置RTC初始時間;
2) 編程鬧鍾,設置鬧鍾時間;
3) 編寫鬧鍾中斷服務函數;
2. 代碼分析
鬧鍾設置函數
代碼 4311 鬧鍾編程代碼
1 /*
2 * 要使能 RTC 鬧鍾中斷,需按照以下順序操作:
3 * 1. 將 EXTI 線 17 配置為中斷模式並將其使能,然后選擇上升沿有效。
4 * 2. 配置 NVIC 中的 RTC_Alarm IRQ 通道並將其使能。
5 * 3. 配置 RTC 以生成 RTC 鬧鍾(鬧鍾 A 或鬧鍾 B)。
6 *
7 *
8 */
9 void RTC_AlarmSet(void)
10 {
11 NVIC_InitTypeDef NVIC_InitStructure;
12 EXTI_InitTypeDef EXTI_InitStructure;
13 RTC_AlarmTypeDef RTC_AlarmStructure;
14
15 /*=============================第①步=============================*/
16 /* RTC 鬧鍾中斷配置 */
17 /* EXTI 配置 */
18 EXTI_ClearITPendingBit(EXTI_Line17);
19 EXTI_InitStructure.EXTI_Line = EXTI_Line17;
20 EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
21 EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
22 EXTI_InitStructure.EXTI_LineCmd = ENABLE;
23 EXTI_Init(&EXTI_InitStructure);
24
25 /*=============================第②步=============================*/
26 /* 使能RTC鬧鍾中斷 */
27 NVIC_InitStructure.NVIC_IRQChannel = RTC_Alarm_IRQn;
28 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
29 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
30 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
31 NVIC_Init(&NVIC_InitStructure);
32
33 /*=============================第③步=============================*/
34 /* 失能鬧鍾,在設置鬧鍾時間的時候必須先失能鬧鍾*/
35 RTC_AlarmCmd(RTC_Alarm_X, DISABLE);
36 /* 設置鬧鍾時間 */
37 RTC_AlarmStructure.RTC_AlarmTime.RTC_H12 = RTC_H12_AMorPM;
38 RTC_AlarmStructure.RTC_AlarmTime.RTC_Hours = ALARM_HOURS;
39 RTC_AlarmStructure.RTC_AlarmTime.RTC_Minutes = ALARM_MINUTES;
40 RTC_AlarmStructure.RTC_AlarmTime.RTC_Seconds = ALARM_SECONDS;
41 RTC_AlarmStructure.RTC_AlarmMask = ALARM_MASK;
42 RTC_AlarmStructure.RTC_AlarmDateWeekDaySel = ALARM_DATE_WEEKDAY_SEL;
43 RTC_AlarmStructure.RTC_AlarmDateWeekDay = ALARM_DATE_WEEKDAY;
44
45
46 /* 配置RTC Alarm X(X=A或B)寄存器 */
47 RTC_SetAlarm(RTC_Format_BINorBCD, RTC_Alarm_X, &RTC_AlarmStructure);
48
49 /* 使能 RTC Alarm X 中斷 */
50 RTC_ITConfig(RTC_IT_ALRA, ENABLE);
51
52 /* 使能鬧鍾 */
53 RTC_AlarmCmd(RTC_Alarm_X, ENABLE);
54
55 /* 清除鬧鍾中斷標志位 */
56 RTC_ClearFlag(RTC_FLAG_ALRAF);
57 /* 清除 EXTI Line 17 懸起位 (內部連接到RTC Alarm) */
58 EXTI_ClearITPendingBit(EXTI_Line17);
59 }
從參考手冊知道,要使能RTC鬧鍾中斷,必須按照三個步驟進行,具體見錯誤!未找到引用源。。RTC_AlarmSet()函數則根據這三個步驟和代碼中的注釋閱讀即可。

圖 435 RTC鬧鍾中斷編程步驟(摘自STM32F4xx 中文參考手冊RTC章節)
在第3步中,配置RTC以生成RTC鬧鍾中,在手冊中也有詳細的步驟說明,編程的時候必須按照這個步驟,具體見錯誤!未找到引用源。。

圖 436RTC鬧鍾編程步驟(摘自STM32F4xx 中文參考手冊RTC章節)
編程鬧鍾的步驟1和2,由固件庫函數RTC_AlarmCmd(RTC_Alarm_X, DISABLE);實現,即在編程鬧鍾寄存器設置鬧鍾時間的時候必須先失能鬧鍾。剩下的兩個步驟配套代碼的注釋閱讀即可。
鬧鍾中斷服務函數
代碼 4312 鬧鍾中斷服務函數
1 // 鬧鍾中斷服務函數
2 void RTC_Alarm_IRQHandler(void)
3 {
4 if (RTC_GetITStatus(RTC_IT_ALRA) != RESET) {
5 RTC_ClearITPendingBit(RTC_IT_ALRA);
6 EXTI_ClearITPendingBit(EXTI_Line17);
7 }
8 /* 鬧鍾時間到,蜂鳴器響 */
9 BEEP_ON;
10 }
如果日歷時間到了鬧鍾設置好的時間,則產生鬧鍾中斷,在中斷函數中把相應的標志位清0。為了表示鬧鍾時間到,我們讓蜂鳴器響。
main函數
代碼 4313 main函數、
1 /**
2 * @brief 主函數
3 * @param 無
4 * @retval 無
5 */
6 int main(void)
7 {
8 /* 初始化LED */
9 LED_GPIO_Config();
10 BEEP_GPIO_Config();
11
12 /* 初始化調試串口,一般為串口1 */
13 Debug_USART_Config();
14 printf("\n\r這是一個RTC鬧鍾實驗 \r\n");
15
16 /*=========================液晶初始化開始============================*/
17 LCD_Init();
18 LCD_LayerInit();
19 LTDC_Cmd(ENABLE);
20
21 /*把背景層刷黑色*/
22 LCD_SetLayer(LCD_BACKGROUND_LAYER);
23 LCD_Clear(LCD_COLOR_BLACK);
24
25 /*初始化后默認使用前景層*/
26 LCD_SetLayer(LCD_FOREGROUND_LAYER);
27 /*默認設置不透明,該函數參數為不透明度,范圍 0-0xff ,0為全透明,0xff為不透明*/
28 LCD_SetTransparency(0xFF);
29 LCD_Clear(LCD_COLOR_BLACK);
30 /*經過LCD_SetLayer(LCD_FOREGROUND_LAYER)函數后,
31 以下液晶操作都在前景層刷新,除非重新調用過LCD_SetLayer函數設置背景層*/
32 LCD_SetColors(LCD_COLOR_RED,LCD_COLOR_BLACK);
33 /*=========================液晶初始化結束===========================*/
34
35 /*
36 * 當我們配置過RTC時間之后就往備份寄存器0寫入一個數據做標記
37 * 所以每次程序重新運行的時候就通過檢測備份寄存器0的值來判斷
38 * RTC 是否已經配置過,如果配置過那就繼續運行,如果沒有配置過
39 * 就初始化RTC,配置RTC的時間。
40 */
41 if (RTC_ReadBackupRegister(RTC_BKP_DRX) != 0X32F3) {
42 /* RTC配置:選擇時鍾源,設置RTC_CLK的分頻系數 */
43 RTC_Config();
44
45 /* 鬧鍾設置 */
46 RTC_AlarmSet();
47
48 /* 設置時間和日期 */
49 RTC_TimeAndDate_Set();
50
51
52 } else {
53 /* 檢查是否電源復位 */
54 if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET) {
55 printf("\r\n發生電源復位....\r\n");
56 }
57 /* 檢查是否外部復位 */
58 else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET) {
59 printf("\r\n發生外部復位....\r\n");
60 }
61
62 printf("\r\n不需要重新配置RTC....\r\n");
63
64 /* 使能 PWR 時鍾 */
65 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
66 /* PWR_CR:DBF置1,使能RTC、RTC備份寄存器和備份SRAM的訪問 */
67 PWR_BackupAccessCmd(ENABLE);
68 /* 等待 RTC APB 寄存器同步 */
69 RTC_WaitForSynchro();
70
71 /* 清除RTC中斷標志位 */
72 RTC_ClearFlag(RTC_FLAG_ALRAF);
73 /* 清除 EXTI Line 17 懸起位 (內部連接到RTC Alarm) */
74 EXTI_ClearITPendingBit(EXTI_Line17);
75 }
76
77 /* 顯示時間和日期 */
78 RTC_TimeAndDate_Show();
79 }
主函數中,我們通過讀取備份寄存器的值來判斷RTC是否初始化過,如果沒有則初識話RTC,並設置鬧鍾時間,如果已經初始化過,則判斷是電源還是外部引腳復位,並清除鬧鍾相關的中斷標志位。
3. 下載驗證
把編譯好的程序下載到開發板,當日歷時間到了鬧鍾時間時,蜂鳴器一直響,但日歷會繼續運行。
43.9 每課一問
1、修改RTC時間的字段掩碼,設置每小時的30分鍾:30秒都產生鬧鍾。
