Arduino Tutorial: Avoiding the Overflow Issue When Using millis() and micros()

One of our most popular blog posts right now this is called Arduino Tutorial: Using millis() Instead of delay(). Here we discuss how to use millis() and micros() and their major advantages compared to delay().

Not a great analogy to a variable overflow in C/C++, but you get the idea…

We mentioned one caveat with these functions, and that is that millis() and micros() overflow after around 50 days and 70 minutes, respectively. This potential issue can very easily be avoided with a small alteration to the code from last time.

The use of millis() throughout this post is interchangeable with micros().

The Easy Fix

Let’s look at the simple non-blocking example we included in the previous blog post:

int period = 1000;
unsigned long time_now = 0;
 
void setup() {
	Serial.begin(115200);
}
 
void loop() {
	if(millis() > time_now + period){
		time_now = millis();
		Serial.println("Hello");
	}
   
	//Run other code
}

Here we will get a buggy behavior after approximately 50 days when millis() will go from returning a very high number (close to (2^32)-1) to a very low number. This is known as overflow or rollover.

Now, let’s see how we can fix this:

int period = 1000;
unsigned long time_now = 0;
 
void setup() {
	Serial.begin(115200);
}
 
void loop() {
	if(millis() - time_now > period){
		time_now = millis();
		Serial.println("Hello");
	}
   
	//Run other code
}

We basically just move time_now to the other side of the inequality operator. For the equation to still make sense we then have to change the sign of the variable, hence the subtraction. You can look at this as comparing a duration to a our period variable instead of working with time stamps.

Hold Your Horses!

“How would this even work?”, you might wonder. This is not a stupid question. This can easily look like as we’re merely moving the problem rather than fixing it.

Looking at this mathematically it doesn’t make much sense since the left side will become negative when the millis() overflow occur (the result of a very small integer minus a very large integer).

The Reason This Works

What’s nice is that we don’t have to worry about this at all. Since both inputs to the calculation are of the unsigned long data type, the answer will also be an unsigned long, and thus the result will overflow in line with the return value of millis(). Remember that both millis() and micros() return unsigned long.

Example/Proof
void setup() {
	Serial.begin(115200);

	unsigned long a = 1;
	unsigned long b = 4294967295; //unsigned long maximum value

	Serial.println(a-b);     
}
 
void loop() {
}

This will print 2 to the serial monitor. The Serial.print() function does not modify the answer in any way in this case.

Additional Info

To be sure that this works across multiple platforms and architectures you can explicitly cast the result of the calculation to the correct data type like this:

int period = 1000;
unsigned long time_now = 0;
 
void setup() {
	Serial.begin(115200);
}
 
void loop() {
	if((unsigned long)(millis() - time_now) > period){
		time_now = millis();
		Serial.println("Hello");
	}
   
	//Run other code
}

Different Data Types

On Arduino Uno (and equivalent) you have to explicitly cast the calculation when dealing with uint8_t for this to work. If you don’t, the result of the subtraction will become negative if given the right input.

uint16_t and uint32_t (the same as unsigned long on Arduino Uno and equivalent) have behavior such that explicit casting isn’t necessary. Other data types than unsigned long (uint32_t) is not relevant when dealing with millis() or micros().

Summary

As mentioned, this is a really easy “fix” to a potential problem when dealing with millis() or micros() (we don’t really fix the problem, we just avoid it). For more information about this topic, read this nice and comprehensive post on Stack Exchange.

If you’re feeling unsure about data types and overflows, we recommend you play around with this and see what happens in different edge-cases with different data types. Make sure your print function doesn’t influence your results in any way (sometimes it can). Remember that this behavior may vary across different platforms, compilers and/or architectures.

Related Posts