Resetting Millis() to zero, reset clock

This topic is a little summary of the research I did this morning on the unsigned long millis(). If it doesn't add any existing knowledge, then let the post be for reference purposes only.

Millis() function itself
Unsigned long 32bit variable. Meaning 2^32-1 milliseconds range (no negative numbers possible). This equates to: (2^32-1) / 1000ms / 60sec / 60min / 24hr = 49.71 days. Or 49 days and 17 hours. Millis() is derived from timer0_millis, and overflows result in the number returning to zero (and continuing counting from zero). Overflows do not crash the arduino, Millis() overflows do not crash your code, and the timer will not stop counting. However, if you want events to happen fewer than once every 50 days, you should use RTC and the time library.

Difficulties arising from Millis() overflow
Take a look at Gammon's explanation of the millis() issue, and what is often done wrong that causes errors. In brief, setting your stop-time ahead of the current time, and creating an if-statement that checks when the current time exceeds the "stop-time" variable will create issues near overflow. However, Using the difference between current time and stop time, and comparing that to a set interval will never create issues. (see link for code examples).

I've edited to text below to accommodate for future reference:
Is it possible to reset Millis()? Yes, though not recommended:
According to the official wiring.c source code (see here: Wire.c), Millis() is derived from timer0_millis. For this reason, timer0_millis can basically be seen as just another unsigned long, in fact it is. Thus, it can be manipulated at will by the programmer. Please realize that the code below is a "hack" that shouldn't be used unless you know and you've tested its effects on your millis()-dependent libraries. It is better to use an external RTC with the Time library for accurate time management.

Before void setup(), place:

extern volatile unsigned long timer0_millis;

Whenever you want to reset millis():

noInterrupts ();
timer0_millis = 0;
interrupts ();

Thanks for reading, hope it adds some new info to this forum

Many thanks to the forum members below, especially Gammon, for careful explanation and elaboration of the issues at hand!

Examples and support for some of the statements are needed.

redtails:
In some situations, it is better to reset timer0 (see below) in a controlled fashion, instead of letting it overflow at its limit.

What would some of those situations be? How is letting the timer overflow less controlled than another method?

Though due to technical limitations, arduino cannot calculate numbers larger than 2^32-1 ...

Technically not true, e.g. long long myInt;

Issues arising from not resetting Millis()
Performing timepoint1 = millis(); near the overflow can (and will) result in erroneous interval comparisons if millis() returns back to zero. A recorded timepoint1, compared to overflowed millis() will return nothing: difference = timepoint2 - timepoint1; difference = 1000 - (2^32-500).

Not true, you might try it. millis() is suitable for any interval comparison, provided that the interval is < 49.71 days.

I personally prefer resetting timer0_millis back to 0 manually in a periodically fashion to prevent any weird stuff from happening every 50 days.

Please refrain from giving your opinion on why it is bad code practice to reset millis();

Though due to technical limitations, arduino cannot calculate numbers larger than 2^32-1,

I'm not a good-enough programmer to understand what timer0_millis is based on

{Sigh}

Check this stopwatch class - Arduino Playground - StopWatchClass - it can be used like a resetable millis(), micros

Does this help?

Let me rephrase that:

Rollovers notwithstanding, millis() is suitable for any interval comparison, provided that the interval is < 49.71 days.

This is simple enough to simulate, so I offer the following example. Please run it and post the results for us.

//millis() rollover simulation

void setup(void)
{
    unsigned long myMillis, t1, t2, lapse;
    
    Serial.begin(9600);

    //set the simulated millis value to 50 milliseconds before the dreaded rollover
    myMillis = 4294967245;    //this is 2^32 - 51
    t1 = myMillis;            //record the start time
    
    //simulate millis incrementing for 100 milliseconds
    for (int i = 0; i < 100; i++) myMillis++;
    
    t2 = myMillis;            //record the end time
    lapse = t2 - t1;          //calculate elapsed time
    
    //print the results
    Serial.println();
    Serial.print("Start ");
    Serial.println(t1, DEC);
    Serial.print("End ");
    Serial.println(t2, DEC);
    Serial.print("Elapsed ");
    Serial.println(lapse, DEC);
}

void loop(void)
{
}

(Yes I know this code or something like it probably already exists in a dozen threads on the forum but I'm too lazy to search for it.)

Example is given in OP. When dealing with large intervals, uncertainty of roll-over increases, making the possibility of negative return on comparisons (smaller number minus larger number) greater.

Unsigned long long variables are not described in Arduino knowledge base. I did not know about this possibility.

I did try. Smaller number minus larger number results in no return on unsigned long. The reasoning is simple, as unsigned cannot contain negative numbers. So even if interval is smaller than 2^32-1, the return can be absent if millis() overflowed!!

robtillaart:
Check this stopwatch class - Arduino Playground - StopWatchClass - it can be used like a resetable millis(), micros

Does this help?

StopWatchClass is an interesting bit of code. I had a look around the code and it's been very cleanly written! I can see that it derives its time from millis() and determines lap-time on return _stoptime - _starttime;. I'm afraid it suffers from the same issues as millis() when starttime is greater than stoptime (like when stoptime is after an overflow). Interesting code, nonetheless

When dealing with large intervals, uncertainty of roll-over increases,

There is no uncertainty, so it cannot increase.
Roll-over will occur.
Deal with it properly, and all will be well.
There is no need to reset the millisecond clock.

Smaller number minus larger number results in no return on unsigned long.

I don't understand that sentence.

Hmm this is very interesting. Even though arduino is calculating 4294967245 - 50 , it's still returning 100. Can you elaborate on this a bit? It seems to go against common logic that 4294967245 - 50 = 100. Am I missing something here? Is Arduino actually taking notes on all unsigned longs that have overflowed and dealing with them correctly arithmetically? Well I know the answer to this is yes, but can someone with deep understanding of this point me to the source-code in which this is described?

Open your calculator, to Progamming mode. Select HEX mode.
Unsigned long goes from 0000 0000 to FFFF FFFF, 32 bits.

Time comparisons are done using subtraction: current time - earlier time.
So a post rollover number, say 0000 0010, minus a pre-rollover number, FFFF FF00, will result in a correct result:
FFFF FFFF 0000 0110
Ignoring the upper 32 bits of the 64 bit result leave 0000 0110, which is the correct answer: 100 befor the rollover + 10 after = 110.
Arduino does the same - results past the lowe 32 bits fall off.
Looking in the decimal world leads to confusion - need to look at in binary world (with HEX just being a convenient to look at binary)

It seems to go against common logic that 4294967245 - 50 = 100.

That would be indeed, but it is in fact 50 - 4294967245 = 100 that is shown in the sketch of Jack C.

one way to look at it is to see 4294967245 as 50 less than MAX_ULONG, you might see it as -50
then 50 - -50 == 50 + 50 = 100

redtails:
Hmm this is very interesting. Even though arduino is calculating 4294967245 - 50 , it's still returning 100. Can you elaborate on this a bit? It seems to go against common logic that 50 - 4294967245 = 100 (edit: jc). Am I missing something here? Is Arduino actually taking notes on all unsigned longs that have overflowed and dealing with them correctly arithmetically? Well I know the answer to this is yes, but can someone with deep understanding of this point me to the source-code in which this is described?

No notes are being taken, it is merely the magic of modular integer arithmetic. The odometer analogy is a good one. Say a six-digit odometer reads 999950 and we drive the car 100 miles. At the end, the odometer reads 50 miles. So the ending reading minus the starting reading (50 - 999950) must be 100 miles. There is no such concept as negative miles on an odometer, just as there is no concept of negative numbers with unsigned integers. The cross-check is to start with 50 miles on the odometer and drive the car backwards for 100 miles. (Assume for the sake of discussion that the odometer moves backward when the car is driven in reverse. I think this may have been true in the past when odometers were purely mechanical, but it is probably not true these days.)

Another way to look at it, add 1,000,000 to the ending reading to guarantee a positive result, then if needed, truncate the result to six digits to fit the odometer, so 1,000,050 - 999950 = 100. The fixed width of the odometer is analogous to the fixed width of an integer variable, except it's base 10 instead of base 2.

No notes are being taken, it is merely the magic of modular integer arithmetic

sp. "modulo"

Hello
I have another question about this topic. How about setting timer0_millis to 4 294 963 696 (1 hour before rolover)
to test if code is rollover proof?

AWOL:

No notes are being taken, it is merely the magic of modular integer arithmetic

sp. "modulo"

Another link for the OP: Modular arithmetic - Wikipedia

Apologies - maybe it was taught as "modulo arithmetic" in English-speaking institutions in the 1970s.
That's my recollection anyway.

redtails:
Overflows are registered in timer0_overflow_count, making it possible to calculate further how long the arduino has really been running beyond the unsigned long limit ...

This is not correct. That variable counts the number of times Timer0 overflows (which itself happens every 1.024 mS). It is used in one place only and that is to return the correct value from micros(). It is not a "millis() overflow count" as you seem to think.

I personally prefer resetting timer0_millis back to 0 manually in a periodically fashion to prevent any weird stuff from happening every 50 days.

This is not a useful thing to do, any more than resetting your kitchen clock each evening to stop it "rolling over" at midnight. For one thing, it isn't necessary. For another, while you are resetting the clock it is no longer counting the time.

If you have any reasonably complex sketch where you need to count multiple intervals then there will be no obvious time in which to reset the timer.

It is better coding practice to read timer0_overflow_count before and after long-running operations ...

No, that achieves nothing, as I said above.

The others are quite right. Something like this always works:

if (millis () - startTime >= interval)
  {
  // do something
  }

(Given that startTime is unsigned long).

redtails:
Example is given in OP. When dealing with large intervals, uncertainty of roll-over increases, making the possibility of negative return on comparisons (smaller number minus larger number) greater.

How can a negative return be possible on comparing unsigned numbers? That is the crux. It isn't possible, and thus you get the correct answer. What other answer could you get?

redtails:
Whenever you want to reset millis():

timer0_millis = 0;

I just want to also point out, in case you ignore my earlier advice, that this is flawed because you haven't "protected" that line from interrupts.

Scroll down to "critical sections". If you must do the above, you have to turn interrupts off:

noInterrupts ()
timer0_millis = 0;
interrupts ();

Without doing that, at the moment you attempt to set the timer to zero, an interrupt may occur which means it is possibly only half set to zero.

AWOL:
Apologies - maybe it was taught as "modulo arithmetic" in English-speaking institutions in the 1970s.
That's my recollection anyway.

Yes you are right, that is what I was taught and what I did teach.

AWOL:
Apologies - maybe it was taught as "modulo arithmetic" in English-speaking institutions in the 1970s.
That's my recollection anyway.

None needed. Probably just another of those differences between English and Usanian :wink: