Pages: [1] 2   Go Down
Author Topic: Reset Arduino millis() and micros()  (Read 2983 times)
0 Members and 1 Guest are viewing this topic.
Romania
Offline Offline
Newbie
*
Karma: 0
Posts: 7
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
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:
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:
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! smiley

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):
« Last Edit: September 21, 2013, 10:57:10 am by Gutza » Logged

Global Moderator
Boston area, metrowest
Online Online
Brattain Member
*****
Karma: 439
Posts: 23791
Author of "Arduino for Teens". Available for Design & Build services. Now with Unlimited Eagle board sizes!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Wouldn't a better test be setting millis or micros very high so the rollover could be tested more frequently?
Logged

Designing & building electrical circuits for over 25 years. Check out the ATMega1284P based Bobuino and other '328P & '1284P creations & offerings at  www.crossroadsfencing.com/BobuinoRev17.
Arduino for Teens available at Amazon.com.

Romania
Offline Offline
Newbie
*
Karma: 0
Posts: 7
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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?
Logged

Global Moderator
Boston area, metrowest
Online Online
Brattain Member
*****
Karma: 439
Posts: 23791
Author of "Arduino for Teens". Available for Design & Build services. Now with Unlimited Eagle board sizes!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Designing & building electrical circuits for over 25 years. Check out the ATMega1284P based Bobuino and other '328P & '1284P creations & offerings at  www.crossroadsfencing.com/BobuinoRev17.
Arduino for Teens available at Amazon.com.

Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 170
Posts: 12465
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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()...

Logged

Rob Tillaart

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

Romania
Offline Offline
Newbie
*
Karma: 0
Posts: 7
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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...
« Last Edit: September 21, 2013, 08:38:39 am by Gutza » Logged

Belgium
Offline Offline
Edison Member
*
Karma: 58
Posts: 1731
Arduino rocks; but with my plugin it can fly rocking the world ;-)
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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:
if (millis () - startTime >= interval)
  {
  // do something
  }

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

Best regards
Jantje
Logged

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 -

Albuquerque, NM
Offline Offline
Newbie
*
Karma: 2
Posts: 46
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Romania
Offline Offline
Newbie
*
Karma: 0
Posts: 7
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 170
Posts: 12465
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset


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



Logged

Rob Tillaart

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

Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 170
Posts: 12465
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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)
Logged

Rob Tillaart

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

Romania
Offline Offline
Newbie
*
Karma: 0
Posts: 7
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.  smiley I'll see how I can introduce that idea into the original post.
Logged

Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 170
Posts: 12465
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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 smiley-wink
And yes still interested if you have ideas how to improve on the current timekeeping.
Logged

Rob Tillaart

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

Romania
Offline Offline
Newbie
*
Karma: 0
Posts: 7
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Code:
#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.
Logged

Global Moderator
Netherlands
Offline Offline
Shannon Member
*****
Karma: 170
Posts: 12465
In theory there is no difference between theory and practice, however in practice there are many...
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

Code:
#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(); 
}
Logged

Rob Tillaart

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

Pages: [1] 2   Go Up
Jump to: