Turn-key PCB assembly services in prototype quantities or low-volume to mid-volume production runs

Implementing AVR interrupts

Before we go to the code part lets what is needed to run interrupts successfully. These conditions applies to all AVR interrupts. First of all, we need to enable global interrupts by setting global Interrupts enable bit (I) in the SREG register. This is crucial as this bit enables or disables all interrupts in AVR microcontroller. So each time you are setting up an interrupt be sure this bit is enabled. Next thing is enabling individual interrupt bits on particular peripheral control register. Without setting this bit – wanted interrupt won’t work either. And the last thing is to to be sure that there are required conditions for an interrupt to occur. For instance, if you set timer overflow interrupt, be sure this overflow will occur.

Defining AVR interrupts

Now we can get to some code as practice is the best teacher. If you look at various C compilers, you’ll find that interrupt defining syntax may be different across them. This is mainly because compilers stay away from hardware specific details, so it is up to software developer how the hardware interacts. This is why we have to use particular libraries to make it possible. So let’s stick with AVR-GCC. As I mentioned, gcc compiler also needs a library that will understand interrupt syntax. So be sure your code includes one:

#include <avr/interrupt.h>

During AVR-GCC development interrupt defining syntax has evolved into a very compact form. Now you can use very generic macro function ISR(isr_vector). All you need is to provide this function with proper ISR vector name and you are set. Let say you want to implement External Interrupt 0 handling function you write:

ISR(INT0_vect)
{
//your  code
}

So far we just defined interrupt service routine. Now its time to set up the interrupt itself remembering that we need to enable global and peripheral interrupts. According to datasheet pin change interrupts provides a way of generating software interrupts as pin-change interrupt can be generated even if the pin is configured as input or output. This will give us the advantage to simulate without any intrusion. Let’s write a simple program that will generate an interrupt on pin change of PCINT0 of AVR. Interrupt service routine will toggle PC0 pin every time will be called. To generate the interrupt, we will toggle PCINT0 (PB0) pin that will be configured as an output to generate software like an interrupt.

#include <avr/io.h>
#include <avr/interrupt.h>
//Set PB0 and PC0 pins as output
void MCUInit(void)
{
DDRB |=(1<<PB0);
DDRC |=(1<<PC0);
//Enable pin change interrupt bits
PCICR |=(1<<PCIE0);
//Set PCINT0 bit in pin change mask register PCMSK0
PCMSK0 |=(1<<PCINT0);
//Enable global interrupts
sei();
}
//pin change interrupt ISR
ISR(PCINT0_vect)
{
    PINC =(1<<PC0);
}

int main(void)
{
MCUInit();
    while(1)
    {
        //toggling PB0 pin
        PINB =(1<<PB0);
    }
}

Issues with interrupts

Interrupts aren’t so innocent as they may look. They may become a real pain in the butt when your program grows bigger. So better take precaution from the start before its too late. First of all, be sure your interrupt routine occupies as little time as possible. Keep ISR short especially if you doing intensive I/O operations like data transfer. If your ISR will take too long to process you can miss next byte coming. Better put data in a buffer and give resources to the main routine. Instead of doing something in ISR better rise a flag indicating the presence of event and leave ISR.

ISR usually share same variables with main routines. For instance variable can indicate that button has been pressed. So these variables usually are declared as global. So mark these values with volatile keyword. This way compiler won’t optimize out this variable knowing that it may be used in some way.

And last probably most dangerous issue is atomic operations. Imagine situation when program has to fetch 16 bit data byte by byte and interrupt occurs between these to bytes and interrupt shares same variable with main routine – this leads to serious errors that may be hard to detect sometimes. So in this case use atomic operation code blocks. Use this is a special macroblock that disables global interrupts until atomic operation is complete.

ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
    SomeValue = SharedValue;
}

You can do more with interrupts

Don’t limit yourself with things you see in front. Don’t afraid to open interrupt.h file and see what else you can do with interrupts. For instance, you can define empty interrupts for some interrupts that will return without doing anything:

EMPTY_INTERRUPT(ADC_vect);

Another great feature is that you can create ISR alias. For example, you have established an ISR for one interrupt and have another interrupt that should perform same, then put them in the alias, so you don’t need to write another ISR.

ISR(INT0_vect)
{
    PORTB = 42;
}
ISR_ALIAS(INT1_vect, INT0_vect);

Or you can take full control of ISR by yourself with ISR_NAKED(). Using this function register values won’t be stored in stack automatically when jumping to ISR – it is all left for you. If you need faster ISR response you can choose which registers to push to stack and restore. And you will not get away with this if you get hands on RTOS. Multitasking practically is impossible without naked ISRs.

And yes you can have interrupts inside interrupts. These are called “nested interrupts” – use these if you really know what you’re doing.

Bookmark the permalink.

2 Comments

  1. As we’ve discussed elsewhere, the modifications to the PIN registers must be only writes, since the read and write functionality on PINx is completely different and independent, so a read-modify-write is going to cause all sorts of problems.

  2. Yep. I have to go through all code examples and locate if there is any of these left. Thanks.

Leave a Reply