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 :


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 à +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, c’est 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.

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
  • Un module LED Grove
  • Un module Bouton Grove


LED


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

Les scripts présentés ci-après sont disponibles dans la zone de téléchargement.

Éditez le script main.py contenu dans le répertoire du disque USB virtuel associé à la NUCLEO-WB55 PYBFLASH :

# 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 machine 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é.

bouton_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

ancien_niveau_bouton = 1

while True : # Boucle sans clause de sortie ("infinie")

	# Si on presse le bouton, bouton_in.value() = 1 => la LED s'allume
	# Si le bouton est relâché, bouton_in.value() = 0 => la LED s'éteint
	
	niveau_bouton = bouton_in.value()
	
	if niveau_bouton != ancien_niveau_bouton:
		print("Niveau logique du bouton :", niveau_bouton)
		ancien_niveau_bouton = niveau_bouton

	led_out.value(niveau_bouton)

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.


Polling 1


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 tooujours occupé à 100% pour exécuter la boucle while True et ses instructions.


Polling 2


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” afin de demander au système MicroPython 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). Ce point est adressé par la suite (section La Classe Signal)

Matériel requis

La carte NUCLEO-WB55 et ses boutons intégrés SW1, SW2 et SW3 :


LED


Le code MicroPython

Les scripts présentés ci-après sont disponibles dans la zone de téléchargement.

É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 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) 

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
ancienne_valeur_sw1 = 0
ancienne_valeur_sw2 = 0
ancienne_valeur_sw3 = 0

while True: # Boucle sans clause de sortie ("infinie")

	# Temporisation pendant 300ms
	sleep_ms(300)
	
	#Récupération de l'état des Boutons 1,2,3
	valeur_sw1 = sw1.value()
	valeur_sw2 = sw2.value()
	valeur_sw3 = sw3.value()
	
	#L'état courant est il différent de l'état précédent ?
	if valeur_sw1 != ancienne_valeur_sw1:
		if valeur_sw1 == 0:
			print( "Le bouton 1 (SW1) est appuyé" )
		else :
			print( "Le bouton 1 (SW1) est relâché" )
		ancienne_valeur_sw1 = valeur_sw1
	if valeur_sw2 != ancienne_valeur_sw2:
		if valeur_sw2 == 0:
			print( "Le bouton 2 (SW2) est appuyé" )
		else :
			print( "Le bouton 2 (SW2) est relâché" )
		ancienne_valeur_sw2 = valeur_sw2
	if valeur_sw3 != ancienne_valeur_sw3:
		if valeur_sw3 == 0:
			print( "Le bouton 3 (SW3) est appuyé" )
		else :
			print( "Le bouton 3 (SW3) est relâché" )
		ancienne_valeur_sw3 = valeur_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 :

Sortie 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’a 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 la classe Signal (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
ancienne_valeur_sw1 = 1
ancienne_valeur_sw2 = 1
ancienne_valeur_sw3 = 1

while True: # Boucle sans clause de sortie ("infinie")

	# Temporisation pendant 300ms
	sleep_ms(300)
	
	#Récupération de l'état des Boutons 1,2,3
	valeur_sw1 = b1.value()
	valeur_sw2 = b2.value()
	valeur_sw3 = b3.value()
	
	#L'état courant est il différent de l'état précédent ?
	if valeur_sw1 != ancienne_valeur_sw1:
		if valeur_sw1: # équivalent à  valeur_sw1 == 1
			print( "Le bouton 1 (SW1) est appuyé" )
		else :
			print( "Le bouton 1 (SW1) est relâché" )
		ancienne_valeur_sw1 = valeur_sw1
	if valeur_sw2 != ancienne_valeur_sw2:
		if valeur_sw2:  # équivalent à  valeur_sw2 == 1
			print( "Le bouton 2 (SW2) est appuyé" )
		else :
			print( "Le bouton 2 (SW2) est relâché" )
		ancienne_valeur_sw2 = valeur_sw2
	if valeur_sw3 != ancienne_valeur_sw3:
		if valeur_sw3:  # équivalent à  valeur_sw3 == 1
			print( "Le bouton 3 (SW3) est appuyé" )
		else :
			print( "Le bouton 3 (SW3) est relâché" )
		ancienne_v

Pour aller plus loin

  • Les boutons peuvent être mieux gérés en utilisant le mécanisme des interruptions, abordé dans ce tutoriel.

  • 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)
  • 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.