Comment utiliser des interruptions en MicroPython avec une RPi Pico ?
(Mis à jour le 22/02/2023)
En électronique, une interruption est un signal envoyé à un processeur qui lui indique qu’une tâche importante doit être exécutée immédiatement, interrompant ainsi le programme en cours.
Les interruptions peuvent être déclenchées de manières diverses, par exemple par un événement externe, un timer
interne… Elles permettent de réaliser des tâches de manière asynchrone, en dehors 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 et que cela déclenche automatiquement une fonction Python.
Exécuter périodiquement des fonctions, par exemple pour faire clignoter une LED toutes les 5 secondes.
Sans utiliser les interruptions, il est possible de créer un script qui allume une LED quand on appuie sur un bouton. Cependant, cette méthode a deux défauts majeurs.
from machine import Pin
pin_button = Pin(14, mode=Pin.IN, pull=Pin.PULL_UP)
pin_led = Pin(25, mode=Pin.OUT)
while True:
if not pin_button.value():
pin_led.on()
else:
pin_led.off()
Le premier inconvénient est que le script passe son temps à surveiller la valeur de la broche pin_button
pour savoir si le bouton a été pressé. Le script ne peut pas faire grand-chose en plus, car sinon le deuxième problème se produira : manquer des événements. Si le script effectue d’autres tâches dans la boucle, il risque de manquer la pression temporaire du bouton.
L’avantage d’utiliser une interruption matérielle est que la détection est complètement séparée du processeur (et donc du script Python). Avec une interruption, la boucle while
du script sera vide. Le matériel responsable de la détection est également beaucoup plus réactif que le script MicroPython.
Note
Avec les interruptions, il n’est plus nécessaire de surveiller constamment la valeur d’un pin : une fonction est automatiquement exécutée lorsqu’un changement est détecté.
Que ce soit avec un timer
ou un événement externe, l’interruption est déclenchée lorsqu’il y a un changement de signal. Découvrons les différentes possibilités 😊.
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.
Note
Les modes RISING
et FALLING
sont les plus couramment utilisés. Notez 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.
Voici les différents types de détection d’interruption possibles :
Pin.IRQ_LOW_LEVEL
: Déclenche l’interruption lorsque le signal est à 0VPin.IRQ_HIGH_LEVEL
: Déclenche l’interruption lorsque le signal est à 3.3VPin.IRQ_RISING
: Déclenche l’interruption lorsque le signal passe deLOW
àHIGH
(De 0V à 3.3V)Pin.IRQ_FALLING
: Déclenche l’interruption lorsque le signal passe deHIGH
àLOW
(De 3.3V à 0V)
Configurer et utiliser les interruptions en MicroPython sur la Pico
Un script Python squelette pour comprendre
Voici un script squelette en MicroPython qui vous permet de déclencher une interruption externe via un signal reçu par votre Pi Pico :
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 code utilise la fonction Pin.irq()
pour créer une demande d’interruption à partir d’un signal descendant appliqué sur la broche pin_button
.
Note
irq
signifie Interrupt Request , ou en français, demander une requête d’interruption. On emploie également le mot isr
pour désigner la routine d’interruption, c’est-à-dire la fonction qui va être exécutée suite à l’interruption (ici elle s’appelle interruption_handler()
)
Quand une interruption se déclenche, la fonction interruption_handler()
s’exécutera automatiquement avec en argument d’entrée, le pin sur lequel l’événement a été détecté.
Il est préconisé de rédiger une fonction d’interruption (isr
) la plus rapide possible afin d’éviter de perturber le programme principal. Par exemple, il est déconseillé d’envoyer des données via I2C, SPI directement depuis une interruption. On peut plutôt utiliser des flags , sous la forme de booléen pour stocker la détection d’un événement et le traiter ensuite dans la boucle principale.
Note
La gestion des interruptions en MicroPython sera toujours plus lente qu’en code Arduino ou en C pur ! Cependant, il est possible de minimiser cette latence en utilisant des paramètres avancés.
Exemple : Allumer une LED quand un bouton-poussoir est pressé
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(16, 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 d’utiliser l’opérateur OU |
pour combiner les modes et que l’interruption se déclenche à la fois sur un front montant et descendant :
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 pouvoir profiter de ces fonctions, vous devez utiliser la variable du pin qui est attachée à une interruption, par exemple pin_button.irq().enable()
.
Vous connaissez à présent les bases de l’utilisation des interruptions en MicroPython. Vous pouvez consulter la documentation officielle pour exploiter pleinement leurs fonctionnalités, par exemple définir des priorités entre plusieurs interruptions qui se déclenchent simultanément. Des astuces et optimisations sont également disponibles 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 effectuées au sein d’une interruption. Il est courant d’incrémenter une variable à l’intérieur de l’ISR et d’effectuer les tâches longues dans le code principal en fonction de la valeur de cette variable. 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 à l’intérieur de 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 d’indiquer à l’interpréteur Python d’utiliser la variable globale plutôt que de créer localement une variable (avec le même nom) qui serait utilisée uniquement dans le contexte d’exécution de la fonction.
Lorsque l’on exécute le script MicroPython, on remarque que la valeur de l’incrément est bien plus importante que le nombre de pressions effectuées. Étrange, n’est-ce pas ? C’est dû aux 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 constaté que les boutons-poussoirs peuvent donner des faux positifs : la routine d’interruption s’exécute alors plus de fois qu’elle ne le devrait. C’est parce que le signal reçu par la Pico n’est pas parfait : on reçoit des pressions parasites venant du bouton.
On parle d”effet rebond (bounce en anglais). On peut réduire le rebond d’un bouton-poussoir via le script Python directement, ce qu’on appelle du debouncing software . Il consiste à ne pas prendre en compte la période transitoire entre les deux é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 premiumDéjà abonné ? Connectez-vous