Writing an Interrupt Service Routine (ISR)

It's simple.

ISRs look like this:

#include <avr/interrupt.h>

ISR( TIM1_COMPA_vect ) {
    // Code goes here.
}


That's pretty easy, but the obvious question is, "What is that TIM1_COMPA_vect and how do I find and use the right one?"

It's what the AVR folks call a signal.  In this case the signal is for the interrupt that happens when Output Compare 1A matches (for timers in CTC mode, that's when the timer reaches the TOP and starts counting from 0 again).  Every interrupt on the part has its own signal.  

So, the only remaining question is how to find a list of signals for the particular part you're using.  These uniquely identify interrupts on the part you are writing for.  Obviously, which interrupts are implemented depend on the part you are using as different size processors have varying peripheral sets.

The best reference for this is on the AVR-libc web page.

If you want to know details of where these are defined, you can start by looking in C:\WinAVR-*\avr\include\avr.  You'll see header files that are part-specific.  iot*.h is for ATTINY and iom*.h is for ATMEGA, etc.

These then go on to include files like iomx8.h which is more generic to a family of parts.  Look in there for the signal names.  You can search for SIG_ using a Windows "Search for a string in files" in C:\WinAVR-*\avr\include\avr in the these header files and the comments will be reasonably descriptive.

So, the one ISR() line not only decorates your function declaration with the extra stuff you need for GNU's avr-gcc to recognize the function as an interrupt handler:

void vector (void) __attribute__ ((signal,__INTR_ATTRS))

It also populates an entry in the interrupt vector table.


I chose a timer example because it involves a few other gotchas and handy tricks that you will want to know about.

The
DisplayRefreshTimer is set to 1000 before the infinite main loop is entered.  Let's say this timer will generate an interrupt every millisecond.  DisplayRefreshTimer will then count down once per millisecond and then stay at zero when it gets there. This is a very simple but effective kind of timer that I have used for a long time.  You can increase its range by simply turning it into an unsigned int or unsigned long.  When !DisplayRefreshTimer is true, the timer has timed out.  Keep in mind that while the timer itself is pretty exact, there will be latency depending on when and how often the main infinite loop checks it and how busy that loop gets with other things.  It's good for scanning keypads and refreshing LED displays.  I would NOT use it for firing fuel injectors, airbags, or cruise missiles.

The
volatile keyword below belongs in that class right up there with whitespace after a line continuation backslash.  There's those who've bash their head against a wall trying to figure out why it doesn't work and those that haven't done that yet.  If you look at just main() the same way a compiler does, you will spot an obvious bug:  DisplayRefreshTimer will never decrement down to zero.  volatile tells the compiler to not optimize out the test to see if DisplayRefreshTimer is zero, usually because an outside force (like an interrupt) will affect the variable.

#include <avr/interrupt.h>

uint16_t volatile DisplayRefreshTimer  = 0;

void main(void) {
    
DisplayRefreshTimer = 15;
    while (1) {
        if( !
DisplayRefreshTimer ) {
            // Refresh display code goes here
            
DisplayRefreshTimer = 15;
        }
    }
}


ISR( TIM1_COMPA_vect ) {
    if ( DisplayRefreshTimer ) {
        DisplayRefreshTimer--;
    }
}



Keep in mind:

  1. ISR() is really a macro defined in interrupt.h
  2. This will also include io.h for you