How to Use Interrupts: Arduino Multi-tasking

As your Arduino projects get more complex with sensors, buttons, LEDs and more, it becomes more difficult to prioritize certain events. Although the Arduino can handle multiple inputs and outputs, it can only execute one command at a time. So doing something as simple as trying to blink 2 LEDs at different rates isn’t possible when code is executed one line at a time from the top of the program to bottom. But there is a way around this! Interrupts can help when you want the Arduino to do more than one thing at a time. You’ll be able to build more intricate and responsive electronics projects by learning how to use interrupts. Implementing software or hardware interrupts can open up a world of possibilities for your robotic creations – adding an extra layer of sophistication that takes your Arduino project to the next level.

How do Interrupts Work?

Interrupts will make the Arduino stop what it’s doing to perform another task. Once the task is complete, the Arduino will resume what it was doing before it was interrupted. Interrupts are great for monitoring events like button presses and alarm triggers or when you need to measure input pulses accurately. For example, if you are waiting for a user to press a button, you can either read the button at a high frequency or use interrupts. With interrupts, you’re sure that you won’t miss the button press.

There are many types of interrupts for the Arduino that fall into 2 main categories:

  • Hardware Interrupts: These come from external signals.
  • Software Interrupts: These are internal signals usually controlled by timers or software.

They all work in roughly the same way. When an interrupt event happens, the Arduino runs the code you put in an Interrupt Service Routine or ISR function. We’ll go into more detail on how to code an ISR function and where it fits in the sketch in just a bit.

Why Use Interrupts?

Knowing how to use interrupts will make you a better coder and it’s a technique that’s not hard to learn! In this tutorial I’ll be demonstrating 3 different types of interrupts using the Arduino Uno but any Arduino or Arduino clone board will work.

But first let’s run a simple experiment as a starting point to illustrate the value of using interrupts. All you’ll need for this is:

Arduino Circuit We Want to Interrupt

In our experiment, we want the red LED to blink constantly at a certain rate and anytime I press the button we want to turn on the blue LED. Although it’s a simple scenario, it has important applications for animated props or robots where you want the user to interact with it or have your project react to environmental cues around it. Here’s what the Arduino circuit looks like:

Arduino circuit without any interrupts.

Arduino Sketch We What to Interrupt

Here’s the sketch I would use to try and accomplish our goal of having the red LED blinking at a rate of 250ms and having the blue LED come on only when I push the button:

 
 int redLED = 12;
 int blueLED = 11;
 int buttonPin = 4;

 void setup() {
  pinMode(redLED, OUTPUT);
  pinMode(blueLED, OUTPUT);
  pinMode(buttonPin, INPUT_PULLUP);
 }

 void loop() {
  int buttonState = digitalRead(buttonPin); // READ STATE OF BUTTON

  if (buttonState == LOW) {     // BUTTON PRESSED!
   digitalWrite(blueLED, HIGH);
  }

  if (buttonState == HIGH) {  // BUTTON NOT PRESSED!
   digitalWrite(blueLED, LOW);
  }

 // BLINKING THE RED LED
  digitalWrite(redLED, HIGH);
  delay(250);
  digitalWrite(redLED, LOW);
  delay(250);
 }
 

After uploading this sketch to your Arduino you should see the red LED blinking as we expect. But every time you push the button, you’ll notice that sometimes the blue LED turns on and other times it doesn’t. Not all your button presses are being reliably read. Because we have a few delay() functions in the sketch, polling (reading from) the button inside the loop is not the best way to get a reading from it. A better solution for this scenario is to use an interrupt.

Hardware Interrupts

Hardware interrupts are triggered by an external event like a press of a button or a signal from a sensor and on most Arduino models are limited to specific pins. These pins can trigger a hardware interrupt by changing their logic state. The Arduino Uno only has 2 pins that are capable of executing hardware interrupts:

  • Pin 2 – Interrupt vector 0
  • Pin 3 – Interrupt vector 1

The number of available hardware interrupt pins depends on the board. Some Arduino boards have many more. This means we’re going to have to move our button from pin 4 and in the circuit diagram below, you can see that I’ve moved it to pin 2:

Arduino circuit with a hardware interrupt.

How to Use a Hardware Interrupt

Implementing hardware interrupts in your Arduino sketch is pretty straightforward and requires only 2 main steps:

  1. Write a function to use as your Interrupt Service Routine. The interrupt service routine will contain all of the code you want to be executed when the interrupt is triggered. In our example, we want the blue LED to turn on.
  2. Attach this function to the interrupt you want to use and specify how to trigger it. In our example we’ll be moving our button connection to pin 2 which is interrupt vector 0 and we want to trigger the interrupt any time the button is pressed.

attachInterrupt Function

In order to connect the ISR function to a specific interrupt (button push) you’ll need to use the attachInterrupt function. It’s normally placed inside the setup() function. Here’s what it looks like and the parameters it takes:

attachInterrupt(InterruptVector, ISR, Mode)

  • InterruptVector – Insert the interrupt number you want to use based on what pin you’re monitoring. Use the vector number, not the pin number! In our case, we’re using vector 0.
  • ISR – The name of the ISR function you want to trigger when the event happens.
  • Mode – There are 4 options for how you want to trigger the interrupt:
    • RISING – When an input goes from LOW to HIGH.
    • FALLING – When an input goes from HIGH to LOW.
    • LOW – When the input stays LOW.
    • CHANGE – Whenever the input changes state (LOW to HIGH or HIGH to LOW).

digitalPinToInterrupt Function

For me, it’s much easier to remember pin numbers rather than the vector numbers associated with them. The digitalPinToInterrupt function allows you to input a pin number and it returns the interrupt vector number for you. Using this function within the attachInterrupt function in the InterruptVector parameter is the preferred way of coding hardware interrupts. The attachInterrupt function would now look like:

attachInterrupt(digitalPinToInterrupt(pin), ISR, Mode)

Not only is using pin numbers easier, it makes the sketch usable on other boards.

Arduino Sketch with Hardware Interrupt

The sketch below adds a hardware interrupt to the existing sketch so that every button press is detected by the Arduino:

 
 int redLED = 12;
 int blueLED = 11;
 int buttonPin = 2; // Remember to connect your input to a hardware interrupt capable pin!
 volatile int buttonState;

 // ISR function
 void buttonInterrupt () {
  buttonState = digitalRead(buttonPin);
   if (buttonState == LOW) {     // Button pressed!
    digitalWrite(blueLED, HIGH); // Turn on blue LED
   }
   if (buttonState == HIGH) {    // Button not pressed!
    digitalWrite(blueLED, LOW);  // Keep blue LED off
   }
 }

 void setup() {
  pinMode(redLED, OUTPUT);
  pinMode(blueLED, OUTPUT);
  pinMode(buttonPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(buttonPin), buttonInterrupt, CHANGE);
 }

 void loop() {
 // BLINKING THE RED LED
  digitalWrite(redLED, HIGH);
  delay(250);
  digitalWrite(redLED, LOW);
  delay(250);
 }
 

ISR Function

In this modified sketch, I named my ISR function buttonInterrupt() and put the button polling code in there. This function will handle the turning on of the blue LED when the button is pressed. Some people put the ISR function towards the top of the sketch before the setup() function and others put it at the very bottom after the loop() function. I put mine towards the top but the choice is yours.

Do’s & Don’ts of ISR Functions

Make the code inside your ISR function short and fast so the Arduino can go back to executing the main loop. If you need to use multiple interrupts, keep in mind that only one can run at a time.

Make the code inside your ISR function short and fast so the Arduino can go back to executing the main loop. If you need to use multiple interrupts, keep in mind that only one can run at a time. The millis(), micros(), and delay() functions all use interrupts themselves so they won’t work inside an ISR function. If you do need to use some kind of timing inside your ISR function, try using the delayMicroseconds() function instead. The Serial.print() also doesn’t always work inside an ISR function because when data is transferred to the serial monitor it happens with an interrupt. ISRs also can’t take inputs or return values.

volatile Variables

You’ll notice that I moved the buttonState variable outside the loop() function so it exists as a global variable and added the word volatile. If you use variables in your ISR function it’s best to declare them as volatile. Normally the compiler optimizes variables and stores them in a storage register but volatile variables get stored in the Arduino’s SRAM instead. This is important or the variable’s value might not be accurate. For instance, if your program is in the middle of carrying out an ISR function and the variable changes within the loop() function, the variable inside the ISR might not get updated to the new value. Using volatile ensures that the variable gets updated if it changes somewhere else in your sketch.

Upload this sketch with the hardware interrupt to your Arduino and now you should be able to push the button and turn on the blue LED reliably.

Software Interrupts

Software interrupts don’t use external signals and instead are generated with software and their timing is based on the Arduino Uno’s 16 MHz clock oscillator. These timers are electronic circuits built into a microcontroller that keep track of time. Timer interrupts are useful for reading or writing to pins at regular intervals. For instance, if you want to blink LEDs at different rates or take a reading from a sensor every 3 seconds, you can use a software interrupt.

Arduino Uno Timers

The Arduino Uno has 3 internal timers:

  • Timer0 – an 8-bit timer used for the delay(), millis(), and micros() functions
  • Timer1 – a 16-bit timer
  • Timer2 – an 8-bit timer

The subject of timers can get complex for beginning coders looking to implement a simple timing element into their projects. I’ll reserve a more detailed tutorial on Arduino timers and how to manipulate clock frequency, use pre-scaler settings and clock select bits for a later date. For this portion of our software timer tutorial we’re going to use a library that handles all these calculations for us! All we have to do is specify our time interval.

How to Use a Software Timer

In this example, we’re going to blink two LEDs at different rates. The blue one will blink at a rate of 1 second while the red LED will blink every 250 milliseconds. You can modify the same circuit we did for the last example and just remove the button. Notice that I also moved the blue LED from pin 11 to pin 10. The Timer1 library we’re going to be using only works on pins 9 and 10 so whatever component you want to control with a timer interrupt must be connected to one of these pins.

The circuit with two LEDs would look like this:

Arduino circuit for software timer interrupt

Arduino Sketch with Timer Interrupt

There are various libraries to choose from to use Arduino timers. But you want to stay away from using Timer0 for interrupts because as you can see it can break any delay(), millis(), and micros() functions you have in your sketch.

The first you have to do is download the Timer1 library. Download it from the Arduino Playground here: TimerOne Library

Once the library is installed, let’s write a sketch using it to control the blink rate of our LEDs:

 
 #include <TimerOne.h> 

 int redLED = 12;
 int blueLED = 10;
 
 // ISR function
 void blueBlink () {
  digitalWrite(blueLED, !digitalRead(blueLED));
 }

 void setup() {
  pinMode(redLED, OUTPUT);
  pinMode(blueLED, OUTPUT);
  Timer1.initialize(1000000); //time in microseconds
  Timer1.attachInterrupt(blueBlink);
 }

 void loop() {
  // Blinking the red LED
  digitalWrite(redLED, HIGH);
  delay(250);
  digitalWrite(redLED, LOW);
  delay(250);
 }
 

The first thing I did was include the TimerOne library at the very top of the sketch. Next I wrote my ISR function just like we did for hardware interrupts. I chose the blue LED to be controlled by the timer so I named my ISR blueBlink(). Remember not to use any delay(), millis() or any function that also uses interrupts in your ISR. Normally we use delay() to turn LEDs on and off so we’ll have to come up with another solution.

There are many ways you could come up with to get around not using a delay() function. I usually use if statements to check the status of the LED and then change it. But here’s a clever way to do it in just one line of code:

digitalWrite(blueLED, !digitalRead(blueLED));

In this line of code I’m taking a digitalRead of the blueLED pin to determine whether it’s HIGH or LOW. The exclamation mark right before the function is the boolean operator for NOT. So whatever the reading is, the exclamation mark reverses it and uses that opposite value for digitalWrite. So if pin 10 reads HIGH then it gets written LOW and vice versa. We’ve managed to blink an LED with just one line of code!

In the setup() function I initialized the Timer1 library with Timer1.initialize(). The argument of the Timer1.initialze() function sets the length of time before the interrupt is triggered in microseconds. In my sketch I put 1000000 in the parenthesis because 1 million microseconds is equal to 1 second.

Also in the setup() section I attached my Interrupt Service Routine (ISR) with the Timer1.attachInterrupt() function. This function also takes just one argument, the name of your ISR function which in my case is blueBlink. Now, every time the timer reaches 1 second, the blueBlink() ISR will be executed.

In the loop() section, we have the familiar LED blink code using delay() to turn our red LED on and off every 250 milliseconds.

Once you upload this code to your Arduino Uno, the blue LED should blink every second while the red LED should be blinking faster at 250 ms. This is a very simple example of how to use a software timer interrupt with a library, but timers are very useful in other applications beyond just LEDs. You can have your ISR read a sensor at a specific time interval to trigger other actions like turning on a 5V relay or have your project react to different signals in the environment.

Some links in this post are to affiliate sites. If you purchase something through them, I may earn a small commission — which costs you nothing! I am very grateful for your support when you use my links to make a purchase.

Stop Scrolling. Start Creating.

Find out about new courses and live events!
Get my latest guides and reference materials.
Unsubscribe anytime!

    Copyright © 2022 Rachel De Barros • All rights reserved.