关于关于STM32F103芯片RTC模块的一些注意事项
1、首先是晶振的问题,只有外部低速晶振LSE支持VBAT供电时持续运行,LSI或者HSE均不行,所以若要求设备断电后,RTC时钟可以继续运行,一定要使用LSE晶振。
2、关于LSE晶振的干扰问题,本次调试设备的过程中发现,LSE虽然正常起振,RTC也正常走时,但刚开机的时候会走的比较慢,之后逐渐稳定,通过抓取LSE波形,发现存在一个周期性的干扰,如下图
通过对波形放大发现,该干扰并不会导致LSE停振,只是会抬高正常的震荡电平,如下图
起初同事推测是由于PC13引脚可作为RTC的入侵检测引脚导致, 经测试关闭TAMPER引脚的侵入检测功能后,并未解决该问题,
后经百度发现,之前也有人遇到过,而且官方的勘误手册上也提到过,是由于使用PC13引脚做LED指示灯导致,关闭该引脚后LSE震荡正常
3、关于玩不低速32.768KHz晶振的匹配电容问题,官方手册上给出的是6pF,实际贴片时为节省一种物料,使用了跟8M晶振一样的18pF,
导致主电源关闭时LSE停振,但RTC始终与后备寄存器依然保持,数据并未丢失,主电源上电之后RTC又继续走时,并未发现异常,
后经测试发现使用官方推荐的6pF电容,或者直接去掉电容,断电后LSE均正常震荡,且震荡频率并不收影响,如下图。
这是主电源断电之后,电池供电的LSE震荡波形,如下图
附优化之后的RTC初始化及时钟配置获取代码(以正点原子例程为父本修改),仅供参考
1 #include "RTC.h" 2 #include "sys.h" 3 #include "delay.h" 4 #include "usart.h" 5 6 rtc_param_def rtc_param;//时钟结构体 7 8 //月份数据表 9 u8 const table_week[12]={0,3,3,6,1,4,6,2,5,0,3,5}; //月修正数据表 10 //平年的月份日期表 11 const u8 mon_table[12]={31,28,31,30,31,30,31,31,30,31,30,31}; 12 13 /* 14 void set_clock(u16 divx) 15 { 16 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟 17 PWR_BackupAccessCmd(ENABLE); //使能RTC和后备寄存器访问 18 19 RTC_EnterConfigMode();/// 允许配置 20 21 RTC_SetPrescaler(divx); //设置RTC预分频的值 22 RTC_ExitConfigMode();//退出配置模式 23 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 24 } 25 */ 26 static void RTC_NVIC_Config(void){ 27 NVIC_InitTypeDef NVIC_InitStructure; 28 NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn; //RTC全局中断 29 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3; //先占优先级3位,最低 30 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //先占优先级3位,最低 31 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能该通道中断 32 NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器 33 } 34 35 //实时时钟配置 36 //初始化RTC时钟,同时检测时钟是否工作正常 37 //BKP->DR1用于保存是否第一次配置的设置 38 //返回0:正常 39 //其他:错误代码 40 u8 RTC_Init(void){ 41 //检查是不是第一次配置时钟 42 u16 temp=0; 43 if (BKP_ReadBackupRegister(BKP_DR1) != 0x5050) { //从指定的后备寄存器中读出数据:读出了与写入的指定数据不相等 44 45 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟 46 BKP_TamperPinCmd(DISABLE); //关闭TAMPER引脚的侵入检测功能 47 PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问 48 BKP_DeInit(); //复位备份区域 49 50 RCC_LSEConfig(RCC_LSE_ON); //设置外部低速晶振(LSE),使用外设低速晶振 51 while((RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) && (temp<250)) {//检查指定的RCC标志位设置与否,等待低速晶振就绪 52 temp++; 53 delay_ms(10); 54 } 55 if(temp>=250) {printf("RTC Init faild!\r\n"); return 1;} //初始化时钟失败,晶振有问题 56 57 RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); //设置RTC时钟(RTCCLK),选择LSE作为RTC时钟 58 RCC_RTCCLKCmd(ENABLE); //使能RTC时钟 59 60 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 61 RTC_WaitForSynchro(); //等待RTC寄存器同步 62 RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能RTC秒中断 63 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 64 RTC_EnterConfigMode(); //允许配置 65 RTC_SetPrescaler(32767); //设置RTC预分频的值 66 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 67 RTC_Set(2024,1,1,0,0,0); //设置时间 68 RTC_ExitConfigMode(); //退出配置模式 69 BKP_WriteBackupRegister(BKP_DR1, 0X5050); //向指定的后备寄存器中写入用户程序数据 70 } 71 else{//系统继续计时 72 RTC_WaitForSynchro(); //等待最近一次对RTC寄存器的写操作完成 73 RTC_ITConfig(RTC_IT_SEC, ENABLE); //使能RTC秒中断 74 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 75 } 76 RTC_NVIC_Config(); //RCT中断分组设置 77 RTC_Get(); //更新时间 78 printf("RTC Init:%04d-%02d-%02d %02d:%02d:%02d\r\n", rtc_param.year, rtc_param.mon, rtc_param.day, rtc_param.hour, rtc_param.min, rtc_param.sec); 79 return 0; 80 } 81 //RTC时钟中断 82 //每秒触发一次 83 //extern u16 tcnt; 84 void RTC_IRQHandler(void) 85 { 86 if (RTC_GetITStatus(RTC_IT_SEC) != RESET)//秒钟中断 87 { 88 RTC_Get();//更新时间 89 // printf("RTC Time:%04d-%02d-%02d %02d:%02d:%02d\r\n", rtc_param.year, rtc_param.mon, rtc_param.day, rtc_param.hour, rtc_param.min, rtc_param.sec);//输出RTC时间 90 } 91 // if(RTC_GetITStatus(RTC_IT_ALR)!= RESET)//闹钟中断 92 // { 93 // RTC_ClearITPendingBit(RTC_IT_ALR); //清闹钟中断 94 // RTC_Get(); //更新时间 95 // printf("Alarm Time:%04d-%02d-%02d %02d:%02d:%02d\r\n", rtc_param.year, rtc_param.mon, rtc_param.day, rtc_param.hour, rtc_param.min, rtc_param.sec);//输出闹铃时间 96 // 97 // } 98 RTC_ClearITPendingBit(RTC_IT_SEC|RTC_IT_OW); //清闹钟中断 99 RTC_WaitForLastTask(); 100 } 101 //判断是否是闰年函数 102 //月份 1 2 3 4 5 6 7 8 9 10 11 12 103 //闰年 31 29 31 30 31 30 31 31 30 31 30 31 104 //非闰年 31 28 31 30 31 30 31 31 30 31 30 31 105 //输入:年份 106 //输出:该年份是不是闰年.1,是.0,不是 107 u8 Is_Leap_Year(u16 year) 108 { 109 if(year%4==0) //必须能被4整除 110 { 111 if(year%100==0) 112 { 113 if(year%400==0)return 1;//如果以00结尾,还要能被400整除 114 else return 0; 115 }else return 1; 116 }else return 0; 117 } 118 //设置时钟 119 //把输入的时钟转换为秒钟 120 //以1970年1月1日为基准 121 //1970~2099年为合法年份 122 //返回值:0,成功;其他:错误代码. 123 //月份数据表 124 u8 RTC_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec) 125 { 126 u16 t; 127 u32 seccount=0; 128 if(syear<1970||syear>2099)return 1; 129 for(t=1970;t<syear;t++) //把所有年份的秒钟相加 130 { 131 if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数 132 else seccount+=31536000; //平年的秒钟数 133 } 134 smon-=1; 135 for(t=0;t<smon;t++) //把前面月份的秒钟数相加 136 { 137 seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加 138 if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数 139 } 140 seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加 141 seccount+=(u32)hour*3600;//小时秒钟数 142 seccount+=(u32)min*60; //分钟秒钟数 143 seccount+=sec;//最后的秒钟加上去 144 145 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟 146 PWR_BackupAccessCmd(ENABLE); //使能RTC和后备寄存器访问 147 RTC_SetCounter(seccount); //设置RTC计数器的值 148 149 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 150 return 0; 151 } 152 153 //初始化闹钟 154 //以1970年1月1日为基准 155 //1970~2099年为合法年份 156 //syear,smon,sday,hour,min,sec:闹钟的年月日时分秒 157 //返回值:0,成功;其他:错误代码. 158 u8 RTC_Alarm_Set(u16 syear,u8 smon,u8 sday,u8 hour,u8 min,u8 sec) 159 { 160 u16 t; 161 u32 seccount=0; 162 if(syear<1970||syear>2099)return 1; 163 for(t=1970;t<syear;t++) //把所有年份的秒钟相加 164 { 165 if(Is_Leap_Year(t))seccount+=31622400;//闰年的秒钟数 166 else seccount+=31536000; //平年的秒钟数 167 } 168 smon-=1; 169 for(t=0;t<smon;t++) //把前面月份的秒钟数相加 170 { 171 seccount+=(u32)mon_table[t]*86400;//月份秒钟数相加 172 if(Is_Leap_Year(syear)&&t==1)seccount+=86400;//闰年2月份增加一天的秒钟数 173 } 174 seccount+=(u32)(sday-1)*86400;//把前面日期的秒钟数相加 175 seccount+=(u32)hour*3600;//小时秒钟数 176 seccount+=(u32)min*60; //分钟秒钟数 177 seccount+=sec;//最后的秒钟加上去 178 //设置时钟 179 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能PWR和BKP外设时钟 180 PWR_BackupAccessCmd(ENABLE); //使能后备寄存器访问 181 //上面三步是必须的! 182 183 RTC_SetAlarm(seccount); 184 185 RTC_WaitForLastTask(); //等待最近一次对RTC寄存器的写操作完成 186 187 return 0; 188 } 189 190 //得到当前的时间 191 //返回值:0,成功;其他:错误代码. 192 u8 RTC_Get(void) 193 { 194 static u16 daycnt=0; 195 u32 timecount=0; 196 u32 temp=0; 197 u16 temp1=0; 198 timecount=RTC_GetCounter(); 199 temp=timecount/86400; //得到天数(秒钟数对应的) 200 if(daycnt!=temp)//超过一天了 201 { 202 daycnt=temp; 203 temp1=1970; //从1970年开始 204 while(temp>=365) 205 { 206 if(Is_Leap_Year(temp1))//是闰年 207 { 208 if(temp>=366)temp-=366;//闰年的秒钟数 209 else {temp1++;break;} 210 } 211 else temp-=365; //平年 212 temp1++; 213 } 214 rtc_param.year=temp1;//得到年份 215 temp1=0; 216 while(temp>=28)//超过了一个月 217 { 218 if(Is_Leap_Year(rtc_param.year)&&temp1==1)//当年是不是闰年/2月份 219 { 220 if(temp>=29)temp-=29;//闰年的秒钟数 221 else break; 222 } 223 else 224 { 225 if(temp>=mon_table[temp1])temp-=mon_table[temp1];//平年 226 else break; 227 } 228 temp1++; 229 } 230 rtc_param.mon=temp1+1; //得到月份 231 rtc_param.day=temp+1; //得到日期 232 } 233 temp=timecount%86400; //得到秒钟数 234 rtc_param.hour=temp/3600; //小时 235 rtc_param.min=(temp%3600)/60; //分钟 236 rtc_param.sec=(temp%3600)%60; //秒钟 237 rtc_param.week=RTC_Get_Week(rtc_param.year,rtc_param.mon,rtc_param.day);//获取星期 238 return 0; 239 } 240 //获得现在是星期几 241 //功能描述:输入公历日期得到星期(只允许1901-2099年) 242 //输入参数:公历年月日 243 //返回值:星期号 244 u8 RTC_Get_Week(u16 year,u8 month,u8 day) 245 { 246 u16 temp2; 247 u8 yearH,yearL; 248 249 yearH=year/100; yearL=year%100; 250 // 如果为21世纪,年份数加100 251 if (yearH>19)yearL+=100; 252 // 所过闰年数只算1900年之后的 253 temp2=yearL+yearL/4; 254 temp2=temp2%7; 255 temp2=temp2+day+table_week[month-1]; 256 if (yearL%4==0&&month<3)temp2--; 257 return(temp2%7); 258 }