I need to read a sensor every five minutes, but since my sketch also has
other tasks to do, I cannot just delay()
between the readings. There
is the Blink without
delay tutorial
suggesting I code along these lines:
void loop()
{
unsigned long currentMillis = millis();
// Read the sensor when needed.
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
readSensor();
}
// Do other stuff...
}
The problem is that millis()
is going to roll over back to zero after
roughly 49.7 days. Since my sketch is intended to run for longer
than that, I need to make sure the rollover does not make my sketch
fail. I can easily detect the rollover condition
(currentMillis < previousMillis
), but I am not sure what to do then.
Thus my question: what would be the proper/simplest way to handle the
millis()
rollover?
4 Answers 4
Short answer: do not try to "handle" the millis rollover, write rollover-safe code instead. Your example code from the tutorial is fine. If you try to detect the rollover in order to implement corrective measures, chances are you are doing something wrong. Most Arduino programs only have to manage events that span relatively short durations, like debouncing a button for 50 ms, or turning a heater on for 12 hours... Then, and even if the program is meant to run for years at a time, the millis rollover should not be a concern.
The correct way to manage (or rather, avoid having to manage) the
rollover problem is to think of the unsigned long
number returned by
millis()
in terms of modular
arithmetics. For the
mathematically inclined, some familiarity with this concept is very
useful when programming. You can see the math in action in Nick Gammon's
article millis() overflow ... a bad
thing?. For the problem at hand,
what's important to know is that in modular arithmetics the numbers
"wrap around" when reaching a certain value – the modulus – so that
1 − modulus is not a negative number but 1 (think of a
12 hour clock where the modulus is 12: here 1 − 12 = 1).
For those who do not want to go through the computational details, I offer here an alternative (hopefully simpler) way of thinking about it. It is based on the simple distinction between instants and durations. As long as your tests only involve comparing durations, you should be fine.
Note on micros(): Everything said here about millis()
applies
equally to micros()
, except for the fact that micros()
rolls over
every 71.6 minutes, and the setMillis()
function provided below
does not affect micros()
.
Instants, timestamps and durations
When dealing with time, we have to make the distinction between at least two different concepts: instants and durations. An instant is a point on the time axis. A duration is the length of a time interval, i.e. the distance in time between the instants that define the start and the end of the interval. The distinction between these concepts is not always very sharp in everyday language. For example, if I say "I will be back in five minutes", then "five minutes" is the estimated duration of my absence, whereas "in five minutes" is the instant of my predicted coming back. Keeping the distinction in mind is important, because it is the simplest way to entirely avoid the rollover problem.
The return value of millis()
could be interpreted as a duration: the
time elapsed from the start of the program until now. This
interpretation, however, breaks down as soon as millis overflows. It is
generally far more useful to think of millis()
as returning a
timestamp, i.e. a "label" identifying a particular instant. It could
be argued that this interpretation suffers from these labels being
ambiguous, as they are reused every 49.7 days. This is, however,
seldom a problem: in most embedded applications, anything that happened
49.7 days ago is ancient history we do not care about. Thus,
recycling the old labels should not be an issue.
Do not compare timestamps
Trying to find out which among two timestamps is greater than the other does not make sense. Example:
unsigned long t1 = millis();
delay(3000);
unsigned long t2 = millis();
if (t2 > t1) { ... }
Naively, one would expect the condition of the if ()
to be always
true. But it will actually be false if millis overflows during
delay(3000)
. Thinking of t1 and t2 as recyclable labels is the
simplest way to avoid the error: the label t1 has clearly been assigned
to an instant prior to t2, but in 49.7 days it will be reassigned
to a future instant. Thus, t1 happens both before and after t2. This
should make clear that the expression t2 > t1
makes no sense.
But, if these are mere labels, the obvious question is: how can we do any useful time calculations with them? The answer is: by restricting ourselves to the only two calculations that make sense for timestamps:
later_timestamp - earlier_timestamp
yields a duration, namely the amount of time elapsed between the earlier instant and the later instant. This is the most useful arithmetic operation involving timestamps.timestamp ± duration
yields a timestamp which is some time after (if using +) or before (if −) the initial timestamp. Not as useful as it sounds, since the resulting timestamp can be used in only two kinds of calculations...
Thanks to modular arithmetics, both of these are guaranteed to work fine across the millis rollover, at least as long as the delays involved are shorter than 49.7 days.
Comparing durations is fine
A duration is just the amount of milliseconds elapsed during some time
interval. As long as we do not need to handle durations longer than
49.7 days, any operation that physically makes sense should also
make sense computationally. We can, for example, multiply a duration by
a frequency to get a number of periods. Or we can compare two durations
to know which one is longer. For example, here are two alternative
implementations of delay()
. First, the buggy one:
void myDelay(unsigned long ms) { // ms: duration
unsigned long start = millis(); // start: timestamp
unsigned long finished = start + ms; // finished: timestamp
for (;;) {
unsigned long now = millis(); // now: timestamp
if (now >= finished) // comparing timestamps: BUG!
return;
}
}
And here is the correct one:
void myDelay(unsigned long ms) { // ms: duration
unsigned long start = millis(); // start: timestamp
for (;;) {
unsigned long now = millis(); // now: timestamp
unsigned long elapsed = now - start; // elapsed: duration
if (elapsed >= ms) // comparing durations: OK
return;
}
}
Most C programmers would write the above loops in a terser form, like
while (millis() < start + ms) ; // BUGGY version
and
while (millis() - start < ms) ; // CORRECT version
Although they look deceptively similar, the timestamp/duration distinction should make clear which one is buggy and which one is correct.
What if I really need to compare timestamps?
Better try to avoid the situation. If it is unavoidable, there is still hope if it is known that the respective instants are close enough: closer than 24.85 days. Yes, our maximum manageable delay of 49.7 days just got cut in half.
The obvious solution is to convert our timestamp comparison problem into a duration comparison problem. Say we need to know whether instant t1 is before or after t2. We choose some reference instant in their common past, and compare the durations from this reference until both t1 and t2. The reference instant is obtained by subtracting a long enough duration from either t1 or t2:
unsigned long reference_instant = t2 - LONG_ENOUGH_DURATION;
unsigned long from_reference_until_t1 = t1 - reference_instant;
unsigned long from_reference_until_t2 = t2 - reference_instant;
if (from_reference_until_t1 < from_reference_until_t2)
// t1 is before t2
This can be simplified as:
if (t1 - t2 + LONG_ENOUGH_DURATION < LONG_ENOUGH_DURATION)
// t1 is before t2
It is tempting to simplify further into if (t1 - t2 < 0)
. Obviously,
this does not work, because t1 - t2
, being computed as an unsigned
number, cannot be negative. This, however, although not portable, does
work:
if ((signed long)(t1 - t2) < 0) // works with gcc
// t1 is before t2
The keyword signed
above is redundant (a plain long
is always
signed), but it helps make the intent clear. Converting to a signed long
is equivalent to setting LONG_ENOUGH_DURATION
equal to
24.85 days. The trick is not portable because, according to the C
standard, the result is implementation defined. But since the gcc
compiler promises to do the right
thing,
it works reliably on Arduino. If we wish to avoid implementation defined
behavior, the above signed comparison is mathematically equivalent to
this:
#include <limits.h>
if (t1 - t2 > LONG_MAX) // too big to be believed
// t1 is before t2
with the only problem that the comparison looks backwards. It is also equivalent, as long as longs are 32-bits, to this single-bit test:
if ((t1 - t2) & 0x80000000) // test the "sign" bit
// t1 is before t2
The last three tests are actually compiled by gcc into the exact same machine code.
How do I test my sketch against the millis rollover
If you follow the precepts above, you should be all good. If you nevertheless want to test, add this function to your sketch:
#include <util/atomic.h>
void setMillis(unsigned long ms)
{
extern unsigned long timer0_millis;
ATOMIC_BLOCK (ATOMIC_RESTORESTATE) {
timer0_millis = ms;
}
}
and you can now time-travel your program by calling
setMillis(destination)
. If you want it to go through the millis
overflow over and over again, like Phil Connors reliving Groundhog Day,
you can put this inside loop()
:
// 6-second time loop starting at rollover - 3 seconds
if (millis() - (-3000) >= 6000)
setMillis(-3000);
The negative timestamp above (-3000) is implicitly converted by the compiler to an unsigned long corresponding to 3000 milliseconds before the rollover (it is converted to 4294964296).
What if I really need to track very long durations?
If you need to turn a relay on and turn it off three months later, then
you really need to track the millis overflows. There are many ways to do
so. The most straightforward solution may be to simply extend millis()
to 64 bits:
uint64_t millis64() {
static uint32_t low32, high32;
uint32_t new_low32 = millis();
if (new_low32 < low32) high32++;
low32 = new_low32;
return (uint64_t) high32 << 32 | low32;
}
This is essentially counting the rollover events, and using this count
as the 32 most significant bits of a 64 bit millisecond count.
For this counting to work properly, the function needs to be called at
least once every 49.7 days. However, if it is only called once per 49.7 days, for some cases it is possible that the check (new_low32 < low32)
fails and the code misses a count of high32
. Using millis() to decide when to make the only call to this code in a single "wrap" of millis (a specific 49.7 day window) could be very hazardous, depending on how the time frames line up. For safety, if using millis() to determine when to make the only calls to millis64(), there should be at least two calls in every 49.7 day window.
Keep in mind, though, that 64 bit arithmetic is expensive on the Arduino. It may be worth to reduce the time resolution in order to stay at 32 bits.
-
4So, you are saying that the code written in the question will actually work correctly?Jasen– Jasen2016年02月07日 09:48:57 +00:00Commented Feb 7, 2016 at 9:48
-
8@Jasen: Exactly! I've seem more than once people trying to "fix" the problem that did not exist in the first place.Edgar Bonet– Edgar Bonet2016年02月07日 10:00:20 +00:00Commented Feb 7, 2016 at 10:00
-
2I'm glad I found this. I've had this question before.Sebastian Freeman– Sebastian Freeman2016年07月02日 01:28:13 +00:00Commented Jul 2, 2016 at 1:28
-
3One of the best and most useful answers on StackExchange! Thanks a lot! :)Falko– Falko2018年05月18日 12:43:37 +00:00Commented May 18, 2018 at 12:43
-
2This is such an amazing answer to the question. I come back to this answer basically once a year because I am paranoid of messing rollovers.Jeffrey Cash– Jeffrey Cash2019年09月04日 16:41:45 +00:00Commented Sep 4, 2019 at 16:41
TL;DR Short version:
An unsigned long
is 0 to 4,294,967,295 (2^32 - 1).
So lets say previousMillis
is 4,294,967,290 (5 ms before rollover), and currentMillis
is 10 (10ms after rollover). Then currentMillis - previousMillis
is actual 16 (not -4,294,967,280) since the result will be calculated as an unsigned long (which can't be negative, so itself will roll around). You can check this simply by:
Serial.println( ( unsigned long ) ( 10 - 4294967290 ) ); // 16
So the above code will work perfectly fine. The trick is to always calculate the time difference, and not compare the two time values.
-
How about a 15ms before rollover and a 10ms after rollover (ie 49.7 days after). 15 > 10, but the 15ms stamp is nearly a month and a half old. 15-10 > 0 and 10-15 > 0
unsigned
logic, so that is no use here!xyz– xyz2015年06月12日 21:15:22 +00:00Commented Jun 12, 2015 at 21:15 -
@prakharsingh95 10ms-15ms will become ~49.7 days - 5ms, which is the correct difference. The math works until
millis()
rolls over twice, but that is very unlikely to occur to the code in question.BrettFolkins– BrettFolkins2015年06月12日 22:16:57 +00:00Commented Jun 12, 2015 at 22:16 -
Let me rephrase. Suppose you have two timestamps 200ms and 10ms. How do you tell which is(are) rolled over?xyz– xyz2015年06月12日 22:28:47 +00:00Commented Jun 12, 2015 at 22:28
-
@prakharsingh95 The one stored in
previousMillis
has to have been measured beforecurrentMillis
, so ifcurrentMillis
is smaller thanpreviousMillis
a rollover occurred. The math happens to work out that unless two rollovers have occurred, you don't even need to think about it.BrettFolkins– BrettFolkins2015年06月12日 22:35:56 +00:00Commented Jun 12, 2015 at 22:35 -
1Ah, ok. if you do
t2-t1
, and if you can guaranteet1
is measured beforet2
then it's equivalent to signed(t2-t1)% 4,294,967,295
, hence the auto wraparound. Nice!. But what if there are two rollovers, orinterval
is > 4,294,967,295?xyz– xyz2015年06月12日 23:11:32 +00:00Commented Jun 12, 2015 at 23:11
I loved this question, and the great answers it generated. First a quick comment on a previous answer (I know, I know, but I don't have the rep to comment yet. :-).
Edgar Bonet's answer was amazing. I've been coding for 35 years, and I learned something new today. Thank you. That said, I believe the code for "What if I really need to track very long durations?" breaks unless you call millis64() at least once per rollover period. Really nitpicky, and unlikely to be an issue in a real-world implementation, but there you go.
Now, if you really did want timestamps covering any sane time range (64-bits of milliseconds is about half a billion years by my reckoning), it looks simple to extend the existing millis() implementation to 64 bits.
These changes to attinycore/wiring.c (I'm working with the ATTiny85) seem to work (I'm assuming the code for other AVRs is very similar). See the lines with the //BFB comments, and the new millis64() function. Clearly it's going to be both bigger (98 bytes of code, 4 bytes of data) and slower, and as Edgar pointed out, you almost certainly can accomplish your goals with just a better understanding of unsigned integer math, but it was an interesting exercise.
volatile unsigned long long timer0_millis = 0; // BFB: need 64-bit resolution
#if defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ISR(TIM0_OVF_vect)
#else
ISR(TIMER0_OVF_vect)
#endif
{
// copy these to local variables so they can be stored in registers
// (volatile variables must be read from memory on every access)
unsigned long long m = timer0_millis; // BFB: need 64-bit resolution
unsigned char f = timer0_fract;
m += MILLIS_INC;
f += FRACT_INC;
if (f >= FRACT_MAX) {
f -= FRACT_MAX;
m += 1;
}
timer0_fract = f;
timer0_millis = m;
timer0_overflow_count++;
}
// BFB: 64-bit version
unsigned long long millis64()
{
unsigned long long m;
uint8_t oldSREG = SREG;
// disable interrupts while we read timer0_millis or we might get an
// inconsistent value (e.g. in the middle of a write to timer0_millis)
cli();
m = timer0_millis;
SREG = oldSREG;
return m;
}
-
5You are right, my
millis64()
only works if it is called more frequently than the rollover period. I edited my answer to point out this limitation. Your version does not have this issue, but it carries another drawback: it does 64-bit arithmetics in interrupt context, which occasionally increases the latency in responding to other interrupts.Edgar Bonet– Edgar Bonet2018年06月08日 17:14:22 +00:00Commented Jun 8, 2018 at 17:14
Wrap the millis()
in a class!
Logic:
- Use id's instead of
millis()
directly. - Compare reversals using id's. This is clean and rollover independent.
- For specific applications, to calculate exact difference between two id's, keep track of reversals and stamps. Calculate the difference.
Keeping track of reversals:
- Update a local stamp periodically faster than
millis()
. This will help you find out ifmillis()
has overflown. - The period of the timer determines accuracy
class Timer {
public:
static long last_stamp;
static long *stamps;
static int *reversals;
static int count;
static int reversal_count;
static void setup_timer() {
// Setup Timer2 overflow to fire every 8ms (125Hz)
// period [sec] = (1 / f_clock [sec]) * prescale * (255-count)
// (1/16000000) * 1024 * (255-130) = .008 sec
TCCR2B = 0x00; // Disable Timer2 while we set it up
TCNT2 = 130; // Reset Timer Count (255-130) = execute ev 125-th T/C clock
TIFR2 = 0x00; // Timer2 INT Flag Reg: Clear Timer Overflow Flag
TIMSK2 = 0x01; // Timer2 INT Reg: Timer2 Overflow Interrupt Enable
TCCR2A = 0x00; // Timer2 Control Reg A: Wave Gen Mode normal
TCCR2B = 0x07; // Timer2 Control Reg B: Timer Prescaler set to 1024
count = 0;
stamps = new long[50];
reversals = new int [10];
reversal_count =0;
}
static long get_stamp () {
stamps[count++] = millis();
return count-1;
}
static bool compare_stamps_by_id(int s1, int s2) {
return s1 > s2;
}
static long long get_stamp_difference(int s1, int s2) {
int no_of_reversals = 0;
for(int j=0; j < reversal_count; j++)
if(reversals[j] < s2 && reversals[j] > s1)
no_of_reversals++;
return stamps[s2]-stamps[s1] + 49.7 * 86400 * 1000;
}
};
long Timer::last_stamp;
long *Timer::stamps;
int *Timer::reversals;
int Timer::count;
int Timer::reversal_count;
ISR(TIMER2_OVF_vect) {
long stamp = millis();
if(stamp < Timer::last_stamp) // reversal
Timer::reversals[Timer::reversal_count++] = Timer::count;
else
; // no reversal
Timer::last_stamp = stamp;
TCNT2 = 130; // reset timer ct to 130 out of 255
TIFR2 = 0x00; // timer2 int flag reg: clear timer overflow flag
};
// Usage
void setup () {
Timer::setup_timer();
long s1 = Timer::get_stamp();
delay(3000);
long s2 = Timer::get_stamp();
Timer::compare_stamps_by_id(s1, s2); // true
Timer::get_stamp_difference(s1, s2); // return true difference, taking into account reversals
}
-
10I edited the code to remove the maaaaany errors that prevented it to compile. This stuff will cost you about 232 bytes of RAM and two PWM channels. It will also start to corrupt the memory after you
get_stamp()
51 times. Comparing delays instead of timestamps will certainly be more efficient.Edgar Bonet– Edgar Bonet2015年06月13日 13:00:55 +00:00Commented Jun 13, 2015 at 13:00 -
Isn't that full static class effectively like namespace?AgainPsychoX– AgainPsychoX2023年08月10日 10:50:52 +00:00Commented Aug 10, 2023 at 10:50
previousMillis += interval
instead ofpreviousMillis = currentMillis
if I wanted a certain frequency of results.previousMillis += interval
if you want constant frequency and are sure that your processing takes less thaninterval
, butpreviousMillis = currentMillis
for guaranteeing a minimum delay ofinterval
.uint16_t previousMillis; const uint16_t interval = 45000; ... uint16_t currentMillis = (uint16_t) millis(); if ((currentMillis - previousMillis) >= interval) ...