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:
- Use better LF crystal with say +/-5PPM tolerance.
- 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); }