Go Down

Topic: Reset Arduino millis() and micros() (Read 5050 times) previous topic - next topic

Gutza

Sep 20, 2013, 11:11 pm Last Edit: Sep 21, 2013, 05:57 pm by Gutza Reason: 1
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:

Code: [Select]

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:

Code: [Select]
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):

Code: [Select]
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):

  • http://www.gammon.com.au/forum/?id=12127

  • http://forum.arduino.cc/index.php?topic=184596.0


CrossRoads

Wouldn't a better test be setting millis or micros very high so the rollover could be tested more frequently?
Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

Gutza

I'm afraid I don't follow... The code I proposed is rolling over every two seconds (and one can obviously adjust it to their desire). Can you please elaborate your request within this context?

CrossRoads

Quote
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.

millis rolls over naturally every 49+ days, micros every 71+ hours, when the unsigned long variable that represents them goes from
0xFFFF FFFE to 0xFFFF FFFF to 0x0000 0000 to 0x0000 0001, etc.
So I would think a better test would allow the user to set millis or micros to some amount before the rollover from 0xFFFF FFFF to 0x0000 0000 and then see how their code responded.

I don't see how your code does something like that.
Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

robtillaart

Quote
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.


first problem is that you do not disable interrupts when resetting the counters so it will fail (chance I guess about 1 in 10000)
second problem is that resetting millis can interfere with proper running of other libraries/code that do not expect a reset, Especially when guarding "longer" durations will get in trouble.

A better solution is to use a derived timer class like - http://playground.arduino.cc/Code/StopWatchClass - This class allows you to have multiple reset-able counters without them affecting millis/micros and thus not disturbing other libraries/code. On the other hand a class like this would be disrupted by your way of resetting millis()...

Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Gutza

#5
Sep 21, 2013, 03:30 pm Last Edit: Sep 21, 2013, 03:38 pm by Gutza Reason: 1
Quote
a better test would allow the user to set millis or micros to some amount before the rollover


CrossRoads, the problem with rollovers is that millis() ends up returning a smaller value than the previous call to the same function, which is frequently overlooked by programmers, at least in the early stages of prototyping. It shouldn't matter if millis() rolls over after N days or after N seconds -- if there are problems in the code, you can catch them as soon as you have to compare unexpected values. Of course, if your code introduces significant delays between successive calls to millis() then you have to adjust the moment you trigger the rollover accordingly. Having said that, the code I posted is just a proof of concept, I fully expect people to adjust it to their needs -- if you want to trigger an actual rollover, then be my guest, change the code as to populate those variables with very large values instead.

Quote
you do not disable interrupts when resetting the counters so it will fail


robtillaart, I don't think that's true. Internally, millis() does indeed disable interrupts, but as far as I can understand it, that's so you don't end up with a deprecated value for millis() -- think about it, if, within millis(), you read the internal value you want to return and just before you return its value an interrupt gets triggered that takes several seconds to complete, then the value for millis() you finally return is just as many seconds behind the actual time at which you're returning the value. But we don't care for that here, because we're not reading the value for millis() -- instead, we're writing it, so we don't care if there is any interrupt going on at some point during execution. If you do have concrete arguments for your concern, I'd be happy to learn more.

Quote
resetting millis can interfere with proper running of other libraries/code that do not expect a reset


That's what we're testing, so great!

Quote
A better solution is to use a derived timer class [which] would be disrupted


I'm afraid that appears not to make much sense... Once again, this code is intended to test whether millis() rollovers affects my code or other libraries; how would a timekeeping class be a better solution for this particular problem? Also, if the library you're suggesting is affected by the same problem in the same way, why would that be better than anything else for any given purpose? Having said that, you did raise an interesting topic: are there any timekeeping libraries (similar to StopWatch) that are NOT affected by millis() and micros() rollovers? Because I'm considering developing one myself, but I wouldn't want to reinvent the wheel...

Jantje

There is a far better solution to the problem than testing and that is using code that is known to work.
read http://www.gammon.com.au/forum/?id=12127
basically this code always works
Code: [Select]
if (millis () - startTime >= interval)
  {
  // do something
  }


Also read this http://forum.arduino.cc/index.php?topic=184596.0

Best regards
Jantje
Do not PM me a question unless you are prepared to pay for consultancy.
Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -

tttt

I agree with CrossRoads.
If at all this whole exercise was to test your code behavior in case of rollover...
then simulate the code to run though the rollover.
For example, during the Y2K testing days the simulation was done by setting the computer date to something in late december 1999, not by just manually jumping it ahead to 1 Jan 2000 and see if it transitioned.

I'd like to know more about the interrupts theory for my own edification.

Thanks.

Gutza

Quote
There is a far better solution to the problem than testing


@Jantje: for the love of all that is good in the world, please understand that the problem is testing! However, the links you provided are excellent, I'm just disappointed that I was unable to find those threads when I originally researched that issue. I will update the original post to include those resources, thank you!

@tttt: well, so do that instead. I honestly can't see why the zero value set by rolling over is different from the zero value you set manually yourself, but if that tickles you in a more adequate fashion then be my guest, go for it! One point regarding Y2K though: at that point, they were testing whether rollovers would occur; in this scenario, we already know that we'll get rollovers, what we're testing for is how they affect the code.

robtillaart



Quote
you do not disable interrupts when resetting the counters so it will fail


robtillaart, I don't think that's true. Internally, millis() does indeed disable interrupts, but as far as I can understand it, that's so you don't end up with a deprecated value for millis() -- think about it, if, within millis(), you read the internal value you want to return and just before you return its value an interrupt gets triggered that takes several seconds to complete, then the value for millis() you finally return is just as many seconds behind the actual time at which you're returning the value. But we don't care for that here, because we're not reading the value for millis() -- instead, we're writing it, so we don't care if there is any interrupt going on at some point during execution. If you do have concrete arguments for your concern, I'd be happy to learn more.

Code: [Select]
void reset_millis()
{
 extern volatile unsigned long timer0_millis, timer0_overflow_count;
 timer0_millis = timer0_overflow_count = 0;
}

setting an unsigned long (4 bytes) to any value including to zero on the Arduino is not atomic, so it can be interrupted. I agree the chances are small but not zero.

Quote

Quote

resetting millis can interfere with proper running of other libraries/code that do not expect a reset

That's what we're testing, so great!

missed that goal; that makes my solution with the stopwatch class indeed irrelevant.

There are libraries that use millis or micros timing to read sensors. Those can be affected. E.g. the DHT temperature sensor may be read once per 2 seconds, if a DHT library remembers the last read in millis it can guard the sensor. IF millis is reset it will take a long time before it can be read again. If the library is written well (using subtraction) it won't be bothered by the millis roll over.


Quote

Once again, this code is intended to test whether millis() rollovers affects my code or other libraries;
...
are there any timekeeping libraries (similar to StopWatch) that are NOT affected by millis() and micros() rollovers? Because I'm considering developing one myself, but I wouldn't want to reinvent the wheel...

As far as I know there are no timekeeping libs on msec/usec level that are not affected by roll over. There is a Time lib that makes seconds steps that does not roll over in a lifetime (don't know details)

To make a library to measure millis() or micros() without roll-over is trivial at first sight, make the internal counters unsigned long long (64 bit) and the time until roll over is way in the future. 4E9 * 70 minutes = approx 500.000 years for micros. 500 million years for millis(). However 64bit math is slower than 32 bit math so it will affect the duration of the low level increments needed. A simple test shows it takes approx 4.7 uSec to increment an uint64_t and 1.3 uSec to increment an uint32_t (UNO 16Mhz). So the first thing affected is the 4us granularity of the micros. Using two uint_32's iso one uint64_t might be an option but it would make code that uses millis() and micros() more complex e.g. what datatype to return and how to compare??

So imho it is not trivial to create such timekeeping and be functional backwards compatible.
I am very interested in your ideas how it can be realized and I am willing to help/test/verify your ideas/code.



Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

robtillaart

Quote
I honestly can't see why the zero value set by rolling over is different from the zero value you set manually yourself,


If the underlying timekeeping can be reset to zero at any time, other code cannot know at what time the reset happened. So they cannot make any assumption any more about the meaning of time references they keep to do their work, except that they happened in the past.
When the overflow only happens at "max value" all time references still have a physical meaning as the duration (subtracting) math that Jantje showed still can be done (until after 2 roll-overs without noticing)
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Gutza

Quote
I am very interested in your ideas how it can be realized and I am willing to help/test/verify your ideas/code.


Thank you, I'll let you know if I end up implementing it. However, the thread that Jantje pointed out a few posts ago suggests that a simple math trick does the job anyway, in which case the solution is just a matter of good practices. I want to test those assumptions extensively before accepting those conclusions, but several people seem to confirm that's the case.

Quote
When the overflow only happens at "max value" all time references still have a physical meaning as the duration (subtracting) math that Jantje showed still can be done (until after 2 roll-overs without noticing)


That's an excellent observation, thank you for taking the time to explain that in spite of my repeated attempts to dismiss your arguments.  :) I'll see how I can introduce that idea into the original post.

robtillaart

Quote
thank you for taking the time to explain that in spite of my repeated attempts to dismiss your arguments.

Welcome, not the first discussion about this subject ;)
And yes still interested if you have ideas how to improve on the current timekeeping.
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Gutza

robtillaart, I don't think there's any need for a new solution for this problem, because StopWatch seems to work just fine:

Code: [Select]
#include <StopWatch.h>

unsigned long currentSecond, initialMillis;
StopWatch MySW;

void setup()
{
  // Stage an overflow 5 seconds from now
  stage_reset_millis(5000);

  Serial.begin(57600);
  currentSecond = 0;
  initialMillis = millis();
  MySW.start();
}

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.print("; SW: ");
  Serial.print(MySW.elapsed());
  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're able to find any inconsistencies in its behavior; I sure can't.

robtillaart

you reset the millis before it is used by the library.
If you reset it while in use trouble starts....

Code: [Select]
#include <StopWatch.h>

unsigned long currentSecond, initialMillis;
StopWatch MySW;

void setup()
{
  // Stage an overflow 5 seconds from now
  stage_reset_millis(5000);

  Serial.begin(57600);
  currentSecond = 0;
  initialMillis = millis();
  MySW.start();
}

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.print("; SW: ");
  Serial.print(MySW.elapsed());
  Serial.println("");

  stage_reset_millis(2000); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<< added
 
  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(); 
}
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Go Up