Periodic 32 kHz XTAL calibration in QN908x

cancel
Showing results for 
Search instead for 
Did you mean: 

Periodic 32 kHz XTAL calibration in QN908x

No ratings

Periodic 32 kHz XTAL calibration in QN908x

Many applications make use of 32 kHz clocks to keep tracking of real-time or to have a low power time reference for the system. Most of the systems might use a 32.768 kHz XTAL for this purpose. However, there might be some exceptions in which the application requires compensate the frequency of this clock due to temperature changes, aging, or just because the clock provides from a source which frequency is close to the ideal 32.768 kHz, but it presents some variations.

QN908x devices require a 32 kHz clock source for some applications like running the BLE stack in low power. 32.768 kHz XTALs are more accurate so they are used to generate a 32 kHz source by compensating for the ppm difference. This provides us with tools to compensate for any external 32 kHz source by first obtaining the ppm difference from the ideal frequency.

The solution consists in determining how off is the external clock input frequency from the ideal 32 kHz by making a comparison with a trusted clock in the system, typically the 32 MHz / 16 MHz XTAL. This process is executed in the background in an interrupt-based system so the application can initialize or run other processes in the meantime. Then, the results of the ppm calculation are reported to the main application to compensate for the changes and provide for a more accurate clock source.

This example makes use of the following peripherals in the QN908x

  • RTC Seconds Timer
  • CTIMER
  • DMA

The RTC Seconds Timer uses the 32 kHz clock source as a reference. It contains an internal 32000 cycles counter that increases each 32 kHz clock period. On overflow, it rises the SEC_INT flag to indicate that one second has elapsed.

The CTIMER makes use of the APB clock which derives from the 32 MHz / 16 MHz clock. This timer is used in free-running mode with a Prescaler value of 1 to count the number of APB pulses.

The algorithm consists of counting the amount of APB pulses (trusted clock reference) elapsed by the time the RTC SEC_INT flag is set. Ideally, if both clocks are in the right frequency, the number of APB pulses must be equal to the reference clock frequency. For example, if the APB clock is 16 MHz, by the time the SEC_INT flag sets, the CTIMER counter must have a value of 16 x 106 counts. Assuming our reference clock is ideal, the difference between the CTIMER counter value and 16 x 106 can be used to obtain the ppm difference given the following formula.

Where f0 is the ideal APB frequency (16 MHz) and f1 is the real measured frequency (CTIMER counter value). Since the pulses counted using the CTIMER correspond to the time it took to the RTC Seconds Timer to count one second, we can extrapolate the obtained ppm value as a difference in the 32 kHz clock source from the ideal 32 kHz.

To prevent from any application or task servicing latency, the algorithm makes use of the DMA to automatically capture the CTIMER Counter value when the SEC_INT flag is set. The program flow is described in the diagram below.

pastedImage_3.png

As a way of demonstrating this algorithm, two APIs were implemented to calculate the ppm value and apply the compensation to the system. Both APIs are included in the file fsl_osc_32k_cal .c and .h files.

OSC_32K_CAL_GetPpm (osc_32k_cal_callback_t pCallbackFnc): Initializes the required timers and DMA and starts with the CTIMER capture. A callback is passed so it can be executed once the ppm calculation sequence completes.

OSC_32_CAL_ApplyPpm (int32_t ppmMeasurement): Uses the previously calculated ppm passed as an input parameter to compensate the RTC and the BLE timer used during sleep mode.

OSC_32K_CAL_GetPpm is called every time the ppm value of the 32 kHz source clock needs to be calculated. It takes around one second to complete (depending on how off the 32 kHz source clock is from the ideal frequency) and the application cannot enter into low power state during this time. The registered callback function is executed once the calculation is complete.

The ppm calculation is performed into the DMA callback. It consists of obtaining the CTIMER counter difference and use it as f1 in the formula shown before. The ppm values are calculated using floating point unit.

/* Calculate PPMs */
ppmResult = (float)((float)1-((float)ApbClockFreq/(float)ApbCountDiff));
ppmResult *= (float)1048576;‍‍‍

Then OSC_32_CAL_ApplyPpm must be called using the ppm value obtained after calling OSC_32K_CAL_GetPpm. This API programs the necessary values in the RTCàCAL register and the BLE Sleep timer registers to compensate for the differences in the 32 kHz source clock.

Finally, the user must account for all those other APIs that make use of the 32 kHz clock frequency and update the values accordingly. For the particular case of the NXP BLE Stack, there are two APIs that need to be updated to return the clock frequency after the calibration has been applied.

uint32_t PWRLib_LPTMR_GetInputFrequency(void) {
 uint32_t result = 32000;
 int32_t ppm = 0;

 if ( RTC->CTRL & RTC_CTRL_CAL_EN_MASK) /* is calibration enabled ? */
 {
 /* Get the current calibration value */
 if (RTC->CAL & RTC_CAL_DIR_MASK)
 {
 /* Backward calibration */
 ppm -= (int32_t) (RTC->CAL & RTC_CAL_PPM_MASK);
 }
 else
 {
 /* Forward calibration */
 ppm += (int32_t) (RTC->CAL & RTC_CAL_PPM_MASK);
 }

 /* Obtain the uncalibrated clock frequency using the formula
 * fUncal = 32000 - (ppm*0.03125) where 0.03125 is the number
 * of Hz per PPM digit obtained from (768 Hz/0x6000 PPM)
 */
 result -= (float) ppm * (float) 0.03125;
 }
 else
 {
#if (defined(BOARD_XTAL1_CLK_HZ) && (BOARD_XTAL1_CLK_HZ == CLK_XTAL_32KHZ))
 result = CLOCK_GetFreq(kCLOCK_32KClk); /* 32,768Khz crystal is used */
#else
 result = CLOCK_GetFreq(kCLOCK_32KClk); /* 32,000Khz internal RCO is used */
#endif
 }
 return result;
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
uint32_t StackTimer_GetInputFrequency(void)
{
 uint32_t prescaller = 0;
 uint32_t refClk = 0;
 uint32_t result = 0;
#if FSL_FEATURE_SOC_FTM_COUNT
 refClk = BOARD_GetFtmClock(gStackTimerInstance_c);
 prescaller = mFtmConfig.prescale;
 result = refClk / (1 << prescaller);
#elif FSL_FEATURE_RTC_HAS_FRC
 int32_t ppm = 0;

 (void)prescaller; /* unused variables */
 (void)refClk; /* suppress warnings */

 result = 32000;

 if ( RTC->CTRL & RTC_CTRL_CAL_EN_MASK) /* is calibration enabled ? */
 {
 /* Get the current calibration value */
 if (RTC->CAL & RTC_CAL_DIR_MASK)
 {
 /* Backward calibration */
 ppm -= (int32_t) (RTC->CAL & RTC_CAL_PPM_MASK);
 }
 else
 {
 /* Forward calibration */
 ppm += (int32_t) (RTC->CAL & RTC_CAL_PPM_MASK);
 }

 /* Obtain the uncalibrated clock frequency using the formula
 * fUncal = 32000 - (ppm*0.03125) where 0.03125 is the number
 * of Hz per PPM digit obtained from (768 Hz/0x6000 PPM)
 */
 result -= (float) ppm * (float) 0.03125;
 }
 else
 {
 #if (defined(BOARD_XTAL1_CLK_HZ) && (BOARD_XTAL1_CLK_HZ == CLK_XTAL_32KHZ))
 result = CLOCK_GetFreq(kCLOCK_32KClk); /* 32,768Khz crystal is used */
 #else
 result = CLOCK_GetFreq(kCLOCK_32KClk); /* 32,000Khz internal RCO is used */
 #endif
 }
#elif FSL_FEATURE_SOC_CTIMER_COUNT
 refClk = BOARD_GetCtimerClock(mCtimerBase[gStackTimerInstance_c]);
 prescaller = mCtimerConfig[gStackTimerInstance_c].prescale;
 result = refClk / (prescaller + 1);
#else
 refClk = BOARD_GetTpmClock(gStackTimerInstance_c);
 prescaller = mTpmConfig.prescale;
 result = refClk / (1 << prescaller);
#endif
 return result;
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
Labels (2)
Attachments
Version history
Revision #:
2 of 2
Last update:
‎09-10-2020 02:54 AM
Updated by: