Sunday 5 February 2012

Interrupts

Since I got started with Arduino there have been a few times where I wanted to write code that did multiple things at the same time. For example, when waiting to receive data from a Serial connection, the Arduino is blocked and not doing anything useful!

The solution to this is to use interrupts. An interrupt can be triggered in a number of situations. The most common are on an external pin change (e.g. when a button is pressed) or when an internal event happens (more on this later). When an interrupt happens, a special interrupt handler method is called. This interrupts the normal flow of the program on the Arduino (hence the name). When the interrupt handler returns the program continues from the point it was interrupted. The only impact this has on the interrupted code is that it affects the timing of the running code. If a call to delay(100) is interrupted and the interrupt handler executes for 500ms then the caller may be surprised that they were actually kept waiting for 500ms!

I mentioned above that internal events can generate interrupts. The ATmega328 used in an Arduino Uno includes 3 counters. These are special registers which automatically count up. These counters can be used to generate interrupts at a configured rate. To configure this you need to setup the following. 
  • Clock rate - the ATmega328 in the Uno runs at 16Mhz. However, the counter supports a range of clock dividers to generate a slower clock. For example a 1/256 divider gives a clock of 62.5Khz. 
  • Compare value - this is the value at which the interrupt will be generated. If you set this larger than 0 this value essentially acts as another clock divider. For example a value of 100 would mean that every time the counter got up to 100 an interrupt will be generated.

  • Continuing our example, we get 62.5Khz/100 = 625Hz. 

    Warning: Remember that some of these values are stored in very small registers so can't hold large values. For example the ATmega328 Counter 2 is an 8 bit counter. This means the compare value can't be larger than 2^8 = 256. 
  • Enable interrupt - the counter doesn't have to be used for interrupts, you have to explicitly enable that. 
  • Counter mode - most useful mode when you want regular interrupts is CTC (Clear Timer on Compare match). This just means that when the counter gets to our configured value we get an interrupt and the value is automatically reset to 0. 
What does this setup look like in practice? This is the setup for an interrupt which we want to use to do something once per second.

  // We will use Timer 2 on the ATmega328.
  // See section 17 of 

  // http://www.atmel.com/dyn/resources/prod_documents/doc8161.pdf
  // Section 17.11 contains details about the registers
  // which need to be setup.
  //
  // Table 17-8 in the datasheet describes how to set the
  // counter mode. To use CTC we must set _BV(WGM21)
  // in register TCCR2A.
  TCCR2A = _BV(WGM21);

 
  // Table 17-9 shows the available clock prescale values.
  // We will use the highest available: 1/1024. This
  // requires that we set 4 bits.
  // This gives a clock of 16MHz/1024 = 15625Hz
  TCCR2B = _BV(CS22) | _BV(CS21) | _BV(CS20);


  // We want an interrupt once per second. This would
  // require a compare value of 15625. However OCR2A
  // is only an 8 bit register which can hold a max value
  // of 255.
  //
  // The highest factor of 15625 lower than 255 is 125
  // so we will use that. 15625 = 125 * 125 so settings
  // OCR2A to 125 will generate 125 interrupts per second.
  // We will need to manually count how many times
  // we have been interrupted and do work every time
  // we are interrupted for the 125th time.
  OCR2A = 125;


  // Finally we need to enable our interrupt.
  TIMSK2 = _BV(OCIE2A);

What can we use this for? How about flashing an LED without any code in our main loop. Here is the code:

unsigned long count = 0;
int ledon = 0;
SIGNAL(TIMER2_COMPA_vect)
{
  // We expect 125 interrupts a second
  count++;
  if (count > 125)
  {
    count = 0;
    if (ledon == 0)
    {
      digitalWrite(4, HIGH);
      ledon = 1;
    }
    else
    {
      digitalWrite(4, LOW);
      ledon = 0;
    }
  }
}

void loop()
{

  // We are free to do anything we want here.
  // The LED flashing is handled entirely in the
  // interrupt handler
}

The complete code is available here. And here are the results.