Blinking an LED - This time with interrupts

In this example, I’ll switch over to using the ATtiny2313’s 8-bit timer module for toggling the LEDs. Everything is still connected exactly as it was in the last example.

Before I get too far along with my examples, I’m going to introduce a new section. Ideally people with little experience in microcontrollers should be able to follow along and pick up useful tips (if not just able to take and compile my code). So, I’ll be introducing the new pieces as I understand it.

The Terminology

Timer module - The ATtiny2313 has many modules that will do a lot of the hard work for you, one set of these is the 8-bit timer and the 16-bit timer. The most basic operation involves setting up a prescaler

Prescaler - The prescaler effectively divides the clock. So, with a prescaler setting of /64 on the timer module, the timer will not tick on every clock cycle, but only every 64 cycles.

Interrupts - One of the more powerful features of working at a low level, interrupts allow modules to stop all code execution and jump to their own interrupt subroutine (ISR). When the ISR returns, everything will pick up as it left off. More precautions have to be taken with interrupts in assembly than in C, such as making sure registers, especially the status register, are preserved. If the status register isn’t preserved, then an interrupt could potentially foul up the results of a simple statement such as “if (foo==3)”.

What’s new

I’ll be using the 8-bit timer module and interrupts to count off the 1 second LED changes.

The other major change from the previous example is that I decided that one of the LEDs should flash twice as slow. To do this, I’m going to simply XOR its current state with the other LEDs new state. No seperate counters required.

Design Considerations

One of the main concerns with using the 8-bit timer to count off seconds is getting it to divide evenly into the clock frequency. The clock frequency is a power of 10, but the prescalers and various division methods are powers of 2.

As you can see in the code comments, I set the timer up to have a prescaler of 64 and interrupt on overflow (256) as well as only toggling the LEDs when another counter hit 61. All told, this gets to 1 second with .06% error. One thing I could have done differently was to change the value the timer would interrupt on. Keeping the prescaler the same (64), the factorization of 1000000/64 is 5^6. If you split that into 5^3*5^3, then you can have the timer interrupt at 125 and toggle when the counter in the code equals 125. That equals exactly 1000000, so it should be as close to 1 second as you can get.

The Code

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

#define F_CPU 1000000UL // Running at 1 MHz

char counter;

int main(void)
{
    DDRA = 0x03; // Set PA0 and PA1 to be outputs
    TCCR0B |= (1<<CS01)|(1<<CS00); //set prescaler to /64, which divides evenly into 1MHz

    TIMSK |= (1<<TOIE0); // Enable timer 0 interrupts

    counter = 0;

    sei(); // Enable interrupts

    while (1)
    {
        // Do nothing. Alternatively, a sleep instruction could be used so the CPU only wakes
        // on interrupts and immediately goes back to sleep.
    }
}

// Set an interrupt on the Timer0 Overflow vector
ISR(TIMER0_OVF_vect)
{
    counter++;
    if (counter == 61) // This gets VERY close to 1 second
                       // The timer prescaler (64) * 8 bit overflow (256) * 61 = .999424 seconds
                       // .06% error should be close enough for this application
    {
        char led1, led2;
        led1 = PINA & 0x01; // Get PA0's pin value
        led1 ^= 0x01; // Toggle PA0
        led2 = (PINA >> 1) & 0x01; // Get PA1's pin value
        led2 ^= led1; // Xor PA1 with the current state of PA0, effectively doubling the period
        PORTA = (led2 << 1) | led1;
        counter = 0;
    }
}

Closing Thoughts

Some improvements I’ve noticed so far using C over assembly? Control flow is much less effort. I don’t need to figure out what label I’m going to use or how I’m going to set up a loop or if statement. Setting bits in registers is more concise and using the << and >> syntax, I can easily determine which bits are being set/cleared.