STM32 RTC时钟

  •          前段日子项目需要做一个RTC时钟,之前也没有做过,想想也不难,到网上搜了下,做好的例子也不少,经过几天的研究,总算给做出来了,
    觉得自己做的东西还是要好好的整理下。功能开机会显示时间,可以利用按键来调整时间。
  •   
  •         RTC----real—Time Clock实时时钟芯片,RTC模块拥有一组连续计数的计数器,修改计数器的值可以重新设置系统当前的时间和日期。RTC模块和时钟配置系统(RCC_BDCR寄存器)是在后备区域,即在系统复位或从待机模式唤醒后RTC的设置和时间维持不变。
  •          stm32 的VBAT引脚需要提供电源, VBAT = 1.8~3.6V:当关闭VDD时,(通过内部电源切换器)为RTC、外部32.768kHz振荡器和后备寄存器供电。存储时间的寄存器是有两个十六位的RTC->CNTL和RTC->CNTH。
  •       
  •         在RTC代码中,最重要的是如下函数,(此函数利用的stm32 库函数实现的)判断是不是第一次使用rtc(没有装电池也是),是的话就配置rtc时钟,设置时间,如果之前已经设置好了,就只需改读取时间。

  • void RTC_CheckAndConfig(struct rtc_time *tm)
  • {
  •      /*在启动时检查备份寄存器BKP_DR1,如果内容不是0xA5A5,
  •  则需重新配置时间并询问用户调整时间*/
  • if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5)
  • {
  •    printf("\r\n\r\n RTC not yet configured....");
  •    /* RTC Configuration */
  •    RTC_Configuration();
  • printf("\r\n\r\n RTC configured....");
  • /* Adjust time by users typed on the hyperterminal */
  •    Time_Adjust(tm);
  • BKP_WriteBackupRegister(BKP_DR1, 0xA5A5);
  • }
  • else
  • {
  •    /*启动无需设置新时钟*/
  • /*检查是否掉电重启*/
  • if (RCC_GetFlagStatus(RCC_FLAG_PORRST) != RESET)
  • {
  •    printf("\r\n\r\n Power On Reset occurred....");
  • }
  • /*检查是否Reset复位*/
  • else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) != RESET)
  •    {
  •      printf("\r\n\r\n External Reset occurred....");
  •    }
  • printf("\r\n No need to configure RTC....");
  • /*等待寄存器同步*/
  • RTC_WaitForSynchro();
  • /*允许RTC秒中断*/
  • RTC_ITConfig(RTC_IT_SEC, ENABLE);
  • /*等待上次RTC寄存器写操作完成*/
  • RTC_WaitForLastTask();
  • }
  •   /*定义了时钟输出宏,则配置校正时钟输出到PC13*/
  • #ifdef RTCClockOutput_Enable
  •  /* Enable PWR and BKP clocks */
  •  RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE);
  •  /* Allow access to BKP Domain */
  •  PWR_BackupAccessCmd(ENABLE);
  •  /* Disable the Tamper Pin */
  •  BKP_TamperPinCmd(DISABLE); /* To output RTCCLK/64 on Tamper pin, the tamper
  •                                 functionality must be disabled */
  •  /* Enable RTC Clock Output on Tamper Pin */
  •  BKP_RTCOutputConfig(BKP_RTCOutputSource_CalibClock);
  • #endif
  •  /* Clear reset flags */
  •  RCC_ClearFlag();
  • }

  •     在调试的过程中,出现以下问题,板子的时钟比实际时钟走的要快,一天24小时后大概快了6s,分析是晶振的频率不稳定所致,利用晶振校准的办法,结果大体满意。一下是转载的原文。
  •   
  •        最近看了一些关于RTC校准的帖子,发现很多人存在疑惑。正好最近我也在STM32中实现了RTC校准。发些心得。这些对老手来说有些罗索,但对新手有益处。

  •  实现RTC 校准的核心之一是库文件Stm321f0x_bkp.c中的void BKP_SetRTCCalibrationValue (uint8_t CalibrationValue) 函数。谈到RTC校准的相关参考文档包括AN2604.pdf,AN2821.pdf和AN2821.zip。这三个文档都可以从STM32官方网站下载。
  •   按照AN2604.pdf描述的原理,RTC 的校准值应在0-127之间。可实现的校准误差对应为0-121ppm。相当于每30天跑快的秒数为0-314s。
  •     这里应注意的一个关键问题是,RTC只能对跑快进行校准,不能对跑慢进行校准。如果手表晶振的标称频率是32768Hz,设其可能的误差范围是±2Hz,则实际频率会在32766Hz-32770Hz之间。如果RTC的内部分频系数设定为32768,则32768Hz是不需要校准的频率,32768Hz-32770Hz是可以校准的频率(最大校准能力大概是32772Hz)。但是32766Hz-32768Hz的跑慢频率段则无法实现校准。为此,在推荐的校准方法中,使用32766代替32768作为分频系数。这样一来,32766Hz是不需要校准的频率,32766Hz-32770Hz是可以校准的频率范围。
  •     剩下的问题是,如何测量误差,并以此得出校准值。一般来说有两种方法,一是测量TamperPin的频率值,然后计算ppm误差;二是实际运行一定的天数,与标准时钟做对比,先得到每30天跑快的秒数,然后计算ppm误差。
  •     AN2604.pdf,AN2821.pdf里都详细描述了第一种方法。AN2821.zip则使用定时器T2对TamperPin的频率值进行自动测量,实现了自动校准。自动校准确实简化了用户操作,但是它要依赖于8MHz主时钟的精度。自动校准不可能达到比8MHz主时钟精度更高的结果。所以给用户留有手动校准界面仍是万全之策。即使有自动校准,也可以手动、自动叠加作用。
  •            另一方面,使用第一种方法进行校准,需要准确测量TamperPin的频率值,比如达到511.xxxHz的精度。普通示波器做不到这一点,一般的频率计也不行,高精度的频率计才可以。只有搞计量的专业人士才会有这种设备。作为搞控制系统的人,搞一个非计量精度的时钟,使用第一种方法还是有困难的。
  •           第一种方法也好,第二种方法也罢,核心都是计算ppm误差。我们先看一下第一种方法是如何计算ppm误差的。由于使用了32766作为分频系数,因此32766Hz是不需要校准的基准频率。不要把32768Hz看得太重,现在它啥也不是,32766Hz可看成新的标称频率。TamperPin的频率应为32766Hz/64=511.968Hz。这也就是文档中计算误差时反复使用的基准频率。按照文档中所举的例子,若实测TamperPin的频率为511.982Hz,则误差为27.35ppm。计算过程为(511.982Hz-511.968Hz)/ 511.968Hz *10^6 = 27.35ppm。文档最后给出最接近的校准值为28。注意这里是最后的校准值28,是由27 ppm查表得到的,而不是有些帖子中误解的将27.35ppm近似成28ppm。
  •          其实ppm误差的计算公式为:ppm误差=偏差/基准值*10的6次方。据此,采用第二种方法时,先得到了每30天跑快的秒数。这跑快的秒数就是偏差,而30天就是基准值。所以ppm误差=每30天跑快的秒数/(30天*24小时*3600秒)*10的6次方。用这个公式可以容易地解释文档AN2604.pdf中提到的“0.65ppm大约是每月误差1.7秒”。因为:1.7/(30*24*3600)*10^6 = 0.65ppm。
  •         计算出了ppm误差,还要解决查表。对文档中给出的表格也不必看重。弄明白这个表格是怎么来的之后,可以使用简单的计算公式代替查表。AN2604.pdf中说,若校准值为1,则RTC 校准时,每2的20次方个时钟周期扣除1个时钟脉冲。这相当于0.954ppm(1/2^20*10^6 = 0.954)。而校准值最大为127,所以最大可以减慢121ppm(0.954ppm*127 = 121)。所以这个校准表就是由简单的乘除运算得来的,当然要使用浮点运算才可以得到准确结果。
  •        以下是采用第二种方法实现的RTC 校准程序。
  •  首先定义了两个常数,一是PPM_PER_STEP,准确到浮点数可表示的精度数0.9536743ppm。另一个是PPM_PER_SEC,即每30天快一秒对应的ppm误差,准确到浮点数可表示的精度数0. 3858025ppm。 
  • #define PPM_PER_STEP  0.9536743 //10^6/2^20.
  • #define PPM_PER_SEC   0.3858025 //10^6/(30d*24h*3600s).
  • 然后定义全局变量FastSecPer30days。通过用户菜单设定并传递到RTC校准程序里。
  • u16 FastSecPer30days = 117; //菜单输入。117只用于演示。
  • 实现的校准函数为:
  • void RTC_Calibration(void)
  • {
  •   float Deviation = 0.0;
  •   u8 CalibStep = 0;
  •   
  •   Deviation = FastSecPer30days * PPM_PER_SEC; //得到ppm误差
  •   Deviation /= PPM_PER_STEP; //得到校准值的浮点数
  •   CalibStep = (u8)Deviation; // 得到校准值的整形数
  •   if(Deviation >= (CalibStep + 0.5))
  •     CalibStep += 1; //四舍五入
  •   if(CalibStep > 127) 
  •     CalibStep = 127; // 校准值应在0—127之间
  •   
  •   BKP_SetRTCCalibrationValue(CalibStep); //调用库函数
  •     
  • }
  • //函数结束RTC_Calibration


你可能感兴趣的:(STM32)