STM32之RTC時鍾


本文介紹如何使用STM32標准外設庫驅動實時時鍾RTC。

實時時鍾RTC(Real Time Clock),是一個掉電后還能繼續運行的定時器,一般用來運行時鍾,掉電后需要額外的電池對RTC電路供電,電池正極接入V­BAT引腳,主電源VDD掉電后,電池通過V­BAT給RTC電路供電,使得時鍾可以繼續運行,確保設備重新上電時,時鍾不丟失。

本文適合對單片機及C語言有一定基礎的開發人員閱讀,MCU使用STM32F103VE系列。

 

RTC分為兩部分,初始化和操作。

1.    初始化

初始化分兩步:通用中斷、RTC

1.1. 通用中斷:優先級分組、中斷源、優先級、使能

  • 優先級分組:設定合適的優先級分組
  • 中斷源:選擇指定的中斷源:RTC_IRQn
  • 優先級:設定合適的優先級,一般實時時鍾優先級可以設的高一些。
  • 使能:調用庫函數即可。

1.2. RTC

分為兩種情況:一種是第一次配置RTC,另外一種是配置好之后的重新上電之后的初始化。

1.2.1. 第一次配置RTC

  1) 使能外設時鍾(PWR和BKP)

  2) 使能后備寄存器訪問

  3) 復位備份區域

  4) 設置低速外部時鍾信號LSE使用外部低速晶振

  5) 等待低速外部時鍾信號LSE就緒

  6) 設置RTC使用低速外部時鍾信號LSE

  7) 使能RTC時鍾

  8) 等待RTC寄存器寫操作完成

  9) 等待RTC寄存器同步完成

  10) 設置RTC預分頻值

  11) 等待RTC寄存器寫操作完成

  12) 設置一個時鍾初始日期時間

  13) 給指定的后備寄存器寫入一個特定值

  14) 使能RTC秒中斷

  15) 等待RTC寄存器寫操作完成

1.2.2. 配置好之后的重新上電之后的初始化

  1) 使能外設時鍾(PWR和BKP)

  2) 使能后備寄存器訪問

  3) 等待RTC寄存器同步完成

  4) 使能RTC秒中斷

  5) 等待RTC寄存器寫操作完成

2.    操作

操作主要分為讀取、設置、時間轉換和秒中斷函數。

RTC是一個每秒加1的計數器,其計數器的值表示了當前的時間,該計數器是一個32位的寄存器,其最大值為232,約為136年,該計數器值又被稱作UNIX時間戳,而計數器為0時代表的時間為1970年1月1日0時0分0秒,又被稱作UNIX時間元年,時間每過1秒,該計數器值加1。舉例來說,2000年1月1日0時0分0秒對應的計數器值為946,684,800。但要注意的是,UNIX時間戳為格林威治時間,比北京時間晚8小時。因此該值對應的北京時間為2000年1月1日8時0分0秒。如果認定0代表北京時間1970年1月1日0時0分0秒,那么就不必考慮8小時的時差,這樣程序處理起來更為簡單且不易出錯。

讀取操作就是將計數器的值讀取出來,調用庫函數RTC_GetCounter()即可返回當前計數器的值。

寫入操作就是更新當前計數器的值,調用庫函數RTC_SetCounter()即可更新當前計數器的值,注意調用RTC_SetCounter()完畢后需要調用RTC_WaitForLastTask()等待RTC寄存器寫操作完成。

因為通過查看計數器的值無法直觀得知當前的時間信息,因此需要時間轉換函數將計數器值與年月日時分秒值進行互相轉換。一個是將當前計數器的值轉換為年月日時分秒值,對應的函數為RTC_Convert ();另一個是將年月日時分秒值轉換為計數器值,對應的函數為RTC_Set(),該函數也包含了寫入計數器值操作。

RTC可以設置一個秒中斷函數,該函數每秒中斷一次,對應的中斷服務函數為RTC_IRQHandler(),該函數執行時可以將當前計數器值讀取出來,用作程序其他部分更新時間用,因為計數器的累計是自動進行的,因此無需在此函數中累加計數器值。

完整代碼(僅自己編寫的部分)

  1 #include "RTC.h"   
  2 #include "delay.h"
  3 
  4 #include <stdio.h>
  5 
  6 #define BKP_VALUE    0x5A5A
  7 
  8 Calendar_s calendar;    //日歷結構體
  9 vu32 rtcCounter;        //RTC計數值
 10 
 11 void RTC_NVIC_Config(void)
 12 {
 13     NVIC_InitTypeDef NVIC_InitStructure;
 14 
 15     /* 嵌套向量中斷控制器組選擇 */
 16     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
 17     NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
 18     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
 19     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
 20     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
 21     NVIC_Init(&NVIC_InitStructure);
 22 }
 23 
 24 //判斷是否是閏年函數
 25 //輸入:年份
 26 //輸出:該年份是不是閏年.1,是.0,不是
 27 uint8_t isLeapYear(uint16_t year)
 28 {              
 29     
 30     if(year % 4 == 0) //必須能被4整除
 31     { 
 32         if(year % 100 == 0) 
 33         { 
 34             if(year % 400 == 0){
 35                 return 1;//如果以00結尾,還要能被400整除        
 36             }else{
 37                 return 0;   
 38             }
 39         }else{ 
 40             return 1;   
 41         }
 42     }else{
 43         return 0;    
 44     }
 45 }
 46 
 47 //月份數據表                                             
 48 uint8_t const table_week[12]={0, 3, 3, 6, 1, 4, 6, 2, 5, 0, 3, 5}; //月修正數據表      
 49 //平年的月份日期表
 50 const uint8_t mon_table[12]={31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
 51 
 52 //獲得現在是星期幾
 53 //功能描述:輸入公歷日期得到星期(只允許1901-2099年)
 54 //輸入參數:公歷年月日 
 55 //返回值:星期號                                                                                         
 56 uint8_t RTC_GetWeek(uint16_t year, uint8_t month, uint8_t day)
 57 {    
 58     uint16_t temp2;
 59     uint8_t yearH, yearL;
 60     
 61     yearH = year / 100;    
 62     yearL = year % 100; 
 63     // 如果為21世紀,年份數加100  
 64     if(yearH > 19){
 65         yearL += 100;
 66     }
 67     // 所過閏年數只算1900年之后的  
 68     temp2 = yearL + yearL / 4;
 69     temp2 = temp2 % 7; 
 70     temp2 = temp2 + day + table_week[month - 1];
 71     
 72     if((yearL % 4 == 0) && (month < 3)){
 73         temp2--;
 74     }
 75     
 76     return temp2 % 7;
 77 }        
 78 
 79 //得到當前的時間
 80 //返回值:0,成功;其他:錯誤代碼.
 81 uint8_t RTC_Convert(Calendar_s * pCalendar)
 82 {
 83     static uint16_t daycnt = 0;
 84     uint32_t timecount = 0; 
 85     uint32_t temp = 0;
 86     uint16_t temp1 = 0;      
 87     
 88     if(pCalendar == NULL){
 89         return 1;
 90     }
 91     
 92     timecount = rtcCounter;
 93     
 94      temp = timecount / 86400;   //得到天數(一天對應的秒數)
 95     
 96     if(daycnt != temp)    //超過一天了
 97     {      
 98         daycnt = temp;
 99         temp1 = 1970;    //從1970年開始
100         while(temp >= 365)
101         {                 
102             if(isLeapYear(temp1))    //是閏年
103             {
104                 if(temp >= 366){
105                     temp -= 366;    //閏年的天數
106                 }else{
107                     temp1++;
108                     break;
109                 }  
110             }else{
111                 temp -= 365;      //平年 
112             }
113             temp1++;  
114         }   
115         pCalendar->year = temp1;//得到年份
116         
117         temp1 = 0;
118         while(temp >= 28)        //超過了一個月
119         {
120             if(isLeapYear(pCalendar->year) && (temp1 == 1)){        //當年是不是閏年/2月份
121                 if(temp >= 29){
122                     temp -= 29;        //閏年的秒鍾數
123                 }else{
124                     break; 
125                 }
126             }else{
127                 if(temp >= mon_table[temp1]){
128                     temp -= mon_table[temp1];    //平年
129                 }else{
130                     break;
131                 }
132             }
133             temp1++;  
134         }
135         pCalendar->month = temp1 + 1;    //得到月份
136         pCalendar->day = temp + 1;      //得到日期 
137     }
138     temp = timecount % 86400;             //得到秒鍾數          
139     pCalendar->hour = temp / 3600;         //小時
140     pCalendar->min = (temp % 3600) / 60;     //分鍾    
141     pCalendar->sec = (temp % 3600) % 60;     //秒鍾
142     pCalendar->week = RTC_GetWeek(pCalendar->year, pCalendar->month, pCalendar->day);    //獲取星期   
143     
144     return 0;
145 }     
146 
147 //得到當前的時間
148 //返回值:0,成功;其他:錯誤代碼.
149 void RTC_Get(void)
150 {
151     static uint16_t daycnt = 0;
152     uint32_t timecount = 0; 
153     uint32_t temp = 0;
154     uint16_t temp1 = 0;      
155     
156     timecount = RTC_GetCounter();     
157      temp = timecount / 86400;   //得到天數(一天對應的秒數)
158     
159     if(daycnt != temp)    //超過一天了
160     {      
161         daycnt = temp;
162         temp1 = 1970;    //從1970年開始
163         while(temp >= 365)
164         {                 
165             if(isLeapYear(temp1))    //是閏年
166             {
167                 if(temp >= 366){
168                     temp -= 366;    //閏年的天數
169                 }else{
170                     temp1++;
171                     break;
172                 }  
173             }else{
174                 temp -= 365;      //平年 
175             }
176             temp1++;  
177         }   
178         calendar.year = temp1;//得到年份
179         
180         temp1 = 0;
181         while(temp >= 28)        //超過了一個月
182         {
183             if(isLeapYear(calendar.year) && (temp1 == 1)){        //當年是不是閏年/2月份
184                 if(temp >= 29){
185                     temp -= 29;        //閏年的秒鍾數
186                 }else{
187                     break; 
188                 }
189             }else{
190                 if(temp >= mon_table[temp1]){
191                     temp -= mon_table[temp1];    //平年
192                 }else{
193                     break;
194                 }
195             }
196             temp1++;  
197         }
198         calendar.month = temp1 + 1;    //得到月份
199         calendar.day = temp + 1;      //得到日期 
200     }
201     temp = timecount % 86400;             //得到秒鍾數          
202     calendar.hour = temp / 3600;         //小時
203     calendar.min = (temp % 3600) / 60;     //分鍾    
204     calendar.sec = (temp % 3600) % 60;     //秒鍾
205     calendar.week = RTC_GetWeek(calendar.year, calendar.month, calendar.day);    //獲取星期   
206 }     
207 
208 //設置時鍾
209 //把輸入的時鍾轉換為秒鍾
210 //以1970年1月1日為基准
211 //1970~2099年為合法年份
212 //返回值:0,成功;其他:錯誤代碼.
213 uint8_t RTC_Set(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t min, uint8_t sec)
214 {
215     uint16_t i;
216     uint32_t seccount=0;
217     
218     if((year < 1970) || (year > 2099)){
219         return 1;
220     }
221 
222     for(i = 1970; i < year;i++)    //把所有年份的秒鍾相加
223     {
224         if(isLeapYear(i)){
225             seccount += 31622400;//閏年的秒鍾數
226         }else{
227             seccount += 31536000;              //平年的秒鍾數
228         }
229     }
230     month -= 1;
231     for(i = 0; i < month; i++)       //把前面月份的秒鍾數相加
232     {
233         seccount += (uint32_t)mon_table[i] * 86400;        //月份秒鍾數相加
234         if(isLeapYear(year) && (i == 1)){
235             seccount += 86400;            //閏年2月份增加一天的秒鍾數       
236         }
237     }
238     seccount += (uint32_t)(day - 1) * 86400;//把前面日期的秒鍾數相加 
239     seccount += (uint32_t)hour * 3600;        //小時秒鍾數
240     seccount += (uint32_t)min * 60;             //分鍾秒鍾數
241     seccount += sec;                        //最后的秒鍾加上去
242 
243     RTC_SetCounter(seccount);    //設置RTC計數器的值
244     RTC_WaitForLastTask();        //等待最近一次對RTC寄存器的寫操作完成      
245 
246     return 0;        
247 }
248 
249 //實時時鍾配置
250 //初始化RTC時鍾
251 //BKP->DR1用於保存是否第一次配置的設置
252 //返回0:正常
253 //其他:錯誤代碼
254 uint8_t RTC_Init(void)
255 {
256     uint8_t temp = 0;
257     
258     RTC_NVIC_Config();            //RCT中斷分組設置                                 
259 
260     RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);    //使能PWR和BKP外設時鍾   
261     PWR_BackupAccessCmd(ENABLE);            //使能后備寄存器訪問  
262     
263     //檢查是不是第一次配置時鍾
264     if (BKP_ReadBackupRegister(BKP_DR1) != BKP_VALUE){        //從指定的后備寄存器中讀出數據
265         BKP_DeInit();                    //復位備份區域     
266         
267         RCC_LSEConfig(RCC_LSE_ON);        //設置外部低速晶振(LSE),使用外設低速晶振
268         while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)    //檢查指定的RCC標志位設置與否,等待低速晶振就緒
269         {
270             if(temp++ >= 250){
271                 return 1;                //初始化時鍾失敗,晶振有問題        
272             }
273             delay_ms(10);
274         }
275         RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);        //設置RTC時鍾(RTCCLK),選擇LSE作為RTC時鍾    
276         
277         RCC_RTCCLKCmd(ENABLE);        //使能RTC時鍾  
278         RTC_WaitForLastTask();        //等待最近一次對RTC寄存器的寫操作完成
279         RTC_WaitForSynchro();        //等待RTC寄存器同步  
280         
281         RTC_SetPrescaler(32767); //設置RTC預分頻的值
282         RTC_WaitForLastTask();    //等待最近一次對RTC寄存器的寫操作完成
283         
284         RTC_Set(2020, 8, 24, 16, 30, 10);  //設置時間    
285         
286         BKP_WriteBackupRegister(BKP_DR1, BKP_VALUE);    //向指定的后備寄存器中寫入用戶程序數據
287     }else{                        //系統繼續計時
288         RTC_WaitForSynchro();    //等待最近一次對RTC寄存器的寫操作完成
289     }
290 
291     RTC_ITConfig(RTC_IT_SEC, ENABLE);        //使能RTC秒中斷
292     RTC_WaitForLastTask();        //等待最近一次對RTC寄存器的寫操作完成
293     
294     RTC_Get();                    //更新時間    
295     
296     return 0;
297 }                             
298 
299 //RTC時鍾中斷,每秒觸發一次,更新一次時間 
300 void RTC_IRQHandler(void)
301 {         
302     if (RTC_GetITStatus(RTC_IT_SEC) != RESET)    //秒鍾中斷
303     {                            
304         RTC_ClearITPendingBit(RTC_IT_SEC);        //清秒鍾中斷
305         RTC_WaitForLastTask();                                                   
306 //        RTC_Get();                                //更新時間   
307         rtcCounter = RTC_GetCounter();     
308      }
309 }

 

源碼下載:(不包括工程文件和庫文件)

https://files.cnblogs.com/files/greatpumpkin/RTC.rar


免責聲明!

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



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