With this article I would like to clarify a hidden potential of Arduino that allows you to define interrupt actions by reading the input signal. With this action loop and ‘if’ delays in the code are avoided
INTERRUPT AND SERIAL COMMUNICATION
With this program, Arduino generates a 20us impulse from the loop function. This impulse enters another Arduino entrance. An interrupt from this second input generates a serial command and a new output is generated. From the serial one gets one for each impulse and the new output represents a square wave whose faces are moments of impulses.
// A simple sketch to read GPS data and parse the $GPRMC string
// see http://www.ladyada.net/make/gpsshield for more info
#include <NewSoftSerial.h>
NewSoftSerial mySerial = NewSoftSerial(2, 3);
#define GPSRATE 4800
//#define GPSRATE 38400
int pbIn = 0; // Interrupt 0 is on DIGITAL PIN 2!
int ledOut = 13; // The output LED pin
volatile int state = LOW; // The input state toggle
int ledPin = 8;
void setup() {
pinMode(ledPin, OUTPUT); // sets the digital pin as output
// Set up the digital pin 2 to an Interrupt and Pin 4 to an Output
pinMode(ledOut, OUTPUT);
//Attach the interrupt to the input pin and monitor for ANY Change
attachInterrupt(pbIn, stateChange, RISING);
Serial.begin(GPSRATE);
mySerial.begin(GPSRATE);
}
void loop() {
digitalWrite(ledPin, HIGH); // sets the LED on
delayMicroseconds(17); // waits for 17ms
digitalWrite(ledPin, LOW); // sets the LED off
delay(1000); // wait for 1s with tune
}
void stateChange() {
Serial.print("1");
state = !state;
digitalWrite(ledOut, state);
}
Introduction to Interrupts
Why would I need an interrupt? Robots spend a lot of time waiting for things to happen. A common example: your robot wants to drive straight until an IR sensor says that an object is too close. Seems fairly simple: Code:
driveForward();
while(sensor_value == DIGITAL_HIGH){
// maybe do some other stuff, like follow a wall.
};
stop();
This code is ‘polling’ the sensor. It keeps checking the value over and over again manually (in software). This works of course, but what if your ‘other stuff’ starts to be a really long process – you might overshoot and run into the wall. What happens if the event we are trying to detect is really short – you might miss it. In these instances you want to use an interrupt. Common examples where interrupts are used:
- Counting pulses from an encoder (they are really short, and come very often)
- Catching some short pulse (like the 10ms pulse given off by a UVTron sensor)
- Using switches or digital IR sensors as bumpers (and you want an instant stop)
What is an interrupt?
The really cool thing about microcontrollers is they have fancy hardware that can do things like PWM, analog-to-digital conversion — and interrupts. An interrupt is a little piece of hardware that sits, waiting to detect a trigger event, such as a particular pin going from a low state to a high state. When this event happens:
- the interrupt triggers
- the microcontroller stops executing it’s current program
- the microcontroller starts executing an Interrupt Service Routine, or ISR
- when the ISR is done, we return to the original program
So a hardware interrupt is sort of like when the President interrupts your nightly TV viewing to tell you the economy has crashed. The trigger event is the economy crashing, and immediately as that happens the hardware (President) runs an ISR (his talk to the nation).
In your ISR, you would have code that does some processing to handle the event. For instance, your ISR would:
- Increment the value of a counter, if you were counting pulses from an encoder
- Set a flag to say ‘fire found’ if you were monitoring a UVTron
- Stop the robot if you were using interrupt bumpers
Most microcontrollers support a wide variety of interrupt triggers:
- A particular pin state going from low to high
- A particular pin state going from high to low
- Any change on a particular pin
Typically, microcontrollers only have a few interrupts, on specific pins. We’ll discuss a slight change to this below in the section ‘Wait, I’ve run out of interrupts’. Another interesting point to note, that won’t really be discussed much here, is that PWM and hardware timer/counters rely entirely on hardware that is similar to interrupts.
The Interrupt-Driven Bumper
Let’s now implement an example using the Arduino. The Arduino is based on an ATMEGA168 AVR. This chip has 2 hardware interrupts (named 0 and 1). The pins that can be used for interrupt triggers are tied to digital 2 and 3, respectively. The Arduino makes using interrupts quite easy, they have a function AttachInterrupt(interrupt, ISR, trigger):
- interrupt is which hardware interrupt 0 (Digital 2) or 1 (digital 3).
- ISR is the function with is to be used as the ISR
- trigger is either: RISING, FALLING, or CHANGE, for which events to trigger on
The code below will use several psuedo functions which you will need to implement for your particular robot:
- DriveForward() – make the robot move forward at some regular speed
- DriveBackward() – make the robot move backward at some regular speed
- Stop() – stops the robot
- TurnLeft() – make a little turn left, like 45 degrees or so
For our example, we will assume you have a bump switch. It should be tied between ground and the digital input pin (we’ll use a pullup resistor to keep them at 5V when not pressed).
There are a few things going on here. First, we start rolling forward. We have an integer used as a flag, that is either 0 when we have not hit an object, or 1 when we hit something. An interrupt occurs when we hit something, it will stop the robot to avoid any damage, and also set our flag. Then, our main loop will handle backing the robot up when it gets a chance. There are a few reasons to implement our code like this. First, delay() relies on interrupts. Second, you don’t ever want interrupts to run for very long, as they will typically stop other interrupts from occurring — this can be devastating when you have something like a system clock that relies on interrupts (as the Arduino does).
Code:
// Interrupt-Driver Bumper Example
// A bumper switch on the front of the robot should be tied to digital pin 2 and ground
#include
volatile int bumper; // have we hit something
void setup(){
pinMode(2, INPUT); // Make digital 2 an input
digitalWrite(2, HIGH); // Enable pull up resistor
// attach our interrupt pin to it's ISR
attachInterrupt(0, bumperISR, FALLING);
// we need to call this to enable interrupts
interrupts();
// start moving
bumper = 0;
DriveForward();
}
// The interrupt hardware calls this when we hit our left bumper
void bumperISR(){
Stop();
bumper = 1;
}
void loop(){
// if bumper triggered
if(bumper > 0){
DriveBackward(); // set motors to reverse
delay(1000); // back up for 1 second
TurnRight(); // turn right (away from obstacle)
bumper = 0;
DriveForward(); // drive off again...
}
// we could do lots of other stuff here.
}
Wait, I ran out of interrupts!
The Arduino library only supports 2 pin interrupts, because it only uses the 2 dedicated hardware interrupts. However, the ATMEGA168 (the chip that the Arduino is built out of) can actually generate interrupts on every port, it’s just slightly more complicated. These are called the Pin Change Interrupts. Each port of the AVR has it’s own interrupt vector, and you can turn on interrupts for as many of the pins as you want. There are a few limitations though. First, an interrupt is generated for any pin change, you can’t limit the hardware to rising or falling only, although you could implement that in your ISR. Second, because all of the pins in a port share an interrupt vector, if you use more than one pin in a port you will have to manually check which pin generated the interrupt. It should be noted that switching into the ISR takes several clock cycles, so your ISR will not run instantaneously, and thus it may be difficult to ‘check’ which pin generated the interrupt.Lastly, because there isn’t a library out there, you have to write a little more code.
Note that the Arduino has arbitrary names for its pins, that have no relevance to the AVR names, you’ll have to use the ATMEGA168 datasheet, plus the Arduino pin out chart to sort out register values. This is definately an advanced topic, but it is a great way to learn a few more details of the AVR architecture.
Code:
// Quick example of using pin change interrupts
// This shows how to use Digital Pin 4 (PCINT18/PD2) as an interrupt
#include
// we need to remember that the Arduino environment uses different pin numbers
// than the ATMEGA168 itself.
void setup(){
// Make digital 4 (PCINT18/PD2) an input
pinMode(4, INPUT);
// this is ATMEGA168 specific, see page 70 of datasheet
// Pin change interrupt control register - enables interrupt vectors
// Bit 2 = enable PC vector 2 (PCINT23..16)
// Bit 1 = enable PC vector 1 (PCINT14..8)
// Bit 0 = enable PC vector 0 (PCINT7..0)
PCICR |= (1 << PCIE2);
// Pin change mask registers decide which pins are enabled as triggers
PCMSK2 |= (1 << PCINT18);
// enable interrupts
interrupts();
}
void loop(){
// do nothing...
}
// we have to write our own interrupt vector handler..
ISR(PCINT2_vect){
// this code will be called anytime that PCINT18 switches
// (hi to lo, or lo to hi)
}
Some Warnings for Arduino Users
Don’t use the delay() or millis() functions inside an ISR on the Arduino. The reason being that they depend on the system clock, which itself is generated from an interrupt. On the AVR architecture, when one interrupt starts processing its ISR, all other intterupts are disabled temporarily. For this same reason, you want to keep your ISR as short as possible (to avoid messing with the system clock itself).
End Notes
That about covers the basics of interrupts, with some examples using the Arduino. Take a look at my tutorial on closed loop feedback for more information about using encoders.