Les interruptions

Une interuption est un processus qui est déclenché de manière asynchrone par un évènement extérieur, qui interrompt momentanément l’éxecution du code en cours, pour exécuter du code plus critique.

A quoi ça sert ?

Imaginez que vous vouliez allumer une LED lorsque vous appuyez sur un bouton qui est relié à un pin GPIO de l’ESP32. Le plus simple est de regarder en permanence dans la fonction loop() si vous avez appuyé sur le bouton :

const int buttonPin = 33;
const int ledPin =  2;

// Etat du bouton poussoir
int buttonState = 0;

void setup() {
    Serial.begin(115200);

    //Configuration du pin en entrée pullup
    pinMode(buttonPin, INPUT_PULLUP);
    pinMode(ledPin, OUTPUT);
}

void loop() {
    buttonState = digitalRead(buttonPin);

    if (buttonState == LOW) {
        digitalWrite(ledPin, HIGH);
    } else if(buttonState == HIGH){
        digitalWrite(ledPin, LOW);
    }
}

Le problème est que le processeur du microcontrôleur est totalement occupé par cette tâche. Alors on peut dire au microcontrôleur de faire d’autres tâches dans la loop(), mais dans ce cas le microcontrôleur ne regardera l’état du bouton qu’une seule fois à chaque itération de la fonction loop(). Il se peut qu’on manque un évènement. On ne peut pas traiter en temps réel des évènements extérieurs.

Les interruptions permettent de détecter un évènement en temps réel tout en laissant le processeur du microcontrôleur faire d’autres tâches. Ainsi le fonctionnement d’une interruption est le suivant :

Détection d’un évènement -> Interruption du programme principal -> Exécution du code de l’interruption -> Le processeur reprend là où il s’est arrêté.

Note

Avec les interruptions, il n’y a plus besoin de regarder en permanence la valeur d’un pin : lorsqu’un changement est détecté, une fonction est exécutée.

Les modes de détection

La détection d’un évenement est basée sur l’allure du signal qui arrive au pin.

../../_images/interruption0.jpg

Différents modes de détection

On peut choisir le mode de détection de l’interruption :
  • LOW : Déclenche l’interruption dès que le signal est à 0V

  • HIGH : Déclenche l’interruption dès que le signal est à 3.3V

  • RISING : Déclenche l’interruption dès que le signal passe de LOW à HIGH (0 à 3.3V)

  • FALLING : Déclenche l’interruption dès que le signal passe de HIGH à LOW (3.3V à 0)

  • CHANGE : Déclenche l’interruption dès que le signal passe de LOW à HIGH ou de HIGH à LOW.

Note

Les modes RISING et FALLING sont les plus utilisés. Noter que si vous utilisez les modes LOW et HIGH, l’interruption se déclenchera en boucle tant que le signal ne change pas d’état.

Utilisation sur l’ESP32

L’utilisation des interruptions sur l’ESP32 est similaire à celle sur l’Arduino avec la fonction attachInterrupt() . N’importe quel pin GPIO peut être utilisé pour les interruptions.

Ainsi pour créer une interruption sur un pin, il faut :
  • Attribuer un pin pour détecter l’interruption attachInterrupt()

attachInterrupt(GPIOPin, fonction_ISR, Mode);

Avec Mode, le mode de détection qui peut être LOW, HIGH, RISING, FALLING ou CHANGE

  • Créer la fonction qui va être exécutée lorsque l’interruption est déclenchée :

void IRAM_ATTR fonction_ISR() {
    //Contenu de la fonction
}

Note

Il est conseillé d’ajouter le flag IRAM_ATTR pour que le code de la fonction soit stocké dans la RAM (et non pas dans la Flash), afin que la fonction s’exécute plus rapidement.

Le code entier sera de la forme :

void IRAM_ATTR fonction_ISR() {
    //Code de la fonction
}

void setup() {
    Serial.begin(115200);
    pinMode(23, INPUT_PULLUP);
    attachInterrupt(23, fonction_ISR, FALLING);
}

void loop() {
}

Dès que la tension passera de 3.3V à 0V, la fonction fonction_ISR() sera exécutée. On peut ensuite faire d’autres tâches dans la fonction loop().

Il faut garder en tête que la fonction d’une interruption doit s’exécuter le plus rapidement possible pour ne pas perturber le programme principal. Le code doit être le plus concis possible et il est déconseillé de dialoguer par SPI, I2C, UART depuis une interruption.

Avertissement

On ne peut pas utiliser la fonction delay() ni Serial.println() avec une interruption. On peut néanmoins afficher des messages dans le moniteur série en remplaçant Serial.println() par ets_printf() qui est compatible avec les interruptions.

Le code ci-dessous affiche « Boutton pressé » lorsqu’on presse sur un bouton relié au pin 33.

void IRAM_ATTR fonction_ISR() {
    ets_printf("Boutton pressé\n");
    //Code de la fonction
}

void setup() {
    Serial.begin(115200);
    pinMode(33, INPUT_PULLUP);
    attachInterrupt(33, fonction_ISR, FALLING);
}

void loop() {
}

Mini Projet

Nous allons refaire le premier mini projet qui consistait à faire clignoter une led lorsqu’on appuie sur un bouton. On va utiliser les interruptions pour gérer l’évenement et libérer le processeur pour qu’il puisse faire d’autres tâches.

Schéma électrique

../../_images/projet0_circuit.png

Circuit électrique

Essayez d’écrire le programme par vous-même !

Solution

const int buttonPin = 32;
const int ledPin =  23;
int buttonState = 0;
int lastMillis = 0;

void IRAM_ATTR fonction_ISR() {
    if(millis() - lastMillis > 10){ //Software debouncing buton
    ets_printf("ISR triggered\n");
    buttonState = !buttonState;
    digitalWrite(ledPin,buttonState);
    }
    lastMillis = millis();
}

void setup() {
    Serial.begin(115200);
    pinMode(buttonPin, INPUT_PULLUP);
    pinMode(ledPin,OUTPUT);
    attachInterrupt(buttonPin, fonction_ISR, CHANGE);
    digitalWrite(ledPin, buttonState);

}

void loop() {
//Code ...
}