millis() is a built-in Arduino function that returns the number of milliseconds (hence its name) elapsed since the sketch started.
This function relies on an internal 32-bit register, continuously updated by a routine attached to timer 0.
Being a 32-bit register, it can hold a maximum value of 232-1, i.e. 4,294,967,295.
This number may seem large, and in most cases it is more than enough to run a sketch on an Arduino that is only switched on for a short time.
But if the board needs to stay on permanently, after a while this register will reach the maximum value it can hold. And then?
Then it will overflow, which in computing means that the value to be stored exceeds what the container can handle.
This happens after 49.7 days (4,294,967,295 ms corresponds to 1,193.05 hours, which is exactly 49.7 days). The register then starts again from zero, with all the problems this can cause if your sketch measures the passage of time by relying on the value returned by millis().
But there is a way to handle millis() overflow so that your sketch can keep running without problems.
The solution for handling overflow lies in the way numbers are stored in memory.
Variables are stored in memory cells of fixed size. The char or byte types, for example, take up 1 byte of memory, i.e. 8 bits;
an int takes up 2 bytes, i.e. 16 bits;
a long takes up 4 bytes, i.e. 32 bits.
As mentioned, an unsigned long can hold a value from 0 to 232-1.
And what about negative numbers, you may ask?
They are handled using the same number of bits, but sacrificing one of them — the most significant bit (the leftmost) — to indicate the sign.
This is how the compiler distinguishes between unsigned and signed numbers.
A long is a signed integer that can hold a number ranging from -2,147,483,648 to +2,147,483,647, while an unsigned long can hold a number from 0 to +4,294,967,295. The long type is in fact a signed long and as such sacrifices the 32nd bit to store the sign: the number is therefore effectively a 31-bit number, and indeed 231 gives exactly 2,147,483,648.
Signed numbers use a different way of storing the value in memory, known as two’s complement.
The number is stored with its bits inverted, i.e. set to the opposite of the value they represent: a NOT operation is performed on each bit of the value, and finally 1 is added.
Let’s take the case of a char, which is a signed 8-bit number.
The value 1 is represented like this:
00000001
while the value -1 is represented like this:
NOT of 00000001 -> 11111110 + 00000001 = 11111111
If we subtract 1 from a variable containing 0, we get -1.
Using binary representation and a signed char, we get:
00000000 – 00000001 = 11111111
If we add 1 to a variable containing -1, we get 0. Let’s represent the operation in binary:
11111111 + 00000001 = 100000000 -> 00000000
As we can see, the result would be a 9-bit number but, since our variable is 8 bits, the 9th bit is truncated and the final result is given by the 8 least significant bits, which represent the value 0. This suggests how to handle overflow of the millis() register.
As mentioned, millis() returns the value of an unsigned 32-bit register, which can hold a maximum of 4,294,967,295. Operations are normally scheduled with millis() by adding a fixed interval to the value returned by millis(). For example:
void loop() {
if (millis() > intervalloPrecedente) {
//code to run
intervalloPrecedente += 1000;
}
}
Suppose we have entered the block shown above, with millis() having returned 4,294,967,001 and intervalloPrecedente equal to 4,294,967,000: the test is true and the code runs.
Once the code has finished, we add 1,000 to intervalloPrecedente (also an unsigned long), which would give 4,294,968,000. However, 4,294,968,000 is a 33-bit number, so it will be truncated and the stored result will be 704.
On the next test, the following check will be performed:
4.294.967.001 > 704
the result will be true and the code block will (wrongly) be executed again, and it will keep being executed until the millisecond counter register overflows and starts again from zero.
The opposite can also happen: the code block may not run again for a very long time. Take the following code:
void setup() {
intervalloPrecedente = millis();
}
void loop() {
if ((millis() + 1000) > intervalloPrecedente) {
//code to run
intervalloPrecedente = millis();
}
}
Suppose that at some point the millis() function returns 4,294,966,001 and intervalloPrecedente is 4,294,967,000: the test will be true because (4,294,966,001 + 1,000) > 4,294,967,000.
At the end of the code block, intervalloPrecedente is assigned the value of millis(), which let’s say is now 4,294,967,000 because our code carried out some operations that took a little time.
On the next check, the following comparison will be performed:
(4.294.967.001 + 1.000) > 4.294.967.000
But 4,294,967,001 + 1,000 would give 4,294,968,001, which is a 33-bit number: it will therefore be truncated to a 32-bit number with the value 705.
The check will therefore be:
705 > 4.294.967.000
This will obviously be false, and will remain so for 49.7 days, until millis() once again reaches 4,294,966,001.
At that point, adding 1,000, the comparison will be 4,294,967,001 > 4,294,967,000 and the check will be true.
Solution 1
Now that we understand how the millis() overflow problem can arise, and how numbers are stored in memory, we can use negative numbers.
To do this, we force the compiler to convert (cast) one data type into another.
We have seen that -1 is represented in an 8-bit variable as 11111111.
And what about a very large number, such as the unsigned long 4,294,967,000?
It will be represented like this:
11111111 11111111 11111110 11011000
But this representation is identical if we use a signed long variable; only its meaning changes when we treat the number in decimal. It in fact becomes -296.
That’s the trick: we simply change the comparison so that it checks whether the difference between the current value of millis() and the interval, cast to a signed long, is less than 0.
If it is, it means millis() has restarted from 0, and the test will return a negative number until millis() has a value greater than zero and than the interval. Below is an example of code that does not suffer from the millis() overflow problem:
void setup() {
intervallo = millis() + 1000;
}
void loop() {
if ((long)(millis() - intervallo) >= 0) {
//code to run
intervallo += 1000;
}
}
The difference (cast to a signed variable) between millis() and the interval becomes negative when the interval overflows, and remains so until millis() also overflows and its value exceeds the interval. At that point the difference returns to 0 or positive, so we know that millis() has once again passed the interval.
To make sure the code works as described, we can either wait 49 days or manipulate the value of the register read by millis(). Upload the following sketch and open the serial monitor.
After a few seconds you will see the value shown go from very large back to very small: this happens when the interval overflows, which is handled by casting the data types.
To change the register containing the millisecond value, we need to modify the timer0_millis variable, declaring it in our sketch with the extern keyword, which means it has been declared elsewhere. To modify it, we disable interrupts (cli();) and then re-enable them immediately afterwards (sei();), to avoid writing to it while it is being incremented by timer 0.
*/the following declaration tells the compiler
that timer0_millis is declared elsewhere*/
extern unsigned long timer0_millis;
static unsigned long myTime;
void setup() {
Serial.begin(115200);
delay(2000);
cli(); //disable interrupts
timer0_millis = 4294950000UL; //change the register value
sei(); //re-enable interrupts
myTime = millis() + 1000;
}
void loop() {
if ((long)(millis() - myTime) >= 0) {
Serial.println(millis(), DEC);
myTime += 1000;
}
}
Solution 2
Another solution to the problem, suggested by the user lesto on the arduino.cc forum, is to reverse the check with the interval. Normally, code performs a check like this:
MILLIS + INTERVAL > PREVIOUS_TIME
As mentioned, this type of check is affected by millis overflow.
Instead, you can use a check like the following:
MILLIS - PREVIOUS_TIME > INTERVAL
This way, the difference between the value returned by millis() and the previous reading will always be a number between 0 and the interval. Let’s take an example.
if (millis() - tempo_precedente > intervallo) {
tempo_precedente = millis();
....
}
Suppose tempo_precedente is 4,294,967,000 and intervallo is 1000. At some point millis() overflows and starts again from zero.
The comparison becomes:
0 - 4294967000 > 1000
but using unsigned integers, the subtraction actually returns 296. This is because an unsigned type cannot handle negative numbers, so the result is actually the maximum value that can be stored, 232, i.e. 4,294,967,296, minus 4,294,967,000, which gives 296.
The comparison has now become:
296 > 1000
which is obviously false.
Only when millis exceeds 704 does the comparison become true, because:
705 - 4294967000 = -4294966295
-4294966295 => 1001
1001 > 1000 = TRUE
Enjoy!
