Go Down

### Topic: Clock with Arduino (Read 12258 times)previous topic - next topic

#### joeSeggiola

##### Jul 06, 2008, 11:59 am
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:

Code: [Select]
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.

#### bens

#1
##### Jul 06, 2008, 12:41 pm
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

#### joeSeggiola

#2
##### Jul 06, 2008, 12:50 pm
Quote
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...

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

Quote
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!

#### bens

#3
##### Jul 06, 2008, 01:14 pmLast Edit: Jul 06, 2008, 01:16 pm by bens Reason: 1
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

#### joeSeggiola

#4
##### Jul 06, 2008, 01:37 pm
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...

#### bens

#5
##### Jul 06, 2008, 02:07 pmLast Edit: Jul 06, 2008, 02:10 pm by bens Reason: 1
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

#### mem

#6
##### Jul 06, 2008, 02:13 pm
Joe, if you are building a clock you may want to look at the library here: http://www.arduino.cc/playground/Code/DateTime

#### joeSeggiola

#7
##### Jul 06, 2008, 07:02 pm
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:
Code: [Select]

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

#### mem

#8
##### Jul 06, 2008, 09:07 pm
Quote

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

#### joeSeggiola

#9
##### Jul 06, 2008, 10:57 pm
Quote
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
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...

#### bens

#10
##### Jul 07, 2008, 01:08 am
Quote
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

#### joeSeggiola

#11
##### Jul 07, 2008, 10:28 amLast Edit: Jul 07, 2008, 10:29 am by joeSeggiola Reason: 1
Quote
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:
Code: [Select]
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:
Code: [Select]
#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!

#### mem

#12
##### Jul 07, 2008, 11:24 am
Quote
Quote
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 this page is this part .

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.

#### joeSeggiola

#13
##### Jul 07, 2008, 11:59 am
Quote
I am surprised to hear you are experiencing errors an order of magnitude greater.

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

#### mem

#14
##### Jul 07, 2008, 12:29 pmLast Edit: Jul 07, 2008, 12:31 pm by mem Reason: 1
Quote
Quote
I am surprised to hear you are experiencing errors an order of magnitude greater.

Bad luck...   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:

Code: [Select]

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.

Go Up