AVR Interrupt Handling in C
Links Mentioned
APP-IV

Links
Home
AWC



NOTICE: THIS IS AN ARCHIVAL COPY OF THIS COURSE. For the latest version, please visit our interactive classroom.

One of the best things about the GCC compiler for the AVR is that it makes handling interrupts very simple. Consider the case where you want to make an interrupt-driven timer. The APP-IV's ATmega 8 has several timers that can generate periodic interrupts. In this example, I'll use timer #1.

Once enabled, the timer counts each clock period (or you can select a divided down input clock if you prefer). When the timer overflows (65536 counts) you get an interrupt. With a 10MHz clock, this doesn't work out to anything very useful. At 10MHz, each clock pulse is 100nS and 65536 of them is 6.5536 milliseconds.

I want a timer that resolves to 100mS, so the first step is to divide down the input clock. That's easy enough to do. You just have to set TCCR1B. Setting it to 3 causes the input clock to divide by 64, so now each clock pulse is worth 6.4uS. That's still not 100mS though.

The trick is to preload the counter with a value that will cause it to count for 100mS. Each time the counter expires, you can force the new number back into the timer register. We need 0.1/.0000064 or 15625 counts. However, the counter expires at 65536, so the correct number to preload is 65536-15625 or 49911. This way, the timer will count up 15625 counts (0.1 second) and expire.

Writing the interrupt handler is easy enough. GCC provides two macros, SIGNAL and INTERRUPT, that allow you to define interrupt handlers. If you use SIGNAL, the handler will execute with interrupts disabled (unless you enable them in your code). If you use INTERRUPT, the handler executes with interrupts enabled (which could cause reentrancy problems if you are not careful -- if you don't understand this, use SIGNAL). The name of the function tells the compiler what interrupt you want to handle.

That is the only problem. If you misspell the name of the interrupt handler, the compiler will cheerfully create the function and not link it to any real interrupt. You'll get no error message but your code won't do what you expect, so be sure to double check the name of the interrupt handler (the GCC headers tell you the exact names of the functions -- in our case SIG_OVERFLOW1).

Here's the handler:

// When enabled, this does the 1 second count down
SIGNAL(SIG_OVERFLOW1)
{
  TCNT1=49911;  // reset counter to .1 mS
  // for every 10 sub ticks, we decrement tick, but
  // only if tick is non zero (so once you hit zero
  // there is no more counting)
  if (--subtick==0)
    {
      subtick=10;
      if (tick) --tick;
    }
}

This routine subtracts one from the subtick variable. When that variable reaches zero, it is reset to 10 (obviously, something else initialized it to 10 in the beginning) and the tick variable is also decremented (unless it is already at zero). So the tick variable tracks seconds and the subtick variable counts the 0.1 second interrupts.

There is a small problem here, however. GCC is very good at optimizing your code. So if you write in your main program:

tick=5;  // delay 5 seconds
while (tick);  // wait 

This won't work! The compiler figures out that tick can't change and just writes an endless loop here. You have to tell the compiler that tick (and subtick) can change unexpectedly (that is, during an interrupt). Here's the correct syntax:

volatile uint8_t tick; // 1 second
volatile uint8_t subtick; // 100mS

Of course, interrupts won't occur until you turn them on. Here's a simple function that enables the timer:

// Start timer running
// ensures you don't get part of the old count
void startsubtimer(void)
{
  TCCR1B=3;  // divide by 64
  TCNT1=49911; // this gives us 15624 counts or about .1s
  TIFR&=~(1<<TOV1);
  TIMSK|=(1<<TOIE1);  // start timer
  timeron=1;
}
void starttimer(void)
{
  subtick=10;
  startsubtimer();
}

This is the code that initializes subtick. IT also sets TCCR1B, TCNT1 (the first time), and defensively clears the interrupt flag (TIFR.TOV1). In addition, TIMSK.TOIE1 is set to enable interrupt generation. However, even if the TOIE1 bit is set, interrupts won't occur unless the global interrupt flag is set. The main function has a call to sei() which takes care of that problem.

And that's it! The biggest trick is figuring out which bits have to be set to get the hardware to generate the interrupt (and the numbers to get the period right, in this case). Actually handling the interrupt is no big deal. Note that the system automatically saves the machine state and clears the interrupt flags, so the handler just focuses on the actual task you want to perform.

Here's the complete code:

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/signal.h>
volatile uint8_t tick;    // 1 second
volatile uint8_t subtick; // 100mS
int timeron;
// When enabled, this does the 1 second count down
SIGNAL(SIG_OVERFLOW1)
{
  TCNT1=49911;  // reset counter to .1 mS
  // for every 10 sub ticks, we decrement tick, but
  // only if tick is non zero (so once you hit zero
  // there is no more counting)
  if (--subtick==0)
    {
      subtick=10;
      if (tick) --tick;
    }
}
// Start timer running
// ensures you don't get part of the old count
void startsubtimer(void)
{
  TCCR1B=3;  // divide by 64
  TCNT1=49911; // this gives us 15624 counts or about .1s
  TIFR&=~(1<<TOV1);
  TIMSK|=(1<<TOIE1);  // start timer
  timeron=1;
}
void starttimer(void)
{
  subtick=10;
  startsubtimer();
}
// Stops the timer
void stoptimer(void)
{
  timeron=0;
  TIMSK&=~(1<<TOIE1);  // stop interrupts
  TCCR1B=0;  // halt timer
}
int main(void)
{
  sei();   // enable interrupts (req'd for timer)
  DDRB=1;  // LED on PORTB.0 is an output
  while (1)
    {
      PORTB=1;
      tick=1; // wait one second
      starttimer();
      while (tick); // wait for it!
      PORTB=0;
      tick=1; // wait 1/2 second
      subtick=5; 
      startsubtimer();
      while (tick); // wait for it!
    }
  return 0; // not reached
}