micros() rollover bug

Issue #11 resolved
Former user created an issue

In NewPing.cpp, there are quite a few instances of

if (micros() > _max_time) ...

where _max_time is the timeout date, which may have been initialized by something like

_max_time = micros() + _maxEchoTime;

This will fail every time micros() is about to roll over. Here is why:

If micros() + _maxEchoTime is larger than 2^32, the result will overflow and _max_time will be a very small number. Then the test if (micros() > _max_time) will immediately and incorrectly detect a timeout.

This bug is probably not very serious, since it will make the library fail for only a very short time every 72 seconds or so. There is, however, no reason to leave it, as there are a couple of easy ways to fix it.

The cleanest fix is probably to always compare durations instead of timestamps. This works because unsigned numbers are guaranteed to follow the rules of modular arithmetic. The correct code would be:

_max_time = _maxEchoTime;     // this is a duration, not a timestamp
_ping_start_time = micros();  // the timestamp
...
if (micros() - _ping_start_time > _max_time) // timeout detected

The drawback is that it requires an extra subtraction every time you test for the timeout condition.

The most efficient fix is to subtract the timestamps to be compared, and look at the sign of the result interpreted as a signed number:

if ((signed long)(_max_time - micros()) < 0) // timeout detected

The drawback is that this depends on implementation defined behaviour (but gcc promises to do the right thing), and it is also somewhat obscure.

Comments (4)

  1. Tim Eckel repo owner

    I'm aware of this but don't consider it a real-world problem. First off, the micros rollover happens once every 71.5 MINUTES, not seconds. Also, the "problem" will only happen if the timeout exactly hits the rollover, which is EXTREMELY rare. Also, ultrasonic sensors are not very accurate to begin with, so pings with no result are common anyway. Finally, on the rare chance that it did exactly hit the rollover, it doesn't cause a hang situation, simply a no ping result (which would be accommodated for in your code or the ping_median() method).

    In any case, in the real world, a micros rollover isn't a problem with NewPing. I'm aware of the extremely slight chance that a rollover could happen, but it was designed specifically to fail gracefully. Because speed is more important, it's a compromise that was purposefully made.

  2. Kevin van Zanten

    I have found that using sonar.ping_cm() returns 0 after micros() rolls over, and it stays stuck at 0 until I power cycle the Arduino (Genuino 101 with HC-SR04). This happens every single time. I ping 20 times per second. I've also tried using sonar.ping_median() but this causes the Arduino to actually lock up when micros() rolls over.

    How should I accommodate for this?

  3. Tim Eckel repo owner

    At most you could get one "0" result in 71.5 minutes under extreme rare situations. And even if that happened, you wouldn't continue to get "0" results. Many people use my library with and without ping_median() for extended periods of time (like years) without issues.

  4. Log in to comment