Preamble: I’m writing this as an answer, not as a question; I just hope that other people will find it useful, and maybe I’ll also get some insightful comments as well. I chose to post this in the hacking section because this is obviously not intended behavior. I came up with this solution myself (not that it was exceedingly difficult) upon finding the source code for millis() and friends.
I’ve been searching high and low on the Internet on how to reset millis() on Arduino, and I came up empty. When someone asks on this forum, people usually answer with another question (“why do you need it?”, or “why don’t you do this, or that instead?”). So, to all the sticklers out there, I needed to reset millis() because I’m deploying a prototype solution on location, and I want to know if my code, or any of the libraries I’m using in the project, are subject to freezing or any other kind of misbehaving on millis() overflow (or rollover, if you prefer that term), without having to wait weeks for each debugging event.
Anyway, here’s a proof of concept for the solution I came up with for an Arduino Uno:
void setup()
{
Serial.begin(57600);
}
void loop()
{
if (millis() < 100)
return; // Just so we show consistent output length
delay(100);
Serial.print(millis());
Serial.print(" -- ");
Serial.print(micros());
Serial.println("");
if(millis() < 800)
return;
Serial.println("<-- RESET -->");
reset_millis();
}
void reset_millis()
{
extern volatile unsigned long timer0_millis, timer0_overflow_count;
noInterrupts();
timer0_millis = timer0_overflow_count = 0;
interrupts();
}
That works well if all you want is to reset the values returned by millis() and micros(). However, as several people have pointed out in response, it’s best to allow millis() and micros() to actually overflow, because the math is different: if you store a very large value for millis() and then subtract it from a very small value (after the overflow) then you do get the correct time delta. This behavior is not properly replicated using the code above, which artificially overflows after a small value for millis(). So the best way to actually test your code is using this function instead:
void stage_reset_millis(unsigned long offset)
{
extern volatile unsigned long timer0_millis, timer0_overflow_count;
noInterrupts();
timer0_millis = timer0_overflow_count = -offset;
interrupts();
}
The function above stages an overflow in the future (the parameter indicates how many milliseconds from now you want the overflow to happen). Here’s a copy/paste test, just to show you that it works as desired (although not necessarily as expected, given that we’re setting both counters to the same value):
unsigned long currentSecond, initialMillis;
void setup()
{
// Stage an overflow 5 seconds from now
stage_reset_millis(5000);
Serial.begin(57600);
currentSecond = 0;
initialMillis = millis();
}
void loop()
{
Serial.print(currentSecond, DEC); // Not actual seconds, just a rough counter
Serial.print(" -- ");
Serial.print("millis = ");
Serial.print(millis(), DEC);
Serial.print("; micros = ");
Serial.print(micros(), DEC);
Serial.print("; delta millis: ");
Serial.print(millis() - initialMillis, DEC); // Keep an eye on this one
Serial.println("");
currentSecond++;
delay(1000);
}
void stage_reset_millis(unsigned long offset)
{
extern volatile unsigned long timer0_millis, timer0_overflow_count;
noInterrupts();
timer0_millis = timer0_overflow_count = -offset;
interrupts();
}
Please let me know if you know about any unexpected side effects (besides what I’m trying to debug), or whether there exists a safer or more generic solution for testing this. But, for the love of all that is good, please don’t answer asking why I need this!
Notes:
- Most people I know use millis() as opposed to micros() in real projects. If that’s applicable to you, I suggest you use the function name I suggested above (“reset_millis()”) for the reset function, because that way you can use your code editor’s Find functionality to identify all calls to millis() and all calls to reset_millis() in the same run (sooner or later, I expect you will need this, given that you’re reading this topic).
- Does anybody know of any timekeeping library (similar to StopWatch) that is not affected by rollovers?
Other resources on this topic (thanks Jantje):