Implementing AVR interrupts

Before we go to the code part lets what is needed in order to run interrupts successfully. These conditions actually applies to all AVR interrupts. First of all we need to enable global interrupts by setting global Interrupts enable bit (I) in 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 last thing is to to be sure that there are required conditions for 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 best teacher. If you will look at various C compilers you’ll find that interrupt defining syntax may be different across them. This is mainly because compilers stays away from hardware specific details, so its up to software developer how hardware is interacted. This is why we have to use specific libraries to make it possible. So lets stick with AVR-GCC. As I mentioned gcc compiler also needs a library that will understand interrupt syntax. So be sure you code includes one:

#include <avr/interrupt.h>

During AVR-GCC development interrupt defining syntax has evolved into 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 just 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 pin is configured as input or output. This will give us an advantage to run a simulation without any intrusion. Lets write simple program that will generate interrupt on pin change of PCINT0 of AVR. Interrupt service routine will toggle PC0 pin every time will be called. To generate interrupt we will toggle PCINT0 (PB0) pin that will be configured as output in order to generate software like 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 like. They may become a real pain in a butt when your program grows bigger. So better take precaution from 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 main routine. Instead doing something in ISR better rise a flag indicating 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 macro block 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 simply returns back without doing anything:

EMPTY_INTERRUPT(ADC_vect);

Another great feature is that you can create ISR alias. For instance you have defined an ISR for one interrupt and have another interrupt that should perform same, then put them in 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

Your email address will not be published. Required fields are marked *