9.1實驗內容
通過本實驗主要學習以下內容:
- RTC簡介
- RTC復位
- RTC實現萬年歷
- RTC使用注意事項
9.2實驗原理
9.2.1RTC簡介
RTC(Real Time Clock)——實時時鐘定時器,可以用作日歷。RTC電路分兩個電源域部分,其一位于備份域中,該部分包括一個32位的累加計數器、一個鬧鐘、一個預分頻器、一個分頻器以及RTC時鐘配置寄存器。備份域這部分電路不會因為系統復位或者MCU進入低功耗而丟失數據,所以在系統復位或MCU從低功耗下喚醒,RTC的設置和時間都可以保持不變。另一部分位于VDD電源域中,該部分只包括APB接口以及一組控制寄存器。
關于RTC的兩個電源域及分布在兩個電源域中的內容,需要讀者牢記,否則會由于細節沒處理好導致RTC工作異常。 |
以下為GD32F303的RTC框圖:
圖中RTC_CNT為計數值,每個SC_CLK時鐘到來時,這個計數值增加+1。SC_CLK時鐘源有三個:LXTAL(外部低速晶振)、IRC40K(內部40K晶振)和HXTAL/128,三個時鐘源經過RTC_DIV產生SC_CLK。RTC_DIV的配置是通過RTC_PSC加載所得,RTC_PSC由寄存器RTC預分頻寄存器高位(RTC_PSCH)和RTC預分頻寄存器低位(RTC_PSCL)配置,有效位20bit,即RTC的時鐘分頻器最大值為2的20次方,完全足夠應用了。
了解了上面的內容,RTC的工作原理就很好理解了。舉個例子,RTC的時鐘源選擇LXTAL,頻率為32.768KHz,然后設置RTC_PSC為32768,那么SC_CLK即為1Hz,也就是說,每秒鐘RTC_CNT值+1,所以我們只要獲得一段時間內RTC_CNT值增加了多少數,也就知道經過了多少秒。
我們從手冊中可以看到RTC_CNT由RTC計數寄存器高位(RTC_CNTH)和RTC計數寄存器低位(RTC_CNTL)設置,這兩個寄存器組合起來的有效位為32bit,即RTC_CNT可以記錄2的32次方,即4,294,967,296個數,按照每秒增加一次的話,可以記錄136多年,
除了基礎的記時間的功能,RTC還有一個鬧鐘功能,RTC運行時,當RTC_CNT的值增加到和RTC_ALRM(由RTC鬧鐘寄存器高位(RTC_ALRMH)和RTC鬧鐘寄存器低位(RTC_ALRML)設置)相等時,則會產生ALRM中斷,當然,程序中需要實現使能ALARM中斷(ALRMIE)。
RTC還有另外兩個中斷,一個是秒中斷,另一個是溢出中斷。秒中斷好理解,即每秒鐘進入一個中斷;溢出中斷則是當RTC_CNT溢出時產生中斷。
9.2.2RTC復位
這里把RTC的復位單獨作為一個章節來說,是因為這里很容易出錯導致想要實現的功能無法實現。
前面說過,RTC分為兩個電源域——備份域和VDD電源域,而一般的復位比如NRST腳復位、軟件復位等只能復位VDD和VDDA電源域 ,而無法復位備份域;備份域復位需要VBAT掉電或者通過備份域控制寄存器(RCU_BDCTL)的BKPDRST來進行備份域復位。
上節中的RTC框圖中被深色框住的即屬于備份域,這里提到一個很容易出錯的地方,即RTC的時鐘源選擇。從框圖看到時鐘源由RTCSRC[1:0]來設置,這個位域屬于備份域控制寄存器(RCU_BDCTL)。
備份域控制寄存器( RCU_BDCTL):
寄存器描述中指出,一旦RTC的時鐘源選擇后,除了將備份域復位,否則時鐘不能被改變。舉個例子:一個產品選擇LXTAL作為RTC時鐘,但可能因為某些原因LXTAL停振了,需要將時鐘源切換到IRC40K,程序如何實現呢?沒錯,需要復位備份域(控制BKPDRST位)才能重新選擇時鐘源,但一旦備份域進行了復位,包括RTC_CNT等數據都會丟失,所以在備份域復位前需要對RTC內的各個數據進行保存處理,待備份域復位后再重新寫入。
9.2.3RTC實現萬年歷
本實驗用RTC做一個萬年歷,其中還需要考慮到閏年閏月的情況。實驗設置的基準時間是1970年,即當RTC_CNT為0時,為1970年,實驗最高可記錄到2106年(1970+136)。
9.3硬件設計
本實驗實現每秒鐘通過串口打印實時時間,即需要使用到開發板USB串口模塊。另外RTC時鐘源使用的是外部低速晶振,外部晶振原理圖如下:
9.4代碼解析
9.4.1RTC配置
在driver_rtc.c中定義了RTC配置函數rtc_configuration
C void rtc_configuration(void) { /*使能備份域和PMU時鐘*/ rcu_periph_clock_enable(RCU_BKPI); rcu_periph_clock_enable(RCU_PMU); /*使能備份域寫功能*/ pmu_backup_write_enable(); /*備份域復位*/ bkp_deinit(); /*依據選擇的時鐘源進行時鐘配置*/ /*使用LXTAL*/ #ifdef RTC_CLOCK_SOURCE_LXTAL /* 使能 LXTAL */ rcu_osci_on(RCU_LXTAL); /* 等待LXTAL Ready */ rcu_osci_stab_wait(RCU_LXTAL); /*選擇LXTAL作為RTC時鐘*/ rcu_rtc_clock_config(RCU_RTCSRC_LXTAL); #endif /*使用IRC40K*/ #ifdef RTC_CLOCK_SOURCE_IRC40K /* 使能 IRC40K*/ rcu_osci_on(RCU_IRC40K); /* 等待IRC40K Ready */ rcu_osci_stab_wait(RCU_IRC40K); /*選擇IRC40K作為RTC時鐘*/ rcu_rtc_clock_config(RCU_RTCSRC_IRC40K); #endif #ifdef RTC_CLOCK_SOURCE_HXTAL_DIV_128 /* 使能LXTAL */ rcu_osci_on(RCU_HXTAL); /* 等待HXTAL Ready */ rcu_osci_stab_wait(RCU_HXTAL); /*選擇HXTAL/128作為RTC時鐘*/ rcu_rtc_clock_config(RCU_RTCSRC_HXTAL_DIV_128); #endif /*使能RTC時鐘*/ rcu_periph_clock_enable(RCU_RTC); /*等待RTC寄存器同步*/ rtc_register_sync_wait(); /*等待上一次操作完成*/ rtc_lwoff_wait(); /*使能RTC秒中斷*/ rtc_interrupt_enable(RTC_INT_SECOND); /*等待上一次操作完成*/ rtc_lwoff_wait(); /*依據選擇的時鐘源來設置分頻,使RTC周期為1s*/ #ifdef RTC_CLOCK_SOURCE_LXTAL rtc_prescaler_set(32767); #endif #ifdef RTC_CLOCK_SOURCE_IRC40K rtc_prescaler_set(40000); #endif #ifdef RTC_CLOCK_SOURCE_HXTAL_DIV_128 rtc_prescaler_set(HXTAL_VALUE/128); #endif /*等待上一次操作完成*/ rtc_lwoff_wait(); } |
時鐘源通過driver_rtc.h中的宏定義來選擇:
C #define RTC_CLOCK_SOURCE_LXTAL //#define RTC_CLOCK_SOURCE_IRC40K //#define RTC_CLOCK_SOURCE_HXTAL_DIV_128 |
9.4.2萬年歷實現
在bsp_rtc.c中定義了實現萬年歷的幾個函數:rtc_time_set、rtc_time_display、is_leap_year等。
rtc_time_set函數——第一次需要手動設置當前時間:
C uint32_t rtc_time_set(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) { uint16_t t; uint32_t seccount = 0; if (bkp_read_data(BKP_DATA_0) != 0xA5A5) { rtc_configuration(); if(year < 1970 || year > 2099) return 1; for(t = 1970; t < year; t++){ if(is_leap_year(t)){ seccount += 31622400; }else{ seccount += 31536000; } } month -= 1; for(t=0; t < month; t++){ seccount += (uint32_t)month_table[t] * 86400; if(is_leap_year(year) && t==1){ seccount+=86400; } } seccount += (uint32_t)(day-1) * 86400; seccount += (uint32_t)hour * 3600; seccount += (uint32_t)minute * 60; seccount += second; rtc_counter_set(seccount); bkp_write_data(BKP_DATA_0, 0xA5A5); return 0; } else { if (rcu_flag_get(RCU_FLAG_PORRST) != RESET){ printf("Power On Reset occurred....\r\n"); }else if (rcu_flag_get(RCU_FLAG_EPRST) != RESET){ printf("External Reset occurred....\r\n"); } rcu_all_reset_flag_clear(); rcu_periph_clock_enable(RCU_PMU); pmu_backup_write_enable(); rtc_register_sync_wait(); rtc_interrupt_enable(RTC_INT_SECOND); rtc_lwoff_wait(); return 0; } } |
該函數中在第一次上電運行時,會進行初始時間設置,然后寫入特定數據到備份域數據寄存器中。當發生系統復位但Vbat未掉電的情況下,則不會重新進行時間設置,但需要重新開啟秒時鐘中斷,因為SCIE處于VDD電源域而不在備份域。
rtc_time_display函數——打印實時時間:
C void rtc_time_display(uint32_t timevar) { static uint16_t daycnt = 0; uint32_t temp = 0; uint16_t temp1 = 0; temp = timevar / 86400; if(daycnt != temp) { daycnt = temp; temp1 = 1970; while(temp >= 365){ if(is_leap_year(temp1)){ if(temp >= 366) temp-=366; else break; }else temp -= 365; temp1++; } calendar.years = temp1; temp1=0; while(temp >= 28) { if(is_leap_year(calendar.years) && temp1 == 1){ if(temp >= 29) temp -= 29; else break; }else{ if(temp >= month_table[temp1]) temp -= month_table[temp1]; else break; } temp1++; } calendar.months = temp1 + 1; calendar.days = temp + 1; } temp = timevar % 86400; calendar.hours = temp / 3600; calendar.minutes = (temp % 3600) / 60; calendar.seconds = (temp % 3600) % 60; printf("Time: %0.4d-%0.2d-%0.2d,%0.2d:%0.2d:%0.2d\r\n", calendar.years, calendar.months, calendar.days, calendar.hours, calendar.minutes, calendar.seconds); } |
is_leap_year函數——判斷當前年是否為閏年:
C uint8_t is_leap_year(uint16_t year) { if((year%4 == 0 && year % 100 != 0) || (year % 400 == 0)){ return 1; }else{ return 0; } } |
9.4.3main函數和中斷函數實現
以下為main函數代碼:
C int main(void) { delay_init();//delay函數初始化 bsp_uart_init(&BOARD_UART);//BOARD_UART串口初始化 nvic_irq_enable(RTC_IRQn,1,0);//打開RTC的NVIC rtc_time_set(year_set,month_set,day_set,hour_set,minute_set,second_set); //設置當前時間 while (1){ /* 判斷是否到1s了*/ if (timedisplay == 1){ /* 顯示實時時間*/ rtc_time_display(rtc_counter_get()); timedisplay = 0; } } } |
本例程main函數首先進行了延時函數初始化,再初始化開發板USB串口,開啟RTC的NVIC后設置了當前時間,在while(1)循環中等待RTC數據更新,然后將實時時間打印出來。
中斷函數代碼:
C void RTC_IRQHandler(void) { if (rtc_flag_get(RTC_FLAG_SECOND) != RESET){ /* 清除RTC秒中斷標志位*/ rtc_flag_clear(RTC_FLAG_SECOND); timedisplay = 1; /* 等待上一次操作完成 */ rtc_lwoff_wait(); } } |
9.5實驗結果
為了驗證萬年歷是否工作正常,設定初始時間為2022年12月31日23時59分50s,當程序開始運行時,每秒鐘打印時間,經過10s后,可以看到時間變為2023年1月1日0時0分0秒,說明萬年歷有效。
-
單片機
+關注
關注
6035文章
44554瀏覽量
634634 -
開發板
+關注
關注
25文章
5032瀏覽量
97371 -
萬年歷
+關注
關注
3文章
186瀏覽量
23921 -
RTC
+關注
關注
2文章
538瀏覽量
66461
發布評論請先 登錄
相關推薦
評論