Arduino – Interrupt

This article shows how interrupt requests can be handled on an Arduino Uno in C/C++.

Example: A pushbutton, which is debounced by software, controls a light emitting diode/LED.

The program (Sketch) reacts to a button by means of two different interrupts both the pressing and the releasing of the button are detected. At the same time the button is debounced by software when pressed and released (remark: a reliable debouncing should better be implemented in the hardware). The button is connected via pin 2, a LED connected via pin 13 should light up as long as the button remains pressed.

Input 2 changes from logical “1” to “0” as soon as the button is pressed.
-> Calls the interrupt handler onButtonDown().
Input 2 changes from logical “0” to “1” as soon as the button is released.
-> Calls the interrupt handler onButtonUp().

The program / sketch (https://github.com/schaeren/micro_controller/tree/master/arduino_avr/c/arduino_avr_interrupt):

#include "Arduino.h"

#define DEBUG

const int ledPin = 13;                  // pin used for LED (same as on-board led)
const int buttonPin = 2;                // pin used for button
unsigned long debouncingTime = 20;      // delay used to debouncing button press & release [ms]
unsigned long lastButtonChangeTime = 0; // time of 1st interrupt when button pressed/released

void onButtonUp();

void onButtonDown() {
    unsigned long now = millis();
    if (now - lastButtonChangeTime > debouncingTime) {
        #ifdef DEBUG
        Serial.print("onButtonDown(): lastBottonChangeTime: ");
        Serial.print(lastButtonChangeTime);
        Serial.print("ms - current time: ");
        Serial.print(now);
        Serial.println("ms");
        #endif

        lastButtonChangeTime = now;
        digitalWrite(ledPin, HIGH);
        // Switch to button release interrupt
        detachInterrupt(digitalPinToInterrupt(buttonPin));
        attachInterrupt(digitalPinToInterrupt(buttonPin), onButtonUp, RISING);
    }
}

void onButtonUp() {
    unsigned long now = millis();
    if (now - lastButtonChangeTime > debouncingTime) {
        #ifdef DEBUG
        Serial.print("onButtonUp(): lastBottonChangeTime: ");
        Serial.print(lastButtonChangeTime);
        Serial.print("ms - current time: ");
        Serial.print(now);
        Serial.print("ms - button press time: ");
        Serial.print(now - lastButtonChangeTime);
        Serial.println("ms");
        #endif

        lastButtonChangeTime = now;
        digitalWrite(ledPin, LOW);
        // Switch to button press interrupt
        detachInterrupt(digitalPinToInterrupt(buttonPin));
        attachInterrupt(digitalPinToInterrupt(buttonPin), onButtonDown, FALLING);
    }
}

void setup() { 
    pinMode(ledPin, OUTPUT);
    pinMode(buttonPin, INPUT_PULLUP);
    #ifdef DEBUG
    Serial.begin(115200);
    Serial.print("debouncingTime: ");
    Serial.print(debouncingTime);
    Serial.println("ms");
    #endif

    // Enable button press interrupt
    attachInterrupt(digitalPinToInterrupt(buttonPin), onButtonDown, FALLING);
}

void loop() {
}

setup()

Initializes the inputs and outputs. The LED ist connected to pin 13 configured as output, i.e. the yellow LED with the designation “L” on the Arduino board (very close to the pin 13) is also controlled in parallel to the LED on the breadboard. The pushbutton is connected to pin 2 configured as input with pullup resistor. This means that in idle state there is a high level (“1”) at the input. As long as the button is pressed, the input is connected to ground resulting in a low level (“0”).

Further the interrupt routine (interrupt handler) onButtonDown() is registered for pin 2 for the “falling edge”, i.e. for the change from logical “1” to “0”. So this is called when the button is pressed down.

loop()

The main loop remains empty, i.e. the program actually does nothing here, except the execution of the interrupt handlers. Of course, other functions could be executed here.

onButtonDown()

This interrupt handler is called when the button is pressed down. The variable lastButtonChangeTime is used to determine whether more than debouncingTime time (20 ms) has elapsed since the last call of one of the two interrupt handlers:

  • If this is true, the LED is switched on, further ButtonDown interrupts are ignored using detachInterrupt() to disable ButtonDown interrupts, and ButtonUp interrupts are enabled with attachInterrupt() to detect the release of the button.
  • If this is not true, the interrupt is ignored.

This mechanism ensures a simple/primitive debouncing of the button: If the button should bounce when pressed down, this would result in one or more further calls to OnButtonDown(), but these are still ignored during the debouncingTime period.

onButtonUp()

This interrupt handler is called when the button is released. Using the variable lastButtonChangeTime the release of the button is debounced, just like the debouncing of the press in onButtonDown().

  • If the debouncingTime time has elapsed since the last interrupt, the LED is switched off, further ButtonUp interrupts are ignored with detachInterrupt() and ButtonDown interrupts are allowed again with attachInterrupt().

Debouncing: If the button should bounce when released, this would results in one or more further calls to OnButtonDown(), but these are still ignored during the debouncingTime period.

debouncingTime

The debouncingTime (in milliseconds) must be selected so that it is longer than the bounce time of the button, but also sufficiently short so that even very short button presses are reliably detected. It must either be determined experimentally or measured e.g. with an oscilloscope.