New core function: overflow-immune comparison of millis() and micros() values

I'd like to propose adding an overflow-immune function to the core for comparing timer values.

There is a subtlety in the way timer examples (millis and micros) are written in almost every sketch: there is no allowance for timer overflow. Whilst this occurs only once every 50 days for millis, it is much more frequent with micros (72 minutes).

Sketches which don't deal with timer overflow when comparing values can suffer infrequent bugs -- every 72 minutes or every 50 days. The manifestation of the bug will depend on what the timer is being used for. The severity can vary from un-noticeable, to a small hiccup, to a total crash of the sketch.

These kinds of infrequent bugs are very hard to duplicate and find. I have learned the hard way to put code for overflow-immune comparison of timer values in any sketch that uses millis or micros. It seems like this should be part of the core.

This is based on the assumption that any comparison of timer values is specifically asking this question:

Does the timer value "A" represent a time earlier or later than the value "B"?

This question can be answered un-ambiguously in the presence of overflow as long as the time span between "A" and "B" is less than one half of the timer overflow interval -- roughly 24 days for millis and 35 minutes for micros.

This may be done by performing an unsigned subtraction of the two values and examining bit 31 of the 32-bit result.

  • If bit 31 is one in the (A-B) result, then A is earlier than B.
  • if bit 31 is zero in the (A-B) result, then A is later than B.

Bit 31 may be tested in C++ by anding the result with 0x80000000, or more efficiently in assembly by testing the "N" bit in the status register after performing the subtraction.

Here's one example of a function to compare timer values, using 20 bytes of program space. I'm not expert on all the different Atmel MCUs so I'm not sure if this example will work on all of them.

It returns one of three possible values:

  • -1 if "a" is earlier than "b"
  • 0 if "a" is identical to "b"
  • +1 if "a" is later than "b"
int8_t TimerCompare(uint32_t a, uint32_t b)
{
    int8_t c;

    __asm__ __volatile__ (
        "; form (a-b)   \n\t"
        "eor  %0,%0     \n\t"
        "sub  %A1,%A2   \n\t"
        "sbc  %B1,%B2   \n\t"
        "sbc  %C1,%C2   \n\t"
        "sbc  %D1,%D2   \n\t"
        "breq .+8       \n\t"
        "brmi .+4       \n\t"
        "ldi  %0,1      \n\t"
        "rjmp .+2       \n\t"
        "ldi %0,0xff   \n\t"
        : "=&r" (c)
        : "r" (a), "r" (b)
    );

    return c;
}

So what do you think? Is this worth making a pull request?

aweatherguy:
There is a subtlety in the way timer examples (millis and micros) are written in almost every sketch: there is no allowance for timer overflow.

Overflow is easily dealt with using subtraction as in the demo Several Things at a Time

This is the standard way of using millis() and micros()

...R

Well, you are correct -- that approach does work. Takes a few more bytes of code, but in most of the time that doesn't matter. I did not realize this was a known way of using the timers -- my bad.

What lead me down this path was looking at some code posted here:

which isn't using this method. Is this subtlety discussed in any of the documentation? Perhaps it wouldn't hurt to add a mention of it to the millis and micros reference pages.

Takes a few more bytes of code,

Rats...I'm going to walk this one back too...the simple subtract and compare approach is either the same or slightly less code space...so there is really no advantage to what I suggested above at all.

I hate it when that happens!

aweatherguy:
I hate it when that happens!

Welcome to the club :slight_smile:

...R