Stable long term time keeping using an nRF5340-DK

Hello,

We require real time clock capabilities for our product with an acceptable drift of several minutes per year. We are currently evaluating using solely the nRF5340 with an external 32.768kHz crystal oscillator to do this (with an nRF5340-DK). We could use an RTC chip but would prefer to avoid it if possible (more complexity, board space, cost). I know that others have found success doing this using just the external oscillator. I wrote a demo that uses UNIX time passed through BLE as the base time, then uses the uptime retrieved from k_uptime_get() to keep time relative to the base time. I didn't add anything related to the clocks in the prj.conf, or change any board files/add any overlay, the code is running with the default board files for the nRF5340-DK. The problem is that this code has a drift of about 10 seconds/day behind real time, which is unacceptably high. Here is the relevant timekeeping section, where unix_time is set through BLE:

int64_t uptime = 0;
int64_t elapsed_time = 0;
int64_t unix_time = 0;
int64_t uptime_last = 0;

int main(void) {
    ble_init();

    while (true) {
        // Update times
        uptime_last = uptime;
        uptime = k_uptime_get();
        elapsed_time = uptime - uptime_last;
        unix_time += elapsed_time / 1000;

        k_msleep(1000);
    }

    return 0;
}
A few questions:
  • One cause of this inaccuracy could be that k_uptime_get() is using the internal RC oscillator by default, not the external crystal oscillator, which is much less accurate. Can anyone confirm if this is the case, and if so how to tell Zephyr to use the external crystal oscillator instead?
  • Is there a better way to do this that would be more accurate?
  • I'm in a similar position as yours, but I still have to figure out what clock to use.

    Have a read at this chapter in the datasheet about the clocks. The LFCLK can be configured to use the external oscillator. I programmed the registers directly using low level access on an NRF52840 (should be similar I hope?) to configure the external oscillator:

        if(NRF_CLOCK->EVENTS_LFCLKSTARTED == 1)
        {
            LOG_INF("Stopping LF clock");
            NRF_CLOCK->TASKS_LFCLKSTOP = 1;
        }
        
        NRF_CLOCK->LFCLKSRC = 0x1;
        NRF_CLOCK->TASKS_LFCLKSTART = 0x1;
        while(!NRF_CLOCK->EVENTS_LFCLKSTARTED)
        {
    
        }
        LOG_INF("Clocks started");
        LOG_INF("LFCLKRUN %08X", NRF_CLOCK->LFCLKRUN);
        LOG_INF("LFCLKSTAT %08X", NRF_CLOCK->LFCLKSTAT);
        LOG_INF("LFCLKSRCCOPY %08X", NRF_CLOCK->LFCLKSRCCOPY);
        LOG_INF("EVENTS_LFCLKSTARTED %08X", NRF_CLOCK->EVENTS_LFCLKSTARTED);

    Once you select the external oscillator as your LFCLK, I'd assume Zephyr will use that as its counter.

    Hope it helps.

    Cheers,

    Alberto

  • One cause of this inaccuracy could be that k_uptime_get() is using the internal RC oscillator by default, not the external crystal oscillator, which is much less accurate. Can anyone confirm if this is the case, and if so how to tell Zephyr to use the external crystal oscillator instead?

    Maybe I am misunderstanding you. But it should should be able to configure the clock source to be external crystal for lfclk.

    And I do not think that external RTC chips will have great difference in performance compared to nRF RTC peripheral when you are not comparing the clock accuracy.

    In this scenario, on nRF chips by default k_uptime_get still should be getting their ticks from the internal RTC which still is the source of the Zephyr RTOS internal tick counter.

Related