Clock with Arduino

Hi everyone, I'm new on Arduino forum.

I would like to realize a clock (hh:mm:ss) with Arduino Diecimila. I've found this code on the web, that should work also with the ~9h millis() overflow:

unsigned long current_millis_value = 0;
unsigned long previous_millis_value = 0;
unsigned long m = 0;
unsigned int seconds = 0;
unsigned int minutes = 0;
unsigned int hours = 0;

void loop() {
  current_millis_value = millis();
  m += current_millis_value - previous_millis_value;
  seconds += m / 1000;
  m = m % 1000;
  minutes += seconds / 60;
  seconds = seconds % 60;
  hours += minutes / 60;
  minutes = minutes % 60;
  hours = hours % 24;
  previous_millis_value = current_millis_value; 
  delay(150);
}

With some tests I notice that Arduino timer is not so precise: every hour it run 1 seconds faster than real time. So, if Arduino starts at 00:00:00, at 02:00:00 (real time), Arduino says 02:00:01. Is it normal???
This problem can be solved by reducing error: every 2 hours, I decrease the seconds variable...

But there is another problem, that really I can't understand! At the precise moment of overflow, the time (started at 00:00:00) goes from 09:32:39 to 09:45:27! Why?!?! Please, help me!

Thank you, Lorenzo.

Your code doesn't correctly handle the millis() overflow because the overflow doesn't occur on an even data boundary. You should check to see when current_millis_value is less than previous_millis_value, at which point you should be able to increment m by 1 (since your loop should be fast enough to catch the overflow before more than a millisecond has elapsed). Or, if you want to be completely accurate, you can increment m by:

MAX_MILLIS_VALUE - previous_millis_value + current_millis_value

Also, you should disable interrupts while calling millis(), otherwise you run the risk of getting corrupted data should the timer0 overflow occur while millis() is performing its computation:

cli(); // disable interrupts
current_millis_value = millis();
sei(); // enable interrupts

And yes, if the uncertainty of your crystal is 0.05%, you can expect a worst case error of around 2 seconds per hour, but this error will be a constant that you can calibrate away.

  • Ben

Your code doesn't correctly handle the millis() overflow because the overflow doesn't occur on an even data boundary. You should check to see when current_millis_value is less than previous_millis_value, at which point you should be able to increment m by 1 (since your loop should be fast enough to catch the overflow before more than a millisecond has elapsed). Or, if you want to be completely accurate, you can increment m by:
MAX_MILLIS_VALUE - previous_millis_value + current_millis_value

Ok! I've completely understand your explanation! In fact, ~9h does not correnspond to 2^32-1 milliseconds...
And what is the MAX_MILLIS_VALUE? How can I get it? I search the forum: someone says that:
"it would appear that the millis() overflow occurs at 34,359,739ms or 9h, 32m, 39s, and 739ms"
There is a way to calculate it? This seems a result of a test, not of a precise calculation...

Also, you should disable interrupts while calling millis(), otherwise you run the risk of getting corrupted data should the timer0 overflow occur while millis() is performing its computation:
cli(); // disable interrupts
current_millis_value = millis();
sei(); // enable interrupts

I will.

And yes, if the uncertainty of your crystal is 0.05%, you can expect a worst case error of around 2 seconds per hour, but this error will be a constant that you can calibrate away.

Ok, perfect.

Thank you Ben!

You can find the millis() and timer0 overflow code in wiring.c in your arduino-0011/hardware/cores/arduino directory, so you should be able to compute from this what the maximum overflow value is.

Actually, since I just went to look for the code myself it was easy enough for me to just calculate the value for you. millis() will reach a maximum value of:

33554431

before overflowing back to zero. This comes out to 9.320675 hours. But as I said, your loop is tight enough that you can probably just detect the overflow and assume 1 ms has elapsed. This will almost always be true, but there is a slim chance that this could cause you to be 1 ms off (and losing one millisecond every 9.3 hours is not going to be that big of a deal).

  • Ben

I've found wiring.c, but I can't understand how you have calculated your maximum value... Sorry, can you explain me?

About the overflow detection... My loop has got a delay of 150ms, so the maximum error is 150ms every 9h, not only 1ms...

Is there a particular reason why you have a 150 ms delay in your main loop? It doesn't seem like it's necessary, but maybe I'm missing something.

Also, I made a miscalculation of the maximum value of millis(). I computed the number of timer0 overflows before millis() overflows, which isn't quite the same thing as the number of milliseconds since one timer0 overflow corresponds to 1.024 ms. To perform the calculation, note that millis() returns:

64UL * 2UL * timer0_overflow_count / (F_CPU / 128000UL)

The numerator will overflow when it exceeds the maximum value that can be stored in an unsigned long (a four-byte number), which is 256^4 - 1. So what you have is that when

timer0_overflow_count = 256^4 / 64 / 2 = 33554432

millis() has overflowed back to zero, since the numerator has become zero.

Therefore, the maximum value that millis() can return occurs when timer0_overflow_count = 33554431, which results in a millis() of:

33554431 * 64 * 2 / (F_CPU / 128000) = 33554431 / (16 MHz / 128000) = 34359737

This would correspond to 9.544 hours.

  • Ben

Joe, if you are building a clock you may want to look at the library here: Arduino Playground - DateTime

I have a 150ms delay because for my clock I will have to use some SIPO registers. I have to do some calculation, and then store them in registers. This will take some time, aprox 150ms. So, I have to consider it...

About the calculation on millis() overflow: thank you. Now it's clear.

Now my Arduino is running a program like:

void loop() {
  cli(); current_millis_value = millis(); sei(); // Get millis();
  if (current_millis_value < previous_millis_value) { // If overflow
    // Save previous_millis_value in EEPROM
    // Save current_millis_value in EEPROM
  }
  delay(1);
}

With this I can verify pratically the exact limit of millis(). In 6 hours I can get the result ;D
I will let you know if your calculation are ok. Thank you Ben!

Mem, your library is good, but it's not for me. Arduino clock is not so precise, as I said in this topic. So I need some direct control over the algorithm that keeps time. And I can't use PC often to sincronize time and date... Thank you anyway.

... Arduino clock is not so precise, as I said in this topic. So I need some direct control over the algorithm that keeps time.

The arduino clock is as accurate as the crystal, which in my experience is within 2 seconds per day of an atomic clock standard. The primary problem with millis is how it rolls over, something that is being fixed in version 12.

The arduino clock is as accurate as the crystal, which in my experience is within 2 seconds per day of an atomic clock standard. The primary problem with millis is how it rolls over, something that is being fixed in version 12.

My Arduino runs 1 second faster every two hour... That is, how Ben said, 0.05% error, the worst I can hope :smiley:
About the rollover, for the moment I will do as Ben advised me. I've already written the full code. Tomorrow I will do a final test...

The arduino clock is as accurate as the crystal, which in my experience is within 2 seconds per day of an atomic clock standard.

I can see this as possible for a given Arduino, but I don't think it's something one should expect given that this would correspond to an accuracy of 0.002%, which is an order of magnitude smaller than I'm used to seeing for crystals. Do you know what the specs say for the accuracy of the crystal used on Arduinos?

  • Ben

Do you know what the specs say for the accuracy of the crystal used on Arduinos?

No, I don't know. I've only measured time difference with a stopwatch, looking at Arduino and an atomic clock via internet. I can be a little imprecise, but over 6 hours of measuring, results are clear:

0h       0.00s
1h      -0.70s
2h      -1.10s
3h      -1.60s
4h      -2.00s
5h      -2.40s
6h      -3.00s
7h      -3.50s

This morning I read result of the overflow test from my Arduino EEPROM. It's exactly the value you calculate: 34359737 before overflow, 0 after overflow. So I will use:

#define MAX_MILLIS_VALUE 34359738
...
if (current_millis_value < previous_millis_value)
     m += MAX_MILLIS_VALUE - previous_millis_value + current_millis_value;
else m += current_millis_value - previous_millis_value;

On the simpler overflow (from 34359737 to 0):
MAX_MILLIS_VALUE - previous_millis_value + current_millis_value = 34359738 - 34359737 + 0 = 1
1 millisecond is elapsed.
Thank you Ben! :wink:

The arduino clock is as accurate as the crystal, which in my experience is within 2 seconds per day of an atomic clock standard.

I can see this as possible for a given Arduino, but I don't think it's something one should expect given that this would correspond to an accuracy of 0.002%, which is an order of magnitude smaller than I'm used to seeing for crystals. Do you know what the specs say for the accuracy of the crystal used on Arduinos?

  • Ben

Crystals of the type used on the Arduino have a specified frequency tolerance of 30ppm.
For example the Arduino crystal specified on [u]this page[/u] is [u]this part[/u] .

30ppm translates to 2.5 seconds per day if the crystal where at the limit of its specified tolerance. I am surprised to hear you are experiencing errors an order of magnitude greater.

I am surprised to hear you are experiencing errors an order of magnitude greater.

Bad luck... :cry: But however I can reduce this error with some lines of code...

I am surprised to hear you are experiencing errors an order of magnitude greater.

Bad luck... :cry: But however I can reduce this error with some lines of code...

hmm, I wonder if its because of the way you are accumulating time, it looks like you could be loosing some milliseconds on each loop. Could you try something like this:

unsigned long previous_millis_value = 0;
unsigned long cumulativeSeconds = 0;
unsigned int seconds = 0;
unsigned int minutes = 0;
unsigned int hours = 0;

void setup(){
  
}

void loop() {
  while( millis() - previous_millis_value >= 1000){
    cumulativeSeconds++;
    previous_millis_value += 1000;
  }
  second = cumulativeSeconds % 60;
  minute = (cumulativeSeconds / 60) % 60;
  hour   cumulativeSeconds  / 3600;
  delay(150);
}

It will still have the rollover problem but I would be interested to see if it is accurate before it rolls over.

I don't think I loose ms on each loop. Let's see the first code I post:

  current_millis_value = millis();
  m += current_millis_value - previous_millis_value;
  seconds += m / 1000;
  m = m % 1000;
  minutes += seconds / 60;
  seconds = seconds % 60;
  hours += minutes / 60;
  minutes = minutes % 60;
  hours = hours % 24;
  previous_millis_value = current_millis_value;

If you think about it, you will notice that m is a "cumulative-ms" variable. On every loop, I increase seconds by m / 1000, but then I do m = m % 1000 in order to not loose left-over ms... Right?

Joe, you are quite right that its not your code that is causing time errors but I remain surprised that your crystal is so inaccurate. I have just completed a test using a third arduino board (as I mentioned, two other boards were tested to be accurate to within a few seconds per day measured over many days) to see if I could reproduce the kind of results you posted.

My test on the third board has been running for over 7 and a half hours and the arduino is within on second of internet checked atomic time. So I am not sure why your results are so different to mine, and why they seem to be outside the spec for a 30ppm crystal.

Hi Joe, update on the arduino time test: My test has been running for over 24 hours with the arduino clock remaining within two seconds of an external time reference over that period. Thats three different boards verified as keeping time within about 2 secs per day.

Is your board using a resonator instead of a crystal?

Is your board using a resonator instead of a crystal?

Umh, I don't know... Mine is an Arduino Diecimila...

Do you know what the specs say for the accuracy of the crystal used on Arduinos?

No, I don't know. I've only measured time difference with a stopwatch, looking at Arduino and an atomic clock via internet. I can be a little imprecise, but over 6 hours of measuring, results are clear:

0h       0.00s

1h      -0.70s
2h      -1.10s
3h      -1.60s
4h      -2.00s
5h      -2.40s
6h      -3.00s
7h      -3.50s




This morning I read result of the overflow test from my Arduino EEPROM. It's exactly the value you calculate: **34359737** before overflow, **0** after overflow. So I will use:


#define MAX_MILLIS_VALUE 34359738
...
if (current_millis_value < previous_millis_value)
    m += MAX_MILLIS_VALUE - previous_millis_value + current_millis_value;
else m += current_millis_value - previous_millis_value;



On the simpler overflow (from 34359737 to 0):
<em>MAX_MILLIS_VALUE - previous_millis_value + current_millis_value = 34359738 - 34359737 + 0 **= 1**</em>
1 millisecond is elapsed.
Thank you Ben! ;)

Nice post indeed. I though I could understand the question in snap... no way :wink:
Question is: why nobody uses the modulo function in order to have a smooth constant elapsed time calculation? Kind of:

elapsed_time=(max_counts+this_tick-last_tick)%max_counts ?

Am I misleading?