Recently, I read some posts about RTC calibration and found that many people have doubts. Coincidentally, I also implemented RTC calibration in STM32 recently. Here are some insights. While this may seem trivial to experienced users, it can be beneficial for beginners.
One of the core implementations of RTC calibration is the function void BKP_SetRTCCalibrationValue (uint8_t CalibrationValue) in the library file Stm321f0x_bkp.c. Relevant reference documents for RTC calibration include AN2604.pdf, AN2821.pdf, and AN2821.zip. All three documents can be downloaded from the official STM32 website.
According to the principles described in AN2604.pdf, the RTC calibration value should be between 0 and 127. The achievable calibration error corresponds to 0-121ppm, which equates to a maximum gain of 0-314 seconds over 30 days.
A key point to note is that the RTC can only be calibrated for fast running, not for slow running. If the nominal frequency of the watch crystal is 32768Hz, and its possible error range is ±2Hz, the actual frequency will be between 32766Hz and 32770Hz. If the internal division coefficient of the RTC is set to 32768, then 32768Hz is a frequency that does not require calibration, while 32768Hz-32770Hz is a frequency that can be calibrated (with a maximum calibration capability of about 32772Hz). However, the slow frequency range of 32766Hz-32768Hz cannot be calibrated. Therefore, in the recommended calibration method, 32766 is used instead of 32768 as the division coefficient. This way, 32766Hz does not require calibration, and the frequency range of 32766Hz-32770Hz can be calibrated.
The remaining question is how to measure the error and derive the calibration value from it. Generally, there are two methods: one is to measure the frequency value of the TamperPin and then calculate the ppm error; the other is to run for a certain number of days and compare with a standard clock to first obtain the number of seconds gained every 30 days, and then calculate the ppm error.
The first method is detailed in AN2604.pdf and AN2821.pdf. AN2821.zip uses timer T2 to automatically measure the frequency value of the TamperPin, achieving automatic calibration. Automatic calibration indeed simplifies user operations, but it relies on the accuracy of the 8MHz main clock. Automatic calibration cannot achieve results with higher accuracy than the 8MHz main clock. Therefore, providing users with a manual calibration interface is still a foolproof strategy. Even with automatic calibration, manual and automatic methods can work together.
On the other hand, using the first method for calibration requires accurately measuring the frequency value of the TamperPin, for example, to an accuracy of 511.xxxHz. Ordinary oscilloscopes cannot achieve this, and general frequency counters also cannot; only high-precision frequency counters can. Only professionals in metrology would have such equipment. As a control systems engineer, obtaining a non-metrology precision clock makes using the first method challenging.
Whether using the first method or the second method, the core is calculating the ppm error. Let’s first look at how the first method calculates the ppm error. Since 32766 is used as the division coefficient, 32766Hz is the reference frequency that does not require calibration. Do not place too much emphasis on 32768Hz; it is irrelevant now. 32766Hz can be considered the new nominal frequency. The frequency of the TamperPin should be 32766Hz/64=511.968Hz. This is the reference frequency repeatedly used in the document for calculating errors. According to the example in the document, if the measured frequency of the TamperPin is 511.982Hz, the error is 27.35ppm. The calculation process is (511.982Hz-511.968Hz)/511.968Hz *10^6 = 27.35ppm. The document finally gives the closest calibration value as 28. Note that this final calibration value of 28 is derived from looking up the table for 27 ppm, not from some posts that mistakenly approximate 27.35ppm to 28ppm.
In fact, the formula for calculating ppm error is: ppm error = deviation/reference value * 10^6. Accordingly, when using the second method, the number of seconds gained every 30 days is first obtained. This gained seconds is the deviation, while 30 days is the reference value. Therefore, ppm error = gained seconds/(30 days * 24 hours * 3600 seconds) * 10^6. This formula easily explains the “0.65ppm corresponds to a monthly error of about 1.7 seconds” mentioned in document AN2604.pdf. Because: 1.7/(30*24*3600)*10^6 = 0.65ppm.
After calculating the ppm error, one must also resolve the lookup table issue. The table provided in the document need not be overly emphasized. Once you understand how this table is derived, you can use a simple calculation formula to replace the lookup table. AN2604.pdf states that if the calibration value is 1, then during RTC calibration, every 2^20 clock cycles deduct 1 clock pulse. This corresponds to 0.954ppm (1/2^20*10^6 = 0.954). The maximum calibration value is 127, so the maximum slowdown can be 121ppm (0.954ppm*127 = 121). Therefore, this calibration table is derived from simple multiplication and division calculations, and of course, floating-point calculations are necessary to achieve accurate results.
Below is the RTC calibration program implemented using the second method.
First, two constants are defined: one is PPM_PER_STEP, accurate to the precision representable by floating-point numbers, which is 0.9536743ppm. The other is PPM_PER_SEC, which is the ppm error corresponding to gaining one second every 30 days, accurate to the precision representable by floating-point numbers, which is 0.3858025ppm.
#define PPM_PER_STEP 0.9536743 //10^6/2^20.
#define PPM_PER_SEC 0.3858025 //10^6/(30d*24h*3600s).
Then a global variable FastSecPer30days is defined. This is set by the user menu and passed to the RTC calibration program.
u16 FastSecPer30days = 117; // Menu input. 117 is used for demonstration only.
The implemented calibration function is:
void RTC_Calibration(void)
{
float Deviation = 0.0;
u8 CalibStep = 0;
Deviation = FastSecPer30days * PPM_PER_SEC; // Obtain ppm error
Deviation /= PPM_PER_STEP; // Obtain floating-point calibration value
CalibStep = (u8)Deviation; // Obtain integer calibration value
if(Deviation >= (CalibStep + 0.5))
CalibStep += 1; // Round up
if(CalibStep > 127)
CalibStep = 127; // Calibration value should be between 0 and 127
BKP_SetRTCCalibrationValue(CalibStep); // Call library function
}
// End of RTC_Calibration function
