CPU, Interrupted — Timers II

When last we spoke on this subject I had left you with a time-accurate but impractical timer interrupt handler.

#define F_CPU 16000000

uint8_t Timer_ms = 0;

static void initTIMER1( void )
{
    // CTC mode (WGM1[3:0] = 0x04)
    TCCR1A = 0;
    TCCR1B = _BV(WGM12);
    // CLK/8 prescaler (see datasheet for table)
    TCCR1B |= _BV(CS11);

    // Automatic comparison register (16Mhz/8) is a 2 MHz tick, i.e. two
    // million ticks per second, or two thousand ticks per millisecond.
    // Note that on AVR we get one more tick than the value we put in
    // this register
    OCR1A = (F_CPU/8)/1000 - 1;

    // Enable timer1 match-A interrupt
    TIMSK = _BV(OCIE1A);
}

// Note the change of vector here from "overflow" to "match"
ISR(TIMER1_COMPA_vect)
{
    // 1 ms has passed
    Timer_ms++;

    // TIFR.OCF1A is automatically cleared when the ISR is called, so we
    // needn't worry about the ISR being incorrectly retriggered
}

Why is this impractical though? Well let’s look a little wider, what might we want to do with a timer in a main loop? Let’s say something simple: flash an LED every quarter of a second. We want to know when the timer has changed, but we’re only updating a counter in the ISR. We also have to be aware that Timer_ms is altered by an interrupt context, so to prevent races we shouldn’t carelessly access it in the main loop context.

Let’s look at the solution.

static uint8_t interruptTicks = 0;

ISR(TIMER1_COMPA_vect)
{
    interruptTicks++; 
}

int main( void )
{
    uint8_t localTicks = 0;
    unit16_t Timer_ms = 0;
    uint16_t Timer_1s = 0;
    uint8_t Alarm_ms = 0;

    initTimer1();

    while(1) {
        // Disable interrupts as a poor-man's atomic operation
        cli();
        localTicks += interruptTicks;
        interruptTicks = 0;
        // Disabling interrupts still leaves them pending
        sei();

        // We could put the processor to low-power sleep here and wait
        // to be woken by the timer interrupt rather than busy-waiting

        // ----- Timer interrupt bottom half

        // We've now got a tick count we can use without worrying about
        // race conditions, localTicks is never accessed outside this
        // context

        if( localTicks == 0 )
            continue;

        // --- Timers tick upwards
        Timer_ms += localTicks;
        if( Timer_ms > 1000 ) {
            Timer_ms -= 1000;
            if( Timer_1s < 0xffff )
                Timer_1s++;
        }

        // --- Alarms tick downward
        if( Alarm_ms < localTicks ) {
            Alarm_ms = 0;
            // Call whatever alarm handler you want here
        } else {
            Alarm_ms -= localTicks;
        }

        // Everyone who needed to count ticks has counted them
        localTicks = 0;
    }

    return;
}

You can see that you could add any number of timers, any number of alarms, and any cascade conditions you want between them. None of this is done in the ISR and can take up to 255 ms (an age in microcontroller land), without ever missing a single tick or creating a race condition. By using interrupts for our timer we’ve also gained the ability to go to sleep and thus use much less power than we would if we were simply polling for an overflow.

I haven’t done it here as it would only obfuscate matters, but there is nothing to stop you writing a generic alarm structure that contains both the alarm time and a pointer to the function to call on timeout. Oh what the heck…

struct sAlarmStructure
{
    uint16_t AlarmTime_ms;
    void (*timeoutCallback)(uint8_t);
    void *opaque:
} AlarmList[MAX_ALARMS];

uint8_t registerTimer( uint16_t timeout, void (*callback)(uint8_t), void *opaque )
{
    uint8_t i;
    for( i = 0; i < MAX_ALARMS; i++ ) {
        // Timers 
        if( AlarmList[i].AlarmTime_ms == 0 ) {
            AlarmList[i].AlarmTime_ms = timeout;
            AlarmList[i].timeoutCallback = callback;
            AlarmList[i].opaque = opaque;
            return i;
        }
    }
    // No spare timer slots left
    return -1;
}

int main( void )
{
    uint8_t localTicks = 0;
    uint8_t i;

    initTimer1();

    while(1) {
        // Disable interrupts as a poor-man's atomic operation
        cli();
        localTicks += interruptTicks;
        interruptTicks = 0;
        // Disabling interrupts still leaves them pending
        sei();

        for( i = 0; i < MAX_ALARMS]; i++ ) {
            if( AlarmList[i].AlarmTime_ms > localTicks ) {
                AlarmList[i].AlarmTime_ms -= localTicks;
            } else {
                AlarmList[i].AlarmTime_ms = 0;
                // The callback function can restart itself if it wants
                // because we're handing it its own AlarmList index
                AlarmList[i].timeoutCallback(i);
            }
        }

        // Rest of main loop can call registerTimer() when it needs to
        // defer a task
    }

    return;
}

So we’ve gone from not being able to rely on the exact number of counts between events to a generic, run-time configurable alarm system. We’ve got a fast ISR and no race condition.

This entry was posted in FussyLogic and tagged , , , , , , . Bookmark the permalink. Trackbacks are closed, but you can post a comment.

Post a Comment

You must be logged in to post a comment.