CentraleSupélecDépartement informatique
Plateau de Moulon
3 rue Joliot-Curie
F-91192 Gif-sur-Yvette cedex
BE 3 : MicroPython, entrées-sorties, interruptions

Exercices

Compétences visées :

  • Utiliser un microcontrôleur pour interagir avec le monde physique.
  • Configurer l'interface d'entrée-sortie d'un microcontrôleur.
  • Distinguer la scrutation des interruptions.
  • Distinguer l'attente active de l'utilisation d'un timer.

Optionnellement :

  • Connaître les défauts des contacts mécaniques et les pallier de manière logicielle.

Prise en main de MicroPython Prise en main

Branchez la pyboard sur un port USB. Un nouveau disque nommé PYBFLASH apparaît.

Se connecter à l'interpréteur Python de la pyboard

La pyboard apparaît également comme un port série, en général /dev/ttyACM0 sous Linux. Vous pouvez vous y connecter à l'aide du programme minicom, mais le plus simple est d'utiliser la commande pyterm qui est installée sur les machines du libre-service. Dans un terminal, tapez : pyterm suivi de la touche return. Vous devez obtenir ce qui suit :

Bienvenue avec minicom 2.7

OPTIONS: I18n
Compilé le Jan  1 2014, 17:13:19.
Port /dev/ttyACM0, 17:42:54

Tapez CTRL-A Z pour voir l'aide concernant les touches spéciales

Micro Python v1.4.4 on 2015-06-24; PYBv1.0 with STM32F405RG
Type "help()" for more information.
>>> 

Si ce n'est pas le cas, il est possible que la pyboard soit en train d'exécuter un programme. Vous pouvez arrêter son exécution en tapant Ctrl-C (appuyez sur la touche Ctrl, puis sur la touche C en gardant Ctrl enfoncée). Si l'invite de commande comporte un seul chevron > au lieu de trois comme montré ci-dessus, c'est que l'interpréteur est en mode brut. Pour sortir de ce mode, tapez Ctrl-B.

Une fois connecté à l'interpréteur, vous pouvez exécuter des commandes Python 3. L'indentation se fait uniquement avec des espaces, les tabulations ne sont pas acceptées.

Par exemple, tapez le code suivant, chaque ligne étant terminée avec la touche return :

2+3
2/3

Vous pouvez également accéder aux LED de la pyboard :

import pyb
led = pyb.LED(2)
led.on()

L'inconvénient de ce mode est que vous devez tout retaper en cas d'erreur, l'historique des commandes (avec les flèches vers le haut et vers le bas) étant très limité.

Pour fermer la connexion à la pyboard et quitter le programme pyterm, tapez :

  • Ctrl-A suivi de X si vous êtes connecté avec minicom (sous MacOS, remplacez Ctrl-A@ par ESC@@)
  • Ctrl-A suivi de Ctrl-X si vous êtes connecté avec picocom.
  • Ctrl-A suivi de :quit si vous êtes connecté avec screen.

Exécuter sur la pyboard un fichier stocké sous Linux

C'est la manière de procéder que nous vous recommandons pour programmer la pyboard.

Éditez votre programme (avec Eclipse, JEdit, gedit ou PyCharm par exemple), et enregistrez-le dans vos documents (vous pouvez créer un dossier correspondant à cette séance). Dans le terminal, utilisez la commande cd pour vous placer dans ce dossier. Par exemple cd Documents/BEPython. Utilisez ensuite la commande pyboard pour charger votre fichier sur la pyboard : pyboard be3.py.

Attention : vous ne pouvez pas utiliser pyterm et pyboard en même temps car les deux programmes utilisent la même connexion série à la pyboard. Vous devez donc sortir de pyterm avant d'utiliser pyboard, et attendre que pyboard termine, ou le tuer avec Ctrl-C, avant de pouvoir vous connecter avec pyterm.

Exemple

Tapez le code suivant dans un fichier leds.py :

import pyb

led = []
for i in range(4):
  led.append(pyb.LED(i+1))

def main():
  idx = 0
  while True:
	led[idx].on()
	pyb.delay(200)
	led[idx].off()
	idx = (idx + 1) % 4

main()

puis, dans le terminal, tapez pyboard leds.py

Vous devez voir les LEDs de la pyboard s'allumer successivement.

Tapez Ctrl-C pour arrêter le programme pyboard. Votre programme Python continue à s'exécuter sur la pyboard. Pour l'arrêter, connectez vous à la pyboard à l'aide de pyterm. Tapez Ctrl-C pour arrêter votre programme, puis Ctrl-B pour sortir du mode brut. Vous avez de nouveau la main. Vous pouvez notamment allumer et éteindre les LEDs an tapant par exemple led[0].on() pour allumer la LED rouge. Votre programme est toujours dans la mémoire de la pyboard, vous pouvez le relancer en appelant la fonction main().

Allumer et éteindre une LED à l'aide de deux boutons poussoirs Allumer/éteindre la LED

Dans cette partie, vous allez contrôler l'allumage d'une LED en fonction de l'appui sur deux boutons poussoirs, un vert (pour allumer la LED) et un rouge (pour l'éteindre).

Montage

Les composants sont montés sur une plaque d'expérimentation comme indiqué ci-dessous :

Les points importants à vérifier, que le montage soit déjà effectué ou que vous ayez à le réaliser vous-même sont :

  • pour que la LED fonctionne, le courant doit la traverser de l'anode vers la cathode. L'anode se repère grâce à sa patte plus longue. Si les pattes ont été coupées, vous pouvez repérer l'anode en regardant dans le bulbe de plastique : c'est l'électrode la plus petite des deux. Sur certaines LEDs, la cathode est repérée par un méplat sur le côté du bulbe ;
  • pour que la LED ne soit pas détruite par un courant trop important, il faut limiter le courant qui la traverse à l'aide d'une résistance de 220Ω ;
  • les boutons poussoirs connectent leurs pattes gauches et droites lorsqu'ils sont enfoncés, un méplat est situé sur le haut de ces boutons afin de repérer leur orientation ;
  • sur la plaque d'expérimentation, les trous alignés horizontalement entre les lignes bleues et rouges sont connectés ensemble. Dans la partie centrale, ce sont les trous alignés verticalement qui sont connectés ensemble. Les trous situés de part et d'autre de la rainure centrale ne sont pas connectés entre eux.

Nous utilisons la rangée de trous du haut, située le long de la ligne bleue et marquée du signe - pour la masse du montage, qui sera donc connectée à la patte GND de la pyboard. Connectez la patte libre (celle qui n'est pas connectée à la masse) du bouton vert sur la patte X1 de la pyboard, de même pour la patte libre du bouton rouge et la patte X2 de la pyboard. Connectez l'anode de la LED à la patte X3 de la pyboard, la cathode étant reliée à la masse à travers la résistance.

Le schéma correspondant à ce montage est donné ci-dessous :

Programmation

Dans votre programme Python, vous allez devoir accéder au niveau logique imposé par les boutons sur les pattes X1 et X2 de la pyboard, et vous aller devoir imposer un niveau logique haut ou bas pour allumer ou éteindre la LED. Pour cela, il vous faut accéder aux pattes X1, X2 et X3, et configurer les deux premières comme des entrées, et la troisième comme une sortie. Vous utiliserez pour cela la classe Pin du module pyb :

pyb.Pin(name)
crée un objet qui représente la patte de nom name. Par exemple, pour notre montage, le bouton vert est branché sur la patte X1, nous pourrions donc écrire : bouton_vert = pyb.Pin("X1")
pin.init(mode, pull)
initialise la patte pin dans le mode mode, qui peut être soit Pin.IN pour configurer la patte comme une entrée, soit Pin.OUT_PP pour la configurer comme une sortie push-pull (qui impose un niveau de tension), soit Pin.OUT_OD pour la configurer comme une sortie en collecteur ouvert (Open Drain). Dans ce dernier cas, aucune tension n'est imposée à l'état haut, seul l'état bas impose une tension proche de 0V. Le paramètre pull indique si les résistances reliant la patte au 0V ou au 5V doivent être connectées. Les valeurs possibles sont Pin.PULL_NONE, Pin.PULL_UP et Pin.PULL_DOWN pour connecter respectivement aucune des résistances, la résistance de pull up (qui tire la sortie vers le niveau haut par défaut), et la résistance de pull down (qui tire la sortie vers le niveau bas par défaut).
pin.high()
impose un niveau logique haut sur la patte pin
pin.low()
impose un niveau logique bas sur la patte pin
pin.value()
rend la valeur présente sur la patte pin
pin.value(val)
impose la valeur val sur la patte pin

Pour cette manipulation, on pourra utiliser le code suivant pour configurer les pattes X1, X2 et X3 :

import pyb

bouton_vert = pyb.Pin("X1")
bouton_rouge = pyb.Pin("X2")
led = pyb.Pin("X3")

bouton_vert.init(pyb.Pin.IN, pyb.Pin.PULL_UP)
bouton_rouge.init(pyb.Pin.IN, pyb.Pin.PULL_UP)
led.init(pyb.Pin.OUT_PP)

Les pattes X1 et X2 sont configurées avec une résistance de pull up afin de tirer la patte au niveau haut lorsque les boutons poussoirs ne sont pas enfoncés.

Première version par scrutation

Vous pouvez maintenant écrire un programme qui teste indéfiniment le niveau des pattes X1 et X2, et qui allume la LED lorsqu'on appuie sur le bouton vert, et l'éteint lorsqu'on appuie sur le bouton rouge.

Seconde version avec interruptions

Le programme précédent monopolise le processeur pour consulter l'état des pattes X1 et X2, alors que cet état change peu souvent. Il serait plus intéressant de pouvoir être prévenu lorsque l'état d'une patte change car on pourrait utiliser le processeur pour faire d'autres choses que scruter l'état des entrées. C'est ce que permettent de faire les interruptions.

Sous MicroPython, il est possible de configurer les pattes de la pyboard pour générer des interruptions. La classe ExtInt du module pyb permet de gérer jusqu'à 16 lignes d'interruption qui peuvent être déclenchées par le changement de niveau d'une patte :

pyb.ExtInt(pin, mode, pull, callback)
crée une ligne d'interruption attachée à la patte pin (qui est automatiquement configurée en tant qu'entrée). mode indique quel changement de niveau déclenchera une interruption : ExtInt.IRQ_FALLING sélectionne les fronts descendants, ExtInt.IRQ_RISING les fronts montants, et ExtInt.IRQ_RISING_FALLING déclenche une interruption à chaque changement de niveau. pull est la configuration des résistances de pull up et de pull down, déjà vue précédemment. callback est la fonction a appeler pour traiter l'interruption. Cette fonction doit prendre un argument, qui est le numéro de la ligne d'interruption qui a provoqué son appel. L'exemple suivant montre comment configurer la génération d'une interruption à chaque fois que l'on appuie sur le bouton vert, avec appel de la fonction interruption_vert :
irq_vert = pyb.ExtInt(bouton_vert, pyb.ExtInt.IRQ_FALLING,
                      pyb.Pin.PULL_UP, interruption_vert)

Vous pouvez maintenant écrire un programme qui installe deux lignes d'interruption, une pour le bouton vert et une pour le bouton rouge, chacune d'elle appelant une fonction qui allume ou éteint la LED.

Lorsque ce programme a fini de s'exécuter, il n'occupe plus le processeur, et en vous connectant à l'interpréteur avec pyterm, vous pourrez exécuter d'autres commandes, tout en constatant que la LED s'allume et s'éteint bien lorsque vous appuyez sur les boutons. Tout le traitement se fait sous interruptions. Comparez ce comportement à celui de la version par scrutation, dans laquelle le processeur était entièrement occupé par l'exécution de votre programme.

Faire clignoter une LED Faire clignoter la LED

En utilisant le même montage que pour l'exercice précédent, nous souhaitons maintenant que la LED clignote à une fréquence de 1Hz lorsqu'on appuie sur le bouton vert, et qu'elle soit allumée fixe lorsqu'on appuie sur le bouton rouge.

Première étape : faire clignoter la LED

Nous allons tout d'abord voir comment faire clignoter la LED. Une première solution consiste à allumer la LED, attendre 500ms grâce à la fonction pyb.delay(500), éteindre la LED et recommencer indéfiniment.

Coder cette solution et vérifier son bon fonctionnement.

Une solution plus efficace, qui évite de bloquer le processeur en attente de l'écoulement des 500ms consiste à utiliser une interruption générée par un timer.

La pyboard dispose de 14 timers, numérotés de 1 à 14. Le timer 3 est réservé à l'usage du système. Les timers 5 et 6 sont utilisés par certaines bibliothèques. Vous pouvez utiliser le timer 4 pour faire clignoter la LED.

pyb.Timer(num)
crée un objet correspondant au timer de numéro num.
timer.init(freq=f)
règle la fréquence du timer à f (en Hertz)
timer.callback(fonc)
indique que la fonction fonc doit être appelée à chaque expiration du timer. Cette fonction doit prendre un argument, qui est le numéro du timer qui l'appelle.
timer.deinit()
désactive le timer

Écrivez maintenant un programme qui installe une interruption générée par un timer pour faire clignoter la LED.

Deuxième étape : clignotement + boutons

Vous pouvez maintenant modifier le programme qui allume et éteint la LED grâce à des interruptions générées par les boutons poussoirs pour qu'il fasse clignoter la LED quand on appuie sur le bouton vert, et qu'il l'allume de manière fixe lorsqu'on appuie sur le bouton rouge.

Attention : il n'est pas possible d'allouer de la mémoire pendant le traitement d'une interruption. Il faudra donc créer le timer dans le programme principal, et l'activer ou le désactiver dans les fonctions qui traitent les interruptions générées par les boutons.

Pour aller plus loin Toujours plus…

Nous avons deux boutons et trois modes (éteint, allumé, clignotant). Il est possible d'utiliser le bouton rouge pour éteindre la LED, le bouton vert pour l'allumer, et un « double-clic » sur le bouton vert pour passer en mode clignotant. Pour cela, il faut être capable de savoir combien de temps s'est écoulé depuis le dernier appui sur le bouton vert : si ce temps est court (< 600ms), on considère que l'on a un double-clic, s'il est supérieur, on considère qu'il s'agit d'un appui simple. Les fonctions suivantes vous seront utiles :

pyb.millis()
donne le nombre de millisecondes écoulées depuis le démarrage de la pyboard.
pyb.elapsed_millis(start)
donne le nombre de millisecondes écoulées depuis start, en tenant compte du fait que le compteur de millisecondes a pu repasser à zéro si la pyboard fonctionne depuis longtemps. start est une date en millisecondes, obtenue par un appel précédent à pyb.millis().

Attention : pour modifier la valeur d'une variable globale, il est nécessaire de la déclarer à l'aide du mot-clef global au début de la fonction qui la modifie.

Votre programme ne se comporte sûrement pas de la manière que vous espériez. Le problème vient des boutons qui ont des rebonds : quand on appuie ou qu'on relâche un bouton, le contact oscille et provoque plusieurs fronts successifs. Une solution consiste à ignorer les interruptions qui se produisent de manière trop rapprochée. Vous pouvez utiliser la fonction print pour afficher la durée écoulée depuis la dernière interruption afin de calibrer le seuil à partir duquel vous prendrez de nouveau en compte les interruptions.