Go Down

### Topic: How accurate is millis()? (Read 3811 times)previous topic - next topic

#### azarur

##### Jan 23, 2015, 06:11 pm
Has anyone encountered issues with the accuracy of millis() when using interrupts? I am using 4 interrupts to measure (count signal from) digital encoders.  I have noted that my time measurement (after all i'm trying to measure #of counts/unit time for each encoder) seems to change depending on the number of interrupts in a period so that millis() no longer match an external chronometer for example.

I also noted that when I try to do the calculation such as:

int counter = 0;
int time = 0;
int RPM=0;

time = millis()/1000/60;
(counter is being updated each time a specific interrupt is triggered)
RPM = counter/time;

RPM either returns zeroes or a bunch of irrelevant numbers (v.g., -1, -3) when it is supposed to be calculating to 1,000 RPM.
The weird thing is that when i serial output time and counter the values are what is expected (v.g. counter = 2000; time = 2). What am i doing wrong?

#### DVDdoug

#1
##### Jan 23, 2015, 06:34 pm
I think it's accurate (within the accuracy of the 16mHz resonator).

But, the steps in your program take some time to execute, so you might not be reading the time when you think you are reading the time...

Quote
int time = 0;

time = millis()/1000/60;
time should probably be a float.   Have you checked the values you're getting for time?    You are going to get integer minutes (whole minutes).

#### azarur

#2
##### Jan 23, 2015, 06:43 pm
Yes.  I've tried everything... float, double, volatile and any combination thereof for time as well as for counter and RPM.  I still get this weird behavior. I'm wondering if it has to do with the interrupts.  The calculations are in the main loop, not as part of the interrupt calculations...

#### Delta_G

#3
##### Jan 23, 2015, 06:44 pm
Why would time need to be a float there?  That would lose accuracy for sure.  If you need more precise measurement, don't try to use floats and millis, just use micros instead.  It is good down to about 4us.
|| | ||| | || | ||  ~Woodstock

Please do not PM with technical questions or comments.  Keep Arduino stuff out on the boards where it belongs.

#### Delta_G

#4
##### Jan 23, 2015, 06:44 pm
OP, please post the whole code.  The error might not be where you think it is.

|| | ||| | || | ||  ~Woodstock

Please do not PM with technical questions or comments.  Keep Arduino stuff out on the boards where it belongs.

#### DVDdoug

#5
##### Jan 23, 2015, 06:57 pmLast Edit: Jan 23, 2015, 07:08 pm by DVDdoug
Quote
Why would time need to be a float there?  That would lose accuracy for sure.
Because if time is less than one minute, you're going to get zero-minutes.  Or, the time difference (which is what we should be interested in) is probably less than than one minute and that's not going to be useful either!

P.S.
It would probably be better to keep the time in milliseconds and include the milliseconds-to-minutes conversion as part of the RPM calculation.

#### Delta_G

#6
##### Jan 23, 2015, 07:06 pmLast Edit: Jan 23, 2015, 07:07 pm by Delta_G
It would be stupid to measure this in minutes.  If you need more precision, floats aren't the answer.  They are by their very nature lower precision than int types.  If you need more precision stick with int types and use smaller time divisions.  For example, if you need fractions of a millisecond, then use microseconds instead.  Then your fractional part comes out to a whole number and you're not stuck losing precision with the float variable.
|| | ||| | || | ||  ~Woodstock

Please do not PM with technical questions or comments.  Keep Arduino stuff out on the boards where it belongs.

#### Robin2

#7
##### Jan 23, 2015, 08:34 pm
millis() returns an unsigned long. If all the variables associated with it are also unsigned longs you have a better chaance of retaining precision.

It is often a good idea to manipulate integer values to improve precision. For example if there is enough headroom then multiplying by 100 is the same as adding 2 decimal places of precision. Do that BEFORE other calculations that will make the value smaller. Do the opposite if there is a risk of overflow. And look carefully at the order of the arithmetic so you don't unnecessarily lose precision. In some cases it is better to divide before multipying, in other cases vice versa.

...R
Two or three hours spent thinking and reading documentation solves most programming problems.

#### nickgammon

#8
##### Jan 23, 2015, 09:07 pmLast Edit: Jan 23, 2015, 09:08 pm by Nick Gammon
Has anyone encountered issues with the accuracy of millis() when using interrupts?
millis() drifts a bit, by design.

There is an interrupt (Timer 0 overflow interrupt) every 1024 µS which updates the variable used by millis().  Therefore millis() will be out by 24 µS after the first interrupt, 48 µS after the second interrupt, and so on. The code eventually compensates so this inaccuracy is not cumulative over time.

In other words, millis() will run slow (it should update every 1000 µS but actually updates every 1024 µS). However the overflow interrupt which is normally called keeps track of the amount it is out, and eventually adds one to compensate (and reduces the overflow amount to compensate). This will happen approximately every 42 overflows (interrupts). At this point, of course, the count returned by millis() will "jump" as this extra compensating amount is added in.

If you are timing small intervals, micros() will be much more accurate, as that reads from the hardware directly, and does not suffer from this creeping error. However it wraps after around 71 minutes. Also, micros has a resolution of 4 µS (not 1 µS) because of the way the timer is configured.

Please post technical questions on the forum, not by personal message. Thanks!

#### nickgammon

#9
##### Jan 23, 2015, 09:34 pm
This comes up from time to time so I've updated my page about millis overflows to mention this, and give a bit more background arithmetic: http://www.gammon.com.au/millis

Having said all that, this is wrong, of course:

Quote
time = millis()/1000/60;
http://www.gammon.com.au/forum/?id=12146
Please post technical questions on the forum, not by personal message. Thanks!