Showing posts with label external. Show all posts
Showing posts with label external. Show all posts

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.

External Interrupts: Needs More Buttons

Any Arduino project you make is most likely going to be doing a lot of waiting. Instead of constantly polling your sensors for input changes, you can use interrupts to free up your processor so it can do other things without having to worry about missing a signal.

The Arduino IDE provides a function, attachInterrupt(), that can set-up external interrupts for you. The Arduino Mega 2560 has six available external interrupts, INT5:0. The ATmega2560 chip has eight external interrupts total, but the Arduino only connects six of those pins to headers. If you are going to be setting the interrupt registers yourself and choose not to use the provided function, be warned that the interrupt numbers don't necessarily correspond to the same pins listed below.

The External Interrupts are connected to the following Pins when using attachInterrupt():
              Digital Pin
INT5 :        18
INT4 :        19
INT3 :        20
INT2 :        21
INT1 :        3
INT0 :        2

The attachInterrupt() function uses three parameters to customize the interrupt:
  • Interrupt Number: Found in the left column in list above. Attach your sensor to the corresponding pin.
  • Function Name: The function that will be called when the interrupt occurs. This would be your Interrupt Service Routine if you were using a different micro-controller.
  • Trigger Mode: Determines when the interrupt will be triggered. Can be one of four values:  RISING, FALLING, CHANGE, and LOW.
    1. RISING: Triggers the interrupt only when the pin changes from low to high; a rising clock edge
    2. FALLING: Triggers the interrupt only when the pin changes from high to low; a falling clock edge 
    3. CHANGE: Triggers when the pin changes from either low to high or high to low; any clock edge
    4.  LOW: Triggers when the pin is low
The syntax of the function with all of the parameters included is attachInterrupt(interrupt, function, mode). If you wanted to create an external interrupt using pin 2, that triggered on a rising clock edge, and jumped to the function button(), the correct syntax would be attachInterrupt(0, button,  RISING).

Let's apply an interrupt to the following sketch:

int button_state = 0;             // variable for storing button's status

void setup() {
  pinMode(53, OUTPUT);           //Set LED on PIN53 to output  
  pinMode(52, INPUT);            //Set button on PIN52 as input


void loop(){
  button_state = digitalRead(52); //Read state of button
  if (button_state == HIGH) {      //If HIGH, the button has been pressed
    digitalWrite(53, HIGH);       //Turn LED on
  }
  else {                          //If LOW, button is open
    digitalWrite(53, LOW);        //Turn LED off:
  }
}


The sketch controls the state of an LED by polling a tactile button. When the button is pressed, the LED toggles ON/OFF. You can make the program more efficient by replacing the need for polling with an external interrupt. 


 More SCIENCE!

The following code sets up an External Interrupt using the attachInterrupt() function:

volatile int state = 0;

void setup(){
  pinMode(53, OUTPUT);
  attachInterrupt(0, button, CHANGE);
}

void loop(){
}

void button(){
  state = !state;
  digitalWrite(53, state);
}


If you are using a switch(button) for your interrupt trigger, you need to make sure to debounce the input (which I haven't) or the Arduino may read a single button press as multiple presses. The Arduino website offers one solution for a software debounce here. Normally, you could get away with just using a delay after the button press, but the delay() function is disabled inside of interrupt routines.