Millis() and rollover tutorial

@peekay123 no, not a specific issue, just about to adapt code from delay() to elapsedMillis() to get rid of delays using mobicle.io (next step maybe Blynk) for my home automation. Tried to read elapsedMillis.h, but was confusing for a beginner like me :thinking:. Thanks for the quick reply.

1 Like

You shouldn't have any problems using the sample from the first post to replace delay.
The concept works perfectly and it's a great tutorial for beginners.

I'm currently writing an application for gardening which will often have long times between function calls, hours or perhaps days depending on the function. I'm trying to decide whether I should be using Software Timers, millis(), or Time.now() in some way to do that.

Reading up on millis(), I am having trouble understanding these two sections which seem to be contrary.

I assume every negative value has a DIFFERENT unsigned equivalent, but not sure what those values would be to understand how comparing them to a value of say 86,400,000 (24hrs in ms) would behave.

Based on the first quoted section, since lastTime is an unsigned long, shouldn't the result of this statement be -1000 which is then converted to some positive number because it's unsigned?

Thanks in advance.

For hours or days, the real-time clock Time.now() is probably the best option.

The examples in the original post are specifically for dealing with how millis wraps after 49 days.

Say the time in lastTime was +4294966796, right before rolling over.

The next time you check, millis() (or the now variable in the original post) is now 500 because it rolled over. The formula now - lastTime would be 500 - 4294966796.

In signed math, thatā€™s a negative number, -4294966296. However, that number is stored in memory as 1000. It really is the same bits in a 4-byte value.

The reason this isnā€™t ambiguous is that -4294966296 canā€™t really be represented in a 32-bit signed value because the minimum value is -2147483648.

But when you compare against 1000, it works properly.

3 Likes

Yeah, I assumed I was going to have to use time.now() and store the last run times for each function in EEProm to compare to because a reboot would reset both the millis() and Software Timers.

Just to complete wrapping my brain around the millis() question though I had one more question.

If -4294966296 is represented the same as 1000 when stored as an unsigned long, what would it be if the number was -3294966296? Itā€™s still below the minimum value of -2147483648, but is the result different in a predictable way?

Thanks again.

Now that I think about it, a better way of explaining it is that -4294966296 or -3294966296 requires a 33-bit twoā€™s complement integer. But since integer is only 32 bits, the sign bit is lost, making a positive 32-bit integer.

-3294966296 = 0b100111011100110101100110111101000 (33 bits)

Removing the MSB:
0b00111011100110101100110111101000 = 1000001000 (decimal)

3 Likes

I had to work this out for myself, a while back, for a machine that I never wanted to reset (for purist reasons, really!). Here is a trivial function that other may like to use, which is roll-over safe - like @rickkas7 code:

// return true if reference time is more than threshold mS behind current time.
// unsigned modulo arithmetic handles any wrap-around in this equation.
bool millisExpired(unsigned long mSref, const unsigned long mSthreshold) {
    return (millis() - mSref > mSthreshold);
}

Example:

// Declarations
const unsigned short EVENT_TIME_OUT=1000; //mS
unsigned long eventTimer; 

// Earlier in code, when needing to time event
eventTimer=millis();

... do stuff...
if (millisExpired(eventTimer, EVENT_TIME_OUT)) {
  ... do time-out stuff...
}
2 Likes

[quote=ā€œrickkas7, post:1, topic:20429ā€]
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());
	}
}

rickkas7, Thank you so much sir for your great tutorials
Regards Kazem

I need to ask: is that code above the same as this code below, or is there some vudu magic that would make it not work properly?

      if ((millis() - lastTime) >= 1000) {

The question is whether declaring the unsigned long now variable changes something or not.
Thanks!

Same. I use the version you entered frequently. And because of the order of operations, you can even leave out the parentheses in the left expression, but it is clearer with them.

Also thereā€™s now System.millis() that returns a uint64_t that never rolls over. The disadvantage is that sprintf and things that use it like Log.info canā€™t print a uint64_t natively, and of course itā€™s twice the size (8 bytes).

3 Likes

So I think I understand the concept of millis() and handling rollover, however the only real way to know for sure is through testing. Is there a way to set the value of millis so the scenario can be bench tested? Iā€™d rather not wait 49 days. :slight_smile:

1 Like

You canā€™t change the value of millis(). For testing what Iā€™ve done is something like this:

unsigned long fakeMillis() {
	return 0xffffff00 + millis();
}

Unfortunately, because the real millis() is an inline C++ function, itā€™s not possible to just #define the fake one in place of the real one, so you kind of have to modify your code to use the testing version.

Once you have a good understanding of the pattern itā€™s easy to know if your code will work or not.

Another alternative is to just make sure the device resets periodically. I often have the device reboot once a week in case I have a memory leak, which also has the benefit of assuring that millis never rolls over.

4 Likes

This is valuable to know, thanks!

2 Likes