Experimenting with better time keeping (non-RTC)

Hello all, this is not really a problem, but a true experiment with the Arduino time keeping.

It is pretty well known that the Arduino time keeping code, related to the millis() function, does not align exactly with the 16Mhz clock. millis() uses the 8bit Timer0, and 16,000,000 is not perfectly divisible by 256.

I decided to run a test against the millis() timekeeping vs using a timekeeping method that perfectly aligns with the 16Mhz clock using Timer1.

I am testing with an Arduino Micro clone (ATMega32u4) with 16Mhz crystal oscillator.

My results were very surprising. After running for 3 days, the times remained almost perfectly aligned. Never deviating more than 1ms. Perhaps my test code is wrong or I am not accounting for something.

volatile uint32_t timer1_millis;
uint32_t millis_tmp;
uint32_t timer1_millis_tmp;
uint32_t next_millis_update;


void setup(void) {
  TCCR1A = 0;
  TCCR1B = _BV(WGM12) | _BV(CS11) | _BV(CS10); // /64 prescale
  TIMSK1 = _BV(OCIE1A); //Overflow counter interupt enable
  OCR1A = 249; // 1ms with /64 prescale @ 16Mhz
  
  Serial.begin(115200);
  
  // synchronize times
  timer1_millis = millis();
  
  // report every 10 seconds
  next_millis_update = timer1_millis + 10000;
}

void loop(void) {
  if(millis() >= next_millis_update) {
    
    // set snapshot values for calculation
    millis_tmp = millis();
    timer1_millis_tmp = timer1_millis;
    
    Serial.print("Timer1: ");
    Serial.println(timer1_millis_tmp,DEC);
    
    Serial.print("Millis: ");
    Serial.println(millis_tmp,DEC);
    
    Serial.print("Diff:   ");
    Serial.println((timer1_millis_tmp - millis_tmp),DEC);
    
    next_millis_update += 10000;
  }
}

ISR(TIMER1_COMPA_vect) {
    timer1_millis++;
}

It is pretty well known that the Arduino time keeping code, related to the millis() function, does not align exactly with the 16Mhz clock.

It is even better known that millis tracks the 16 MHz processor clock perfectly.

millis() uses the 8bit Timer0, and 16,000,000 is not perfectly divisible by 256.

Correct. But the millis code accounts for that fact.

It is even better known that millis tracks the 16 MHz processor clock perfectly.

Even in subsecond ranges??

pito:
Even in subsecond ranges??

I don't understand the question. Are you asking if millis accurately tracks a wall clock?

I finally took a second (okay, 100th) look at the Arduino code. millis() does lose time like I was expecting, but keeps track of the missing "fractional" time and then adds it to millis() once the fractional time adds up to a whole millisecond.

This does explain the behavior I was seeing with my sketch. millis() and my timer would be synchronized, and after a few seconds millis() would lose a millisecond, but would eventually gain the lost millisecond back. Wash, rinse, repeat ad infinitum.

So, millis() does lose time in the short term, but is ultimately "correct" over the long term.

In reality, millis() is never completely correct.... it is always off by less than 1000 nanoseconds [edit] microseconds... but that does not really matter in terms of time keeping.

I use micros() for time tracking, seems to be very accurate with crystal driven processors.

Please do not think that I am saying that there is something wrong with the Arduino code. This is purely an academic experiment.

millis() and micros() is more than adequate for timing purposes, but it can never align perfectly with the 16MHz clock source using an 8bit timer in [Fast-]PWM mode.

I knew millis() could not align perfectly and I wanted to see how quickly it would drift, but I did not expect millis() to keep up over the long term.