> For the complete documentation index, see [llms.txt](https://monasheng.gitbook.io/zerotohero/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://monasheng.gitbook.io/zerotohero/arduino-zero-to-hero/advanced-skills/interrupts.md).

# Interrupts

## Motivations

We've now learned how to read a range of switches/buttons/states using `digitalRead()`.  Until now, we have only considered calling this function to poll a pin state on an interval, either  at every loop iteration by placing this call in the body of our main `loop()` function, or at some other interval using [Timers](/zerotohero/arduino-zero-to-hero/core-skills/timers.md) (e.g. `millis()`).

Provided the rest of `loop()` is not intensive, we can achieve essentially-real-time measurement by placing this call in the body, as Arduino boards run at a reasonably high clock speed -  potentially allowing for measurements at $$>1000;\text{Hz}$$.

`digitalRead()` takes some time to execute, however, and if coupled with other intensive functions (e.g. lots of math, writing to the serial monitor, etc.) can lead to slowing of the Arduino. This slowing can have implications for time-sensitive tasks (e.g. blinking an LED at a fixed frequency).

In addition, polling a pin state every loop iteration can have issues when the `delay()` function is also used, as sensor changes (e.g. button presses) that happen during the delay period may not be captured.&#x20;

Mercifully, Arduino has a built-in *interrupt* functionality for this exact purpose - able to trigger a `digitalRead()` only where a change to pin voltage has actually occurred!

## Hardware Interrupts

Arduino has provision for hardware- and software-based interrupts, though we will only consider the former here.

A *hardware interrupt* comprises of:

1. A [#trigger](#trigger "mention"), defined by physical change to the voltage on an Arduino pin, and,
2. An [#interrupt-service-routine](#interrupt-service-routine "mention") (ISR), which is a function that is invoked whenever the trigger occurs.

### Trigger

Interrupt trigger definitions are performed once, within `setup()`, using the `attachInterrupt()` function.

{% embed url="<https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/>" %}

This function has syntax as follows:

```arduino
attachInterrupt(digitalPinToInterrupt(PIN), ISR, mode)
```

where:

* `PIN` is the pin number to which the trigger will be bound,
  * Note that only some pins are interrupt-compatible. Consult your datasheet for more information. On the Arudino Mega, these are pins `2`, `3`, `18`, `19`, `20`, and `21`.
* `ISR` is the name of the interrupt service routine function to be called whenever the trigger is satisfied, and,
* `mode` is the type of trigger to be used - one of the following options:
  * `LOW`, which trigger whenever the pin is in a `LOW` state
  * `CHANGE`, which triggers whenever the pin changes value (e.g. from `LOW` to `HIGH` or vice versa),
  * `RISING`, which triggers only whenever the pin changes from `LOW` to `HIGH` specifically, and,
  * `FALLING`, which triggers only whenever the pin changes from `HIGH` to `LOW` specifically.

### Interrupt Service Routine

Interrupt Service Routines (ISRs) are user-defined functions to be called whenever a trigger is satisfied. They can be written like any other function, but have two special constraints:

1. They must be `void` type functions, - they can take no inputs and provide no outputs - i.e. they can only interact with the rest of the code via [Arduino C](/zerotohero/arduino-zero-to-hero/arduino-programming/arduino-c.md#global-variables), and these should be defined as `volatile`.
2. They cannot utilise timers or timer-based functions (e.g. `millis()` or `delay()`).

Because an interrupt will *interrupt* any running code and immediately shift focus of the MCU to executing an ISR, it is essential that these are kept as small and lightweight as possible, as:

* Internal timers (e.g. `millis()`) elsewhere in the code are not updated while interrupts are running, and,
* If your sketch uses multiple ISRs, only one can run at a time, with other interrupts being queued to execute after the current one finishes.

Consequently, ISRs should be limited to e.g. setting flags that can be actioned in the main `loop()` of the code.

## Complete Sample Code

Consider a situation where a switch position is to be measured and reported via the serial monitor. Without interrupts, our code might look something like:

```arduino
void setup() {
  // Set PIN 2 as an input.
  pinMode(2, INPUT);
  // Setup the Serial Monitor for later
  Serial.begin(9600);
}

void loop() {
  // Read the state of PIN 2, and print this to the serial monitor
  int pinTwoState = digitalRead(2);
  Serial.println(pinTwoState);
}
```

This code works, and provides near-real-time measurement of the pin state, but will slow in real life due to the constant printing the serial monitor.&#x20;

<figure><img src="/files/ALnXexGawCeZC085ELWj" alt=""><figcaption><p>Trying to measure and write to the serial monitor every loop iteration leads to a constant stream of data in the monitor and to slowing down of the Arduino board.</p></figcaption></figure>

To speed up our code, we can use an interrupt to determine when the pin has changed state and only perform a measurement and write to the monitor in the main `loop()` if this has happened using a *flag* variable.

<pre class="language-arduino" data-overflow="wrap"><code class="lang-arduino">volatile int stateHasChangedFlag;

void setup() {
  // initialise the stateHasChangedFlag as false
  stateHasChangedFlag = 0;
  // Set PIN 2 as an input.
  pinMode(2, INPUT);
  // Setup the Serial Monitor for later
  Serial.begin(9600);
  // Setup the interrupt to occur on a CHANGE to the pin 2 state
  attachInterrupt(digitalPinToInterrupt(2), myISR, CHANGE);
}

void loop() {
<strong>  // Only read the state of PIN 2, and print this to the serial monitor, if the stateHasChanged flag has been set to TRUE by the interrupt.
</strong>  if (stateHasChanged) {
    int pinTwoState = digitalRead(2);
    Serial.println(pinTwoState);
    // Reset stateHasChangedFlag
    stateHasChangedFlag = 0;
  }
}

void myISR() {
  stateHasChangedFlag = 1;
}
</code></pre>

<figure><img src="/files/VDhNsKzBPdll4zJtouZr" alt=""><figcaption><p>By using an interrupt, we avoid having to <code>digitalRead()</code> and <code>Serial.println()</code> every loop iteration, instead only doing so when the switch changes state. This is FAR more efficient!</p></figcaption></figure>
