How Interrupts are Handled in Arduinos
2018-12-28 | By Maker.io Staff
License: See Original Project Wearables Arduino
Bad Interrupts are the pop ads that are closed out immediately when browsing online.
Good interrupts I associate with MCUs - Like the ones I originally discovered in a PIC Microcontroller. In particular, the PIC16F72-I/SP-ND (added to our system in 2002 and still active). To get a 1 second sports timer to work properly, I had to set the right bits, using assembly language, in the INTCON register, the OPTION register configuring the Prescaler and by using the right crystal frequency (4.096MHz – X082-ND) I was able to get the correct timing for my display. All too complicated these days and much easier using an Arduino. Below, SparkFun Electronics presents Interrupts tutorial on the Arduino. Also, linked is a good video presented by Shawn Hymel. Check it out below!
Courtesy of Sparkfun
Introduction
Interrupts - what are they? They are people that intermittently prevent you from doing your current work. Ha! Well maybe… but what we really want to know is what they are in the context of embedded electronics and microprocessors.
So let’s ask that again - what is an interrupt? In a nutshell, there is a method by which a processor can execute its normal program while continuously monitoring for some kind of event, or interrupt. There are two types of interrupts:
- Hardware Interrupts - These occur in response to an external event, like a pin going high or low.
- Software Interrupts - These occur in response to a software instruction.
Generally speaking, most 8-bit AVR microcontrollers (i.e. Arduinos) aren’t innately capable of software interrupts, so for the purposes of this tutorial, we will focus on hardware interrupts.
Suggested Reading
If you aren’t familiar with the following concepts, we recommend checking out these tutorials before continuing.
- What is an Arduino? What is this 'Arduino' thing anyway?
- Installing Arduino IDE: A step-by-step guide to installing and testing the Arduino software on Windows, Mac, and Linux.
- How to Install FTDI Drivers: How to install drivers for the FTDI Basic on Windows, Mac OS X, and Linux.
How Does It Work?
When the event or interrupt happens, the processor takes immediate notice, saves its execution state, runs a small chunk of code (often called the interrupt handler or interrupt service routine), and then returns back to whatever it was doing before.
The programmer defines the code that is to be executed when a particular interrupt occurs within the program itself. In Arduino, we use a function called attachInterrupt() to do this and the recommended syntax looks similar to the output below.
attachInterrupt(digitalPinToInterrupt(pin), ISR, mode)
This function takes three parameters:
- First Parameter (i.e. digitalPinToInterrupt(pin)) - Pin number of the interrupt, which tells the microprocessor which pin to monitor. The pin depends on the microcontroller being used.
- Second Parameter (i.e. ISR) - The location of code we want to execute if this interrupt is triggered.
- Third Parameter (i.e.mode) - Tells it what type of trigger to look for: a logic high, a logic low, or a transition between the two.
For more information and some example code if you’d like to read further, check out Arduino’s attachInterrupt() page.
Hardware Hookup
In the following sections, we’ll look at a simple example to make more sense of interrupts and how they work. If you’d like to follow along, grab a Sparkfun RedBoard, an LED, a button, 330Ω resistor, jumper wires, and a cable to power it all.
- SparkFun RedBoard - Programmed with Arduino
- Multicolor Buttons - 4-pack
- Jumper Wires Premium 6" M/M Pack of 10
- USB Mini-B Cable - 6"
- LED - Basic Red 5mm
- Resistor 330 Ohm 1/4 Watt PTH - 20 pack (Thick Leads)
Connect the LED to pin 13 and the button to pin 2 as you see in the Fritzing diagram below:
If you take a good look at what you’ve just hooked up, you’ll notice that the LED is actually redundant. We could just use the built-in LED on pin 13, but for visual purposes, we added the external LED.
Example: Simple Interrupt
Note: This example assumes you are using the latest version of the Arduino IDE on your desktop. If this is your first time using Arduino, please review our tutorial on installing the Arduino IDE.
Now that we’ve got our hardware hooked up, let’s look at a simple example that continuously sends an “Off” signal to an LED. We’ll attach an interrupt to pin 2; this pin will monitor a button that will send an “On” signal to the LED when pressed and increment a counter.
Most Arduinos have 2 external interrupts built in: interrupt0 (on digital pin 2) and interrupt1 (on digital pin 3). Some boards have more (like the Arduino Mega 2560) - refer to the user manual or datasheet for more information on what your specific board supports. Arduino also has more details on a handful of boards on their attachInterrupt() page. Since we are using a RedBoard here, this example uses pin 2 to monitor for interrupts.
Simple Interrupt Example 1
Select the board and COM port for the RedBoard. Then upload the following.
/*
Simple Interrupt Example 1
by: Jordan McConnell
SparkFun Electronics
created on 10/29/11
*/
int ledPin = 13; // LED is attached to digital pin 13
int x = 0; // variable to be updated by the interrupt
void setup() {
//enable interrupt 0 (pin 2) which is connected to a button
//jump to the increment function on falling edge
pinMode(ledPin, OUTPUT);
attachInterrupt(0, increment, RISING);
Serial.begin(9600); //turn on serial communication
}
void loop() {
digitalWrite(ledPin, LOW);
delay(3000); //pretend to be doing something useful
Serial.println(x, DEC); //print x to serial monitor
}
// Interrupt service routine for interrupt 0
void increment() {
x++;
digitalWrite(ledPin, HIGH);
}
The main loop of this program sends an “OFF” signal to the LED every 3 seconds. Meanwhile, this program watches digital pin 2 (which corresponds to interrupt 0) for a rising edge. In other words, it looks for a voltage change going from logic low (0V) to logic high (5V), which happens when the button is pressed. When this happens the function increment is called. The code within this function is executed, variable x is incremented, and the LED is turned on. Then the program returns to wherever it was in the main loop.
If you play around with it, you’ll notice that the LED stays on for seemingly random amounts of time but never longer than 3 seconds. How long the LED stays on depends on where you interrupted the code in the main loop. For example, if the interrupt was triggered right in the exact middle of the delay function, the LED would remain lit for about 1.5 seconds after you hit the button.
Managing Bounce
One common problem with interrupts is they often can trigger multiple times for a single event. When you look at the serial output of the code in example 1, you’ll notice that even if you press the button just once, x will increment many times. To explore why this happens, we have to take a look at the signal itself. If we took an oscilloscope to monitor the voltage of the pin at the moment we pressed the button, it would look something like this:
Image courtesy of AllAboutCircuits
While the main transition of the pin is from low to high, during the process, there are several spikes which can cause multiple interrupts. This is referred to as noise or bounce. A button push might seem like a single step, but in reality the mechanical parts within that button come into contact multiple times before settling into a particular state. There are several ways to remedy this. Often you can fix bounce issues with hardware by adding an appropriate RC filter to smooth the transition. Another option is to address it in software by temporarily ignoring further interrupts for a small time frame after the first interrupt is triggered. Going back to our old example, let’s add in a fix that allows the variable x to only be incremented once each button press instead of multiple times.
Simple Interrupt Example 2
Select the board and COM port for the RedBoard if you have not already. Then upload the following.
/*
Simple Interrupt example 2
by: Jordan McConnell
SparkFun Electronics
created on 10/29/11
*/
int ledPin = 13; // LED is attached to digital pin 13
int x = 0; // variable to be updated by the interrupt
//variables to keep track of the timing of recent interrupts
unsigned long button_time = 0;
unsigned long last_button_time = 0;
void setup() {
//enable interrupt 0 which uses pin 2
//jump to the increment function on falling edge
pinMode(ledPin, OUTPUT);
attachInterrupt(0, increment, RISING);
Serial.begin(9600); //turn on serial communication
}
void loop() {
digitalWrite(ledPin, LOW);
delay(3000); //pretend to be doing something useful
Serial.println(x, DEC); //print x to serial monitor
}
// Interrupt service routine for interrupt 0
void increment() {
button_time = millis();
//check to see if increment() was called in the last 250 milliseconds
if (button_time - last_button_time > 250)
{
x++;
digitalWrite(ledPin, HIGH);
last_button_time = button_time;
}
}
Let’s look again at the serial output as you press the button. Open ther serial monitor set at 9600 baud. Note that increment only gets called once for each button press. This fix works because each time the interrupt handler is executed, it compares the current time retrieved by the millis() function with the time the handler was last called. If it’s within a certain defined window of time, in this case a fourth of a second, the processor immediately goes back to what it was doing. If not, it executes the code within the if statement updating the variable x, turning on the LED and updating the last_button_time variable so the function has a new value to compare to when it's triggered in the future.
Interrupt Priority Levels
What happens when two interrupts occur at the same time? Most AVRs do not support what we call interrupt priority levels. Should two interrupts occur simultaneously or there are two or more interrupts waiting in a queue, the priority is determined by the order of their vector addresses. Lower vector addresses are serviced first, Reset will take precedence over all interrupt requests. Again, your datasheet will have more information on your specific board.
Example: Interrupting an LED Sequence
Interrupts can also come in handy when dealing with long sequences of things. Let’s look at another simple example with LEDs - let’s say that we are going to use the built-in RGB LED on a LilyPad USB Plus to cycle through a sequence of colors, fading each color on and off. The fade cycle time for each color is 10 seconds and we have a number of these boards sewn into costumes on stage. What happens if one of these costumes gets out of sync?
Rather than having to wait for the cycle to end and trying to reset the board in sync with the other boards, we can add an interrupt to pin 10 (this is interrupt 0 on the LilyPad USB Plus board). When the button is pressed, the interrupt is triggered and we move on to the next color. This allows us to get the offending costume in sync faster and the show can go on.
Let’s make this happen for ourselves. If you’d like to follow along, grab a LilyPad USB Plus. You’ll need the buttons, jumpers, and power supply from the previous experiment. You will also need a few alligator clip to pigtail wires to connect to the LilyPad sew tabs.
Hook it all up as you see here:
Heads up! Make sure that you install the LilyPad board definition when using the LilyPad USB Plus. Otherwise, you could use the RedBoard, a common cathode LED (diffused or clear), and current limiting resistors. Just make sure to redefine the pins and adjust the connections as necessary.
Select the board and COM port for the the LilyPad USB Plus. Then upload the code below.
/*
Example: Interrupting an LED sequence
SparkFun Electronics
Follow the tutorial at:
https://learn.sparkfun.com/tutorials/processor-interrupts-with-arduino#example-interrupting-an-led-sequence
This code is released under the MIT License (http://opensource.org/licenses/MIT)
******************************************************************************/
// Cycling through a series of colors using the built-in LED on the LilyPad USBPlus. Using an interrupt to switch quickly between colors
// The built-in LED:
int RGB_red = 12;
int RGB_green = 13;
int RGB_blue = 14;
int x = 0; // variable to be updated by the interrupt
//Fade variables
int ledMode = 0; //color mode to control LEDs
int colorSwitch = 0; //compare to current_FadeVal to know whether or not to switch colors yet
int prev_FadeVal = 0;
int current_FadeVal = 0;
boolean increasing = true;
//variables to keep track of the timing of recent interrupts
unsigned long button_time = 0;
unsigned long last_button_time = 0;
void setup() {
// Make all of our LED pins outputs:
pinMode(RGB_red, OUTPUT);
pinMode(RGB_green, OUTPUT);
pinMode(RGB_blue, OUTPUT);
attachInterrupt(0, increment, CHANGE);
Serial.begin(9600); //turn on serial communication
}
void loop()
{
// In this code we'll step through seven rainbow colors (primary, secondary, tertiary).
// Unlike digitalWrite, which can be only HIGH (on) or LOW (off),
// analogWrite lets you smoothly change the brightness from 0 (off) to 255 (fully on).
// When analogWrite is used with the RGB LED, you can create millions of colors!
FadeColor();
}
// Interrupt service routine for interrupt 0
void increment() {
button_time = millis();
//check to see if increment() was called in the last 250 milliseconds
if (button_time - last_button_time > 250)
{
//increment counter
x++;
//turn led off
analogWrite(RGB_red,0);
analogWrite(RGB_green,0);
analogWrite(RGB_blue,0);
Serial.println(x, DEC); //print x to serial monitor
delay(10000);
//set the last button time to the current button time
last_button_time = button_time;
//Switch colors
if (ledMode < 7){
ledMode++;
}
else {
//start over with Red
ledMode = 1;
}
//reset fade values
prev_FadeVal = 0;
current_FadeVal = 0;
}
}
void FadeColor() {
switch (ledMode) {
case 1://FADE RED
analogWrite(RGB_green, 0);
analogWrite(RGB_blue, 0);
analogWrite(RGB_red, prev_FadeVal);
break;
case 2://FADE YELLOW
analogWrite(RGB_red, prev_FadeVal);
analogWrite(RGB_green, prev_FadeVal);
analogWrite(RGB_blue, 0);
break;
case 3://FADE GREEN
analogWrite(RGB_red, 0);
analogWrite(RGB_green, prev_FadeVal);
analogWrite(RGB_blue, 0);
break;
case 4://FADE CLEAR BLUE
analogWrite(RGB_red, 0);
analogWrite(RGB_green, prev_FadeVal);
analogWrite(RGB_blue, prev_FadeVal);
break;
case 5://FADE BLUE
analogWrite(RGB_red, 0);
analogWrite(RGB_green, 0);
analogWrite(RGB_blue, prev_FadeVal);
break;
case 6://FADE MAGENTA
analogWrite(RGB_red, prev_FadeVal);
analogWrite(RGB_green, 0);
analogWrite(RGB_blue, prev_FadeVal);
break;
default:
analogWrite(RGB_red, prev_FadeVal);
analogWrite(RGB_green, prev_FadeVal);
analogWrite(RGB_blue, prev_FadeVal);
break;
}
delay(100);
if (increasing == true) {
current_FadeVal += 5;
}
else { //decreasing
current_FadeVal -= 5;
}
if (current_FadeVal > 255) {
increasing = false;
prev_FadeVal -= 5;//undo addition
current_FadeVal = prev_FadeVal;
}
else if (current_FadeVal < 0) {
increasing = true;
prev_FadeVal += 5;//unto subtraction
current_FadeVal = prev_FadeVal;
}
prev_FadeVal = current_FadeVal;
if(current_FadeVal == colorSwitch)
{
if (ledMode < 7){
ledMode++;
}
else {
//start over with Red
ledMode = 1;
}
}
}
Note that each time you press the button, you switch to the next color. Perhaps not the most common use case, but visually more obvious how interrupts address immediate needs.
What Are the Advantages?
At this point you might wonder, “Why use an interrupt at all? Why not just occasionally use a digitalRead() on pin 2 to check its status? Won’t that do the same thing?”
The answer depends on the situation. If you only cared what the status of the pin was at a certain point in your code or time frame, then a digitalRead() will probably suffice. If you wanted to continuously monitor the pin, you could poll the pin frequently with digitalRead()'s. However, you could easily miss data between reads. This missed information could be vital in many real time systems. Not to mention, the more often you’re polling for data, the more processor time that is being wasted doing that rather than executing useful code.
Let’s look at the system that monitors and controls the anti-lock braking of a car as a critical timing example. If a sensor detects the car starting to lose traction, you really don’t care about what part of program is currently being executed, because something needs to be done about this situation immediately to assure the car retains traction and hopefully avoids an accident or worse. If you were just polling the sensor in this situation, the sensor may be polled too late and the event could be missed entirely. The beauty of interrupts is that they can prompt execution immediately, when it’s necessary.
Resources and Going Further
Now that we have a bit better idea of how interrupts work, can you use them in your next project? Or if you want or need more information on interrupts, you can check out some of the links below:
Shawn Hymel also has some fun and informative video tutorials on interrupts. Check them out!
Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.
Visit TechForum