关于STM32中RTC的校准方法

转自:https://www.amobbs.com/forum.php?mod=viewthread&tid=3803285

最近看了一些关于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

----------------------------------------------------------------- 分割线 --------------------------------------------------------------------

原贴只考虑了同一温度下的补偿,温度变化会影响晶振频率,更高精度的需考虑实时温度补偿。

  • 5
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一份基于STM32RTC校准代码,使用备份寄存器进行校准: ```c #include "stm32f10x.h" // RTC时钟频率 #define RTC_CLOCK_FREQ 32768 void RTC_Config(void) { // 使能PWR和BKP外设时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); // 使能备份区域访问 PWR_BackupAccessCmd(ENABLE); // 检查备份寄存器的值是否合法 if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5) { // 如果不合法,将RTC重置为默认时间并设置校准值为0 RCC_BackupResetCmd(ENABLE); RCC_BackupResetCmd(DISABLE); // 使能LSE时钟 RCC_LSEConfig(RCC_LSE_ON); while (RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET) {} // RTC时钟源为LSE RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); // 使能RTC时钟 RCC_RTCCLKCmd(ENABLE); // 等待RTC时钟启动 RTC_WaitForSynchro(); // 设置RTC预分频器为32767,使得RTC时钟频率为1Hz RTC_SetPrescaler(RTC_CLOCK_FREQ-1); // 初始化RTC时间为2022年1月1日0时0分0秒 RTC_SetCounter(0); RTC_SetDate(1); RTC_SetMonth(1); RTC_SetYear(22); RTC_SetHour(0); RTC_SetMinute(0); RTC_SetSecond(0); // 将校准值设置为0 RTC_CalibOutputConfig(RTC_CalibOutput_1Hz); RTC_CalibConfig(RTC_CalibSign_Positive, 0, 0); // 写入备份寄存器 BKP_WriteBackupRegister(BKP_DR1, 0xA5A5); } else { // 如果备份寄存器的值合法,恢复RTC时钟源为LSE RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE); RCC_RTCCLKCmd(ENABLE); RTC_WaitForSynchro(); } } // 获取当前的RTC时间 void RTC_GetTime(uint16_t *year, uint8_t *month, uint8_t *day, uint8_t *hour, uint8_t *minute, uint8_t *second) { RTC_TimeTypeDef RTC_TimeStruct; RTC_DateTypeDef RTC_DateStruct; RTC_GetTime(RTC_Format_BIN, &RTC_TimeStruct); RTC_GetDate(RTC_Format_BIN, &RTC_DateStruct); *year = RTC_DateStruct.RTC_Year; *month = RTC_DateStruct.RTC_Month; *day = RTC_DateStruct.RTC_Date; *hour = RTC_TimeStruct.RTC_Hours; *minute = RTC_TimeStruct.RTC_Minutes; *second = RTC_TimeStruct.RTC_Seconds; } // 设置RTC校准值 void RTC_SetCalibration(int8_t calib) { RTC_CalibOutputConfig(RTC_CalibOutput_1Hz); if (calib > 0) { RTC_CalibConfig(RTC_CalibSign_Positive, calib, RTC_CALIBDIV_32); } else if (calib < 0) { RTC_CalibConfig(RTC_CalibSign_Negative, -calib, RTC_CALIBDIV_32); } else { RTC_CalibConfig(RTC_CalibSign_Positive, 0, RTC_CALIBDIV_1); } } ``` 该代码包含了以下功能: - 初始化RTC,如果备份寄存器的值不合法,则将RTC时间设置为默认值,并将校准值设置为0; - 获取当前的RTC时间; - 设置RTC校准值。 在使用该代码时,您可以根据自己的需求修改RTC的默认时间和校准值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值