對於單片機轉ARM的同學來說,RTC可能比較少接觸。提到實時時鍾,更經常想到的是DS1302。當然,在STM32里,自己一個CPU已經足夠,不需要DS1302。
實際上,RTC就只一個定時器而已,掉電之后所有信息都會丟失,因此我們需要找一個地方來存儲這些信息,於是就找到了備份寄存器。因為它掉電后仍然可以通過紐扣電池供電,所以能時刻保存這些數據。我們在本期教程中將詳細講述RTC原理及例程,以引導大家順利進入RTC的世界。
1.STM32的RTC模塊
RTC模塊之所以具有實時時鍾功能,是因為它內部維持了一個獨立的定時器,通過配置,可以讓它准確地每秒鍾中斷一次。下面就來看以下它的組成結構。
1.1RTC的組成
RTC由兩個部分組成:APB1接口部分以及RTC核心部分(感覺說了等於沒說,因為任何模塊都會有接口部分和它自己的核心部分。請注意,權威的STM32系列手冊是這么說的?)。筆者猜想原因可能是STM32所有的外設默認時鍾無效,使用某個外設時,再開啟時鍾,用這樣的方式來降低功耗。這里的RTC,APB1接口由APB1總線時鍾來驅動。為了突出時鍾吧?不過據說APB1接口部分還包括一組16位寄存器。
RTC核心部分又分為預分頻模塊和一個32位的可編程計數器。前者可使每個TR_CLK周期中RTC產生一個秒中斷,后者可被初始化為當前系統時間。此后系統時間會按照TR_CLK周期進行累加,實現時鍾功能。
1.2對RTC的操作
我們對RTC的訪問,是通過APB1接口來進行的。注意,APB1剛被開啟的時候(比如剛上電,或剛復位后),從APB1上讀出來的RTC寄存器的第一個值有可能是被破壞了的(通常讀到0)。這個不幸,STM32是如何預防的呢?我們在程序中,會先等待RTC_CRL寄存器中的RSF位(寄存器同步標志)被硬件置1,然后才開始讀操作,這時候讀出來的值就是OK的。
那么對RTC寄存器的寫操作會不會有類似的情況呢?對於寫操作,我們只要注意,每一次寫操作,必須確保在前一次寫操作完成后進行。這個“確保”,
是通過查詢RTC_CR寄存器中的RTOFF狀態位,判斷RTC寄存器是否處於更新中。只有當RTOFF狀態位是1,才可以寫RTC寄存器。
2.RTC的編程
RTC的例程,主要是設置RTC時鍾,使得其在超級終端上顯示出當前的時鍾。這個時鍾的顯示是“不停地走”。而且掉電后,重新上電,時鍾仍然在走,仍然顯示當前的時間。當然,如果感興趣,您可以讓它在LCD上顯示——那就是一個名副其實的電子鍾了。
編程的時候,首先要注意備份寄存器BKP_DR1,它做了一件關鍵的事情:判斷RTC是否已經被設置過。因為RTC跟其他計時器不同,它是使用紐扣電池單獨供電工作,所以它不會每次上電或者復位都被重置。判斷RTC是否已經被設置過,可以決定當前是否需要去設置RTC。如果剛安裝電池,第一次上電,自然需要去設置。否則的話,我們只要讓它顯示當前時鍾即可。
當第一次使用RTC的時候,可以參考手冊。這里對第一次配置需要做的工作進行了總結:
1、打開電源管理和備份寄存器時鍾。注意,一定要打開備份寄存器的時鍾。我們正是通過在備份寄存器寫固定的數據來判斷芯片是否第一次實用RTC,從而在系統運行RTC時提示配置時鍾的。
2、使能RTC和備份寄存器的訪問(復位默認是關閉的,以防止可能存在的意外的寫操作。)。
3、選擇外部低速晶體為RTC時鍾,並使能時鍾。筆者當初調試RTC的時候,犯了一個低級錯誤:由於沒有定義如下:#defineRTCClockSource_LSE
導致程序一直停留在這里:/*WaittillLSEisready*/
while(RCC_GetFlagStatus(RCC_FLAG_LSERDY)==RESET)
{
}
希望大家看完本教程后,能避免這個錯誤。
4、使能秒中斷,程序里在秒中斷里置位標志位來通知主程序顯示時間數據,同時在32位計數器到23:59:59時清零;
5、設置RTC預分頻器值產生1秒信號計算公式fTR_CLK=fRTCCLK/(PRL+1),我們設置32767來產生秒信號。
我們再次強調:所有在對RTC寄存器操作之前都要判斷讀寫操作是否完成,即內部是否有讀寫操作。
下面來看代碼:/*SystemClocksConfiguration*/RCC_Configuration();
/*NVICconfiguration*/
NVIC_Configuration();/*ConfiguretheGPIOs*/GPIO_Configuration();
/*ConfiguretheUSART1*/
USART_Configuration();
以上四個函數調用,雖然最平常不過,但是還是要引起大家的關注。特別是中斷NVIC_Configuration();以及USART_Configuration();,希望大家仔細查閱具體的函數實現。
與本期教程有關系第一個要點,就是時鍾,為避免遺漏,筆者將其代碼放在第一位:RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR|RCC_APB1Periph_PWR,ENABLE);
接着我們讀取備份寄存器BKP_DR1中的值來判斷是否是第一次上電,如果不是則直接顯示時鍾,否則進行時間設置。當BKP_DR1的值不為0xAAAA,說明是第一次上電,此時需要對RTC進行初始化。注意初始化的實現函數RTC_Configuration();,為什么那么寫,請參考我們之前給出的“第一次使用RTC的配置工作總結”,然后進行時鍾設置。注意,因為我們需要進行寫操作,所以根據固件庫手冊,要先調用RTC_WaitForLastTask(),等待標志位RTOFF被設置,保證在前一次寫操作結束后才能進行。調用RTC_SetCounter(Time_Regulate());,將計數值寫入RTC計數器。
由於后面要通過BKP_WriteBackupRegister()函數對BKP_DR1寫操作,因此之前還需要進行一次RTC_WaitForLastTask(),這樣,對時間的設置就完成了。剩下的代碼,比較簡單,主要是注意如下:
RTCCount=RTC_GetCounter();//獲得計數值並計算當前時鍾/*Computehours*/
THH=RTCCount/3600;/*Computeminutes*/
TMM=(RTCCount%3600)/60;/*Computeseconds*/
TSS=(RTCCount%3600)%60;
這是通過RTC_GetCounter();函數獲取計數值,然后把這個計數值分別用小時、分鍾、秒來表示的過程。最后還需要調用printf函數把它顯示出來。
以上就是整個RTC的過程,期待大家拍磚。如需拍磚,請直接前往猛拍,以促進芯達STM32進一步改善教程,謝謝!