How to Detect Bit Rate Using an Timer Input Capture Pin

The previous page introduced a tool to automatically detect bit rate. This is helpful for fine-tuning serial transmission rates on microcontroller with built-in oscillators, which are commonly inaccurate. The tool can also help when there is a gross mismatch in baud rates, such as when you thought the device was 9600 baud, but is actually 38400 baud.

The tool works by timing the width of all pulses being transmitted from the project being tested. Pulses less than 16 clocks long are ignored as spikes. The shortest pulse is assumed to be the length of a single bit. It doesn’t matter if it is a start bit or a data bit.

Dividing the tool clock (18432000) by the shortest pulse timer count (say 480) gives the bit rate (38400). This method works for both asynchronous and synchronous serial communication.

This algorithm won’t work if the transmitter only sends data patterns that are longer than one bit. For example, the wrong rate will be display if the jerk sends 00000000 or 00110011 over and over. Fortunately, most transmissions will have at least one single bit pulse in them. For example, the receipt of a carriage return will be enough.

Input Capture

The input capture hardware built into the Atmel AVR microcontroller makes this easy. Simply:

  1. Set the built-in hardware timer to run at the same rate as the microcontroller clock
  2. Tell it to capture the time when the timer capture input pin changes states
  3. Set an interrupt if the timer runs over (so you can measure really slow rates)
  4. Set an interrupt when the input changes states to perform the calculation and update the display

Here is the heart of the C code for the ImageCraft AVR compiler:

Boolean gDisplayNeedsUpdate;

Byte gPulsesCaptured; // unsigned char
Boolean gPulseStarted;
unsigned short gTimerOverflow;
unsigned short gStartOfPulse;
unsigned long gPulseLength;

void ResetPulseCount(void)
{
    Byte savedSREG = SREG; // Save interrupt state.

    CLI();
      // Temporarily disable interrupts because we’re going to be
      // modifying a lot of global variables. We don’t want a partially
      // written value to be stored if the timer capture goes off
      // during the next couple of lines of code.

    gPulseStarted = false;
    gPulsesCaptured = 0;
    gTimerOverflow = 0;

    gDisplayNeedsUpdate = true;

    TIMSK1 =   BIT(ICIE1)  // Enable input capture interrupt
             | BIT(TOIE1); // Enable overflow interrupt

    TCCR1B =   BIT(ICNC1)  // Enable noise cancelation
             | BIT(CS10);  // Prescale 1 (maximum timer speed)

    SREG = savedSREG;      // Restore interrupts if they were enabled.
}

#pragma interrupt_handler TimerOverflow:iv_TIMER1_OVF
void TimerOverflow(void)
{
    // The built-in hardware timer is only 16 bits.
    // This value will roll over for slow bit pulses.
    // Every time it does, increment another 16 bit
    // counter. Thus, the timer is now 32 bits long.

    // Stop incrementing if the timer maxes out.
    if ( gTimerOverflow < 0xFFFF )
    {
        gTimerOverflow++;
    }
}

#pragma interrupt_handler TimerCapture:iv_TIMER1_CAPT
void TimerCapture(void)
{
    unsigned long startOfPulse;
    unsigned long endOfPulse;
    unsigned long pulseLength;

    if ( BitIsClear(TCCR1B, ICES1)) // Captured falling edge
    {
        SetBit(TCCR1B, ICES1); // Capture rising edge next time.
    }
    else // Captured rising edge
    {
        ClrBit(TCCR1B, ICES1); // Capture falling edge next time.
    }

    if ( gPulseStarted )
    {
        // Remember when the pulse started (saved in global).
        startOfPulse = gStartOfPulse;
        // No high byte because we clear gTimerOverflow at the
        // start of a pulse.

        // Combine the overflow 16 bits with the current timer capture
        // capture to create a 32 bit value.
        endOfPulse = (((unsigned long)gTimerOverflow) << 16) + ICR1;

        // The difference between now and then is the pulse length.
        pulseLength = endOfPulse - startOfPulse;

        if ( pulseLength >= 16 // Not a glitch
              & & (gPulsesCaptured == 0 || gPulseLength > pulseLength ) )
             // Haven’t captured yet or this is the shortest pulse so far
        {
            gPulseLength = pulseLength;

            gDisplayNeedsUpdate = true;
                         // Display TARGET_CLOCK_IN_HZ / gPulseLength;

            // Could be a Boolean. But other modes use this.
            if ( gPulsesCaptured < 255 )
            {
                gPulsesCaptured += 1;
            }
        }
    }

    gPulseStarted = true;
    gStartOfPulse = ICR1;

    gTimerOverflow = 0;
    // If the timer has rolled over during this interrupt,
    // the rollover interrupt will subsequently be called
    // and gTimerOverflow will get incremented to 1.
    // So, no worries about a condition
    // where the end of pulse appears to be before the
    // start of the pulse.
    // The interrupt priority order is guaranteed.
}

This code could be improved by averaging multiple short pulses. Otherwise, a slightly short pulse will dominate the displayed value, giving a faster bit rate than average.

The code could be made to measure faster rates by queuing the captured times and performing the calculations outside of the interrupt. Presently, the interrupt takes too long and the second half of a short pulse is missed on rates greater than 230400 bps.

In any case, the input capture pin is superior to a standard interrupt pin in that the timer value is copied immediately when the pin changes state. With a standard interrupt pin, the CPU may be busy servicing interrupts, and there will be some overhead time required to execute your interrupt code. The timer value will have changed significantly by the time the firmware copies it. Thus, the pulse measurement will be inaccurate.

Next we'll see how the project box was machined. Lastly, you'll see a serial error that appears to be related to timing, but is actually a software bug.