Programmer un ou plusieurs boutons
Ce tutoriel explique comment interagir avec des boutons, y compris ceux de la NUCLEO-WB55. Cet exemple est également l’occasion de présenter les GPIO, l’acronyme pour General Purpose Input Output (en français : Ports d’entrées-sorties à usage général). Les ports d’entrées-sorties à usage général désignent l’ensemble des fonctions (programmables) que peuvent remplir les broches du microcontrôleur. Ils permettent de sélectionner les circuits internes au microcontrôleur que l’on souhaite utiliser afin d’interagir avec des composants externes et connectés à celui-ci tels que mémoires, moteurs, capteurs…
Quelques notions sur les GPIO
En l’occurrence, les boutons SW1, SW2, SW3 (tout comme les diodes électroluminescentes LED1, LED2, LED3) de la carte NUCLEO-WB55 sont connectés à des broches du STM32WB55 et nécessitent la configuration des GPIO correspondantes, une opération simplifiée par le langage MicroPython. Nous allons apporter ici quelques compléments d’informations sur les GPIO dans les microcontrôleurs afin que la syntaxe des instructions MicroPython vous paraisse moins énigmatique.
La figure qui suit résume les paramètres et configurations possibles des GPIO :
Une broche peut être configurée en entrée, en sortie ou en fonction alternative :
- Configurée en entrée, la broche transmet au microcontrôleur un signal provenant de l’extérieur. Le microcontrôleur va alors se charger d’analyser ce signal, par exemple lire l’état (appuyé ou relâché) d’un bouton.
- Configurée en sortie, la broche transmet un signal vers l’extérieur du microcontrôleur. Par exemple allumer ou éteindre une LED connectée dessus.
- Configurée en fonction alternative, la broche est connectée à un périphérique interne du microcontrôleur tel qu’un bus USB, un contrôleur de port série (USART/UART), un timer (par exemple pour générer ou pour analyser des signaux de type PWM) ou toute autre fonction avancée. Voir ce tutoriel pour une application de la fonction PWM, entre autres possibilités de fonctions alternatives.
Les broches en entrée peuvent fonctionner selon quatre modes :
- Floating input : entrée flottante, le niveau sera déterminé par les composants connectés à cette entrée.
- Pull-Down input : entrée polarisée à 0V via une résistance intégrée.
- Pull-Up input : entrée polarisée à +Vcc = +3,3V via une résistance intégrée.
- Analog input : entrée connectée à un convertisseur analogique-numérique (ADC). C’est le cas par défaut des broches reliées aux connecteurs Arduino A0 à A5.
Les broches en sorties ou en alternate function peuvent fonctionner selon deux modes :
- Le mode Push-Pull dans lequel la broche peut forcer un état logique 0 ou 1.
- Le mode Open-Drain dans lequel la broche peut forcer un état logique bas, ou se placer dans un état haute impédance (utile notamment pour certains protocoles de communication). On utilise en général un circuit extérieur constitué d’une résistance de pull-up, qui va permettre à la sortie d’être polarisée à +Vcc.
Liste des fonctions alternatives par GPIO
Vous trouverez sur cette page la description des méthodes de la classe Pin pour la carte Pyboard, également valable pour la NUCLEO-WB55. Par exemple, ce script renvoie la liste de toutes les fonctions alternatives associées à la broche A0 :
# Objet du script :
# Liste de toutes les fonctions alternatives associées à la broche 'A0' :
import pyb # Bibliothèque de MicroPython permettant les accès aux périphériques (GPIO, LED, etc.)
a0_pin = pyb.Pin('A0')
liste_alternate_functions = a0_pin.af_list()
for val in liste_alternate_functions:
print(val)
Premier exemple : allumer ou éteindre une LED avec un bouton
L’exemple qui suit montre comment configurer les GPIO pour commander une LED avec un bouton. Ce code est un exemple de programmation par scrutation ou “polling” en anglais : une boucle infinie occupe à 100% le microprocesseur et vérifie ou modifie à très grande fréquence l’état des GPIO.
Matériel requis
- La carte NUCLEO-WB55
- Un Shield de base Grove pour Arduino
- Un module LED Grove
- Un module Bouton Grove
Crédit image : Seeed Studio
Connectez le module LED sur D2 et le module bouton sur D4.
Attention nous vous rappelons que les LED sont polarisées ; si vous les branchez incorrectement, vous les détruirez probablement. La patte la plus longue de la LED que vous utiliserez devra être insérée dans la borne “+” du module Grove et la plus courte dans sa borne “-“.
Le code MicroPython
Vous pouvez télécharger les scripts MicroPython de ce tutoriel (entre autres) en cliquant ici.
Éditez le script main.py contenu dans le répertoire du disque USB virtuel associé à la NUCLEO-WB55 PYBFLASH et copiez-y le code qui suit :
# Objet du script : Allumer une LED en maintenant un bouton appuyé.
# Le bouton est géré en "polling" (scrutation) : une boucle infinie
# surveille l'état de la broche à laquelle il est connecté.
# Matériel requis en plus de la NUCLEO-WB55 : un bouton connecté à la broche
# D4 et une LED connectée à la broche D2.
from pyb import Pin # Pour gérer les GPIO
# On configure le bouton en entrée (IN) sur la broche D4.
# Le mode choisi est PULL UP : le potentiel de D4 est forcé à +3,3V
# lorsque le bouton n'est pas appuyé.
button_in = Pin('D4', Pin.IN, Pin.PULL_UP)
# On configure la LED en sortie Push-Pull (OUT_PP) sur la broche D2.
# Le mode choisi est PULL NONE : le potentiel de D2 n'est pas fixé.
led_out = Pin('D2', Pin.OUT_PP, Pin.PULL_NONE) # Broche de la LED
old_button_level = 1
while True : # Boucle sans clause de sortie ("infinie")
# Si on presse le bouton, button_in.value() = 1 => la LED s'allume
# Si le bouton est relâché, button_in.value() = 0 => la LED s'éteint
button_level = button_in.value()
if button_level != old_button_level:
print("Niveau logique du bouton : ", button_level)
old_button_level = button_level
# Change l'état de la LED
led_out.value(button_level)
Les dessins qui suivent résument ce que fait ce programme :
Lorsque le bouton est relâché, le potentiel de la broche D4 est à +3,3V (Pull-Up) et bouton_in a pour valeur 0. Une broche de la LED est reliée à la masse, donc au potentiel 0V et l’autre broche, D4, est commandée par le STM32WB55. Elle est aussi au potentiel 0V du fait de l’instruction led_out.value(0). Le microprocesseur du STM32WB55 (CPU) est occupé à 100% pour exécuter la boucle while True et ses instructions.
Lorsque le bouton est appuyé, le potentiel de la broche D4 passe à 0V et bouton_in a pour valeur 1. Remarque : la valeur et le potentiel ne sont pas liés, la fonction bouton_in.value() renvoie systématiquement “1” lorsque le bouton est appuyé. L’instruction led_out.value(1) fait passer le potentiel de la broche D2 à +3,3V et la LED, qui voit une différence de potentiel non nulle à ses bornes, s’allume. Le microprocesseur du STM32WB55 (CPU) est toujours occupé à 100% pour exécuter la boucle while True et ses instructions.
Deuxième exemple : gérer les trois boutons de la NUCLEO-WB55
Nous allons voir dans cette sous-partie comment initialiser une broche (“pin” en anglais) en mode “Entrée” et afficher un message lors de l’appui sur l’un des 3 boutons en utilisant pyb.Pin.
Nous utiliserons la méthode dite de “polling” pour lire l’état de la broche (1 ou 0).
Pour des raisons de conception électronique, l’état de la broche au repos, bouton relâché, est “1” (potentiel fixé à +3,3V, car broche configurée en pull-up) alors que l’état lors d’un appui bouton est “0” (potentiel fixé à 0V). Ceci peut sembler contre-intuitif ; ce point est traité par la suite par la section La Classe Signal.
Matériel requis
La carte NUCLEO-WB55 et ses boutons intégrés SW1, SW2 et SW3 :
Le code MicroPython
Vous pouvez télécharger les scripts MicroPython de ce tutoriel (entre autres) en cliquant ici.
Éditez le script main.py contenu dans le répertoire du disque USB virtuel associé à la NUCLEO-WB55 PYBFLASH :
# Objet du script :
# Exemple de configuration des GPIO pour une gestion des boutons de la NUCLEO-WB55
from machine import Pin # Contrôle des broches
from time import sleep_ms # Pour temporiser
print( "Les GPIO avec MicroPython c'est facile" )
# Initialisation des broches d'entrées pour les boutons (SW1, SW2, SW3)
# Le potentiel des broches sera à +3,3V lorsque les boutons seront relâchés (Pull up)
# Le potentiel des broches sera à 0V lorsque les boutons seront enfoncés
# Le paramètre 'af = -1' signifie que l'on ne souhaite pas attribuer un fonction alternative à la broche.
sw1 = Pin( 'SW1' , Pin.IN)
sw1.init(Pin.IN, Pin.PULL_UP, af=-1)
sw2 = Pin( 'SW2' , Pin.IN)
sw2.init(Pin.IN, Pin.PULL_UP, af=-1)
sw3 = Pin( 'SW3' , Pin.IN)
sw3.init(Pin.IN, Pin.PULL_UP, af=-1)
# Initialisation des variables
old_value_sw1 = 1
old_value_sw2 = 1
old_value_sw3 = 1
while True: # Boucle sans clause de sortie ("infinie")
# Temporisation pendant 100 ms
sleep_ms(100)
# Récupération de l'état des Boutons 1,2,3
value_sw1 = sw1.value()
value_sw2 = sw2.value()
value_sw3 = sw3.value()
# L'état courant est-il différent de l'état précédent ?
if value_sw1 != old_value_sw1:
if value_sw1 == 0:
print( "Le bouton 1 (SW1) est appuyé" )
else :
print( "Le bouton 1 (SW1) est relâché" )
if value_sw2 != old_value_sw2:
if value_sw2 == 0:
print( "Le bouton 2 (SW2) est appuyé" )
else :
print( "Le bouton 2 (SW2) est relâché" )
if value_sw3 != old_value_sw3:
if value_sw3 == 0:
print( "Le bouton 3 (SW3) est appuyé" )
else :
print( "Le bouton 3 (SW3) est relâché" )
# Sauvegarde de l'état des boutons pour la prochaine itération
old_value_sw1 = value_sw1
old_value_sw2 = value_sw2
old_value_sw3 = value_sw3
Enregistrez les modifications avec ([CTRL] + [S] sous Notepad++). Vous pouvez lancer le script avec Ctrl + D sur le terminal PuTTY et observer les messages qu’il renvoie lorsque vous appuyez sur les différents boutons :
Troisième exemple : la classe Signal
Vous pourriez trouver déconcertant que la logique de fonctionnement du bouton soit inversée. Le niveau bas de la broche correspond au bouton pressé, ce qui fait que l’on obtient un signal à “0” dans ce cas. Peut-être préfèreriez-vous qu’à un niveau de tension bas sur la broche corresponde une valeur “1” renvoyée par le bouton (et inversement) ?
MicroPython offre une solution à ce problème (que je trouve inutilement compliquée !) avec la classe Signal. Le script suivant utilise celle-ci (instances b1, b2, b3) pour inverser le signal des broches (instances sw1, sw2 et sw3) :
# Objet du script :
# Exemple de configuration des GPIO pour une gestion des boutons de la NUCLEO-WB55
# Inversion des états logiques àl'aide de la classe Signal.
from machine import Pin, Signal # Contrôle des broches
from time import sleep_ms # Pour faire des pauses système
print( "Les GPIO avec MicroPython c'est facile" )
# Initialisation des broches d'entrées pour les boutons (SW1, SW2, SW3)
# Le potentiel des broches sera à +3.3V lorsque les boutons seront relâchés (Pull up)
# Le potentiel des broches sera à 0V lorsque les boutons seront enfoncés
# Le paramètre 'af = -1' signifie que l'on ne souhaite pas attribuer un fonction alternative à la broche.
sw1 = Pin('SW1', Pin.IN)
sw1.init(Pin.IN, Pin.PULL_UP, af=-1)
# Inverse le niveau logique du bouton
b1 = Signal(sw1, invert = True)
sw2 = Pin('SW2', Pin.IN)
sw2.init(Pin.IN, Pin.PULL_UP, af=-1)
b2 = Signal(sw2, invert = True)
sw3 = Pin('SW3', Pin.IN)
sw3.init(Pin.IN, Pin.PULL_UP, af=-1)
b3 = Signal(sw3, invert = True)
# Initialisation des variables
old_value_sw1 = 1
old_value_sw2 = 1
old_value_sw3 = 1
while True: # Boucle sans clause de sortie ("infinie")
# Temporisation pendant 100 ms
sleep_ms(100)
#Récupération de l'état des Boutons 1,2,3
value_sw1 = b1.value()
value_sw2 = b2.value()
value_sw3 = b3.value()
#L'état courant est il différent de l'état précédent ?
if value_sw1 != old_value_sw1:
if value_sw1: # équivalent à value_sw1 == 1
print( "Le bouton 1 (SW1) est appuyé" )
else :
print( "Le bouton 1 (SW1) est relâché" )
if value_sw2 != old_value_sw2:
if value_sw2: # équivalent à value_sw2 == 1
print( "Le bouton 2 (SW2) est appuyé" )
else :
print( "Le bouton 2 (SW2) est relâché" )
if value_sw3 != old_value_sw3:
if value_sw3: # équivalent à value_sw3 == 1
print( "Le bouton 3 (SW3) est appuyé" )
else :
print( "Le bouton 3 (SW3) est relâché" )
# Sauvegarde de l'état des boutons pour la prochaine itération
old_value_sw1 = value_sw1
old_value_sw2 = value_sw2
old_value_sw3 = value_sw3
Pour aller plus loin
- Les boutons peuvent être mieux gérés en utilisant le mécanisme des interruptions, abordé dans ce tutoriel.
- Les boutons de mauvaise qualité posent souvent un problème de rebond. Lorsque vous appuyez sur un tel bouton, il ne passe pas immédiatement de l’état “ouvert” à l’état “fermé” mais peut osciller plusieurs fois entre les deux et générer des comportements aléatoires sur votre montage. Ce problème classique peut être résolu en attendant quelques millisecondes que l’état du bouton soit stable. Ce sujet est abordé dans ce tutoriel.