Ignorer et passer au contenu

Livraison offerte à partir de 50€ d'achats, livrée sous 48h !

Livraison à partir de seulement 2.50€ !

Contents Menu Expand Light mode Dark mode Auto light/dark mode

Comment utiliser des interruptions en MicroPython sur un ESP32 ?

(Mis à jour le 29/03/2023)

En électronique, une interruption est un signal envoyé à un processeur pour lui indiquer qu’une tâche importante doit être exécutée immédiatement, interrompant ainsi l’exécution du programme en cours.

Les interruptions peuvent être générées de différentes manières, par exemple suite à un événement externe, un timer interne… Elles permettent d’exécuter certaines tâches de manière asynchrone, c’est-à-dire indépendamment du programme principal.

Les interruptions en MicroPython

En pratique, les interruptions sont généralement utilisées :

  • Pour exécuter des portions de code critique lorsqu’un évènement extérieur se produit. Par exemple, lorsqu’on appuie sur un bouton, une fonction Python va automatiquement s’exécuter.

  • Pour exécuter périodiquement des fonctions. Par exemple, faire clignoter une LED toutes les 5 secondes.

Vous allez me dire que l’on peut déjà faire ce genre de script, sans utiliser les interruptions. Oui, c’est vrai, mais il y a 2 défauts majeurs sans les utiliser. Prenons l’exemple d’un script qui allume une LED quand on appuie sur le bouton :

from machine import Pin

pin_button = Pin(14, mode=Pin.IN, pull=Pin.PULL_UP)
pin_led    = Pin(2, mode=Pin.OUT)

while True:
    if not pin_button.value():
        pin_led.on()
    else:
        pin_led.off()

Le premier défaut est que le script passe son temps à scruter la valeur de la broche pin_button pour savoir si l’on a pressé le bouton. Le script ne peut faire d’autres choses en plus car sinon le deuxième défaut va se produire : rater des événements. Si le script réalise d’autres tâches dans la boucle, il risque de ne plus pouvoir détecter la pression du bouton temporaire.

L’avantage d’utiliser une interruption hardware est que la détection est complètement détachée du processeur (et donc du script Python). Avec une interruption la boucle while du script sera vide. Le bloc matériel chargé de la détection est aussi beaucoup plus réactif que le script MicroPython.

Note

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

Que ce soit avec un timer ou un événement extérieur, l’interruption se déclenche suite à un changement de signal. Découvrons les différentes variations possibles 😊.

Déclencher une interruption matérielle : les modes de détection

La détection d’un événement est basée sur l’allure du signal qui arrive à la broche.

Détection des évènements sur un signal numérique

Différents modes de détection

Voici les différents types de détection possible d’une interruption :

  • Pin.IRQ_LOW_LEVEL : Déclenche l’interruption dès que le signal est à 0V

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

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

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

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.

Configurer et utiliser les interruptions en MicroPython sur l’ESP32

Un script squelette de base

Voici un code squelette, pour déclencher une interruption via un signal externe sur votre carte ESP32 avec MicroPython :

from machine import Pin

pin_button = Pin(14, mode=Pin.IN, pull=Pin.PULL_UP)

def interruption_handler(pin):
    ...

pin_button.irq(trigger=Pin.IRQ_FALLING,handler=interruption_handler)

while True:
    ...

Le script utilise la fonction Pin.irq() qui permet de créer une requête d’interruption sur un front descendant du signal présent sur la broche pin_button .

Note

irq signifie Interrupt Request , soit en français, demander une requête d’interruption.

Lorsqu’une interruption se déclenche la fonction interruption_handler() s’exécutera automatiquement. La routine d’interruption aura en argument d’entrée, le pin sur lequel a été détecté l’événement.

C’est une bonne pratique d’avoir une fonction d’interruption (isr ) la plus rapide possible pour éviter de perturber le programme principal qui a été interrompu. Par exemple, on évitera d’envoyer des données via I2C, SPI directement depuis une interruption. On peut utiliser des flags , sous la forme de booléen pour stocker la détection d’un événement puis le traiter dans la boucle principale.

Note

La gestion des interruptions en microPython sera toujours beaucoup plus lente qu’en code Arduino ou en C pur ! Il est tout de fois possible de minimiser cette latence en utilisant des paramètres avancés .

Exemple : Allumer une LED quand un bouton-poussoir est pressé

Câblage sur plaque de prototypage du bouton-poussoir et de la led

Circuit électrique à réaliser

Voici le script complet qui permet de détecter lorsque l’on appuie sur un bouton avec une interruption et qui allume la LED en conséquence :

import time
from machine import Pin

pin_button = Pin(14, mode=Pin.IN, pull=Pin.PULL_UP)
pin_led    = Pin(2, mode=Pin.OUT)


def button_isr(pin):
  pin_led.value(not pin_led.value())

pin_button.irq(trigger=Pin.IRQ_FALLING,handler=button_isr)

while True:
  ...

Note

Dans cet exemple, l’interruption se déclenche sur un front descendant. Il est possible de combiner les modes pour que l’interruption se déclenche à la fois sur un front montant et descendant avec l’opérateur OU | :

pin_button.irq(trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING,handler=button_isr)

Voici quelques fonctions qui pourront vous être utiles :

  • irq.init()  : Ré-initialiser l’interruption. Elle sera automatiquement réactivée.

  • irq.enable()  : Activer l’interruption.

  • irq.disable()  : Désactiver l’interruption.

  • irq()  : Lancer manuellement l’appel de la routine d’interruptions.

  • irq.flags()  : Connaître le type d’événement qui a déclenché l’interruption. Utilisable uniquement dans l’isr .

    def pin_handler(pin):
        print('Interrupt from pin {}'.format(pin.id()))
        flags = pin.irq().flags()
        if flags & Pin.IRQ_RISING:
            # handle rising edge
        else:
            # handle falling edge
        # disable the interrupt
        pin.irq().deinit()
    

Pour utiliser ces fonctions, il faut utiliser la variable du pin qui est attaché à une interruption, par exemple pin_button.irq().enable() .

Vous connaissez maintenant les bases de l’utilisation des interruptions en MicroPython. Vous pouvez consulter la documentation officielle pour utiliser tous leurs potentiels, par exemple établir des priorités entre plusieurs interruptions qui se déclenchent en même temps. Certaines astuces et optimisation sont présentés dans la section avancée 😎.

Utiliser des variables globales pour gérer les événements dans le programme principal

On essaye de limiter le nombre d’actions faites dans une interruption. Il est commun d’incrémenter une variable dans l’isr et de faire les tâches longues dans le code principal en fonction de la valeur de celle-ci. Voici un exemple qui compte le nombre de fois que vous appuyez sur un bouton-poussoir.

import time
from machine import Pin

button_pressed_count = 0 # global variable
pin_button = Pin(14, mode=Pin.IN, pull=Pin.PULL_UP)

def button_isr(pin):
    global button_pressed_count
    button_pressed_count += 1


if __name__ == "__main__":
    button_pressed_count_old = 0
    pin_button.irq(trigger=Pin.IRQ_FALLING,handler=button_isr)

    while True:
        if button_pressed_count_old != button_pressed_count:
           print('Button value:', button_pressed_count)
           button_pressed_count_old = button_pressed_count

        if button_pressed_count > 10: # heavy task here
            ...

On utilise une variable globale pour pouvoir écrire dans celle-ci dans la routine d’interruption.

Avertissement

Même si la variable est définie tout en haut du script Python, vous devez ajouter le mot-clé global lorsque la variable est utilisée dans une fonction. Cela permet de dire à l’interpréteur Python d’utiliser la variable globale au lieu de créer localement une variable (avec le même nom), qui serait utilisé uniquement dans le contexte d’exécution de la fonction.

Résultat interruption bouton

Lorsqu’on exécute le script MicroPython, la valeur de l’incrément est beaucoup plus importante que le nombre de pression que l’on a faite. Bizarre 🤔 ? C’est à cause des transitions des niveaux logiques au niveau du bouton-poussoir qui ne sont pas parfaites…

Améliorer la fiabilité des détections d’une interruption

Vous avez sûrement remarqué qu’avec des boutons-poussoirs, il y avait des faux positifs : la routine d’interruption s’exécute plus de fois qu’elle devrait. C’est parce que le signal reçu par l’ESP32 n’est pas parfait : c’est comme s’il avait reçu une “double pression” du bouton :

effet rebond d’un bouton-poussoir

On parle d’effet rebond (bounce en anglais). On peut réduire le rebond d’un bouton-poussoir via le script Python directement. C’est qu’on appelle faire du debouncing software. Il consiste à ne pas prendre en compte la période transitoire entre les 2 états logiques en attendant un certain délai.

Cette section est réservée aux abonnés. Il vous reste 92% à découvrir.

Devenir membre premium

Déjà abonné ? Connectez-vous