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

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).

So basically you were wrong, OK, thanks for clarifying that.

a7

@alto777 are you a MCU programmer or just a kid who wants to win regardless?

I'm only trying to answer why @GameMaker_1 has encountered this behavior and suggest a better way from my point of view (without RTC).
Instead you only worry about who is right ...

You are right, OK?
Happy for this?

Please, let's stop with this annoying flame and have a nice day.

I support to @alto777 and agree that millis() provides the correct accuracy in 16MHz Arduino.

This is because I know that the ISR that controls the Arduino millis has a process that counts the number of extra 0.024ms and makes an additional increment when it reaches 1ms.

In other words, millis() sometimes has a number that is skipped by one. :hushed:

One "arduino millisecond" IS NOT equal to 1024 real milliseconds!!! Whether you reset your timer for each "alarm" does not change anything.

The problem is that the oscillator is not completely accurate nor is it temperature compensated. You cannot use the on-board (or on-chip) oscillators for accurate timing and that's just the way it is.

You should get a temperature compensated RTC module which allows you to calibrate the aging register (DS3231). If calibrated well, such a module will only loose or gain less than a second per month!

It should be emphasized that my opinion is that "there is no reason why counting with a hardware timer is better than millis".

Keep in mind that cheap DS3231 clones made in China often same or less accuracy found in Arduino with on-board xtal.
You need got a genuine component, if you need more accuracy.

1 Like

Not true, even a cheap RTC module is more accurate than a genuine arduino. My cheap DS3231 china clones are very accurate after aging calibration. I've just checked one of my modules and it is off by less than 500ms over a period of 9 weeks.

Hi @GameMaker_1

The choice of microcontroller clock source (ceramic oscillator or crystal) depends on the absolute accuracy you require with respect to your reference point. (Time like space is measured from some predefined reference point).

Errors increase gradually as over time, as your clock source measurement diverges from the ideal, in the same way your wrist watch slowly diverges from UTC time + time zone. The accuracy of you clock source determines how fast this divergence take place.

The official Arduino AVR microcontrollers (Uno/Mega) only use a ceramic resonator, so as Danois90 says, they're not that accurate and drift with time and temperature. Using ceramic resonators you'll get plus or minus minutes or more of drift per day.

Some of the later ARM microcontrollers (Due/Zero/MKR) offer a 32.768kHz external crystal that can be used in conjuction with microcontroller's internal RTC. Using these crystals you'll get up to plus or minus 1 to 2 seconds of drift day.

Again as Danois90 pointed out, some external RTC chips such as the DS3231 can offer excellent temperature compensated time measurements with minimal drift.

If you're using an Arduino Uno and need to trigger real-time events during the day then an external RTC is the way to go.

1 Like

You are lucky man. :wink:
I got many DS3231 clones, at least 3 out of 20 and a monthly difference of more than 1 minute.
Of course, I try calibration it, but it couldn't. These are surprisingly missing some registers!
Write commands are ignored and read commands always return the same value.
Certainly some have excellent accuracy, but they loses accuracy in high temperatures condition such as inside a car.
Not surprisingly, there are probably many clone variants in China.

What has always mystified me is where the almost good clones come from.

Does someone be grabbing chips from bad manufacturing runs that sorta work but not if you shine a flashlight into every corner?

Seems like crooks go to more trouble than it would be to just run an honest business.

Not being much of a crook it is hard to say what the motives are.

a7

That is a new one to me. I've never seen a DS3231 clone, but the DS1302 is known to be cloned, that's been going on for years. Obviously, it's harder to make a DS3231 because the crystal has to be embedded in the package. Did they maybe combine a DS1302 clone with a crystal somehow and falsely label it? Hmmm.

A real DS3231 shouldn't lose accuracy with high temperatures, as it is temperature compensated.

1 minute per month is less than 1.0 PPM and is a reasonable expectation from a single chip TCXO. It would be hard to calibrate it for any higher performance than that, unless there are absolutely no environmental changes. Eventually it will age, too.

You are using FastLED. When updating the LEDs interrupts are disabled, which greatly affects the millis count.

1 Like