0

I'm hoping to time external events on a '328p Arduino that will occur slowly enough that they'll overflow the 16-bit counter TCNT1. In a different scenario I'd prescale the counter, but I need the full timer resolution in this case for other purposes.

So I enable the TOV1 interrupt and just increment an 8-bit value, letting it roll over as needed.

Then on an ICP1 interrupt, I shift this value << 16 into a 32-bit variable, and add in the value captured in ICR1, yielding a 24-bit value that I can use to measure the slow interval.

Are there hidden gotchas in this plan? Possible race conditions where TOV1 hasn't yet been updated if ICR1 is latched just when TCNT1 has just rolled over, or generates a TOV1 after capturing ICR1 but before I shift in the overflow count?

1 Answer 1

1

There is, indeed, a possible race condition. Consider this naive code, which provides 32-bit timestamps:

/* Count overflows. */
volatile uint16_t timer1_overflow_count;
ISR(TIMER1_OVF_vect)
{
    timer1_overflow_count++;
}

/* Timestamp input events. */
volatile uint32_t captured_timestamp;
volatile bool did_capture;
ISR(TIMER1_CAPT_vect)
{
    captured_timestamp = timer1_overflow_count << 16 | ICR1;
    did_capture = true;
}

Since TIMER1_CAPT has higher priority than TIMER1_OVF, then:

  • If ICF1 rises before TOV1, TIMER1_CAPT_vect runs first, which is fine.

  • If ICF1 and TOV1 rise simultaneously, TIMER1_CAPT_vect runs first, which is still fine, as the captured value is 0xffff.

  • If TOV1 rises before ICF1, either ISR may run first:

    • if the CPU can process the TIMER1_OVF IRQ right away, then TIMER1_OVF_vect runs first and the result is correct

    • if the interrupt processing is delayed until after ICF1 has risen, then TIMER1_CAPT_vect runs first and grabs a wrong value for timer1_overflow_count.

Solution

There is an unhandled overflow when bit_is_set(TIFR1, TOV1). The overflow may have happened at the same time or after the capture, in which case it doesn't affect the timestamp. We know it happened before the capture when the captured value is small, where “small” could be defined as having its most significant bit clear. If this is the case, we have to add 0x10000 to the timestamp we obtained.

A race-proof ISR can be written as follows:

ISR(TIMER1_CAPT_vect)
{
    uint16_t captured_low = ICR1;
    uint16_t captured_high = timer1_overflow_count;
    if (bit_is_set(TIFR1, TOV1) && !(captured_low & 0x8000)) {
        captured_high++;
    }
    captured_timestamp = (uint32_t) captured_high << 16 | captured_low;
    did_capture = true;
}
2
  • Thanks for your analysis. I get everything about your solution except the phrase we have to add 0x0100 to the timestamp. In code you (effectively) add 0x10000, which is what I'd expect for a missed overflow. Typo?
    – Jim Mack
    Commented Feb 19, 2022 at 14:23
  • @JimMack: Oops! You are right, I made a typo. Fixed, thanks! Commented Feb 19, 2022 at 15:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.