This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

High accuracy clock source for time keeping

Hello Nordic,

nRF52840, SDK16.0, Softdevice 7.0.1.

I'm using LFCLK to generate an accurate tick for device time keeping (with Prescaler=255, Freq=128Hz, Tick=7.8125ms). The crystal being used is LFXTAL009678REEL with +/-20PPM frequency tolerance. The result we are getting, after practical testing, is as follows:

Time drift in 30.5 SC = 00:08 (SS:CS) = 8CS = 80ms
Time drift in 1 Hour = (60/30.5)*80 = 157ms
Time drift in 6 Hour = (60/30.5)*80 = 942ms
Time drift in 24 Hour = (60/30.5)*80 = 3768ms

This accuracy is not acceptable for our application. To improve the accuracy, I can think of two solutions:

  1. Use better LF crystal with say +/-5PPM tolerance.
  2. Use internal HFINT oscillator which, hopefully, is more accurate. (With some timer) 

However, The datasheet says it's frequency tolerance is +/-1.5%. I'm not sure if we can use this crystal for the required accuracy. What accuracy should we expect from it?

Can we improve accuracy of current code (without changing crystal) as I feel there is still room. 

Here is the current code being used for RTC :

#include "nrf.h"
#include "nrf_gpio.h"
#include "nrf_drv_rtc.h"
#include "nrf_drv_clock.h"
#include "boards.h"
#include "app_error.h"
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include <stdint.h>
#include <stdbool.h>


#include "scr_execute.h"
#include "adc_driver.h"
#include "Services/scr_service.h"

#define COMPARE_COUNTERTIME  (3UL)    /**< Get Compare event COMPARE_TIME seconds after the counter starts from 0. */

const nrf_drv_rtc_t rtc = NRF_DRV_RTC_INSTANCE(2); /**< Declaring an instance of nrf_drv_rtc for RTC2. */


uint8_t local_time[4] = {0};
uint8_t remote_time[4] = {0};
uint8_t remote_time_diff[4] = {0};
uint8_t pause_resume_gap[4] = {0};
uint8_t current_time[4] = {0};
bool remote_time_diff_add = true;
uint8_t converted_time_array[4] = {0};
uint64_t converted_time_int = 0;
uint32_t local_time_start=0, local_time_pause=0, local_time_resume=0, pause_resume_period=0;

uint8_t tick_count=0;
//#define SCR_EXECUTE_CYC       3           // time in ticks (1 tick= 10ms)
#define SCR_EXECUTE_CYC         4           // time in ticks (1 tick= 7.8125ms)
//#define PRESCALAR             327         // =>99.9Hz => 10.01ms tick
//#define FREQUENCY             100         // It's actually 10.01ms and cause lag after 20-30 mins of show run
#define PRESCALAR               255         // =>128Hz => 7.8125ms tick
#define FREQUENCY               128         // frequency for 255 (more precise) prescalar

/** @brief: Function for handling the RTC0 interrupts.
 * Triggered on TICK and COMPARE0 match.
 */
static void rtc_handler(nrf_drv_rtc_int_type_t int_type)
{
    if (int_type == NRF_DRV_RTC_INT_COMPARE0)
    {
        //nrf_gpio_pin_toggle(COMPARE_EVENT_OUTPUT);
        NRF_LOG_INFO("RTC: Compare event");
    }
    else if (int_type == NRF_DRV_RTC_INT_TICK)
    {
        tick_count++;
        if (tick_count >= SCR_EXECUTE_CYC){
            tick_count=0;

        // Execute script if script is started AND there is no initial delay
        if (scr_service_start_flag > 0){
            //NRF_LOG_INFO("Script execute being called..");
            scr_execute();
            //cue_turn_off();          
        }
        else{
            // Enable back the SAADC sampling
            if (adc_sampling_disabled){
                adc_sampling_disabled = false;
                saadc_sampling_event_enable();
            }
        }

        }
        
        //__NOP();
        //nrf_gpio_pin_toggle(22);
        //NRF_LOG_INFO("RTC: Tick event");
        //uint32_t cnts = NRF_RTC2->COUNTER;//nrf_drv_rtc_counter_get(&rtc);
        //NRF_LOG_INFO("RTC: Counter value: %lu", cnts);
    }
}


/** @brief Function starting the internal LFCLK XTAL oscillator.
 */
static void lfclk_config(void)
{
    ret_code_t err_code = nrf_drv_clock_init();
    APP_ERROR_CHECK(err_code);

    nrf_drv_clock_lfclk_request(NULL);
}

/** @brief Function initialization and configuration of RTC driver instance.
 */
static void rtc_config(void)
{
    uint32_t err_code;

    //Initialize RTC instance
    nrf_drv_rtc_config_t config = NRF_DRV_RTC_DEFAULT_CONFIG;
    config.prescaler = PRESCALAR;
    err_code = nrf_drv_rtc_init(&rtc, &config, rtc_handler);
    APP_ERROR_CHECK(err_code);

    //Enable tick event & interrupt
    nrf_drv_rtc_tick_enable(&rtc,true);

    //Set compare channel to trigger interrupt after COMPARE_COUNTERTIME seconds
    err_code = nrf_drv_rtc_cc_set(&rtc,0,COMPARE_COUNTERTIME * 8,true);
    APP_ERROR_CHECK(err_code);

    //Power on RTC instance
    nrf_drv_rtc_enable(&rtc);
}

void rtc_init(void)
{
    lfclk_config();
    rtc_config();
}


uint32_t rtc_get_counter_val(void){
    uint32_t cnts = NRF_RTC2->COUNTER;//nrf_drv_rtc_counter_get(&rtc);
    //NRF_LOG_INFO("RTC: Counter value: %lu", cnts);
    return cnts;
}


void rtc_get_time_local(){
    local_time[0] = (NRF_RTC2->COUNTER)%FREQUENCY;
    local_time[1] = (NRF_RTC2->COUNTER/FREQUENCY)%60;
    local_time[2] = (NRF_RTC2->COUNTER/(FREQUENCY*60))%60;
    local_time[3] = (NRF_RTC2->COUNTER/(FREQUENCY*60*60))%24;

    //NRF_LOG_INFO("Local time  :%02d:%02d:%02d:%02d", rtc_hr, rtc_mn, rtc_sc, rtc_cs);
}

void rtc_set_time_remote(uint8_t time[]){
    remote_time[0] = time[0];
    remote_time[1] = time[1];
    remote_time[2] = time[2];
    remote_time[3] = time[3];

    // Find time difference
    uint32_t remote_time_cs = remote_time[0] + (remote_time[1] + remote_time[2]*60 + remote_time[3]*60*60)*100;
    //uint32_t local_time_cs = NRF_RTC2->COUNTER;
    uint32_t local_time_cs = ((NRF_RTC2->COUNTER)*100)/FREQUENCY;
    uint32_t remote_time_diff_cs = 0;
    if (remote_time_cs >= local_time_cs){
        remote_time_diff_cs = remote_time_cs - local_time_cs;
        remote_time_diff_add = true;
    }
    else{
        remote_time_diff_cs = local_time_cs - remote_time_cs;
        remote_time_diff_add = false;
    }
    
    // Frequecy factor already mitigated in local_time_cs            
    remote_time_diff[0] = (remote_time_diff_cs)%100;
    remote_time_diff[1] = (remote_time_diff_cs/100)%60;
    remote_time_diff[2] = (remote_time_diff_cs/(100*60))%60;
    remote_time_diff[3] = (remote_time_diff_cs/(100*60*60))%24;

    NRF_LOG_INFO("Remote time CS:%lu", remote_time_cs);
    NRF_LOG_INFO("Local time CS :%lu", local_time_cs);
    NRF_LOG_INFO("Remote time:%02d:%02d:%02d:%02d", remote_time[3], remote_time[2], remote_time[1], remote_time[0]);
    NRF_LOG_INFO("Time diff  :%02d:%02d:%02d:%02d", remote_time_diff[3], remote_time_diff[2], remote_time_diff[1], remote_time_diff[0]);

}

void rtc_get_time_current(void){
    uint32_t r_diff_cs = remote_time_diff[0] + (remote_time_diff[1] + remote_time_diff[2]*60 + remote_time_diff[3]*60*60)*100;
    uint32_t l_time_cs = ((NRF_RTC2->COUNTER)*100)/FREQUENCY;
    uint32_t c_time_cs = 0;
    if (remote_time_diff_add){       
        c_time_cs = l_time_cs + r_diff_cs;
    }
    else{
        c_time_cs = l_time_cs - r_diff_cs;    
    }

    current_time[0] = (c_time_cs)%100;
    current_time[1] = (c_time_cs/100)%60;
    current_time[2] = (c_time_cs/(100*60))%60;
    current_time[3] = (c_time_cs/(100*60*60))%24;


//    NRF_LOG_INFO("Remote time :%02d:%02d:%02d:%02d", remote_time[3], remote_time[2], remote_time[1], remote_time[0]);
//    NRF_LOG_INFO("Local time  :%02d:%02d:%02d:%02d", local_time[3], local_time[2], local_time[1], local_time[0]);
//    NRF_LOG_INFO("Current time:%02d:%02d:%02d:%02d", current_time[3], current_time[2], current_time[1], current_time[0]);
}

void rtc_save_local_time_start(){
    local_time_start = ((NRF_RTC2->COUNTER)*100)/FREQUENCY;
}

void rtc_save_local_time_pause(){
    local_time_pause = ((NRF_RTC2->COUNTER)*100)/FREQUENCY;
}

void rtc_save_local_time_resume(){
    local_time_resume = ((NRF_RTC2->COUNTER)*100)/FREQUENCY;
}

uint32_t rtc_get_time_current_int(void){
    uint32_t r_diff_cs = remote_time_diff[0] + (remote_time_diff[1] + remote_time_diff[2]*60 + remote_time_diff[3]*60*60)*100;
    uint32_t l_time_cs = ((NRF_RTC2->COUNTER)*100)/FREQUENCY;
    uint32_t c_time_cs = 0;
    if (remote_time_diff_add){       
        c_time_cs = l_time_cs + r_diff_cs;
    }
    else{
        c_time_cs = l_time_cs - r_diff_cs;    
    }
    
    return c_time_cs;
}


void time_convert_to_array(uint64_t time_int){
    
    // Reset values
    converted_time_array[0] = converted_time_array[1] = converted_time_array[2] = converted_time_array[3] = 0;

    converted_time_array[0] = (time_int)%100;
    converted_time_array[1] = (time_int/100)%60;
    converted_time_array[2] = (time_int/(100*60))%60;
    converted_time_array[3] = (time_int/(100*60*60))%24;

    //NRF_LOG_INFO("converted_time_array: %2d:%2d:%2d:%2d", converted_time_array[3], converted_time_array[2], converted_time_array[1], converted_time_array[0]);
}

void time_convert_to_int(uint8_t time_array[4]){
    // Reset value
    converted_time_int = 0;

    converted_time_int = time_array[0] + (time_array[1] + time_array[2]*60 + time_array[3]*60*60)*100;

    //NRF_LOG_INFO("converted_time_int: %lu", converted_time_int);
}

Parents
    1. Yes, a more accurate crystal will help. There are two other parameters of a crystal that you need to account for and that is crystal aging and frequency stability over temperature. The crystal you're using will have a total of ~40ppm at 50C or 0C. Adding an additional 3PPM in aging per year. 

      Another option is to tune the loading caps. 

    2. HFINT is an internal 64MHz RC oscillator who's only intended to the CPU or peripherals that don't require precise timings. Its accuracy is measured in %, not PPMs, so you really do not want to use that as your clock source. 
Reply
    1. Yes, a more accurate crystal will help. There are two other parameters of a crystal that you need to account for and that is crystal aging and frequency stability over temperature. The crystal you're using will have a total of ~40ppm at 50C or 0C. Adding an additional 3PPM in aging per year. 

      Another option is to tune the loading caps. 

    2. HFINT is an internal 64MHz RC oscillator who's only intended to the CPU or peripherals that don't require precise timings. Its accuracy is measured in %, not PPMs, so you really do not want to use that as your clock source. 
Children
No Data
Related