本文介绍如何使用STM32标准外设库驱动实时时钟RTC。
实时时钟RTC(Real Time Clock),是一个掉电后还能继续运行的定时器,一般用来运行时钟,掉电后需要额外的电池对RTC电路供电,电池正极接入V-BAT引脚,主电源VDD掉电后,电池通过V-BAT给RTC电路供电,使得时钟可以继续运行,确保设备重新上电时,时钟不丢失。
本文适合对单片机及C语言有一定基础的开发人员阅读,MCU使用STM32F103VE系列。
初始化分两步:通用中断、RTC
分为两种情况:一种是第一次配置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) 使能外设时钟(PWR和BKP)
2) 使能后备寄存器访问
3) 等待RTC寄存器同步完成
4) 使能RTC秒中断
5) 等待RTC寄存器写操作完成
操作主要分为读取、设置、时间转换和秒中断函数。
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
原文:https://www.cnblogs.com/greatpumpkin/p/13752789.html