Arduino Millis() Rollover Handling

Millis Police

On the Arduino microcontroller, the millis() function counts the number of milliseconds since the program started running. Unfortunately, this count resets to zero after approximately 9 hours and 32 minutes. I have written a millisRollover() function that detects these rollovers, so that programs can respond properly to the overflow event. This can solve problems with servo routines, steppers, timed pauses and a variety of other calculations. In addition, because my millisRollover() function counts the number of times rollover has happened, it is now possible to record total Arduino runtime with a counter that’s good for over 35 years.

You want to blink an LED only on Christmas during leap years? Totally possible now.

(yes, there really is a millis police department)

11 Comments on “Arduino Millis() Rollover Handling

  1. I’m having a little difficulty understanding how I would use your rollover function in a scenario where I tick a clock if 1000 ms has elapsed, like:

    if( millis() – previousMillis > 1000 ) {
    previousMillis = millis();
    // One second has elapsed, tick the clock.
    }

    It seems like this will “hiccup” during a rollover.

  2. Glad you asked because it’s worth noting that the millis() function was greatly improved in Arduino 0012:

    * Improved millis(): it now overflows after 49 days instead of 9 hours, but
    now uses slightly more processing power.

    I’m assuming this means that it now fills the full unsigned long variable. So in your case you should just be able to do the math. The rollover will cause your subtraction to overflow an the result would be correct.

  3. Just a thought – why not use the older, computationally cheap code for 49 minute roll overs and use a second 16 bit variable as a count for the roll overs and use seconds to return the sum of the two variables as a 64 bit value. Gives a long, long time before rollover.
    eg:
    short int seconds = 0; //
    unsigned long millisLong() {
    return unsigned long int (seconds) * 1000 + unsigned long int (millis());
    }

    I know this requires the ‘second’ variable to have millisecond precise accuracy but that is a relatively small price to pay for a very large, high precision counter.

    Probably best set by a build flag because of the extra interrupts required.

  4. Hi folks,

    There’s a much better generic way of handling rollovers on timers. Instead of using unsigned timer values you use signed timer values. Then to decide whether time1 > time2 you simply calculate (time1-time2>0) which returns correct results for all cases where the period itself is b isn’t equivalent to a-b>0, due to the nature of 2’s compliment arithmetic. Thus, if instead we have:

    long eventTimeout=(long)millis()+1000;
    if((long)millis()-eventTimeout>=0) {
    eventTimeout=(long)millis()+1000;
    }

    We will *never* get a roll-over condition! Consider, in the old code, the if statement would fail when millis=0, and oldMillis was 4294967000. Here, millis-oldMillis (very much) >= 1000 even though the timer should not have expired.

    However, with the new code, we instead calculate the eventTimeout and compare with 0, so in the case above (long)millis – eventTimeout = -704, so there’s no timeout, until millis>=704.

    You might think this technique would fail when the signed values roll-over to unsigned values. However, it doesn’t. Consider when eventTimeout was 2147483000. When millis() reached that value, so millis()-eventTimeout was >=0, eventTimeout was then set to 2147484000 which is actually -2147483296. The timeout looks like it’s in the past, but because we’re doing signed arithmetic (long)millis()-eventTimeout = 2147483000-(-2147483296) = -1000 and so it sees it as being in the past (which it is). Similarly when (long)millis() crosses from 2147483647 to -2147483648 the subtraction will yield -353, then -352 and will only result in >=0 when (long)millis() = -2147483296 as intended.

    This can be combined into a function:

    #define smillis() ((long)millis())

    boolean after(long timeout)
    {
    return smillis()-timeout>0;
    }

    Then your code will always work if you do:

    long timeout=smillis()+1000;


    if(after(timeout)) {
    … do something.
    timeout=1000;
    }

    -cheers from Julz @P

  5. you might check out a tutorial I wrote for the playground wiki,

    http://www.arduino.cc/playground/Code/TimingRollover

    There is a much simpler solution illustrated here. It’s the one I always use, and is actually similar to what the Linux kernel does for its kernel timers based on ‘jiffies’. But I didn’t steal my method from Linux. I just happened to observe that it does the same kind of thing.

  6. may i know how to reset millis function for a 24-hour period.. coz, im doing a project called rainfall monitoring with the use of a tipping bucket rain gauge that sends pulses. and i want to record raainfall data for a 24-hour period only after then, the tipcount rsets to 0. thank u

  7. Where did the value 17179868 come from? Twice this value is 34359736, which is not a power of two, suggesting that the max value is arbitrarily set and not a result of binary arithmetic.

    • As I recall this number was empirically determined. It’s not a simple register overflow but dependent upon several other factors as well.

      • I believe this article is based on the old millis function, as is the article by Mahto (link at the bottom of page). The millis function was greatly improved in Arduino 1.0 to rollover in approximately 50 days, which is the result of a 32 bit unsigned integer overflowing. The number 34359736 milliseconds is approximately 9.5 hours, which fits the release notes that the rollover time was increased from 9.5 hours to 50 days. Reference wiring.c in the current repository.

  8. Yeah, the simplest way is to calculate a time span like this:

    unsigned timeSpan(unsigned long startTime, unsigned long endTime)
    {
    return (unsigned long)((long)endTime – (long)startTime);
    }

    So timeSpan(0, 1) = 1 and timeSpan(0xffffffff, 0) = 1