microsSince() - dealing with the roll over

Not the euromillions roll over :slight_smile:

My project needs to control 2 stepper motors and an optoisolator simultaneously, so I need to use the micros() timer to work out how long it’s been since the last time I got to a place in my loop.

According to the documentation, approximately every 70 minutes, the micros timer rolls over, so this would cause a problem if it happened in a middle of the run, so I need a microsDifference(now, then) function. When the ‚Äėnow‚Äô variable is less than ‚Äėthen‚Äô we have obviously poped the overflow. So my question:

whats the magic max number? is it a 32 bit unsigned? 0xffffffff??

I think I want this:

if(now < then) {
  return 0xffffffff - then + now;
} else {
  return now - then;
}

I would try it out, but I’d rather understand it before I had to wait several multiples of 70 minutes :slight_smile:

Thanks
Nigel

You just have to subtract and then it all works automagically.

if (micros () - then > some_interval)
  {
  // do something
  }

Subtract AND cast to unsigned.

http://www.cmiyc.com/blog/2012/07/16/arduino-how-do-you-reset-millis/

I don’t know about the cast. In the examples on that page the subtraction is in brackets, so you already have an unsigned number. So why bother casting it?

[quote author=James C4S link=topic=118922.msg895047#msg895047 date=1345189044] Subtract AND cast to signed.

http://www.cmiyc.com/blog/2012/07/16/arduino-how-do-you-reset-millis/ [/quote]

I dont think this will work.

if micros() > waitUntil then trigger and waitUntil += interval

If my waitUntil rolls over because of unsigned magicalness ahead of the midros counter, then micros will always be > waitUntil, until the micros() counter rolls over itself.

So, if micros is returning 0xffffffff - 90, and I trigger each 100, and my new waitUntil is 10, then each loop for the next 90us will trigger - this sounds bad.

I think Nicks solution is what I'm after... i think, my brian doesn't want to understand it fully, but I think... :)

Thanks guys, I'll give it a go and watch for any jitters at about 70 minutes... I'll clear the mantle piece of the good stuff, just in case :)

Watch what happens when you run this code with and without the cast to signed char:

unsigned char counter = 0;
unsigned char waitUntil = 0;
#define interval 47;
void setup() {
  Serial.begin(9600);
  while (!Serial); // Leonardo Only
  waitUntil += interval;
}
  
void loop() {
  for (int x=0; x<1000; x++) { // run through uchar a few times
    counter++;  // simulate millis()
    Serial.println(counter);
    if ((char)(counter - waitUntil) >=0) {  // check for rollover
      Serial.println("Trigger Event!");
      waitUntil += interval;
    }
  }
  while(1);  // stop the serial monitor's output
}

Yes but the recommended test is:

now - start_time >= interval

You have:

now - future_time >= 0

Adding to now to get a future time will roll over the wrong way.

[quote author=Nick Gammon link=topic=118922.msg895813#msg895813 date=1345237613] Yes but the recommended test is:

now - start_time >= interval

You have:

now - future_time >= 0

Adding to now to get a future time will roll over the wrong way. [/quote]

The first one will work with unsigned longs happily up to 2^32-1 us ahead (about 72mins), the second must be cast to signed to do the compare and only works to 2^31 us ahead (about 36mins):

  long  signed_diff = micros () - future_time ;
  if (signed_diff >= 0L) 
    ...

The best way is to use 2 stored values, start-time and interval, with "now", millis or micros.

However you can set a future-time and subtract that from "now". When "now" passes future-time the subtraction will result in a large value. Thing is that your longest interval becomes less than half as much.

Sketch for playing with unsigned's:

unsigned long a, b, c;

void setup() {
  Serial.begin( 9600 );
  a = 0xffffff00UL;
  b = 0x10UL;
  Serial.println( "\n unsigned math\n" );
  Serial.print( "a = ");
  Serial.print( a, DEC );
  Serial.print( " = 0x");
  Serial.print( a, HEX );
  Serial.print( " = 0b");
  Serial.println( a, BIN );
  Serial.print( "b = ");
  Serial.print( b, DEC );
  Serial.print( " = 0x");
  Serial.print( b, HEX );
  Serial.print( " = 0b");
  Serial.println( b, BIN );
  if ( b >= a ) Serial.println( "b >= a" );
  else          Serial.println( "a > b" );
  c = a - b;
  Serial.print( "a - b = ");
  Serial.print( c, DEC );
  Serial.print( " = 0x");
  Serial.print( c, HEX );
  Serial.print( " = 0b");
  Serial.println( c, BIN );
  c = b - a;
  Serial.print( "b - a = ");
  Serial.print( c, DEC );
  Serial.print( " = 0x");
  Serial.print( c, HEX );
  Serial.print( " = 0b");
  Serial.println( c, BIN );
  c = b - (b + 1);
  Serial.print( "b - (b + 1) = ");
  Serial.print( c, DEC );
  Serial.print( " = 0x");
  Serial.print( c, HEX );
  Serial.print( " = 0b");
  Serial.println( c, BIN );
}

void loop() {};

Okay, but even this check only works when is cast to a signed value:

unsigned char counter = 0;
unsigned char start_time = 0;
#define interval 47

void setup() {
  Serial.begin(9600);
  for (int x=0; x<1000; x++) { // run through uchar a few times
    counter++;  // simulate millis() or micros()
    Serial.print(counter); Serial.print(","); Serial.println(start_time);
    if ((char)(counter - start_time) >= interval) {  // check for rollover
      Serial.println("Trigger Event!");
      start_time=counter;
    }
  }
}
  
void loop() {
}

If you remove the cast to signed char, the if-statement won’t be true once counter rolls over.

Try running that sketch I just posted.

[quote author=James C4S link=topic=118922.msg896719#msg896719 date=1345331062] [quote author=Nick Gammon link=topic=118922.msg895813#msg895813 date=1345237613] Yes but the recommended test is:

now - start_time >= interval

[/quote] Okay, but even this check only works when is cast to a signed value: [/quote] No, not true, it works best as unsigned as I've already said.

If you remove the cast to signed char, the if-statement won't be true once counter rolls over.

If you remove the cast you are using 16 bit arithmetic and comparisons, not 8 bit - replace the (char) cast by (unsigned char) and you'll be able to allow the interval to range up to 255. Your code only works with interval ranging up to 127. Try it and see.

Read my previous post!

Signed math is like a ruler. Unsigned math is like a clock. It is 3:00, how long ago was 11:00?

I apologize for basically hijacking this thread, but I am finding that this code DOES NOT work after rollover occurs:

    if ((counter - start_time) >= interval)

I resisted what you guys were saying at first because the example code I have provided demonstrates this fact. Once the variable ‚Äúcounter‚ÄĚ rolls over, the check always fails. Now that I have tested some more, I understand what Mark and others are saying about the maximum value that can be used if the operation is cast to signed. I agree, in the case of a char it limits you to 127.

What I still don‚Äôt understand is why in this code, the ‚Äúrecommended check‚ÄĚ fails after roll over. UNLESS you specifically cast it to unsigned.

unsigned char counter = 0;
unsigned char start_time = 0;
#define interval 147

void setup() {
  Serial.begin(9600);
  for (int x=0; x<1000; x++) { // run through uchar a few times
    counter++;  // simulate millis() or micros()
    Serial.print(counter); Serial.print(","); Serial.println(start_time);
    if ((counter - start_time) >= interval) {  // check for rollover  // WITHOUT A CAST TO (unsigned char) this will NOT WORK
      Serial.println("Trigger Event!");
      start_time=counter;
    }
  }
}
  
void loop() {
}

Change line 10 to:

    if ((unsigned char)(counter - start_time) >= interval) {

and the check works after rollover. Why is the explicit cast to unsigned char necessary?

Because you‚Äôve changed the domain of the example. Dealing with millis(), etc. we are dealing with unsigned longs. You‚Äôve changed the parameters, and entered into the world of ‚Äúwhat the compiler chooses to do to optimize this case,‚ÄĚ which breaks the rules - because the compiler has not done what you expected.

The following works perfectly fine, as has been known for some time:

unsigned long counter = 0;
unsigned long start_time = 0;
#define interval 10000000

void setup() {
  Serial.begin(9600);
  for (int x=0; x<1000; x++) { // run through uchar a few times
    counter += 47721860;  // simulate millis() or micros()
    Serial.print(counter); Serial.print(","); Serial.println(start_time);
    if (counter - start_time >= interval) {  // rollover works just fine
      Serial.println("Trigger Event!");
      start_time=counter;
    }
  }
}
  
void loop() {
}

Note the results:

4104079960,4056358100
Trigger Event!
4151801820,4104079960
Trigger Event!
4199523680,4151801820
Trigger Event!
4247245540,4199523680
Trigger Event!
104,4247245540
Trigger Event!
47721964,104
Trigger Event!
95443824,47721964
Trigger Event!
143165684,95443824

You‚Äôre doing a different case than ‚Äúhow to check when millis‚Äô unsigned long rolls over‚ÄĚ and have changed the case to ‚Äúhow to check when a small data type rolls over‚ÄĚ - they are not the same, and the compiler is treating the smaller case differently, hence why it works when you explicitly cast the results.

!c

EDIT: Adding this will be fairly illuminating as to what’s happening in your temporary variable handling:

    Serial.println(counter - start_time);

Results:

223,248
-25
224,248
-24
225,248
-23
226,248
-22
227,248
-21
228,248
-20

[quote author=James C4S link=topic=118922.msg897404#msg897404 date=1345404823]Why is the explicit cast to unsigned char necessary? [/quote]

I said this:

If you remove the cast you are using 16 bit arithmetic and comparisons, not 8 bit - replace the (char) cast by (unsigned char) and you'll be able to allow the interval to range up to 255.

C always widens to ints for intermediate values. On the Arduino ints are 16 bit.

Actually the unsigned math works out for 8, 16, 32 or 64 bit values if you keep to the type. I posted a sketch showing unsigned longs math that can be modified to any type easily.

What unsigned can't handle is intervals longer than the range of the integer type. Du-uhhhh. Unsigned longs handle milliseconds intervals up to 49.71.... days. Go past that and it doesn't work.

Using bytes (unsigned 8 bit): end - start = elapsed 8 - 120 = 16 120 - 110 = 10

Do that with chars (signed 8 bit): start time is 120, what is end time 16 units later? How do you subtract 120 from 120+16 signed? How do you subtract start from end when you can't properly represent end? Oh yeah, you check conditions and apply a correction as necessary. How smooth! The answer violates the signed math concept, overflow is not the same as rollover.

It works fine without correction using unsigned math, exactly smooth - 1 case always.

If you are having problems then check that you aren't mixing types or something. Or maybe just having problems with how unsigned's work.