- changed status to resolved
micros() rollover bug
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)
-
repo owner -
repo owner - marked as proposal
-
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?
-
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.
- Log in to comment
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.