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);
}