Go Down

Topic: microsSince() - dealing with the roll over (Read 1 time) previous topic - next topic

nigeljohnson73

Not the euromillions roll over :)

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

Thanks
Nigel

Nick Gammon

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

Code: [Select]

if (micros () - then > some_interval)
  {
  // do something
  }
http://www.gammon.com.au/electronics

James C4S

#2
Aug 17, 2012, 09:37 am Last Edit: Jan 15, 2013, 07:39 am by James C4S Reason: 1
Subtract AND cast to unsigned.

http://www.cmiyc.com/blog/2012/07/16/arduino-how-do-you-reset-millis/
Capacitor Expert By Day, Enginerd by night.  ||  Personal Blog: www.baldengineer.com  || Electronics Tutorials for Beginners:  www.addohms.com

Nick Gammon

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?
http://www.gammon.com.au/electronics

nigeljohnson73


Subtract AND cast to signed.

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


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

James C4S

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

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
}


Capacitor Expert By Day, Enginerd by night.  ||  Personal Blog: www.baldengineer.com  || Electronics Tutorials for Beginners:  www.addohms.com

Nick Gammon

Yes but the recommended test is:

Code: [Select]
now - start_time >= interval

You have:

Code: [Select]
now - future_time >= 0

Adding to now to get a future time will roll over the wrong way.
http://www.gammon.com.au/electronics

MarkT


Yes but the recommended test is:

Code: [Select]
now - start_time >= interval

You have:

Code: [Select]
now - future_time >= 0

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


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

  long  signed_diff = micros () - future_time ;
  if (signed_diff >= 0L)
    ...
[ I won't respond to messages, use the forum please ]

GoForSmoke

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:

Code: [Select]

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() {};
I find it harder to express logic in English than in Code.
Sometimes an example says more than many times as many words.

James C4S


Yes but the recommended test is:
Code: [Select]
now - start_time >= interval

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

Code: [Select]

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.
Capacitor Expert By Day, Enginerd by night.  ||  Personal Blog: www.baldengineer.com  || Electronics Tutorials for Beginners:  www.addohms.com

GoForSmoke

Try running that sketch I just posted.
I find it harder to express logic in English than in Code.
Sometimes an example says more than many times as many words.

MarkT



Yes but the recommended test is:
Code: [Select]
now - start_time >= interval

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

No, not true, it works best as unsigned as I've already said.
Quote


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!
[ I won't respond to messages, use the forum please ]

GoForSmoke

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

I find it harder to express logic in English than in Code.
Sometimes an example says more than many times as many words.

James C4S

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

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

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

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

and the check works after rollover.  Why is the explicit cast to unsigned char necessary?
Capacitor Expert By Day, Enginerd by night.  ||  Personal Blog: www.baldengineer.com  || Electronics Tutorials for Beginners:  www.addohms.com

drone

#14
Aug 19, 2012, 11:24 pm Last Edit: Aug 19, 2012, 11:32 pm by drone Reason: 1

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

Code: [Select]

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:

Code: [Select]

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:

Code: [Select]

    Serial.println(counter - start_time);


Results:

Code: [Select]

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

Go Up