Arduino programming guide series
How to deal with the millis rollover
The Arduino contains a 32-bit register that is actually a counter. It counts the number of milliseconds elapsed since the time you powered-up the Arduino. We use this counter to count time. But, what happens when the counter reaches its maximum value? Let's figure it out with the help of an example.
The image above is borrowed from the Atmega328P datasheet, page 24. It depicts the clock distribution block diagram.
In a separate article in this series, I explained that the problem with the delay() function is that it blocks execution. As a result, your Arduino is stuck at counting milliseconds instead of doing useful work.
A solution to the delay() blocking problem looks like this:
if (currentMillis - previousMillis >== interval) {
previousMillis = currentMillis;
//... work to be done periodically goes here
}
... where previousMillis is an unsigned int that stores reading from the last call to millis(), and currentMillis contains the millis() reading for this check.
Apart from the additional code that you need to support this option, one significant problem you have to deal with is that the millis register will roll-over after around 50 days. That means that its register (that holds an unsigned long has a width of 4 bytes) will return to zero, and then start counting again towards its maximum value, 4,294,967,295 (= 2^32)
So how can we deal with this problem?
It turns out that this is not a problem at all. This code can deal with the millis register rollover without any modification.
Let's have a quick look at why it works, by considering a rollover situation.
The millis register is 4 bytes in width, so the largest unsigned number it can hold is:
11111111 11111111 11111111 11111111.
Let's say that we are interested in tracking a duration of 10 seconds.
That's 10,000 millis.
This duration, in binary, is (I keep the duration value also as a long int):
00000000 00000000 00100111 00010000
In the next couple of steps, I will calculate the difference between the current millis and the last millis at two times:
- before the rollover,
- after the rollover.
Before the rollover, the previousMillis variable will contain a number smaller than the millis max, and smaller than the current max.
Let's make:
previousMillis = 11111111 11111111 10110001 11011111
In the decimal system, previousMillis contains 4,294,947,295.
If we test the elapsed time at 5,000 millis after previous Millis was taken, we will have this subtraction:
111111111 11111111 10001010 1100111 - 11111111 11111111 10110001 11011111 = 1001110001000
In decimal notation, this is:
3,000 - 4,294,947,295 = 23,001 > 10,000
So, the duration of 10 seconds has elapsed and despite the rollover, we were able to detect it.
Remember that these values are unsigned, so the subtraction does not yield a negative number, but a positive that is the result of the difference between 4,294,947,295 and 4,294,967,295 (the max value for a 4-byte unsigned number) plus 3,000.
If all this looks complicated and you are not convinced that the rollover can be dealt with without any special provisions, use your Arduino and run this sketch on it:
00000000 00000000 00001011 10111000 - 11111111 11111111 10110001 11011111 = 00000000 00000000 01011001 11011001
In decimal, this is:
3,000 - 4,294,947,295 = 23,001 > 10,000
So, the duration of 10 seconds has elapsed. Despite the rollover, we were able to detect this. Remember that these values are unsigned, so the subtraction does not yield a negative number, but a positive that is the result of the difference between 4,294,947,295 and 4,294,967,295 (the max value for a 4-byte unsigned number) PLUS 3,000.
If all this looks too complicated and you are not convinced that the rollover can be dealt with without any special provisions, use your Arduino and run this sketch on it:
void setup() {
Serial.begin(9600);
// Prior to rolling over
unsigned long currentMillisA = 4294952295;
// An earlier time than currentMillis
unsigned long lastMillisA = 4294947295;
Serial.print("1. Difference: ");
// Find out the difference
// between the two times
Serial.println(currentMillisA-lastMillisA);
Serial.println();
// Rolled over
unsigned long currentMillisB = 3000;
// Prior to rolling over
unsigned long lastMillisB = 4294947295;
Serial.print("2. Difference: ");
// Find out the difference
// between the two times
Serial.println(currentMillisB-lastMillisB);
}
void loop() {
}
This sketch does the same calculation I described earlier.
In the first subtraction, both current millis and last millis are before the rollover.
In the second subtraction, the millis register has rolled over, and the current millis is 3000.
Feel free to try out different value to see how they are handled.
New to the Arduino?
Arduino Step by Step Getting Started is our most popular course for beginners.
This course is packed with high-quality video, mini-projects, and everything you need to learn Arduino from the ground up. We'll help you get started and at every step with top-notch instruction and our super-helpful course discussion space.
Jump to another article
3. Focus on the type parameter in "println()"
4. "0" or "A0" when used with analogRead()?
5. What is the "_t" in "uint8_t"?
6. Save SRAM with the F() macro
7. What is the gibberish in the Telnet output?
9. Confusing keywords? follow the source code trail
10. The interrupt service routine and volatile variables
11. The problem with delay() and how to fix it
12. How to deal with the millis rollover
13. Can you use delay() inside Interrupt Service Routine?
15. A closer look at line feeds and carriage returns
16. Understanding references and pointers
17. Simple multitasking on the Arduino
19. Concurrency with the Scheduler library on the Arduino Due and Zero
20. Bitshift and bitwise OR operators
21. What is a "static" variable and how to use it
22. Understanding the volatile modifier
Last Updated 10 months ago.
We publish fresh content each week. Read how-to's on Arduino, ESP32, KiCad, Node-RED, drones and more. Listen to interviews. Learn about new tech with our comprehensive reviews. Get discount offers for our courses and books. Interact with our community. One email per week, no spam; unsubscribe at any time