关于关于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 }              

 

posted @ 2024-08-29 19:52  通宵敲代码  阅读(229)  评论(0编辑  收藏  举报