[SOLVED] Accuracy of millis() for keeping time

I have been using the time library to display current time.

Both on a genuine Uno and a genuine Mega2560 the "clock" runs very slow.

In the now() function is the code

  while (millis() - prevMillis >= 1000) {
		// millis() and prevMillis are both unsigned ints thus the subtraction will always be the absolute value of the difference
    sysTime++;
    prevMillis += 1000;	
  }

I have found by trial and error that if I change the >= 1000 to >= about 770 then the "clock" only drifts a second or so over 40 minutes, but that is still quite a lot. (I'm checking it against time from a GPS)

I tried modifying the library to track the correct the millis()/second and home in on the magic value for my particular Arduino. However, I don't really like modifying libraries as it is a pain when there is an update.

Anybody else have this problem and have a more elegant solution?

Thanks in advance.

Gareth

(deleted)

If the drift matters you need to get a Real Time Clock (RTC) module for accurate time keeping.

I think no matter what fudge factor you use to correct for millis() you will find that the drift changes with temperature.

IMHO changing the library is not appropriate because millis() is directly derived from the Arduino's 16MHz oscillator.

...R

In this project as I have a GPS, I can keep the "clock" accurate by repeatedly resetting it to the GPS time. If I leave the library as it is, then I have to add a second every 4 seconds... I'm not really okay with that.

Anybody got any ideas other than changing the "1000" in the now() function to about 750 to make my clock not have to be reset to GPS time so often?

Or anybody want to rewrite the Time library as an object and then I could use function overloading to fix up for my particular clock drift... :wink:

Gareth

then I have to add a second every 4 seconds...

That is one hell of a drift. Have you got interrupt code that blocks out the millis counter for a long time?

I agree. Code could be the problem.
A resonator based clock shouldn't be off by more than several seconds per hour.

Post#0
"// millis() and prevMillis are both unsigned ints"
I think that's not right. They're unsigned longs.

Most Arduino's, including the official ones, use ceramic resonators for the "heartbeat".
Some cheaper clones use a more stable/accurate crystal.
Don't know why.
Leo..

Most Arduino's, including the official ones, use ceramic resonators for the "heartbeat".

Having said that I have two Arduino Unos, a R1 and an R3 and both have crystals in them.

Hi,
If you are using a GPS, does it have a 1Sec pulse output?

Tom... :slight_smile:

That's definitely wrong. Show us the rest of the code please. You've only shown one function, which you understand quite well. The problem is in the other code that appears to be irrelevant.

GarethMoffatt:
I have found by trial and error that if I change the >= 1000 to >= about 770 then the “clock” only drifts a second or so over 40 minutes

That is much too much!

Typical drift of boards with resonator clock is 0.8%, which means:
0.8s in 100s or 8 seconds within 1000 seconds.
But you vcan easily make it worse with your code, using many thousands of interrrupts per second.
Do you?
Show your full code and tell us which board you are using!

Boards with resonator are (for example) the R3 design boards UNO R3 oe MEGA2560 R3.

Boards with a crystal oscillator for clocking the microcontroller are 100 times more accurate than boards just using a ceramic resonator.

The time related constants in the library could be made variables, which can be adjusted in application code for every single board.
See MICROSECONDS_PER_TIMER_OVERFLOW and the derived FRACT_INC and MILLIS_INC in wiring.c.

Grumpy_Mike:
That is one hell of a drift. Have you got interrupt code that blocks out the millis counter for a long time?

DING! DING! DING! DING! I believe this is the correct answer/reason.
(timer interrupts are being blocked for too long so timer ticks are being missed).

Consider the evidence:

  • Both on a genuine Uno and a genuine Mega2560 the "clock" runs very slow.
  • I have to add a second every 4 seconds...

This means that in order for this to happen, the resonators on two boards must both be off by 25% to get that much drift.

and yet....
Both boards can still be downloaded, which means that the UART in them is running at a baud rate clock that falls within acceptable tolerance, to work with the FTDI/16u2/16u4 chip - which is about 5%.

I don't see how the millis() clock can be off by that much and yet uart baud rates are still accurate enough to allow uploading since both are driven from the same clock.

My money is on some s/w issue that is blocking interrupts too long or some interrupt routines that are running too long, either of which is causing timer interrupts to be missed.

--- bill

Try this simple count up timer, see if you are seeing the same drift.

unsigned long currentMicros;
unsigned long previousMicros;
unsigned long elapsedTime;

byte hundredths;
byte tenths;
byte secondsOnes;
byte oldSecondsOnes;
byte secondsTens;
byte minutesOnes = 1;
byte minutesTens = 4;
byte hoursOnes = 0;
byte hoursTens = 0;

void setup(){

  Serial.begin(115200); // make serial monitor match
  currentMicros = micros();
  previousMicros = currentMicros;
  Serial.println ("Setup Done");
}

void loop(){

  currentMicros = micros();

  // how long's it been?
  elapsedTime = currentMicros - previousMicros;
  //Serial.print ("Elapsed: ");  
  //Serial.println (elapsedTime);
  if ( elapsedTime >=10000UL){  // 0.01 second passed? Update the timers
    elapsedTime = 0;
    previousMicros  = previousMicros + 10000UL;
    hundredths = hundredths+1;
    if (hundredths == 10){
      hundredths = 0;
      tenths = tenths +1;
      if (tenths == 10){
        tenths = 0;
        secondsOnes = secondsOnes + 1;
        if (secondsOnes == 10){
          secondsOnes = 0;
          secondsTens = secondsTens +1;
          if (secondsTens == 6){ 
            secondsTens = 0;
            minutesOnes =  minutesOnes + 1;
            if (minutesOnes == 10){
              minutesOnes = 0;
              minutesTens = minutesTens +1;
              if (minutesTens == 6){
                minutesTens = 0;
                hoursOnes = hoursOnes +1;
                if (hoursOnes == 4 && hoursTens ==2){
                  hoursOnes = 0;
                  hoursTens = 0;
                }
                if (hoursOnes == 10){
                  hoursOnes = 0;
                  hoursTens = hoursTens +1;
                }
              } // minutesTens rollover check
            } // minutesOnes rollover check
          } // secondsTens rollover check
        } // secondsOnes rollover check
      } // tenths rollover check
    } // hundredths rollover check
  } // hundredths passing check

  if (oldSecondsOnes != secondsOnes){  // show the elapsed time
    oldSecondsOnes = secondsOnes;
    Serial.print ("Time: ");
    Serial.print (hoursTens);
    Serial.print(hoursOnes);
    Serial.print(":");
    Serial.print(minutesTens);
    Serial.print(minutesOnes);
    Serial.print(":");
    Serial.print(secondsTens);
    Serial.print(secondsOnes);
    Serial.print(" micros: ");
    Serial.println (currentMicros);

  } // end one second check

} // end loop

As has been stated here using millis() to timekeep is the wrong approach.

An RTC is what you need and you need the correct one. Watch my YouTube video (#5 Arduino compatible Real Time Clock modules (RTC) - DS1307 & DS3231) to find out all about them (and why you need to choose the DS3231).

All this talk about time drift is all very well but regardless of you blocking interrupts, an RTC is definitely what you need. Or something else that can grab the time like a wifi module or gps unit (depending on your project’s needs, of course!).

URL is in the footer of this post, enjoy!

Don't use the Arduino's clock for keeping real time.

I'm using 12 nanos in a network mesh. Each nano has its own frequency, that is, most are off at least 12 seconds per hour and none track another.

That said, millis() is great for short periods of time. I use it for delays and timeouts.

About 18 months after I first wrote this post, I moved my project on to the Mega permanently and as a result of the reading on other threads, I moved the GPS from SoftwareSerial to Serial1.

Lo-and-behold, by calibration for millis() is now about 998/1000 (Was 770/1000), so you guys who threw out the interrupts idea were on the right line, I just did not associate SoftwareSerial with being abusive of interrupts!

So much belated, thanks for your help, and a warning to others; SoftwareSerial screws up the time library pretty badly!

Thanks,

Gareth

void aTimer1000() {
unsigned long currentMillis = millis();
DIFF1000 = currentMillis - previousMillis; //DIFF=1001 -0 =1001
if (DIFF1000 >= 1000) {
previousMillis = currentMillis - (DIFF1000 - 1000);
//1001+1, make next time we get in here w/ a little fast

}
}

I have tested interrupts stuffed with loops. They are more solid than millis(). If you provide something heavy millis lose while interrupts don't lose their accuracy that much.....