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:
- ISR() is really a macro defined in interrupt.h
- This will also include io.h for you