Millis() and rollover tutorial

The millis() function is handy for timing things with the Particle Photon (and Electron, and Core). It executes very quickly and has a good resolution (milliseconds). From the manual:

Returns the number of milliseconds since the device began running the current program. This number will overflow (go back to zero), after approximately 49 days.
Note: The return value for millis is an unsigned long, errors may be generated if a programmer tries to do math with other datatypes such as ints.

Now in reality, most devices will reboot before 49 days. Some programs even force the device to reboot periodically, say once every few weeks; this could be used to clear memory fragmentation and other issues. But ideally, the device should be able to run forever without rebooting, so here are some handy tips for properly dealing with millis(). It’s easy once you know the trick!

Here’s a simple recipe that you see pretty often. It does not, however, handle rollover properly. When you get close to the limit, adding 1000 (in the example) wraps around to being a small number, so for a period of time the code in the if statement will get executed on every single loop. This would be bad if you were doing something like transmitting on a Particle Electron and were using up your data allowance!

unsigned long nextRun = 0;

void setup() {
	Serial.begin(9600);
}

void loop() {
	// Don't do this!
	if (millis() >= nextRun) {
		nextRun = millis() + 1000;
		Serial.printlnf("%lu", millis());
	}
}

And this similar recipe, that doesn’t quite work either. It fails in the same way.

unsigned long lastRun = 0;

void setup() {
	Serial.begin(9600);
}

void loop() {
	// Don't do this!
	if (millis() >= (lastRun + 1000)) {
		lastRun = millis();
		Serial.printlnf("%lu", millis());
	}
}

But this one, this one is good. It works exactly as you’d hope!

unsigned long lastTime = 0;

void setup() {
	Serial.begin(9600);
}

void loop() {
	unsigned long now = millis();
	if ((now - lastTime) >= 1000) {
		lastTime = now;
		Serial.printlnf("%lu", now);
	}
}

If you’re new to this, and maybe even if you’re not, you’re probably wondering how that’s even possible. It looks more or less like the other ones. Why does this one work?

Here’s some code you could compile that shows how math works with unsigned long integers:

Serial.printlnf("0xffffffff=%lu", 0xfffffffflu); // 4294967295
Serial.printlnf("3000-1000=%lu", 3000 - 1000); // 2000
Serial.printlnf("4294967295 - 499=%lu", 4294967295lu - 499); // 4294966796
Serial.printlnf("500 - 4294966796=%lu", 500 - 4294966796lu); // 1000

The first one is the maximum unsigned long value, 0xffffffff (hexadecimal) that’s 4294967295 (decimal). Or 2^32 - 1 if you prefer.

The second one is just an obvious simple math calculation. So is the third one, though the numbers are bigger.

The last one, however, would normally result in a negative value if we were doing signed math (small - big = negative number).

500 - 4294966796=-4294966296 (signed math)

The thing is that when the C compiler stores a signed integer value it uses a method of representing a value called two’s complement. Every negative value has an unsigned equivalent, and, -4294966296 is stored in memory the same as 1000, so it works! Magic!

Also, as an added benefit, this recipe waits the wait interval after booting before running the code. This may be handy if you’re using the Electron and uploading data in the if statement. By waiting a period of time, if something goes horribly wrong and a bug causes your device to keep rebooting shortly after your code runs, you don’t have to worry about it uploading very frequently. It will always wait the minimum wait interval.

If you really want it to run immediately on boot, you can initialize lastTime this way:

unsigned long lastTime = 0 - 1000;
18 Likes

Hello,

unsigned long lastTime = 0;

void setup() {
Serial.begin(9600);
}

void loop() {
unsigned long now = millis();
if ((now - lastTime) >= 1000) {
lastTime = now;
Serial.printlnf("%lu", now);
}
}

This solution works as long as the mills() don't overflow. The problem occurs when mills() rollover to zero and lastTime doesn't. Take the following situation for example:

After some days of execution without reboot,
mills() = 4294967295;
lastTime = 4294966295;

The condition, now - lastTime is equal to 1000 and lastTime is now which is 4294967295. After this point of time, mills() roll over to zero and start counting up again all the way to 4294967295.

From this point onwards, now - lastTime is always negative because lastTime is very big. The condition becomes false and the logic won't recover until 49.7 days!

The following function should be added to take care of the rollover.

unsigned long checkForMillisRollover(unsigned long currentMillisValue, unsigned long * lastSavedMillisValue)
{
if(currentMillisValue - *lastSavedMillisValue < 0){
*lastSavedMillisValue = 0;
return (4294967296 - *lastSavedMillisValue); //add the time before rollover
}
return (currentMillisValue - *lastSavedMillisValue);
}

The correct code would be:

unsigned long lastTime = 0;

void setup( ) {
Serial.begin(9600);
}

void loop( ) {
unsigned long delta = checkForMillisRollover(mills(), &lastTime);

if( delta >= 1000 ) {
    Serial.println("Doesn't stop after 49.7 days!!");
    lastTime = mills();
}

}

Thank you
Dheeraj

No, the original code is correct. If you run the code above you can see that it indeed works correctly because every unsigned value has a two’s complement equivalent.

4 Likes

How will this be ever true after mills() rollover? There isn't any typecast for this.

After rollover, say now = 500
The lastTime was before rollover, a big number: 4294966796

500 - 4294966796=-4294966296 (signed math)

However, because now and last time are both unsigned numbers, the result is the unsigned equivalent of -4294966296, or 1000. That’s why you can compare it to 1000 and it works.

2 Likes

Yes. But for that to work, it should be

if( (unsigned long)(now - lastTime) >= 1000){
//do something
}

I am curious why you think that should be? All of the types are unsigned long (32-bits). Have you tried it?

In C, integer arithmetic is wrap arithmetic, so the value of uint8_t n = -1; for instance will be 255.

4 Likes

I tried this using unsigned short datatypes. Here's what I tried.

int main()
{
unsigned short a = 10000;
unsigned short b = 1000;

unsigned long c = 100;
unsigned long d = 10;

if(b - a > 0){
printf("Choice 1\n");
}

if(d - c > 0){
printf("Choice 2");
}
return 0;
}

This works for datatypes unsigned long but not for unsigned short! I get it. In that case, no correction is needed. When dealing with unsigned short, typecast is needed. The proposed solution by @rickkas7 works indeed :slight_smile:

Thanks
Dheeraj

what else would you expect here?

by thier very definition...the result of addition or subtraction of unsigned numbers can only be an integer greater than or equal to zero.

2 Likes

Can you explain why the difference between two unsigned short is not greater than or equal to zero? In the code which is posted above, why is the condition (b - a > 0) false?

What do you mean?

in your example the result of (b-a) and (d-c) are both greater than zero.

google "C or C++ usual arithmetic conversions"

No. If you execute this code, the output is Choice 2 only. Please take a moment to read my earlier comment.

Thanks
Dheeraj

Thanks for posting this. I was thinking of doing something similar, because many times i see people using millis(), and often wonder thinking based on their code snippet they are not considering the effects of the rollover.

which compiler are you using?

Tried it on

  1. gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.4)
  2. Apple LLVM version 9.0.0 (clang-900.0.38)
    Target: x86_64-apple-darwin16.7.0
  3. ARM GCC 541
  4. Particle cloud compiler

All results were same.

This is C integer promotion from short to int. The assumption built-in to the language is that doing arithmetic on int and uint is faster than doing it on the smaller types.

This stack overflow has some good info and pointers:

The code as written for millis() using unsigned int’s is correct, but might not be correct if millis() returned a type smaller than int. Since it returns unsigned int, all is well.

5 Likes

why 1000? so for all the negative numbers will be 1000? what would happen in the second, third…loop (all will be negative, so all will be 1000)? so it would not reach the interval time?

I want to run my code for 12 months, will it work?

#define publish_cycle 30000 
unsigned long lastPublish = 0;

int led = D7;
double reading = 0.0;
double mvolts = 0.0;
double tempC = 0.0;
double tempF = 0.0;
boolean flag = false;

double readingl=0.0;

void setup() {
    pinMode(led,OUTPUT);
    Particle.keepAlive(120);
    Particle.function("led", ledStatus);

}

void loop() {
    unsigned long now = millis();
    reading = analogRead(A0);
    mvolts = (reading * 3300.0)/4095.0;
    mvolts = mvolts -500.0;
    tempC = mvolts/10.0;
    tempF = (tempC*1.8) + 32.0;
    readingl = analogRead(A1);
    
   //to IFTTT
    if ( tempC > 26.0 && flag == false){
    Particle.publish("toohot");
    flag = true;
    }
    if (tempC < 24.0 && flag == true){
    Particle.publish("tempnormal");
    flag = false;
    }

    
    if ((unsigned long)(now - lastPublish) > publish_cycle) {
    //to LOSANT
    Particle.publish("temp-reading", String(tempC), PRIVATE) ;
    Particle.publish("light-intensity",String(readingl), PRIVATE) ;
    delay(500);
    //to ThingSpeak
    Particle.publish("temperature", "{ \"1\": \"" + String(tempC) + "\"," + "\"2\": \"" + String(readingl) + "\"}", PRIVATE);
    lastPublish=now;
    }
    

}





int ledStatus(String command){
    if(command.equalsIgnoreCase("on")){
        digitalWrite(led,HIGH);
        return 1;
    }
    else if (command.equalsIgnoreCase("off")){
        digitalWrite(led,LOW);
        return 0;
    }
    else{
        return -1;
    }
    
}

This question could also have been asked in your own thread instead of just dropping your code there without further comment - particularly not answering any of the questions asked.
That's not really appreciated by people trying to help in their little spare time

BTW, as the name suggests unsigned variable types (as uint32_t or unsigned long returned by millis()) cannot become negative and 1000 is just an exemplary number.

3 Likes

@rickkas7: Is the rollover of millis() an issue while using elapsedMillis.h (and f.e. elapsedMillisDemo.cpp) from the library? if so, please add a note to the .cpp or change the code of .h please, a great help for newbies, else they are wondering why things stop running after a certain time, and turn away from particle. for purposes like home automation it’s a must-have to run w/o reset for more than 49 days…

@mf2105, elapsedMillis() uses millis() and will used unsigned long values internally so there should be no rollover problems. Are you having a specific issue?

1 Like