Multitasking in the AVR Microcontroller

Well, multitasking would be stretching it…

When writing AVR micro-controller code, you typically end up with something like this:

int main(void) {
	... initialization code ...
	while(1) {
		... main loop that never exists here ...
	}
}

The main loop spins forever and is generally the bit of code that updates various output ports based on program state. It might likely also read inputs, too. And it might contain bits of code that delays execution for a while. And it is very likely rife with conditionals that’ll cause any given pass through the loop to very in execution time.

Not a very good place to do time sensitive stuff like, say, debouncing a button press or responding to other user events.

Really, it is far more effective to separate input processing entirely from the execution of the main loop. Input processing can update a global once the input has been fully validated and the main event loop can then process the event and reset the global.

Fortunately, the AVR has an exceedingly flexible interrupt system that can allow a chunk of code to effectively be executed “out of band” from the main loop.

Specifically, you can configure the chip to process an interrupt when an input pin changes state or based on a timer. Timers can be configured to count up over time and then send an overflow interrupt when the counter overflows either an 8 bit or 16 bit value. Furthermore, the timer can be prescaled such that it increments only every 8, 64, 256, or 1024 clock cycles, effectively causing the interrupt to be fired every 8x or 16x the prescaled values number of clock cycles.

And that is just the internal mode. The timer can also be incremented based on a clock signal provided on one of a couple of pins on the chip.

There are a boatload of other options and configurations available.

So, starting with the same circuit as described in the previous post (two LEDs), the following code uses a timer with maximum prescaler (so you can see the state change!) to cause the LEDs to act as a 2 bit counter.

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

#define bit_set_8(var, mask)   ((var) |= (uint8_t)(mask))
#define bit_clear_8(var, mask)   ((var) &= (uint8_t)~(mask))
#define bit_toggle_8(var, mask)   ((var) ^= (uint8_t)(mask))
#define bit_read_8(var, mask)   ((var) & (uint8_t)(mask))

#define BIT(x)   (1 << (x))

volatile unsigned char ledState = 0;

// timer 0 overflow interrupt handler
ISR(TIMER0_OVF_vect) {
    // cycle LED state
    ledState ++;
    ledState = ledState % 4;
}

int main(void) {
    DDRB = 0xFF; // Make all PB* -- PORT B -- pins output.
    PORTB = 0xFF; // turn all PB* -- PORT B -- pins off.
    
	// increment timer every 1024 clock cycles
    bit_set_8(TCCR0B, BIT(CS02) | BIT(CS00));
    bit_set_8(TIMSK0, BIT(TOIE0)); // turn on overflow interrupt for timer 0
    
    sei(); // turn on interrupts
        
    while (1) {
        PORTB = ledState;
    }
}

Note that the timer updates the ledState global completely independently of the while(){} loop in main(). That is, main() simply renders the user interface, as it were, while the interrupt updates the state.

And, thus, we have the foundation for dealing with a button press, debounce and all, in isolation from rendering the UI (which will eventually be a flipper coil — a rather time sensitive operation that will require the main loop to be quite consistent in execution speed).



8 Responses to “Multitasking in the AVR Microcontroller”

  1. Wes says:

    ledState = ledState % 4;

    Wouldn’t ledState &= 0b11 be vastly more efficient than a modulus operation?

  2. bbum says:

    Nope — the modulus is there to bound the values between 0 and 3. Whether a conditional + assignment would be more efficient, I know not (haven’t looked at the assembly).

    In this case, the CPU is running at 1mhz and I have cycles to burn.

    [I am, of course, completely wrong. Wes’s suggestion is dead on.]

  3. Aaron says:

    I think his point was that “ledState = ledState & 3;” is equivalent to “ledState = ledState % 4;” and just requires a logical and op whereas modulo requires a division which will end up being done in software on something like the AVR as it doesn’t have a divide op. Using “ledState = (ledState==3)?0:(ledState+1);” would let you do something other than a power of 2 and is probably slower than the logical and.

    Sure, you may have cycles to burn now, but just keep this in mind for when you don’t.

  4. bbum says:

    Oh, duh! Of course!

    Thanks, Aaron & Wes.

    I’m gradually dragging my mindset into embedded programming and, as you can tell, I’m not entirely there yet.

  5. Wes says:

    I’m gradually dragging my mindset into embedded programming and, as you can tell, I’m not entirely there yet.

    Its certainly a different mindset and its great fun to be working at such a low level, especially when it gets to the point where every cycle counts.

  6. Tim says:

    I would stick with whatever is more readable. As long as you are just dealing with constant powers of two, compiler optimization should be able to convert the modulo operation into the bit-wise AND operation, so there should be no performance difference. I’m pretty sure this is true for the gcc versions I use for embedded ppc development; I’m not sure about this platform. Premature optimization is no more justified in the embedded space than it is anywhere else.

  7. Jeremy says:

    Have you checked out the MakeController from Makingthings? It’s also an Atmel (ARM7 in this case), includes a crapload of I/O, and runs a true RTOS? Did I also mention it uses g++ and has a ROM bootloader?

    I admit, it’s quite a step up from the AVR, but Makingthings has made it reasonably easy to use. For the project you’ve selected, your AVR should be more than enough; but it is quite easy to do SAM7X code on the Mac, and the MakeController is a great place to start.

  8. Juha says:

    There’s also the option of not bounding the counter at all,
    might be appropriate in some situations.

    isr
    count++

    mainloop
    pins = count & 3

Leave a Reply

Line and paragraph breaks automatic.
XHTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>