Arduino programming guide series

12

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. 

"Arduino programming" series

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.

>