Uno millis() falls behind over long periods (hours/days)

Hi all!

I've built a new device (edit: Uno R3) that reminds me to read books throughout the day.

Problem: It falls behind the longer it runs.

I'm trying to use nothing but millis(), and can't seem to figure out what I'm doing wrong. The code is at

Basically, at 7:30AM, 9, 12:30PM, and 2:30, I have to remove a book from the shelf and read it for one minute, or it will turn off my work monitor until I comply. :slight_smile:

As I mentioned, the problem is that I can set the time correctly when I start it up, but then it starts losing time every hour. It's losing several minutes every couple of hours, so by the next day it's 10+ minutes behind.

In loop(), I basically call millis() once, then do my own simple clock calculations. I tried a time library, but it exhibited the same problem, so I think it's something else I'm doing.

I found some sites that said calling micros() might be a problem, because it disables an interrupt, which might cause you to miss some of the timer events, but I thought it said millis() didn't have that problem?

I also found a site to make sure I don't have the overflow error, but that's not really the issue here. (And it hasn't been running for 50+ days or whenever unsigned long would overflow.)

Super appreciative of any ideas what I'm doing wrong!

Sorry for the code - you can ignore a lot of the functions, I think - they're mostly about lighting LEDs. (Although...does FastLED maybe interfere with the timer somehow, maybe? Hm...)

Thank you so much!
Aaron

Which board are you using? Some use a ceramic resonator rather than a more accurate quartz crystal for system timing.

Yeah. That's garbage.

Oh, right, sorry - Uno R3

you may find that restarting your timer alarm interval will solve some of your problems

The drift won’t be cumulative, but rather limited by the interval between alarm n, and the next.

For the type of alarm you’re talking about, if you characterise the drift per every 4 hours as ‘no’ milliseconds, you can subtract that from the period when each alarm interval is set.

This forum is littered with things like this for dealing with wall clock discrepancies. I've found them to be effective for Arduinos running at a stable temperature and voltage.

Without trawling through your code - could it be you are calling a delay() between updating your millis() timings?

Using millis() to implement an accurate timer is not a good idea because the error accumulates over time and after a few hours there are already several seconds of difference.

You should use one of the 3 available hardware timers to create an accurate 1 second clock using interrupt.

So, you're saying millis per minute is 60000 and Arduino is running 10 minutes (600 seconds) slow per day (86400seconds).
Millis per minute SHOULD be:
Arduino time / clock time * 60000.
85800 / 86400 = 0.993055555556 * 60000 = 59583.
So try:

void advanceTime()
{
  if (nowMillis - lastMinuteMillis > 59583)
  {
    incrementMinute();
    lastMinuteMillis += 59583;
  }
}
1 Like

...which is only as accurate as millis or micros, significantly more work, and significantly more difficult to correctly code.

Missed it by one character...
if (nowMillis - lastMinuteMillis >= 59583)

Copied from @cotestatnt's OP.

This is not true!
millis() or micros() are functions intended to be used in the same way with all platforms, so with a lot of over-head lines of code that cause the time shifting.

Setting up a timer interrupt based clock is quite simple and accurate as oscillator can (tested in real world!)


volatile uint64_t now = 1629881966; // current epoch time
int hour, minute, second;

void setupTimer1() {
  noInterrupts(); 
  TCCR1A = 0;  // Clear registers
  TCCR1B = 0;
  TCNT1 = 0;
  OCR1A = 15624;                        // 1 Hz (16000000/((15624+1)*1024))
  TCCR1B |= (1 << WGM12);               // CTC
  TCCR1B |= (1 << CS12) | (1 << CS10);  // Prescaler 1024  
  TIMSK1 |= (1 << OCIE1A);              // Output Compare Match A Interrupt Enable
  interrupts();
}

void setup() {
  Serial.begin(115200);
  setupTimer1();
}

void loop() {
  static uint32_t printTime;
  if (millis() - printTime > 1000) {
    printTime = millis();
    hour = (now / 3600) % 24;
    minute = (now / 60) % 60;
    second = now % 60; 

    Serial.print("Actual time ");
    Serial.print(hour);
    Serial.print(":");
    Serial.print(minute);
    Serial.print(":");
    Serial.println(second);     
  }

}

// Timer1 interrupt service routine
ISR(TIMER1_COMPA_vect) {
  now++;
}

A curious idea indeed and one of the weirdest projects I've read about...

Regarding the implementation, though, have you considered using a real time clock? Seems like the ideal case for it, since you want the circuit to do something at specific wall clock times.

It looks like your little demo program would be easy for you to modify to show us all that your assertions are correct.

Show us how your timer based clock keeps better time than the simpler implementation using only millis().

You should be able to have both methods in one program at once, otherwise one or both ways woukd be useless in a real program.

Perhaps you are right, perhaps you are setting up a straw man by using millis() in correctly.

a7

why not use arduino board with built in real time clock, the library provides a way to set alarms any way you like, why not use the right tools for the job?

1 Like

Some days ago a user of italian forum has the same behavoiur.
This is his the reply "roughly translated"

OK, once the procedure is implemented, I check RTC, Hardware Timer and millis at the same time, the result is this:
After 1000 seconds (I did a quick test, I'll do another longer one) I find the Hardware Timer perfectly (from what I see on the display) synchronized with the RTC (which marks 1000 and apparently at the same time as the Hardware Timer), while the millis marks me 996 .......

maybe there are a lot of straw men around, however you do not have to take my word for it!
To perform a real world test and verify, it takes no longer than 15 minutes.

double post, sorry.

Edit.
From more technical point of view, the reason for time drift has to be searched in Arduino core sources.

The millis() function is defined inside wiring.c file located in the IDE installation folder ..\Arduino\hardware\arduino\avr\cores\arduino

As clearly written in the comments, with AVR MCUs like the ATmega328p of Uno R3 board

// the prescaler is set so that timer0 ticks every 64 clock cycles, and the
// the overflow handler is called every 256 ticks.
#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))

With a clock of 16Mhz we have that duration of a single cycles is equal to 1/16.000.000 = 0,0000000625 seconds

So Timer0 will overflow every 0,0000000625 * 64 * 256 = 0,001024 seconds that is to say millis() will increment of 1 every 1.024 ms.

This is why millis() is not a good choice for accurate time keeping

Not gonna do it.

You: Write some code that shows a millis() based clock pulling ahead or falling behind a timer/counter based clock.

Then we can examine your code for correctness, and run it ourselves.

There is nothing “real world” about this. A simulation would show the difference you assert would result.

We aren’t comparing to the passage of time, we are talking about the difference between two ways of counting off the system clock.

I am too sure you are wrong and too lazy to bother proving it, so until you do the (minimal) work to change my mind, I am filing you away.

FWIW it is way easier to just use a $3 RTC module that keeps time, real world time, way better.

a7

I have no one intention to changing your mind.

From the bottom of my laziness I went to check and I've just edited my previuos wrong post proving why millis () is not a good way to count accurately time if you don't take into account the error it introduces: millis() increments the counter every 1.024ms !!!
You have to handle this for an accurate clock using millis() function, this is the point!

There is no example to write, but only datasheet and source code to read, examine (and possibly understand).

Off course, an RTC is far away the better choice for this kind of problems, but this is not what OP has asked for (and in any case it has already been suggested to him).