RTC drift ?

Hi,

We have a custom board with an nRF9160. We are using NCS 2.0.2 and we are using the RTC to keep track of local time. For this we are using the "counter" from Zephyr. 

In the overlay file we have configured the RTC to count in 1/256 second steps. (We need to have 1/256s accuracy)

&rtc0 {
  /* f_rtc = f_lfclk/(prs+1)
   * zephyr driver automatically subtracts -1
   * -> prs of 128 needed in device tree for 256Hz */
  prescaler = <128>;
};

We always get the time of our Server for reference when we do a sync over LTE and when the times are off by >=5s, we log this and resync the local timestamp. We have noticed, that over the course of a bit more than a day, we need to resync the timestamps, meaning the RTC was 5s off. This offset was observed to grow gradually over time.

Here is a code snippet, of how we use the RTC to get the time and handle RTC Overflow. Maybe something here is wrong as well, but I fail to see it.

/** time stamp structure */
typedef struct
{
  u32 u32SysTime;    // [1 seconds - starting from 31.12.1999 00:00:00]
  u8  u8SysTime;     // [1/256 seconds]
} TRtc_Stamp;

static const struct device* psRtc_Dev = DEVICE_DT_GET(DT_NODELABEL(rtc0));

static TRtc_Stamp sRtc_StoredStamp      __attribute__((section(".noinit")));
static uint32_t   u32Rtc_TicksLastRead;
static u32        u32Rtc_Flags;

/* This function is called at least once a minute, to ensure RTC cannot overflow twice. */
void Rtc_Read(TRtc_Stamp* psStamp)
{
  uint32_t u32CurrTicks = 0;
  u32      u32DeltaTicks;
  u32      u32OverflowTicks;

  counter_get_value(psRtc_Dev, &u32CurrTicks);

  /* handle counter overflow */
  if(u32CurrTicks >= u32Rtc_TicksLastRead)
    u32OverflowTicks = 0;
  else
    u32OverflowTicks = counter_get_top_value(psRtc_Dev) + 1;

  u32DeltaTicks = u32CurrTicks + u32OverflowTicks - u32Rtc_TicksLastRead;
  sRtc_StoredStamp.u32SysTime += u32DeltaTicks / counter_get_frequency(psRtc_Dev);

  /* handle 1/256s overflow */
  u32DeltaTicks = u32DeltaTicks & 0xFF;
  if(u32DeltaTicks + sRtc_StoredStamp.u8SysTime > 0xFF)
    sRtc_StoredStamp.u32SysTime++;

  sRtc_StoredStamp.u8SysTime += u32DeltaTicks;

  if(psStamp)
  {
    psStamp->u32SysTime = sRtc_StoredStamp.u32SysTime;
    psStamp->u8SysTime  = sRtc_StoredStamp.u8SysTime;
  }

  u32Rtc_TicksLastRead = u32CurrTicks;
}

/* @param psInit initial timestamp, for example from external RTC */
void Rtc_Init(TRtc_Stamp* psInit)
{ 
  /* Prescaler should be set in device tree to ensure 256Hz */
  counter_start(psRtc_Dev);
  counter_get_value(psRtc_Dev, &u32Rtc_TicksLastRead);
  
  if(psInit)
  {
    sRtc_StoredStamp.u32SysTime = psInit->u32SysTime;
    sRtc_StoredStamp.u8SysTime  = psInit->u8SysTime;
  }
  else if(!Rtc_IsStoredStampValid())
  {
    sRtc_StoredStamp.u32SysTime = 0;
    sRtc_StoredStamp.u8SysTime  = 0;
  }

  u32Rtc_Flags |= RTC_FLAG_INIT_DONE;
}

Are there maybe additional configs that need to be set to get the RTC to drift less? I have looked in the debugger and the registers say, that LFXO is active instead of LFRCO.

Thank you in advance!

  • Hi, 
    We just came back from Easter vacation and that was the reason for no response from out end. Due to very high traffic during this period on devzone and fewer people working this week, we did not manage to assign this to an expert yet. We will look into this as soon as possible, but please expect more delays the first two days. Thanks for your patience so far.

  • Hi MaLu,

    Thank you for your patience.

    I have gone over your code. I find the part where you calculate delta RTC tick fine. However I don't fully understand the 1/256s handling part. Could you please elaborate about this part?

      sRtc_StoredStamp.u32SysTime += u32DeltaTicks / counter_get_frequency(psRtc_Dev);
    
      /* handle 1/256s overflow */
      u32DeltaTicks = u32DeltaTicks & 0xFF;
      if(u32DeltaTicks + sRtc_StoredStamp.u8SysTime > 0xFF)
        sRtc_StoredStamp.u32SysTime++;
    
      sRtc_StoredStamp.u8SysTime += u32DeltaTicks;

    The tolerance for the LFXO of the nRF9160 SiP is +-20ppm, according to its Product Specification. This means that it should drift only ~1.73s a day and would take 2.89 days to drift 5s. Thus, your observed drift of ~5s/day is indeed abnormal. But that's if this is tested with a new SiP in the specified temperature.

    What is the temperature the SiP is being tested in, and how long has the SiP been running, by the way? These can affect the tolerance.

    Hieu

  • Hi,

    Thanks for the reply. We are testing at about room temperature in the office, so ~21°C.

    We need a precision of 1/256s. A single u32 is too small to contain the absolute timestamp with the resolution needed, so we split it in two. 

    We store the full seconds of our absolute timestamp in the sRtc_StoredStamp.u32SysTime and the number of 1/256th seconds, that overshoot the full seconds, in sRtc_StoredStamp.u8SysTime. Since the RTC counts with 256Hz, this u8 Value is basically the number of ticks, that overshoot the full seconds.

    This adds the full seconds, that have passed to the variable that stores the full seconds of our absolute timestamp

    sRtc_StoredStamp.u32SysTime += u32DeltaTicks / counter_get_frequency(psRtc_Dev); 

    but this disregards the additional ticks, that have passed in this measurement. 

    This isolates the ticks, that are above the full seconds, that have passed since the last read.

    u32DeltaTicks = u32DeltaTicks & 0xFF;

    Since the ticks have a resolution of 1/256s, they are the same unit as the sRtc_StoredStamp.u8SysTime of the absolute timestamp. We now need to add the ticks to the 1/256s part of the absolute timestamp. If this would result in a value above 255, then the addition of the 1/256s part of the delta ticks caused the absolute timestamp to roll over to the next second.

      if(u32DeltaTicks + sRtc_StoredStamp.u8SysTime > 0xFF)
        sRtc_StoredStamp.u32SysTime++;

    At last, we just add the 1/256s part of the delta ticks to the 1/256s part of the absolute timestamp.

    sRtc_StoredStamp.u8SysTime += u32DeltaTicks;

    I know this may be a little confusing,so here is an example:

    lets say the stored absolute timestamp is 4.9765625s. This is 4 full seconds and 976.5625ms = 250 * 1/256s
    So the absolute stamp would be:
    sRtc_StoredStamp.u32SysTime = 4
    sRtc_StoredStamp.u8SysTime = 250

    then we do an Rtc_Read() and calculate, for example, a 
    u32DeltaTick = 522
    which is equivalent to 2.0390625s (because we tick with 256Hz), that have passed since the last time we called for a read.
    so thats 2 full seconds and 39.0625ms = 10 * 1/256s

    so we do 
    sRtc_StoredStamp.u32SysTime += 2, for the full 2 seconds since the last read
    for the u8 part, we would need to do 250 + 10, which would overflow to the next second, since a value of 260 would be 1.015625s.
    So we do another second in this case.
    sRtc_StoredStamp.u32SysTime++
    Would the u8 part not overflow to the next second, we would not need to do this.

    Now we do
    sRtc_StoredStamp.u8SysTime += 10
    which would result in a value of 260, but because an u8 can only hold values up to 255, it then rolls over. since we have 256 ticks in a second, an u8 is rather helpful here. We have handled the rollover above, by adding another second.

    I hope you are less confused than before, but I could understand if this made you more confused haha.

  • Hi MaLu,

    Thanks a lot for the detailed explanation. I definitely am less confused than before Smiley

    I think your code ought to be fine then. However, I am also out of ideas at this point, unless your nRF9160 has been running for two years.

    Let me check with other engineers here to see if this could be something else.

    I am partially out of office for the rest of the week, so I hope you could wait until early next week for my follow-ups. If this is urgent, please let me know and we can look for alternatives.

  • Hi,
    I just realized in the code, a channel alarm is also called

    static void Rtc_SetFlushWakeup(void)
    {
      struct counter_alarm_cfg sAlarmCfg =
      {
        .callback = Rtc_FlushWakeupHandler,
        .flags    = 0,
        .ticks    = counter_us_to_ticks(psRtc_Dev, RTC_FLUSH_TIMEOUT_S * RTC_USEC_IN_SEC),
        .user_data = NULL
      };
    
      counter_set_channel_alarm(psRtc_Dev, RTC_ALARM_CHANNEL, &sAlarmCfg);
    }

    Can this cause the rtc to desync?

    EDIT:

    I've removed this alarm and the drift still occurs.

Related