SLAAEN0 September 2024 MSPM0L1227 , MSPM0L1228 , MSPM0L1228-Q1 , MSPM0L2227 , MSPM0L2228 , MSPM0L2228-Q1
Calibration of the RTC takes place over 60 seconds using the Q0 output of RT0PS. This approximates corrections of ±1ppm by adding or subtracting 1 clock pulse every quarter second. A maximum of ±240ppm can be adjusted for during one calibration cycle. Each cycle includes the adjustments for both offset and temperature errors. Adjustments exceeding the ±240ppm bounds saturates, but total compensation can be done over multiple cycles. Calibration is disabled if the RTC is not enabled.
There are two registers, CAL and TCMP, that are used for calibration. Offset adjustments are written to the CAL register and temperature written to TCMP. The TCMP register; however, stores the net compensation value between the two error types. When the CAL register is set, the temperature adjustment is reset to 0 and the TCMP only stores the offset adjustment.
When calibrating to adjust for offset error, the RTC offers a range of frequency outputs for external measurement. The test frequencies are LFCLK (32kHz), 512Hz, 256Hz, and 1Hz. The selection is stored in the RTCOCALFX bit field of the CAL register, and if set to 0, offset calibration is disabled. The offset error can be calculated using Equation 6.
where
Once the error has been calculated, the sign and magnitude bit fields can be written to. The sign indicates whether the adjustment is up or down. If the error is positive (Up calibration), set RTCOCALS, clear RTCOCALS if the sign is negative (Down calibration). RTCOCALX is the magnitude and limited to a maximum of 240ppm. If the signed result is written to RTCOCALX, negative values are represented in two’s complement and saturate to 240ppm.
Temperature drift calibration is done by taking the temperature approximation from the ADC, calculating the frequency error caused by deviation from the turnover temperature of the crystal, and writing the result to the TCMP register. The temperature approximation process is described in Section 3.5.1. Use Equation 7 to calculate the frequency error.
where
The previous information is found in the Crystal Oscillator data sheet.
This equation changes based on what is generating the clock signal. Specifically, the turnover temperature and parabolic coefficient variables are dependent on the type of oscillator used. In this example a tuning fork external crystal is used and the turnover temperature and parabolic coefficient are found in the data sheet. A unique property of a tuning fork crystal is that temperature deviation results in a negative frequency deviation. This means the direction of compensation is up. Once the error has been calculated, similar to the offset error, the sign (Up) is written to the RTCTCMPS bit field and the magnitude written to the RTCTCMPX bit field. After the write is successful, when reading the TCMP register, it has stored the net compensation between the offset error and temperature deviation. If the net compensation is too large, RTCTCMPX saturates to 240ppm. The compensation determined by temperature deviation is introduced in the next calibration cycle, not the current. Because each cycle is 60 seconds long, this example only measures the temperature once a minute and is triggered by the RTC_A interval interrupt. This can be changed to measure temperature multiple times and take the average.
#include "ti_msp_dl_config.h"
/*
* The following trim parameter is provided in the device datasheet in chapter
* "Temperature Sensor"
*/
#define TEMP_TS_TRIM_C ((uint32_t)30)
/*
* Constant below is (1/TSc). Where TSc is Temperature Sensor coefficient
* available in the device datasheet
*/
#define TEMP_TS_COEF_mV_C (-555.55f)
#define ADC_VREF_VOLTAGE (1.4f)
#define ADC_BIT_RESOLUTION ((uint32_t)(1)<<12)
/*
* The following turnover and Parabolic Coefficient parameter is provided
* in the crystal oscillator datasheet
*/
#define TEMP_CRYSTAL_Ti_C ((uint32_t)25)
#define TEMP_CRYSTAL_B_PPM_C2 (-0.04f)
/* Offset measurements */
typedef struct measFreq {
double freq;
DL_RTC_COMMON_OFFSET_CALIBRATION_SIGN sign;
} measFreq;
volatile bool gStatus = false;
volatile bool gCheckADC;
void offset_error_correction(void) {
measFreq slowCrystal;
measFreq fastCrystal;
/* Simulated frequency measurements (will need to be manually measured)*/
slowCrystal.freq = 511.9658;
fastCrystal.freq = 512.0241;
/* Correction sign is adjusted up for slow measured frequencies and down for fast measured frequencies */
slowCrystal.sign = DL_RTC_COMMON_OFFSET_CALIBRATION_SIGN_UP;
fastCrystal.sign = DL_RTC_COMMON_OFFSET_CALIBRATION_SIGN_DOWN;
measFreq examples[2] = {slowCrystal, fastCrystal};
/* Process for updating calibration register (for both slow and fast measurements)*/
for(uint8_t i = 0; i < 2; i++) {
/* Division Factor for 512Hz output frequency */
uint8_t divFact = 64;
/* Ideal Crystal Oscillation frequency*/
uint16_t optOsc = 32768;
/* Error Correction Formula:
* Frtcclk = Frtcclk,meas * divider factor
* Offset Value = Round(60 * 16384 * (1 - Frtcclk/32768))
*/
double Frtcclk = examples[i].freq * divFact;
uint8_t offVal = fabs(round(60 * 16384 * (1 - (Frtcclk / optOsc))));
/* Wait until RTC is ready for compensation */
while (!DL_RTC_A_isReadyToCalibrate(RTC_A)) {
;
}
/* Sets the offset error calibration adjustment value */
DL_RTC_A_setOffsetCalibrationAdjValue(RTC_A, examples[i].sign, offVal);
/* Check if write was successful */
gStatus = DL_RTC_A_isCalibrationWriteResultOK(RTC_A);
/* Stop the debugger to examine the output. At this point,
* gStatus should be equal to "true" and the CAL register
* should be updated.
* slow crystal adjustment should be +66ppm
* fast crystal adjustment should be -46ppm
*/
__BKPT(0);
}
}
void temp_drift_correction(float vTrim) {
uint32_t adcResult, tComp;
float vSample, tSample;
DL_RTC_COMMON_TEMP_CALIBRATION tSign;
gCheckADC = false;
/* Read stored ADC value */
adcResult = DL_ADC12_getMemResult(ADC12_0_INST, ADC12_0_ADCMEM_0);
/*
* Convert ADC result to equivalent voltage:
* Vsample = (VREF_VOLTAGE_MV*(adcResult -0.5))/(2^ADC_BIT_RESOLUTION)
*/
vSample = ADC_VREF_VOLTAGE / ADC_BIT_RESOLUTION * (adcResult -0.5);
/*
* Apply temperature sensor calibration data
* TSAMPLE = (TEMP_TS_COEF_mV_C) * (vSample - vTrim) + TEMP_TS_TRIM_C
*/
tSample = TEMP_TS_COEF_mV_C * (vSample - vTrim) + TEMP_TS_TRIM_C;
/*
* Compensation sign and value
* TCOMP = (tSample - TEMP_CRYSTAL_Ti_C)^2 * TEMP_CRYSTAL_B_PPM_C2
*/
tSign = RTC_TCMP_RTCTCMPS_UP;
tComp = fabs(pow((int16_t)tSample - (int16_t)TEMP_CRYSTAL_Ti_C, 2) * TEMP_CRYSTAL_B_PPM_C2);
/* Wait until RTC is ready for compensation */
while (!DL_RTC_A_isReadyToCalibrate(RTC_A)) {
;
}
/* Sets the temperature error calibration value */
DL_RTC_Common_setTemperatureCompensation(RTC_A, tSign, tComp);
/* Check if write was successful */
gStatus = DL_RTC_A_isCalibrationWriteResultOK(RTC_A);
/* Stop the debugger to examine the output. At this point,
* gStatus should be equal to "true" and the TCMP register
* should be updated.
* The TCMP register will show the net error
* of the offset and temperature errors.
*/
__BKPT(0);
}
int main(void)
{
/* Output frequency for offset calculation initialized to 512Hz */
SYSCFG_DL_init();
/* Enable RTC interrupts on device */
NVIC_EnableIRQ(RTC_A_INST_INT_IRQN);
/* Start RTC clock */
DL_RTC_A_enableClockControl(RTC_A);
/* Disclaimers:
* Writing to the RTCOCAL register resets the temperature
* to zero.
*
* The maximum correction in one calibration cycle is +-240ppm.
* Any error outside this range will be ignored.
*/
/* Offset Error Correction Mechanism */
offset_error_correction();
/* Temperature Drift Correction Mechanism */
/*
* Convert TEMP_SENSE0 result to equivalent voltage:
* Vtrim = (ADC_VREF_VOLTAGE*(TEMP_SENSE0 -0.5))/(2^12)
*/
float vTrim;
vTrim = ADC_VREF_VOLTAGE / ADC_BIT_RESOLUTION * (DL_SYSCTL_getTempCalibrationConstant() -0.5);
gCheckADC = false;
while (1) {
if(gCheckADC) {
temp_drift_correction(vTrim);
}
DL_ADC12_startConversion(ADC12_0_INST);
__WFI();
DL_ADC12_stopConversion(ADC12_0_INST);
}
}
void RTC_A_INST_IRQHandler(void)
{
switch (DL_RTC_A_getPendingInterrupt(RTC_A)) {
case DL_RTC_A_IIDX_INTERVAL_TIMER:
gCheckADC = true;
break;
default:
break;
}
}