For our project here — where we only expect a single
character at infrequent intervals — an interrupt is the
Enable Global Interrupts
The final step is to enable global interrupts. When
describing global interrupts, the datasheet refers to setting
the Global Interrupt Flag, or sometimes the “I” bit in the
SREG register to a “1.” To make it simpler to read code, I
prefer to use the macro sei() to enable global interrupts
and cli() to disable them. I mentioned these statements
earlier in the article, and you’ll see that the sei() statement
is the last one before we launch into the main while(1)
loop in Listing 1.
Where did that Interrupt Go?
So, we’ve set the interrupts and they’re
happily firing away when we press buttons
or receive data, but where are they firing
to? You’ve probably guessed from the
earlier parts of the article that we need to
If you jump to the end of Listing 1,
you’ll see the two functions named ISR.
These are the functions that handle the
interrupts, with the parameter specifying
which interrupt each ISR handles. This may
look a little cryptic — what exactly do
USART_RX_vect or PCINT2_vect mean,
and how are we supposed to know that
we need to use this syntax?
The datasheet refers to interrupt
vectors which are basically the memory
addresses the code execution jumps to
when an interrupt occurs. The Atmel AVR
microcontrollers use an interrupt vector
table (a table of interrupt types and
addresses) — refer to section 12.1 of the
datasheet. Figure 7 shows an excerpt, with
vector 6 being the one
we’re interested in for our
However, this table doesn’t
give us the actual
PCINT2_vect syntax we
need in our code. Atmel
Studio is quite helpful with
its auto-complete and does
guide you towards the
correct syntax. However, the
definitive reference is the
file iom328p.h — you’ll find
it under your dependencies
tree in Atmel Studio’s solution explorer (Figure 8). Open
this up and scroll down to a section labelled “Interrupt
Vectors” — here, you’ll find the vector names that you
should use in your ISR definitions (Figure 9).
Now that we’ve dealt with the vectors and naming the
ISR, the rest is pretty straightforward. Each of the ISRs sets
a flag to indicate to the main loop that an interrupt has
• The PCINT2 ISR disables further pin-change
interrupts in order to prevent the interrupt firing on
a signal bounce and on the button release. We re-enable this once we’ve finished processing the
player’s button press.
• The USART_RX ISR reads a byte from the UART
data buffer (compare this to the code from
last month where we had to check a
register before reading from the UART).
The variables used in the ISR are
globally defined (and using the volatile
keyword). You’ll note that we’ve adhered
to our principles and kept the ISR as short
as possible. With the flags set, we can exit
the ISR and leave the main loop to handle
One of the benefits of interrupts is that
your main while(1) loop becomes clean
and uncomplicated. While complicated
code may look impressive and may give
the author a sense of achievement, I am in
the camp that feels simple code is better.
Simple code is easier to maintain, easier to
hand over to others to work on, and less
likely to contain obscure bugs that are hard
to track down.
32 July 2015
Figure 7: Section of the datasheet's Interrupt Vector table.
Figure 8: "iom328p.h" in
the Solution Explorer.