Sunday, May 08, 2011

Setting Interrupts Manually: The Real INT0

Last post I covered how to set External Interrupts using the provided attachInterrupt() function. While convenient, it's important to note that there's always going to be a bit more overhead when using these functions instead of setting the registers yourself. It's also a good exercise to figure out what these functions are doing behind the scenes.

There are eleven external interrupts for the ATmega2560 chip, but the Arduino only allows you to use nine of them and only six of them, INT5:0, can be accessed through attachInterrupt(). These three extra mystery interrupts are the Pin Change Interrupts, referred to as PCI2:0. These three interrupts are set whenever the pins they monitor are toggled, which would be the same function as the CHANGE setting for the six other external interrupts. Each of the PCI interrupts have eight pins they are attached to, PCINT23:0.

The external interrupts and the pins they are wired to:
PCI2:              Arduino:
PCINT23        PIN ANALOG15
PCINT22        PIN ANALOG14
PCINT21        PIN ANALOG13
PCINT20        PIN ANALOG12
PCINT19        PIN ANALOG11
PCINT18        PIN ANALOG10
PCINT17        PIN ANALOG9
PCINT16        PIN ANALOG8
       
PCI1:       
PCINT15        N/A
PCINT14        N/A
PCINT13        N/A
PCINT12        N/A
PCINT11        N/A
PCINT10        PIN 14
PCINT9          PIN 15
PCINT8          PIN 0
       
PCI0:       
PCINT7         PIN 13
PCINT6         PIN 12
PCINT5         PIN 11
PCINT4         PIN 10
PCINT3         PIN 50
PCINT2         PIN 51
PCINT1         PIN 52
PCINT0         PIN 53 

INT7:0
INT7             N/A
INT6             N/A
INT5             PIN 3
INT4             PIN 2
INT3             PIN 18
INT2             PIN 19
INT1             PIN 20
INT0             PIN 21


You may notice that the pins that INT5:0 are attached to are not the same used for attachInterrupt(). This is import to note if you have multiple interrupts since certain interrupts will take priority over one another and can interrupt lower priority interrupts. The lower the interrupt vector, the higher the priority of the interrupt, as seen on the table below.

 
Found on page 105 of ATmega2560 data sheet

Let's take our button program from last post and replace the attachInterrupt() by manually setting the external interrupt control registers.

The trigger mode from attachInterrupt() is instead controlled by the EICRA and EICRB registers. Each INT has two corresponding bits in the registers used to set the mode. You can reference the table below on how to set either LOW, CHANGE, FALLING, or RISING.

 Found on page 114 of ATmega2560 data sheet

To enable any of the INT5:0 interrupts, edit the External Interrupt Mask Register, EIMSK. The INT7:0 enable bits correspond to the eight bits of the register. To enable INT4 for instance, you would set EIMSK = 0x10.

Circuit diagram, again, use your imagination

#include <avr/interrupt.h> 
#include <avr/io.h>

volatile int state = 0;

void setup(){
  pinMode(53, OUTPUT);   //attached to LED annode
  pinMode(2, INPUT);     //
Button input tied to INT4

  EICRB = 0x01;          //INT4, triggered on any edge
  EIMSK = 0x10;          //Enable only INT4
}

void loop(){
}

ISR(INT4_vect) {
  state = !state;
  digitalWrite(53, state);
}

Note that Pin 2 used in the last sketch is now attached to INT4 when we manually set the registers. If we were to use INT0 like last time, we would need to connect our button to Pin 21 instead. The "real" INT0 is attached to Pin 21 and Pin 2 is attached to the "real" INT4. Refer to the previous table at the beginning for reference.

The code for the interrupt vector is just the source of the vector with _vect added to the end of it. For the button code, INT4 is used so the interrupt routine is IRS(INT4_vect). If you look at the interrupt vector table above you can figure out the vectors for any other interrupt. Before, when we used the Timer2 Overflow Interrupt if you look at the table you can see it uses the source TIMER2 OVF. So, the interrupt routine would be ISR(TIMER2_OVF_vect).

Also, you don't have to manually clear the interrupt flag bits at the end of the interrupt service routine as I previously thought. The ATmega automatically clears the flag once the service routine starts.

2 comments:

  1. Thanks, this cleared up the strange interrupt linking I was confused about.

    ReplyDelete
  2. any ideas about the arduino due interrupt priorities?

    ReplyDelete