Language: EN


What are and how to use interrupts in Arduino

Interrupts are a very powerful and valuable mechanism in processors and automatons. Arduino, of course, is no exception. In this post we will see what interrupts are, and how to use them in our code.

To understand the usefulness and need for interrupts, let’s suppose we have an Arduino connected to a sensor, for example an optical encoder that counts the revolutions of a motor, a detector that emits a water level alarm in a tank, or a simple stop button.

If we want to detect a change of state in this input, the method we have used so far is to use the digital inputs to repeatedly check the value of the input, with a time interval (delay) between checks.

This mechanism is called “poll”, and it has 3 clear disadvantages.

  • Assuming continuous processor and energy consumption, as it has to continuously ask for the status of the input.
  • If the action needs to be attended to immediately, for example in a collision alert, waiting until the point in the program where the query is made may be unacceptable.
  • If the pulse is very short, or if the processor is busy doing another task while it occurs, it is possible that we skip the trigger and never see it.

To solve these types of problems, microprocessors incorporate the concept of interrupt, which is a mechanism that allows associating a function with the occurrence of a certain event. This associated callback function is called ISR (Interrupt Service Routine).

When the event occurs, the processor “exits” immediately from the normal flow of the program and executes the associated ISR function completely ignoring any other task (that’s why it’s called an interruption). When the associated ISR function is finished, the processor returns to the main flow, at the same point where it was interrupted.

As we can see, interrupts are a very powerful and convenient mechanism that improves our programs and allows us to perform actions that would not be possible without using interrupts.

To use interrupts in physical devices (such as buttons, optical sensors, … ) we must eliminate the “bounce” effect beforehand, as you can see in the entry Applying debounce when using interrupts in Arduino


Arduino has two types of events in which to define interrupts. On the one hand, we have timer interrupts (which we will see at the time of talking about timers). On the other hand, we have hardware interrupts, which respond to events that occur in certain physical pins.

Within the hardware interrupts, which are the ones that concern us in this post, Arduino is capable of detecting the following events.

  • RISING, occurs on the rising edge from LOW to HIGH.
  • FALLING, occurs on the falling edge from HIGH to LOW.
  • CHANGING, occurs when the pin changes state (rising + falling).
  • LOW, it runs continuously while it is in the LOW state.

The pins capable of generating interrupts vary depending on the Arduino model.

In Arduino and Nano, there are two interrupts, 0 and 1, associated with digital pins 2 and 3. The Arduino Mega has 6 interrupts, on pins 2, 3, 21, 20, 19, and 18 respectively. Arduino Due has interrupts on all its pins.

Mini Pro23
DueOn all pins


The function associated with an interrupt is called ISR (Interrupt Service Routines) and, by definition, it has to be a function that takes no arguments and returns nothing.

Two ISRs cannot run simultaneously. If another interrupt is triggered while an ISR is being executed, the ISR function is executed one after the other.


When designing an ISR, we must maintain the shortest possible execution time, since while the main loop is running and all other functions are stopped.

Imagine, for example, that the main program has been interrupted while a motor was bringing an arm to pick up an object. A long interruption could cause the arm to not stop in time, pulling or damaging the object.

Frequently the function of the ISR will be limited to activating a flag, incrementing a counter, or modifying a variable. This modification will be addressed later in the main thread, when appropriate.

Do not use a time-consuming process in an ISR. This includes complex calculations, communication (serial, I2C and SPI) and, as far as possible, changing both digital and analog inputs or outputs.


To be able to modify a variable external to the ISR within it, we must declare it as “volatile”. The “volatile” indicator tells the compiler that the variable has to be queried every time before being used, since it may have been modified outside the normal flow of the program (which is precisely what an interrupt does).

By indicating a variable as Volatile, the compiler disables certain optimizations, which results in a loss of efficiency. Therefore, we should only mark as volatile the variables that really require it, that is, those that are used in both the main loop and within the ISR.


Interrupts have effects on the time measurement of Arduino, both inside and outside the ISR, because Arduino uses Timer interrupts to update time measurement.

Effects outside the ISR

During the execution of an interrupt, Arduino does not update the value of the millis and micros functions. That is, the execution time of the ISR is not counted and Arduino has a time measurement lag.

If a program has many interrupts and these take a long time to execute, the time measurement of Arduino can be very distorted compared to reality (again, a reason to make ISR short).

Effects inside the ISR

Inside the ISR, the rest of the interrupts are disabled. This means:

  • The millis function does not update its value, so we cannot use it to measure time within the ISR. (we can use it to measure the time between two different ISRs)
  • As a consequence, the delay() function does not work, since it is based on the millis() function.
  • The micros() function updates its value within an ISR, but begins to give inaccurate time measurements after the range of 500us.
  • Consequently, the delayMicroseconds function works in that time range, although we should avoid using it because we should not introduce waits within an ISR.


To define an interrupt in Arduino we use the function:

attachInterrupt(interrupt, ISR, mode);

Where interrupt is the number of the interrupt we are defining, ISR is the associated callback function, and mode is one of the available options (Falling, Rising, Change, and Low).

However, it is cleaner to use the function digitalPinToInterrupt(), which converts a Pin to the equivalent interrupt. This way, the change of board model is favored without having to modify the code.

attachInterrupt(digitalPinToInterrupt(pin), ISR, mode);

Other interesting functions for interrupt management are:

  • DetachInterrupt(interrupt ), cancels the interrupt.
  • NoInterrupts(), disables the execution of interrupts until further notice. Equivalent to cli()
  • Interrupts(), reactivates the interrupts. Equivalent to sei()

Testing interrupts in Arduino

To test interrupts in Arduino, we will use a digital output of Arduino to emulate a digital signal. In the real world, it would be another device (a sensor, another processor…) that would generate this signal, and we would capture it with Arduino.

We connect digital pin 10 to digital pin 2, associated with interrupt 0, using a cable.


In the following code, we define digital pin 10 as an output, to emulate a square wave with a period of 300ms (150ms ON and 150ms OFF).

To visualize the operation of the interrupt, on each active edge of the simulated pulse, we turn the integrated LED on the board on/off, so the LED blinks at intervals of 600ms (300ms ON and 300ms OFF)

At this point, seeing an LED blink may not seem very spectacular, but it is not as simple as it seems. We are not turning on the LED with a digital output, but it is the interrupt that triggers and turns off the LED (the digital pin only emulates an external signal).

const int emuPin = 10;

const int LEDPin = 13;
const int intPin = 2;
volatile int state = LOW;

void setup() {
  pinMode(emuPin, OUTPUT);
  pinMode(LEDPin, OUTPUT);
  pinMode(intPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(intPin), blink, CHANGE);

void loop() {
  //this part is to emulate the output
  digitalWrite(emuPin, HIGH);
  digitalWrite(emuPin, LOW);

void blink() {
  digitalWrite(LEDPin, state);

Pruébalo online

Counting interrupt triggers

In the following code, we use the same digital pin to emulate a square wave, this time with a 2s interval (1s ON and 1s OFF).

In each interrupt, we update the value of a counter. Later, in the main loop, we check the value of the counter, and if it has been modified, we display the new value.

When executing the code, we will see consecutive numbers printed to the serial monitor at two-second intervals.

const int emuPin = 10;

const int intPin = 2;
volatile int ISRCounter = 0;
int counter = 0;

void setup()
  pinMode(emuPin, OUTPUT);
  pinMode(intPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(intPin), interruptCount, FALLING);

void loop()
  //this part is to emulate the output
  digitalWrite(emuPin, HIGH);
  digitalWrite(emuPin, LOW);

  if (counter != ISRCounter)
    counter = ISRCounter;

void interruptCount()

Pruébalo online

Logically, our goal is to use hardware interrupts not only with other digital devices, but also with physical devices such as buttons, optical sensors…

However, as we have mentioned, these devices generate a lot of noise in state changes, causing the interrupts to trigger multiple times. This phenomenon is called “bounce” and we will learn how to eliminate it in the next entry.

Download the code

All the code from this post is available for download on Github. github-full