micros() wrapping problem

I am seeing an error in the behaviour of my program when the 32-bit micros() value wraps. It's most likely that I'm doing something wrong, so I'm posting this here before blaming the micros() implementation.

I have written a test case to demonstrate the problem in isolation (see below).

I am simulating events at 20ms intervals. The real application gets a signal derived from the mains at 50Hz, giving periods of 20ms. The beginning of each period I'm calling 't0'. At t0, I set a test pin high. 10ms into the period, I test that it is still high. 6ms later (16ms after t0) I set the test pin low.

I'm using millis to produce the basic 20ms events and the time to test for high. I'm using micros to decide when to set the test pin low i.e. 16000us after t0. This reflects the need in the real application to have microsecond resolution on actions timed from t0.

This test case runs happily, measuring high at 10ms every 'period', until the micros value wraps at just over 71 minutes after reset. At this point, the t0 event is detected normally, but the condition controlling the setting of the test pin low is immediately satisfied. The test pin is set low and the test for high at 10ms fails.

I can make this happen sooner after reset by adding a fixed value to the one returned by micros(). 0xFFF00000 shows the problem in a couple of seconds.

The test case requires a link between pins 52 and 53 and I've run it on a Mega and a Due, with the same result. It flashes the pin 13 LED slowly when the test in each period passes, faster when it fails. I also put some test pins in so that I could check on a 'scope that it was working correctly.

I've tried the new micros() implementation described in this thread:

... and it doesn't change the result.

Have I missed something basic? I feel a facepalm coming on...

Thanks.

The code:

#define LED_PIN 13
#define IN_PIN 52
#define OUT_PIN 53
#define T0_PIN 21
#define HIGH_TEST_PIN 22
#define HIGH_CLEAR_PIN 23

unsigned int t0Count = 0;
unsigned long nextT0ms = 0;
unsigned long lastT0ms = 0;
unsigned long lastT0us = 0;
unsigned long ledNextChangeMs = 0;
boolean ledOn = false;
boolean errorDetected = false;

unsigned long highTestMs = 10; // ms after t0
boolean highTestDone = false;

unsigned long highClearUs = 16000; // us after t0
boolean highClearDone = false;

void setup() {
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);

  pinMode(IN_PIN, INPUT);
  
  pinMode(OUT_PIN, OUTPUT);
  digitalWrite(OUT_PIN, LOW);
  
  pinMode(T0_PIN, OUTPUT);
  digitalWrite(T0_PIN, LOW);
  
  pinMode(HIGH_TEST_PIN, OUTPUT);
  digitalWrite(HIGH_TEST_PIN, LOW);
  
  pinMode(HIGH_CLEAR_PIN, OUTPUT);
  digitalWrite(HIGH_CLEAR_PIN, LOW);
}

void handleLed(const unsigned long nowMs)
{
  unsigned long ledToggleTimeMs = 1000;
  if (errorDetected) // faster
    ledToggleTimeMs = 100;
  
  if (nowMs >= ledNextChangeMs) // toggle state
  {
    ledNextChangeMs = nowMs + ledToggleTimeMs;
    
    if (ledOn)
    {
      digitalWrite(LED_PIN, LOW);
      ledOn = false;
    }
    else
    {
      digitalWrite(LED_PIN, HIGH);
      ledOn = true;
    }
  }
}

void loop()
{
  unsigned long nowMs = millis();
  unsigned long nowUs = micros() + 0xFFF00000; // get to wrap sooner
    
  if ( ! errorDetected)
  {
    if (nowMs >= nextT0ms) // starting a new period
    {
      // pulse T0_PIN
      digitalWrite(T0_PIN, HIGH);
      digitalWrite(T0_PIN, LOW);

      t0Count++;
      nextT0ms += 20;
      lastT0ms = nowMs;
      lastT0us = nowUs;
      highTestDone = false;
      highClearDone = false;
      digitalWrite(OUT_PIN, HIGH);
    }
    
    if ( ! highTestDone && nowMs >= (lastT0ms + highTestMs))
    {
      highTestDone = true;
      
      // should still measure HIGH
      if (digitalRead(IN_PIN) != HIGH)
        errorDetected = true;

      // pulse HIGH_TEST_PIN
      digitalWrite(HIGH_TEST_PIN, HIGH);
      digitalWrite(HIGH_TEST_PIN, LOW);
    }
    
    if ( ! highClearDone && nowUs >= (lastT0us + highClearUs))
    {
      highClearDone = true;
      
      digitalWrite(OUT_PIN, LOW);
      // pulse HIGH_CLEAR_PIN
      digitalWrite(HIGH_CLEAR_PIN, HIGH);
      digitalWrite(HIGH_CLEAR_PIN, LOW);
    }
  }
  
  handleLed(nowMs);
}
    if ( ! highClearDone && nowUs >= (lastT0us + highClearUs))

Subtraction is guaranteed. Addition is not. Rearrange this to involve subtraction. See if that fixes your problem.

Yes, subtraction is needed. Generally:

if ( (currentTime - earlierTime) >= duration ){
// time has passed, do something
}

Thus after the rollover:
currentTime - earlierTime = result
0x00000100 - 0xFFFFFF00 = 0x00000200 with the bits above 32 falling off and correct result is returned.

Thanks, both - that was it. Annoyingly, I was sort-of aware of needing to do the calculation the right way round and thought I'd tried it the other way, and it still failed. Must have done it wrong.

Thanks again...

I am not too knowledgeable about 2s compliment etc, and binary digits falling away, but I have changed the clocks in my scoreboards to micros ( as suggested by Crossroads ) to get better accuracy.
I am not too worried about a 50 day rollover ( or whatever it is for micro() ) unless it happens in the middle of a soccer game, and they have to play all afternoon :slight_smile:

Will this simple bit of code solve the problem ? ( even if it loses a bit of time for one second at a rollover ? )

if ( micros () < (previousMicros - 1000000)) // its rolled over
{ previousMicros = micros() + 1000000;  } // reset to start again
 if(micros() > previousMicros)          //  this bit is working anyway
 { previousMicros = micros() + 1000000; secunits -- ;                   //  and so on counting down the timer

I am not too worried about a 50 day rollover ( or whatever it is for micro() )

232 / 106 = 4 294 seconds, or 1 hour 11 minutes 34.9 seconds.
How long is a soccer match again? :wink:

It could be an extra 71 minutes :slight_smile:

Why so complicated John?
You don't care that its rolled over. The math works.

currentTime = micros();
if ( currentTime - previousTime >= oneSecond){
previousTime = previousTime + oneSecond;
// count down from there
if (seconds >0){
seconds = seconds -1;
}
else {
seconds = 59;
if (minutes >0){
minutes =  minutes-1;
}
else {
minutes = 59;
if (hours >0){
hours = hours -1;
}
// and enough }'s to make this work out, CRL-T for formatting, not enough screen to see them all here ...
}

I tried a few variations on that, but when I hit the countdown run button on the remote, it zapped through the first few seconds in rapid time, and then counted once per second, so I reverted to the micro version of the old sketch and it worked.

The project had to go out today, so I didn't have any time to experiment further, but hopefully after this week I can get some breathing space to see where I was going wrong !

It is this bit, when micros has started again at zero, and our previous target is enormous, that I havn't had time to get my head around

if ( currentTime - previousTime >= oneSecond) it looks at first glance that current time ( say 100 ) - previous enormous count will be a minus figure for a long time.

Obviously if it is an unsigned long it wont be minus, so it is a large number greater than one Second ?

Boffin1:
It is this bit, when micros has started again at zero, and our previous target is enormous, that I havn't had time to get my head around

if ( currentTime - previousTime >= oneSecond) it looks at first glance that current time ( say 100 ) - previous enormous count will be a minus figure for a long time.

Obviously if it is an unsigned long it wont be minus, so it is a large number greater than one Second ?

No. In binary, if you take 1 from 0000 you get (-)1111, so your result will be:
Largest number variable can hold - previous enormous count (+ your 100) = a positive number, less than 1 second.

@Boffin1: Think of a clock, the minute hand. It goes from 0 to 59, then "rolls over" to 0. Note that there are no negtive minutes, there are only the positive numbers 0..59. The same with [b]unsigned[/b] long (or unsigned integer etc). The high number is just a bit bigger than 59.

So you want to measure off 10 minutes. On your analog clock face you know that is "an angle" of about 60 degrees. And it has that angle irrespective if we start the count at 5 or 55 minutes. (OK, slight digression, there is no "angle" in unsigned long.... but I want to get accross the point you do not worry about the "rollover" if you start at 55, do you?)

OK, so now or the math version.
Start at 5 minutes past (put that in PreviousMinutes) then look at the clock every now and then (say at 7 minutes, 12 minutes and 16 minutes past). The calculation is Minutes()-PreviousMinutes, which at thoses times yields 2, 7 and 11 respectivly, where 11 is more than our 10 minute - time done. Let's assume that you dont like doing subtraction so you simply COUNT from the minute hand backwards the minute tick marks on the face until you reach the start point (which was 5). Same result.

Now repeat the exercise but start at 55, and look at the clock at 58, 2, and 6 minutes. Your objection is that at "2" the calculation is 2-55 which yields -53. But there are no negative minutes!! Whenever you get negative minute you add 60 minutes, that "complements" the number (beause 60 is all the unique numbers we have on the dial). And that gives 7. Same way, if you dont like this subtract&complement (which is what unsigned math is) then just count the minute marks from the "2" backward to the "55" gives 7. Yes, 7 minutes elapsed. And at "6" it is 11 minutes, we've done the 10 minute interval.

So the "magic" works. As long as you always use the formula ( millis() - PreviousMillis > Interval ) for the if-condition then the rollover does not matter. Ditto micros().

There is a limit. You can see that you can not measure an interval greater than 60 minutes on the clock with only a minute hand. It also gets a bit tricky if you measure, say 55 minutes; if you look at the clock every now and then you may miss the 5 minutes where the subtraction(&complement) yields a number bigger than 55. Once we have wrapped around and past the starting point, it seems like only a short interval has passed again. So for micro-based timers you have to keep to roughly less than 70 minutes, and for millis roughly less than 49 days. (You can "make your own" special timer by taking a few highbits from the micros and multiply by a few low bits from the millis to make a timer that covers - say one&half day before rollover in 32 microseconds steps - but we'll cover that another day)

I hope this is all "clear as mud" :slight_smile:

Yes,well explained.
Fire up your windows calculator, put it in Scientific mode, or Programming mode if it has that.

Select Hex.
Enter 10 - ffffff10 (currentTime - previousTime), result is FFFF FFFF 0000 0100, which is a 64 bit answer. Throw away the left 32 bits which don't exist in a 32 bit unsigned long number, and you have 0000 0100, which is the answer you want.
(similar for a byte, which only counts 0-FF, or an unsigned int, 0-FFFF, unsigned long is 0-FFFF FFFF)

(10 is 10 after the rollover, and FFFFFF10 is F0 before the rollover, 10 + F0 = 100.)

Msquare:
I hope this is all "clear as mud" :slight_smile:

Well done! Nominated for a sticky, or a prominent place in the playground, or somewhere that it can be conveniently reused.

I used to think the same thing and now I'm not so sure.

I find no problem adding interval to now to know when the interval should be up since the ending time rolls over.

What I do find a problem is after that knowing if the current time has reached or passed the mark if start time > end time.

I get a sneaking feeling that Nick Gammon knew this long ago but is too polite to disagree.
Unless this is wrong, and then he knew that too.

And there's a gotcha with this method, spoiler at the bottom.

I call this Rollover_Rover:

// rollover on 16 by steps

byte mynow, mystart, myend, flag;

byte timeOver( byte n, byte s, byte e )
{
  flag = 0; // interval is not over
  if ( s > e )
  {
    if (( n < s ) & ( n >= e ))
    {
      flag = 1;
    }
  }
  else
  {
    if ( n >= e )
    {
      flag = 1;
    }
  }
  return flag;
}

void setup( void )
{
  Serial.begin( 9600 );
  Serial.println( "Do you really have to subtract unsigneds?" );

  mynow = mystart = 10;
  myend = ( mystart + 8 ) & 15;

  Serial.println( );
  Serial.print( " Start = " );
  Serial.print( mystart, DEC );
  Serial.print( ", End = " );
  Serial.println( myend, DEC );

  flag = 0;
  while ( !flag )
  {
    Serial.print( " Now = " );
    Serial.println( mynow, DEC );
    flag = timeOver( mynow, mystart, myend );
    mynow = ( mynow + 1 ) & 15;
  }

  mynow = mystart = 4;
  myend = ( mystart + 8 ) & 15;

  Serial.println( );
  Serial.print( " Start = " );
  Serial.print( mystart, DEC );
  Serial.print( ", End = " );
  Serial.println( myend, DEC );

  flag = 0;
  while ( !flag )
  {
    Serial.print( " Now = " );
    Serial.println( mynow, DEC );
    flag = timeOver( mynow, mystart, myend );
    mynow = ( mynow + 1 ) & 15;
  }
}

void loop( void )
{
}

The output:

Do you really have to subtract unsigneds?

Start = 10, End = 2
Now = 10
Now = 11
Now = 12
Now = 13
Now = 14
Now = 15
Now = 0
Now = 1
Now = 2

Start = 4, End = 12
Now = 4
Now = 5
Now = 6
Now = 7
Now = 8
Now = 9
Now = 10
Now = 11
Now = 12

The gotcha .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
To make this work you still have to keep 2 values, same as with using unsigned subtraction only it takes more code and more cycles than using unsigned subtraction.

GoForSmoke, what? You are not using millis() or micros(), so what will be wrapping over?

Here's a simple test I wrote that keeps time pretty well synced with the official US time
http://www.time.gov/
on a crsytal clocked Duemilanove anyway.

unsigned long currentmicros = 0;
unsigned long nextmicros = 0;
unsigned long interval = 10000; 

byte tens_hours = 0; // 
byte ones_hours = 0;  // seems to gain 1 second/day1/24
byte tens_minutes = 0;
byte ones_minutes = 0;
byte tens_seconds = 0;
byte ones_seconds = 0;
byte tenths = 0;
byte hundredths= 0;

byte prior_seconds = 0;

void setup()

{
  Serial.begin(57600);
  nextmicros = micros();
}

void loop()

{

  currentmicros = micros(); // read the time.

  if ((currentmicros - nextmicros) >= interval) // 10 milliseconds have gone by

  {

    hundredths = hundredths +1;

    if (hundredths == 10){
      hundredths = 0;
      tenths = tenths +1;
    }

    if (tenths == 10){
      tenths = 0;
      ones_seconds = ones_seconds +1;
    }

    if (ones_seconds == 10){
      ones_seconds = 0;
      tens_seconds = tens_seconds +1;
    }

    if (tens_seconds == 6){
      tens_seconds = 0;
      ones_minutes = ones_minutes +1;
    }

    if (ones_minutes == 10){
      ones_minutes = 0;
      tens_minutes = tens_minutes +1;
    }

    if (tens_minutes == 6){
      tens_minutes = 0;
      ones_hours = ones_hours +1;
    }

    if (ones_hours == 10){
      ones_hours = 0;
      tens_hours = tens_hours +1;
    }
    if ((tens_hours == 2) && (ones_hours == 4)){
      ones_hours = 0;
      tens_hours = 0;
      delay(1000);
    }

    nextmicros = nextmicros + interval; // update for the next comparison

  }  // end time interval check

  // counters are all updated now, send to display

  if (prior_seconds != ones_seconds){

    Serial.print (tens_hours, DEC);
    Serial.print (" ");
    Serial.print (ones_hours, DEC);
    Serial.print (" : ");
    Serial.print (tens_minutes, DEC);
    Serial.print (" ");
    Serial.print (ones_minutes, DEC);
    Serial.print (" : ");
    Serial.print (tens_seconds, DEC);
    Serial.print (" ");
    Serial.println (ones_seconds, DEC);

    prior_seconds = ones_seconds;   // show time update once/second
  }  // end one second passing check
  
  // do other stuff in the meantime ...

} // end void loop

Well thanks guys for the explanations,
I had to get that job out ( I was still working on it till 4 am this morning, but that's another story for Barsports that I will call "painting myself into a corner "

I tested my sketch for 90 minutes ( to guarantee a micros rollover ) and it is working, though I might lose a bit of a second at rollover time , once per soccer game.

I have one other job to get out this week, ( the giant robot controller ) and then hopefully I can do some RTFM on all these points that I seem to get stuck on.

Thanks guys

CrossRoads:
GoForSmoke, what? You are not using millis() or micros(), so what will be wrapping over?

The same thing that wraps when you do use millis() or micros(). Bits.

I use the same math to just do math that I use when I work with sensors or money. I made a small demo to show that subtraction is not the only way though it is the better way. I thought I'd get lulz.

Not sure what that example is getting at, but so long as the interval is less that 2^32 microseconds
(or less than 2^31 microseconds if you use signed longs) then the simple subtractive test works fine.

GoForSmoke:
I made a small demo to show that subtraction is not the only way though it is the better way. I thought I'd get lulz.

It looked complicated, but as far as I could see you were passing in 'now', 'start' and 'end' times as signed values and then explicitly looking for each case that could have occurred (rollover between start and now, and between start and end, and between now and end). As I see it, the point of the 'one true way' to compare time values by subtraction is that it avoids the need to handle the rollover situations explicitly. If you do handle rollover explicitly then you can code it using addition, subtraction or whatever method you want - just as long as you get it right.