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

App Timer (RTC1) drift

Hi, we have the need to develop an application that needs a precise second counter over time.

Our firmware is using the S110 SoftDevice (v8.0.0) and SDK v8.0.0 to perform scannable advertising (the second counter value is sent in the advertising data along with other custom data).

The firmware is loaded on a custom NRF51822 board that uses an external 20PPM LFCLK oscillator.

The SoftDevice is initialized in the main.c with this code:

static void ble_stack_init(void)
{
    uint32_t err_code;

    // Initialize the SoftDevice handler module.
    SOFTDEVICE_HANDLER_INIT(NRF_CLOCK_LFCLKSRC_XTAL_20_PPM, NULL);

    // Enable BLE stack 
    ble_enable_params_t ble_enable_params;
    memset(&ble_enable_params, 0, sizeof(ble_enable_params));
    err_code = sd_ble_enable(&ble_enable_params);
    APP_ERROR_CHECK(err_code);

    // Register with the SoftDevice handler module for BLE events.
    err_code = softdevice_ble_evt_handler_set(ble_evt_dispatch);
    APP_ERROR_CHECK(err_code);

    // Register with the SoftDevice handler module for SYS events.
    err_code = softdevice_sys_evt_handler_set(sys_evt_dispatch);
    APP_ERROR_CHECK(err_code);
}

The code that performs the seconds counting has been taken from the Nordic Eddystone firmware and is the following:

static void update_timestamp(uint32_t *p_sec_cnt)
{
    static uint32_t previous_tick = 0;
    static uint32_t ms_remainder = 0;
    static uint32_t ns_remainder = 0;
    static uint32_t le_time = 0; //Little endian time
    uint32_t        be_time = 0; //Big endian
    uint32_t        current_tick = 0;
    uint32_t        tick_diff;
    uint32_t        ms = 0;


    APP_ERROR_CHECK(app_timer_cnt_get(&current_tick));

    if (current_tick > previous_tick)
    {
        //ticks since last update
        tick_diff = current_tick - previous_tick;
    }
    else if (current_tick < previous_tick)
    {
        //RTC counter has overflown
        tick_diff = current_tick + (RTC1_TICKS_MAX - previous_tick);
    }

    uint32_t ns;
    const uint32_t NS_PER_MS = 1000000;

    ns = tick_diff*NS_PER_TICK;
    ms = ns/NS_PER_MS;
    ns_remainder += ns % NS_PER_MS;

    if (ns_remainder >= NS_PER_MS)
    {
        ms += ns_remainder/NS_PER_MS;
        ns_remainder = ns_remainder % NS_PER_MS;
    }

    const uint32_t RESOLUTION = 1000;
    //The spec requires 1s resolution, so only update the time when ms > 1000 ms
    if (ms >= RESOLUTION)
    {
        ms_remainder += ms % RESOLUTION;
        if (ms_remainder >= RESOLUTION)
        {
            ms += ms_remainder/RESOLUTION;
            ms_remainder = ms_remainder % RESOLUTION;
        }
        //For very first function call, ms will be >> 1000, since previous_tick = 0. 
        //So only increment the time by 1.
        if (le_time == 0)
        {
            le_time++;
        }
        else
        {
            le_time += ((ms)/RESOLUTION);
        }
        be_time = BYTES_REVERSE_32BIT(le_time);
        // Write second counter in function parameter
    *p_sec_cnt = be_time;
    }
    else
    {
        ms_remainder += ms;
    }
    previous_tick = current_tick;
}

And the app_timer library is initialized in the main.c using:

APP_TIMER_APPSH_INIT(APP_TIMER_PRESCALER, APP_TIMER_MAX_TIMERS, APP_TIMER_OP_QUEUE_SIZE, true);

and

#define APP_TIMER_PRESCALER          0
#define APP_TIMER_MAX_TIMERS         8
#define APP_TIMER_OP_QUEUE_SIZE      16

Our firmware starts advertising with a timeout of 1s and the advertising data are updated each time with the current value of the second counter (in this way I can see the increment of the second counter at each second on the advertising data using a BLE-enabled phone).

static void advertising_start(bool restart)
{
    uint32_t err_code;
    ble_gap_adv_params_t adv_params;
    uint32_t sec_counter;
    ble_advdata_t adv;
    uint8_t adv_data[ADV_SIZE];
    ble_advdata_t sr;
    uint8_t sr_data[SR_SIZE];

    ...

    // Set advertising parameters
    memset(&adv_params, 0, sizeof(adv_params));
    adv_params.type = BLE_GAP_ADV_TYPE_ADV_IND;
    adv_params.p_peer_addr = NULL;
    adv_params.fp = BLE_GAP_ADV_FP_ANY;
    adv_params.interval = MSEC_TO_UNITS(ADV_INTERVAL, UNIT_0_625_MS);
    adv_params.timeout  = 1;
    

    // Get the current second timestamp value
    update_timestamp(&sec_counter);

    // Set the second timestamp value in the scan response data 
    memcpy(&sr_data[idx], &sec_counter, sizeof(uint32_t));

    ...

    // Set the advertising and scan response data
    err_code = ble_advdata_set(&adv, &sr);
    APP_ERROR_CHECK(err_code);

    // Start Advertising
    err_code = sd_ble_gap_adv_start(&adv_params);
    APP_ERROR_CHECK(err_code);
}

The problem we are currently experiencing is that the seconds timekeeping is drifting and in 3 days we have detected around 5 minutes of drift, and this is too big for a 20PPM oscillator.

What could be the cause of this drift? Are there any others SoftDevice settings that I could try to improve the performance of the RTC1 timekeeping?

Parents Reply Children
No Data
Related