本文介绍如何使用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小时的时差,这样程序处理起来更为简单且不易出错。

2.1 读取

读取操作就是将计数器的值读取出来,调用库函数RTC_GetCounter()即可返回当前计数器的值。

2.2 写入

写入操作就是更新当前计数器的值,调用库函数RTC_SetCounter()即可更新当前计数器的值,注意调用RTC_SetCounter()完毕后需要调用RTC_WaitForLastTask()等待RTC寄存器写操作完成。

2.3 时间转换

因为通过查看计数器的值无法直观得知当前的时间信息,因此需要时间转换函数将计数器值与年月日时分秒值进行互相转换。

C语言标准库函数中已经包含了这样的函数,mktime()函数可以将年月日时分秒转换为UNIX时间戳,localtime()函数可以将UNIX时间戳转换为年月日时分秒。除此之外,ctime()函数和asctime()函数还可将UNIX时间戳和存储的年月日时分秒结构体转化为可显示的字符串,可以直接用来打印输出。这些函数包含在<time.h>中。

2.4 秒中断函数

RTC可以设置一个秒中断函数,该函数每秒中断一次,对应的中断服务函数为RTC_IRQHandler(),该函数执行时可以将当前计数器值读取出来,用作程序其他部分更新时间用,因为计数器的累计是自动进行的,因此无需在此函数中累加计数器值。

完整代码(仅自己编写的部分)

1 #include "RTC.h"   
  2 #include "delay.h"
  3 
  4 #include <stdio.h>
  5 #include <time.h>
  6 #include <string.h>
  7 
  8 #define BKP_VALUE    0x5A5A
  9 
 10 struct tm calendar;    
 11 uint32_t RTC_counter;        //RTC计数值
 12 
 13 void RTC_NVIC_Config(void)
 14 {
 15     NVIC_InitTypeDef NVIC_InitStructure;
 16 
 17     /* 嵌套向量中断控制器组选择 */
 18     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
 19     NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn;
 20     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
 21     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
 22     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
 23     NVIC_Init(&NVIC_InitStructure);
 24 }
 25 
 26 /*
 27     设置时间
 28         把输入的时间存入RTC计数器
 29         1970~2099年为合法年份
 30     返回值:
 31         0:成功
 32         其他:失败
 33 */
 34 void RTC_Get(void)
 35 {
 36     struct tm * ptm;
 37     
 38     RTC_counter = RTC_GetCounter();
 39     
 40     ptm = localtime(&RTC_counter);
 41     
 42     memcpy(&calendar, ptm, sizeof(calendar));
 43 }
 44 
 45 
 46 /*
 47     设置时间
 48         把输入的时间存入RTC计数器
 49         1970~2099年为合法年份
 50     返回值:
 51         0:成功
 52         其他:失败
 53 */
 54 uint8_t RTC_Set(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t min, uint8_t sec)
 55 {
 56     time_t unixTime;
 57     
 58     if((year < 1970) || (year > 2099))        return 1;
 59     
 60     calendar.tm_sec = sec;
 61     calendar.tm_min = min;
 62     calendar.tm_hour = hour;
 63     calendar.tm_mday = day;
 64     calendar.tm_mon = month - 1;
 65     calendar.tm_year = year - 1900;
 66     
 67     unixTime = mktime(&calendar);
 68 
 69     RTC_SetCounter(unixTime);    //设置RTC计数器的值
 70     RTC_WaitForLastTask();        //等待对RTC寄存器的写操作完成      
 71 
 72     return 0;        
 73 }
 74 
 75 /*
 76     实时时钟配置
 77     初始化RTC时钟
 78     BKP->DR1用于保存是否第一次配置的设置
 79     
 80     返回值
 81         0:正常
 82         其他:失败
 83 */
 84 uint8_t RTC_Init(void)
 85 {
 86     uint8_t temp = 0;
 87     
 88     RTC_NVIC_Config();            //RTC中断分组设置
 89 
 90     RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);    //使能PWR和BKP外设时钟
 91     PWR_BackupAccessCmd(ENABLE);            //使能后备寄存器访问
 92     
 93     //检查是不是第一次配置时钟
 94     if (BKP_ReadBackupRegister(BKP_DR1) != BKP_VALUE){        //从指定的后备寄存器中读出数据
 95         BKP_DeInit();                    //复位备份区域     
 96         
 97         RCC_LSEConfig(RCC_LSE_ON);        //设置外部低速晶振(LSE),使用外设低速晶振
 98         while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET)    //检查指定的RCC标志位设置与否,等待低速晶振就绪
 99         {
100             if(temp++ >= 250){
101                 return 1;                //初始化时钟失败,晶振有问题        
102             }
103             delay_ms(10);
104         }
105         RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);        //设置RTC时钟(RTCCLK),选择LSE作为RTC时钟    
106         RCC_RTCCLKCmd(ENABLE);        //使能RTC时钟  
107         RTC_WaitForLastTask();        //等待对RTC寄存器的写操作完成
108         RTC_WaitForSynchro();        //等待RTC寄存器同步  
109         
110         RTC_SetPrescaler(32767);     //设置RTC预分频的值
111         RTC_WaitForLastTask();        //等待对RTC寄存器的写操作完成
112         
113         RTC_Set(2020, 8, 24, 16, 30, 10);  //设置时间    
114         
115         BKP_WriteBackupRegister(BKP_DR1, BKP_VALUE);    //向指定的后备寄存器中写入用户程序数据
116     }else{                        //系统继续计时
117         RTC_WaitForSynchro();    //等待RTC寄存器同步
118     }
119 
120     PWR_BackupAccessCmd(DISABLE);            //禁用后备寄存器访问
121 
122     RTC_ITConfig(RTC_IT_SEC, ENABLE);        //使能RTC秒中断
123     RTC_WaitForLastTask();                    //等待对RTC寄存器的写操作完成
124     
125     RTC_Get();                    //更新时间    
126     
127     return 0;
128 }                             
129 
130 //RTC时钟中断,每秒触发一次,更新一次时间 
131 void RTC_IRQHandler(void)
132 {         
133     if (RTC_GetITStatus(RTC_IT_SEC) != RESET)    //秒钟中断
134     {                            
135         RTC_ClearITPendingBit(RTC_IT_SEC);        //清秒钟中断
136         RTC_WaitForLastTask();                      //等待对RTC寄存器的写操作完成                                         
137         RTC_Get();                                //更新时间   
138      }
139 }
140 
141 void RTC_display(void)
142 {
143     printf("current time:%d/%d/%d %d:%d:%d\n", 
144         calendar.tm_year + 1900, calendar.tm_mon + 1, calendar.tm_mday, 
145         calendar.tm_hour, calendar.tm_min, calendar.tm_sec);
146     printf("ctime:%s", ctime(&RTC_counter));
147     printf("asctime:%s", asctime(&calendar));
148     
149 }