Table des matières
La pyboard est une carte équipée d'un processeur STM32F4 et conçue pour le développement sous Micropython.
Elle est très facile à utiliser grâce à son système de fichiers (elle se comporte comme une clef USB quand on la branche) et son interpréteur python (accessible sur un port série) qui permet de prototyper très rapidement.
Premier contact
Pour programmer la pyboard, il suffit de disposer d'un éditeur de texte.
Une fois la pyboard connectée à votre ordinateur, ouvrez le volume qui a dû apparaître (probablement sous le nom PYBFLASH
). Vous devriez y trouver :
- boot.py, qui contient le code exécuté au démarrage de la carte
- main.py, qui contient le code exécuté après le démarrage
- pybcdc.inf, driver de port série pour Windows
- README.txt
Pour exécuter du code sur la pyboard, il suffit de le placer dans le fichier main.py d'éjecter le volume et de redémarrer la carte en appuyant sur son bouton RST
.
Écrivez le code suivant dans le fichier main.py, enregistrez le fichier, éjectez le volume et faites redémarrer la pyboard : (:brush=python:) import pyb
leds = [pyb.LED(i+1) for i in range(4)] i = 0 while True :
for j in range(4) : if j == i : leds[j].on() else: leds[j].off() pyb.delay(100) i = (i+1)%4
(:endbrush:)
Vous devriez voir les 4 LEDs de la carte s’allumer successivement.
%
Dans un terminal
Il est assez fastidieux de devoir éjecter le volume et de redémarrer la carte à chaque modification du code. De plus, pour expérimenter, il est plus agréable de pouvoir interagir avec l'interpréteur python. On va pour cela utiliser le port série de la carte.
Voyez les instructions du site officiel (en anglais) pour référence. Je donne ci-dessous ma version en français.
Ceci fonctionne également avec une carte ESP32 sous Micropython.
Executer un fichier Python
Plutôt que de taper directement du code Python dans l'interpréteur de la carte, il est souvent plus pratique d'éditer son code dans un fichier et de le faire exécuter ensuite par l'interpréteur de la carte.
Il existe pour cela une commande pyboard
, mais il faut lui donner explicitement le périphérique sur lequel on souhaite exécuter le fichier. Par exemple pyboard --device /dev/cu.usbmodem3472354933332 mon_programme.py
De même que pour pyterm
(voir plus haut), j'utilise un script pyload
qui détecte les pyboards disponibles et utilise la première si aucune n'est précisée :
pyload est disponible sur ce dépôt Github
Ceci fonctionne également avec une carte ESP32 sous Micropython.
Une alternative, quand on programme une pyboard, est de copier le fichier sur la pyboard (elle apparaît comme un volume nommé PYBFLASH), puis de se connecter au REPL de la carte (avec pyterm par exemple) et de taper :
import moncode
si le fichier est moncode.py
.
rshell
Rshell est un environnement de développement en ligne de commande pour MicroPython. Une traduction en français de sa documentation est disponible sur le wiki de MC Hobby
De même que pour les commandes picocom
et pyboard
, j'utilise un script pyrshell
qui détecte les pyboards présentes et s'y connecte automatiquement.
pyrshell est disponible sur ce dépôt Github
Ceci fonctionne également avec une carte ESP32 sous Micropython. C'est d'ailleurs une manière simple d'accéder au système de fichier sur ESP32, car il n'apparaît pas comme un disque externe, contrairement à ce qui se passe avec la pyboard.
Pour installer Micropython sur une ESP32, consultez le tutoriel (en anglais).
Autres environnements
L'éditeur Mu Editor a un mode spécial pour MicroPython que vous pouvez essayer.
Quelques exemples
Je propose ici quelques exemple de code pour pyboard ou pour ESP32 sous MicroPython, qui ne font appel qu'au matériel disponible sur la carte. Vous n'aurez pas besoin d'accessoires, à part le cable USB pour connecter la carte à votre ordinateur.
Allumer et éteindre une LED LED
On utilise ici le bouton disponible sur la carte (USR
sur la pyboard, BOOT
sur l'ESP32) pour allumer la LED intégrée à la carte. La LED ne reste allumée que tant que l'on maintient le bouton enfoncé.
Cet exemple montre comment configurer les ports, lire et modifier leur état, sur la pyboard et l'ESP32 :
(:brush=python:) """ Allumer la LED intégrée lorsque le bouton poussoir est enfoncé.
Le code permettant de créer les pattes d'entrées-sorties, ainsi que leur nom, diffèrent sur la pyboard et sur l'ESP32, mais la structure du code est ma même.
Nous récupérons tout d'abord l'objet correspondant à la patte d'entrée à laquelle est connecté le bouton poussoir. Ce bouton connecte la patte à la masse quand il est pressé. Afin d'avoir un niveau logique haut quand le bouton est relâché, nous configurons la patte avec une résistance qui la tire par défaut au niveau haut (c'est le rôle de l'argument Pin.PULL_UP dans la création de l'objet Pin).
Ensuite, nous récupérons l'objet correspondant à la patte à laquelle est connectée la LED, et nous la configurons comme sortie. Sur la pyboard, nous utilisons Pin.OUT_PP, qui est l'équivalent de Pin.OUT sur l'ESP32. Il s'agit d'une sortie classique, qui impose son niveau électrique. Il existe également un mode collecteur ouvert (open drain), qui n'impose que le niveau bas et laisse flotter librement la sortie quand elle est à 1. The mode correspond à la constante Pin.OUT_OD sur la pyboard, et Pin.OPEN_DRAIN sur l'ESP32.
Le code principal est le même pour les deux cartes : une boucle infinie consulte l'état du bouton poussoir. Si la patte est à 0, c'est que le bouton est pressé, et on allume la LED. Sinon, c'est que le bouton est relâché (et la résistance tire l'entrée au niveau haut), donc on éteint la LED. """ import sys
def main() :
if sys.platform == 'pyboard' : """ Si nous sommes sur une pyboard, configurer les pattes pour le bouton USR et la LED bleue """ from pyb import Pin button = Pin('B3', Pin.IN, Pin.PULL_UP) led = pyb.Pin('B4', Pin.OUT_PP) elif sys.platform == 'esp32' : """ Si nous sommes sur une ESP32, configurer les pattes pour le bouton BOOT et la LED intégrée (qui est bleue sur le modèle que j'utilise) """ from machine import Pin button = Pin(0, Pin.IN, Pin.PULL_UP) led = Pin(2, Pin.OUT) # Remplacer 2 par 1 sur certaines cartes else: print('Carte Micropython inconnue') return while True : """ Si le bouton est enfoncé, on allumer la LED, sinon, on l'éteint """ if button.value() == 0 : led.on() else : led.off()
if __name__ == "__main__" :
""" Si le fichier est importé, ne rien exécuter. Si le fichier est exécuté, appeler la fonction main. """ main()
(:endbrush:)
Allumer et éteindre une LED, autre version LED 2
On utilise ici le bouton disponible sur la carte (USR
sur la pyboard, BOOT
sur l'ESP32) pour allumer ou éteindre la LED intégrée à la carte à chaque fois que l'on appuie sur le bouton.
(:brush=python:) """ Allumer ou éteindre la LED à chaque fois que le bouton intégré est pressé.
Le code permettant de créer les pattes d'entrées-sorties, ainsi que leur nom, diffèrent sur la pyboard et sur l'ESP32, mais la structure du code est ma même.
Nous récupérons tout d'abord l'objet correspondant à la patte d'entrée à laquelle est connecté le bouton poussoir. Ce bouton connecte la patte à la masse quand il est pressé. Afin d'avoir un niveau logique haut quand le bouton est relâché, nous configurons la patte avec une résistance qui la tire par défaut au niveau haut (c'est le rôle de l'argument Pin.PULL_UP dans la création de l'objet Pin).
Ensuite, nous récupérons l'objet correspondant à la patte à laquelle est connectée la LED, et nous la configurons comme sortie. Sur la pyboard, nous utilisons Pin.OUT_PP, qui est l'équivalent de Pin.OUT sur l'ESP32. Il s'agit d'une sortie classique, qui impose son niveau électrique. Il existe également un mode collecteur ouvert (open drain), qui n'impose que le niveau bas et laisse flotter librement la sortie quand elle est à 1. The mode correspond à la constante Pin.OUT_OD sur la pyboard, et Pin.OPEN_DRAIN sur l'ESP32.
Le code principal est le même pour les deux cartes : une boucle infinie consulte l'état du bouton poussoir. Si la patte est à 0, c'est que le bouton est pressé, et on inverse l'état de la LED, puis on attend que le bouton soit relâché en bouclant tant qu'il reste enfoncé. Sans cette attente, nous ne pourrions pas faire la différence entre un nouvel appui sur le bouton et le fait qu'il reste enfoncé. """ import sys
def main() :
if sys.platform == 'pyboard' : """ Si nous sommes sur une pyboard, configurer les pattes pour le bouton USR et la LED bleue """ from pyb import Pin button = Pin('B3', Pin.IN, Pin.PULL_UP) led = pyb.Pin('B4', Pin.OUT_PP) elif sys.platform == 'esp32' : """ Si nous sommes sur une ESP32, configurer les pattes pour le bouton BOOT et la LED intégrée (qui est bleue sur le modèle que j'utilise) """ from machine import Pin button = Pin(0, Pin.IN, Pin.PULL_UP) led = Pin(2, Pin.OUT) else: print('Carte Micropython inconnue') return while True : """ Si le bouton est pressé, inverser l'état de la LED """ if button.value() == 0 : led.value(not led.value()) """ Attendre que le bouton soit relâché """ while button.value() == 0 : pass
if __name__ == "__main__" :
""" Si le fichier est importé, ne rien exécuter. Si le fichier est exécuté, appeler la fonction main. """ main()
(:endbrush:)
Faire clignoter une LED LED clignotante
On fait ici simplement clignoter la LED intégrée à la carte. Cet exemple montre comment attendre l'écoulement d'une durée. (:brush=python:) """ Faire clignoter la LED intégrée à l'aide d'une boucle infinie.
L'initialisation de la patte de sortie qui pilote la LED diffère sur la pyboard et sur l'ESP32, comme dans les exemples précédents. La fonction à appeler pour attendre un nombre donné de millisecondes n'est pas la même sur les deux cartes, on définit donc une fonction wait_ms qui est codée de manière adéquate selon la carte utilisée. """ import sys
def main() :
if sys.platform == 'pyboard' : from pyb import Pin, delay led = pyb.Pin('B4', Pin.OUT_PP) def wait_ms(milliseconds) : delay(milliseconds) elif sys.platform == 'esp32' : from machine import Pin from time import sleep_ms led = Pin(2, Pin.OUT) def wait_ms(milliseconds) : sleep_ms(milliseconds) else: print('Carte Micropython inconnue') return """ Cette boucle infinie allume la LED, attend 500ms, éteint la LED, attend 500ms et recommence. """ while True : led.on() wait_ms(500) led.off() wait_ms(500)
if __name__ == "__main__" :
""" N'exécuter le code de la fonction que si le fichier n'est pas simplement importé. """ main()
(:endbrush:)
Faire clignoter une LED avec un timer LED et timer
On fait ici clignoter la LED intégrée à la carte à l'aide d'un timer. Cet exemple montre comment configurer un timer pour exécuter une tâche périodiquement. (:brush=python:) """ Utiliser un timer pour faire clignoter la LED intégrée.
La seule partie commune aux versions pour pyboard et pour ESP32 est la fonction appelée lorsque le timer arrive à échéance. Cette fonction inverse l'état de la LED à l'aide de l'opérateur "not".
Les versions spécifiques à chaque carte sont toutefois peu différentes. On retrouve les classiques différences de nom pour la patte reliée à la LED et la création de l'objet Pin. La création du timer est également différente :
- sur la pyboard, il faut choisir un timer matériel (ici, le numéro 4), et la périodicité est donnée par la fréquence du timer en Hertz. - sur l'ESP32, on peut utiliser un timer "virtuel", de numéro -1, et la périodicité est donnée par la période du timer en millisecondes.
""" import sys
def main() :
""" Cette fonction sera appelée à chaque fois que le timer arrive à échéance, il est important de déclarer le paramètre qui lui sera passé lors de l'appel, même si on ne s'en sert pas. """ def timer_callback(timer) : led.value(not led.value()) if sys.platform == 'pyboard' : """ Sur la pyboard, on utilise la patte B4 pour accéder à la LED bleue, et on configure le timer numéro 4 à 2Hz pour faire clignoter la LED. """ from pyb import Pin, Timer led = pyb.Pin('B4', Pin.OUT_PP) timer = Timer(4, freq=2, callback=timer_callback) elif sys.platform == 'esp32' : """ Sur l'ESP32, on utilise la patte 2 pour accéder à la LED intégrée, et on alloue un timer virtuel de période 500ms pour faire clignoter la LED. Afin que le timer soit réarmé à chaque échéance, on le configure en mode PERIODIC. """ from machine import Pin, Timer led = Pin(2, Pin.OUT) timer = Timer(-1) timer.init(period=500, mode=Timer.PERIODIC, callback=timer_callback) else: print('Carte Micropython inconnue') return
if __name__ == "__main__" :
main() if sys.platform == "esp32" : """ Sur l'ESP32, la carte est redémarrée à la fin du script Python, il faut donc garder l'interpréteur actif pour que le timer ne soit pas réinitialisé immédiatement. """ while True : pass
(:endbrush:)
Choisir la luminosité d'une LED avec la PWM LED et PWM
La modulation de largeur d'impulsion (Pulse Width Modulation en anglais) est une technique qui permet de faire varier la tension moyenne d'un signal binaire. Elle évite de recourir à un convertisseur numérique-analogique pour faire varier la puissance transmise à une LED ou à un moteur à courant continu.
Le principe est de faire varier le rapport cyclique (rapport du temps passé au niveau haut sur la période du signal) d'un signal de fréquence suffisamment élevée pour être imperceptible. Par exemple, à 5kHz, le clignotement d'une LED alimentée en PWM n'est pas perçu par l'œil. En faisant varier le rapport cyclique de 0% à 100%, on passe d'une LED éteinte à une LED allumée à sa luminosité maximale. Dans la réalité, la LED est allumée et éteinte 5000 fois par seconde, mais on ne perçoit que la luminosité moyenne, qui est proportionnelle au rapport cyclique. Il est d'ailleurs plus facile de choisir la luminosité de la LED en PWM qu'en réglant sa tension d'alimentation avec un convertisseur numérique-analogique, car la réponse de la luminosité en fonction de la tension n'est pas linéaire.
On fait ici varier périodiquement la luminosité de la LED intégrée en modifiant progressivement le rapport cyclique du signal PWM. (:brush=python:) """ Faire varier la luminosité d'une LED grâce à la PWM. """ import sys
def main() :
if sys.platform == 'pyboard' : from pyb import Pin, Timer, delay """ Sur la pyboard, l'attente se fait avec la fonction delay du module pyb, et le réglage de rapport cyclique se donne directement en pourcentage """ def wait_ms(milliseconds) : delay(milliseconds) def set_pwm_pct_duty(pwm, pct) : pwm.pulse_width_percent(pct) """ Sur la pyboard, on retrouve la LED bleue sur la patte B4, et il faut choisir un timer qui a un canal PWM connecté à cette patte pour pouvoir la piloter. Ici, il s'agit du canal 1 du timer 4. """ led = pyb.Pin('B4', Pin.OUT_PP) timer = Timer(3, freq=5000) pwm = timer.channel(1, mode=Timer.PWM, pin=led) elif sys.platform == 'esp32' : from machine import Pin, PWM from time import sleep_ms """ Sur l'ESP32, l'attente se fait avec la fonction sleep_ms du module machine, et le réglage de rapport cyclique se donne en 1/1024 (résolution sur 10 bits) """ def wait_ms(milliseconds) : sleep_ms(milliseconds) def set_pwm_pct_duty(pwm, pct) : pwm.duty((1024*pct)//100) """ Sur l'ESP32, on retrouve la LED intégrée sur la patte 2, et la PWM est gérée par un objet PWM qu'il suffit de configurer pour une patte et une fréquence données. """ led = Pin(2, Pin.OUT) pwm = PWM(led, freq=5000) else: print('Carte Micropython inconnue') return """ Notre programme part d'une intensité (pourcentage de la période où le signal est haut) nulle et l'augmente d'une unité toutes les 10 millisecondes. Si on dépasse 100%, le pas de changement passe à -1 et l'intensité diminue toutes les 10ms. Lorsqu'on arrive à 0, le pas est remis à 1 et l'intensité augmente de nouveau. """ intensity = 0 step = 1 while True : set_pwm_pct_duty(pwm, intensity) wait_ms(10) if intensity >= 100 : step = -1 if intensity <= 0 : step = 1 intensity += step
if __name__ == "__main__" :
main()
(:endbrush:)
Allumer et éteindre la LED intégrée avec des interruptions Interruptions
Nous avons vu plus haut comment allumer et éteindre la LED intégrée à chaque fois qu'on appuie sur le bouton poussoir de la carte. Cette solution monopolise le processeur qui passe son temps à scruter l'état du bouton poussoir pour détecter les appuis. Elle peut être déléguée au matériel, qui peut nous prévenir automatiquement lorsque le signal d'une patte d'entrée passe de 1 à 0 (ou de 0 à 1, il suffit de choisir ce qui nous intéresse). Le processeur est alors déchargé de ce travail de scrutation et peut faire des choses plus utiles.
Le problème est de savoir comment le processeur est prévenu lorsque le matériel détecte un changement de valeur du signal sur une patte. C'est le mécanisme de traitement des interruptions qui va permettre au matériel de signaler un changement. Quand un changement de valeur pour lequel on a demandé à être prévenu se produit, le traitement que fait le processeur est interrompu, et il va exécuter le code d'une fonction que l'on aura indiquée.
Il y a des limites à ce que l'on peut faire dans cette fonction, et on ne peut notamment pas créer de nouveaux objets car il est possible que nous ayons interrompu le processeur alors qu'il était justement en train de créer un objet, et que la mémoire soit dans un état transitoire où le marquage des espaces libres et des espaces occupés n'est pas à jour. Il est malheureusement assez facile de créer des objets sans s'en rendre compte en Python, par exemple en faisant une division qui crée un flottant, on en ajoutant un élement à une liste ou un dictionnaire qui se redimensionne.
On se limitera donc à du code court qui fait peu de calculs et qui se contente de mettre à jour des variables globales. (:brush=python:) import sys
def main() :
""" Cette fonction sera appelée à chaque fois que le bouton sera enfoncé. Elle ne fait qu'inverser l'état de la LED. Attention : il est très important de déclarer le paramètre qu'elle reçoit, même si on ne l'utilise pas. """ def irq_handler(irq_line) : led.value(not led.value()) if sys.platform == 'pyboard' : from pyb import Pin, ExtInt """ Sur la pyboard, le bouton USR est connecté à la patte B3, que l'on configure en entrée avec une résistance de pull up. La LED bleue est connectée à la patte B4, que l'on configure en sortie. L'interruption se configure grâce à un objet ExtInt que l'on crée pour appeler la fonction irq_handler à chaque fois que la patte associée au bouton passe de 1 à 0 (IRQ_FALLING). """ button = Pin('B3', Pin.IN, Pin.PULL_UP) led = pyb.Pin('B4', Pin.OUT_PP) irq = ExtInt(button, ExtInt.IRQ_FALLING, Pin.PULL_UP, irq_handler) elif sys.platform == 'esp32' : from machine import Pin """ Sur l'ESP32, le bouton BOOT est connecté à la patte 0, que l'on configure en entrée avec une résistance de pull up. La LED intégrée est connectée à la patte 2, que l'on configure en sortie. L'interruption se configure grâce à la méthode irq de la classe Pin, que l'on utilise pour appeler la fonction irq_handler à chaque fois que la patte associée au bouton passe de 1 à 0 (IRQ_FALLING). """ button = Pin(0, Pin.IN, Pin.PULL_UP) led = Pin(2, Pin.OUT) irq = button.irq(trigger=Pin.IRQ_FALLING, handler=irq_handler) else: print('Carte Micropython inconnue') return
if __name__ == "__main__" :
main() if sys.platform == "esp32" : """ Sur l'ESP32, la carte est redémarrée à la fin du script Python, il faut donc garder l'interpréteur actif pour que la gestion des interruptions que nous avons mise en place ne soit pas réinitialisée immédiatement. """ while True : pass
(:endbrush:)