Développement rapide d'applications basées sur des microcontrôleurs en temps réel avec MicroPython

Par Jacob Beningo

Avec la contribution de Rédacteurs nord-américains de Digi-Key

Les systèmes embarqués en temps réel sont de plus en plus complexes et exigent non seulement une compréhension approfondie des microcontrôleurs 32 bits évolués, mais également des capteurs, des algorithmes, des protocoles Internet et des applications extrêmement diverses des utilisateurs finaux. Avec des cycles de développement plus courts et des fonctionnalités plus étendues, les équipes de développement doivent trouver des méthodes pour accélérer la conception tout en pouvant porter leur code sur de nouveaux produits : une plateforme de développement intégrée et flexible est nécessaire.

Plusieurs plateformes spécifiques aux microcontrôleurs sont disponibles pour faciliter l'accélération du processus de développement, mais le problème avec ces solutions réside dans le fait que les développeurs dépendent d'un seul fournisseur de microcontrôleurs. Le portage des logiciels d'une plateforme à une autre peut s'avérer lent et coûteux.

L'une des solutions récentes bénéficiant d'une adoption et d'une ampleur croissantes consiste à jumeler le matériel de microcontrôleur de bas niveau à un langage de programmation de haut niveau, comme Python. Parmi ces solutions figure MicroPython. Ce langage open-source s'exécute sur plusieurs composants de fournisseurs de microcontrôleurs différents, et les développeurs peuvent donc l'utiliser et le personnaliser facilement selon leurs propres besoins.

MicroPython.org décrit MicroPython comme une mise en œuvre simple et efficace du langage de programmation Python 3, qui inclut un sous-ensemble réduit de la bibliothèque Python standard, optimisé pour un fonctionnement sur des microcontrôleurs et en environnements limités. Initialement, MicroPython était un projet à démarrage rapide, non seulement doté d'un financement réussi, mais bénéficiant également d'un large public, et il est maintenant utilisé dans des projets couvrant de nombreux secteurs, comme les systèmes industriels et spatiaux.

Sélection du microcontrôleur adapté

MicroPython s'exécute sur plusieurs microcontrôleurs différents et aucune limite ne s'applique à son portage sur d'autres microcontrôleurs, tant que la mémoire RAM, la mémoire Flash et la puissance de traitement sont suffisantes pour exécuter l'interpréteur. Cela dit, le développeur doit prendre en compte plusieurs exigences essentielles lorsqu'il choisit un microcontrôleur pour exécuter MicroPython :

  • Mémoire Flash d'au moins 256 Ko
  • Mémoire RAM d'au moins 16 Ko
  • Horloge de processeur d'au moins 80 MHz

Il s'agit de recommandations générales, mais les développeurs peuvent s'en éloigner selon les besoins de leurs applications et le temps qu'ils souhaitent consacrer à la personnalisation du noyau MicroPython. Par exemple, MicroPython peut être modifié de sorte à utiliser moins de 256 Ko de mémoire Flash. Ces recommandations visent à offrir une expérience optimale au développeur et à fournir une possibilité d'extension dans le code d'application.

MicroPython a déjà été porté sur plusieurs séries différentes de microcontrôleurs qui peuvent être utilisées comme un excellent point de départ pour le portage sur une nouvelle plateforme ou pour la sélection d'un microcontrôleur déjà pris en charge. Le répertoire principal du code source de MicroPython est illustré dans la Figure 1. Le lecteur peut voir plusieurs dispositifs de microcontrôleurs différents pris en charge, notamment les suivants :

Image de l'exemple de structure d'un répertoire de dossier

Figure 1 : Exemple de structure d'un répertoire de dossier affichant les plateformes de microcontrôleurs disponibles prenant actuellement en charge MicroPython. ARM, CC3200, ESP8266, Microchip PIC et STM32 sont inclus. (Source de l'image : Beningo Embedded Group)

Chaque dossier répertorié dans le répertoire racine est un dossier détaillé comprenant le support et les pilotes généraux pour la gamme de puces. Plusieurs cartes de développement ou processeurs différents peuvent être pris en charge dans chaque dossier. Par exemple, le dossier stmhal prend en charge certaines cartes de développement, comme la carte STM32F429 Discovery et le kit de nœuds IoT STM32 Discovery (STM32L) de STMicroelectronics, ainsi que certaines cartes pyboard STM32F405 d'Adafruit Industries. Le dossier ESP8266 prend en charge la carte Breakout Huzzah d'Adafruit pour ESP8266 et la carte Feather Huzzah.

Les cartes de développement pouvant exécuter MicroPython sont économiques. Il est même recommandé au développeur d'en acheter plusieurs pour tester la mémoire, l'espace de stockage et la puissance de traitement nécessaires pour une application. Par exemple, un développeur peut commencer à utiliser la carte pyboard STM32F405, puis décider de la remplacer par la série STM2F429 dans son produit final, en prévision des fonctionnalités évolutives et des mises à niveau. Le STM32F429 est doté de 2 Mo de mémoire Flash, de 256 Ko de mémoire RAM et d'une mémoire RAM spéciale sans cycle d'attente, appelée CCM.

Le code d'application MicroPython écrit par un développeur ne doit pas nécessairement être stocké dans la mémoire Flash interne du microcontrôleur. Le noyau MicroPython doit se trouver sur le microcontrôleur, mais le code d'application peut se trouver sur un support de stockage externe, comme la carte microSD 8 Go de Panasonic. L'utilisation d'un dispositif de stockage mémoire externe pour stocker le code d'application permet d'utiliser un microcontrôleur avec une mémoire réduite et de potentiellement réduire le coût global du système.

Installation et démarrage de MicroPython

MicroPython est préinstallé sur la carte pyboard STM32F405 d'Adafruit. Cependant, pour les autres kits de développement ou matériels personnalisés, le développeur doit télécharger la source MicroPython, développer la source pour la carte cible et envoyer le logiciel au microcontrôleur. L'accès au code source MicroPython est simple, car l'intégralité du code est hébergée sur GitHub. Le développeur doit suivre plusieurs étapes pour paramétrer la chaîne d'outils et pour configurer l'environnement de développement MicroPython. Dans cet exemple, nous allons configurer MicroPython pour la carte STM32F429 Discovery.

Tout d'abord, le développeur doit créer une machine virtuelle basée sur Linux ou utiliser une installation Linux native. Lorsque le système Linux est disponible sur un terminal, le développeur doit installer la chaîne d'outils de compilateur ARM à l'aide de la commande suivante :

sudo apt-get install gcc-arm-none-eabi

 

Si l'installation Linux est récente, le système de contrôle de révision, git, peut ne pas être installé. Le système git peut être installé à partir du terminal à l'aide de la commande suivante :

 

sudo apt-get install git

 

Une fois git installé, la source MicroPython peut être extraite du référentiel en exécutant la commande suivante sur le terminal :

 

git clone https://github.com/micropython/micropython.git

Le processus peut prendre plusieurs minutes, mais le développeur devrait voir la séquence indiquée (Figure 2).

Image du clonage du référentiel MicroPython sur le système de fichiers local

Figure 2 : Clonage du référentiel MicroPython sur le système de fichiers local où un développeur peut ensuite configurer MicroPython pour sa carte cible ou personnaliser le noyau pour ses propres applications. (Source de l'image : Beningo Embedded Group)

Après le clonage de la source MicroPython sur le système de fichiers local, le développeur doit sélectionner ce répertoire, puis exécuter une commande "cd stmhal" sur le terminal. Le répertoire stmhal contient le fichier makefile de MicroPython pour les microcontrôleurs STM32. Le développeur peut également vérifier le dossier "boards" qui indique toutes les cartes STM32 actuellement prises en charge. Le développeur peut ensuite créer toute carte située dans le dossier "boards" à partir du terminal. Par exemple, le développeur peut créer une carte STM32F4 Discovery en tapant la commande suivante :

make BOARD=STM32F4DISC

MicroPython requiert plusieurs minutes pour la création. Durant la création, le développeur doit installer un outil de mise à niveau micrologicielle (DFU) pour la programmation de MicroPython via USB sur un microcontrôleur. L'installation de l'outil ne doit être effectuée qu'une fois et peut être réalisée en tapant la commande suivante sur le terminal :

sudo apt-get install dfu-util

Une fois le développement MicroPython terminé et l'outil dfu-util installé, le développeur peut alors charger MicroPython dans son microcontrôleur. Le développeur doit d'abord placer son microcontrôleur en mode de chargeur d'amorçage DFU. Cela peut se faire en paramétrant les broches d'amorçage afin de charger le chargeur d'amorçage interne lors de la réinitialisation, au lieu d'exécuter le code à partir de la Flash.

Lorsque le microcontrôleur est en mode de chargeur d'amorçage et est connecté à l'ordinateur hôte via USB, l'outil dfu-util peut être utilisé pour télécharger MicroPython à l'aide de la commande suivante :

dfu-util -a 0 -d 0483:df11 -D build-STM32F4DISC/firmware.dfu

L'outil dfu-util utilise le fichier dfu créé par le processus de compilation. Le processus prend plusieurs minutes, étant donné que le microcontrôleur est totalement effacé et reprogrammé. Le processus est très similaire au processus illustré ci-après (Figure 3). Dès que l'outil a terminé, les cavaliers d'amorçage doivent être ajustés pour chargement à partir de la Flash interne, puis le microcontrôleur peut être remis sous tension. MicroPython s'exécute désormais sur le microcontrôleur cible.

Image du chargement de MicroPython sur un microcontrôleur à l'aide de l'outil dfu-util

Figure 3 : Chargement de MicroPython sur un microcontrôleur à l'aide de l'outil dfu-util. (Source de l'image : Beningo Embedded Group)

Interfaçage des capteurs et des dispositifs connectés

Le principal avantage de l'utilisation d'un langage de programmation de haut niveau comme MicroPython pour développer un logiciel embarqué en temps réel est que le logiciel est indépendant du matériel sous-jacent. Cela signifie que le développeur peut développer un script MicroPython à exécuter sur une carte pyboard, et ce, avec le minimum de modifications. Il peut également exécuter le script sur une carte ESP8266 ou STM32F4 Discovery. Examinons à quoi ressemble un script MicroPython servant à interfacer un capteur barométrique et de température BMP280 de Bosch Sensortec à un bus I2C, puis à transmettre les données sur une liaison série Bluetooth à l'aide du module Bluetooth RN-42 de Microchip Technology.

Le BMP280 est un capteur barométrique et de température basé sur I2C, doté d'une adresse esclave I2C par défaut en décimal 119. La meilleure façon pour l'interfacer à la carte pyboard est d'utiliser la carte Gravity de DFRobot, qui fournit un connecteur robuste simplifiant l'accès à l'alimentation du dispositif et à I2C. Le développeur peut sélectionner le bus I2C1 ou I2C2 pour connecter la carte Gravity. Une fois les cartes connectées, le script de MicroPython est simple.

Le développeur doit d'abord importer la classe I2C de la bibliothèque pyb. La bibliothèque pyb fournit un accès aux fonctions périphériques du microcontrôleur, comme SPI, I2C et UART. Avant de pouvoir utiliser un périphérique, le développeur doit instancier la classe du périphérique pour créer un objet pouvant être utilisé pour commander le périphérique. Une fois la classe du périphérique initialisée, le développeur peut effectuer les autres initialisations, comme la vérification des dispositifs présents, avant d'entrer la boucle de l'application principale. Le code d'application primaire échantillonnera ensuite le capteur une fois par seconde. Un exemple d'exécution du processus est illustré ci-après (Liste de code 1).

Copy
from pyb import I2C
 
GlobalTemp = 0.0
GlobalBarometer = 0.0
 
# Initialize and Instantiate I2C peripheral 2
I2C2 = I2C(2,I2C.MASTER, baudrate=100000)
 
while True:
            SensorSample()
            pyb.delay(1000)
 
def SensorSample():
            #Read the Temperature Data
            TempSample = I2C2.readfrom_mem(119, 0xFA,3)
 
            #Read the Pressure Data
            PressureSample = I2C2.readfrom_mem(119, 0xF7,3)

Liste de code 1 : Script MicroPython pour initialiser le périphérique I2C et communiquer avec la carte Gravity de DFRobot pour acquérir les données du capteur barométrique et de température. (Source de code : Beningo Embedded Group)

Échantillonner les données de capteur sans les exploiter ne constitue pas vraiment une démonstration utile de la puissance que MicroPython peut offrir à une équipe de développement. De nombreuses équipes de développement rencontrent des défis techniques lorsqu'il s'agit de connecter les capteurs à Internet ou à un concentrateur local de capteurs via Bluetooth.

Un moyen simple d'ajouter une fonctionnalité Bluetooth à un projet est d'utiliser le RN-42. Le RN-42 peut être placé dans un mode dans lequel le microcontrôleur envoie simplement les données UART devant être transmises via Bluetooth, et le RN-42 gère l'ensemble de la pile Bluetooth (Figure 4).

Image de pyboard exécutant MicroPython avec connexion à un module Bluetooth RN-42 via UART

Figure 4 : Connexion de pyboard exécutant MicroPython à un module Bluetooth RN-42 via UART. (Source de l'image : Beningo Embedded Group)

Une fois la carte Bluetooth connectée, le développeur peut créer un script très simple qui collecte les données de capteur reçues et les transmet via Bluetooth à un dispositif mobile qui peut enregistrer les données ou les envoyer sur le cloud pour une analyse supplémentaire. Un exemple de script est illustré ci-après (Liste de code 2). Dans cet exemple, UART1 est configuré pour une transmission 115 200 bps, 8 bits, sans parité et un seul bit d'arrêt.

Copy
from pyb import uart
from pyb import I2C
 
GlobalTemp = 0.0
GlobalBarometer = 0.0
 
# Initialize and Instantiate I2C peripheral 2
I2C2 = I2C(2,I2C.MASTER, baudrate=100000)
 
# Configure Uart1 for communication
Uart1 = pyb.UART(1,115200)
Uart1.init(115200, bits=8, parity=None, stop=1)
 
while True:
            SampleSensor()
            pyb.delay(1000)
 
def SensorSample():
            #Read the Temperature Data
            TempSample = I2C2.readfrom_mem(119, 0xFA,3)
 
            #Read the Pressure Data
            PressureSample = I2C2.readfrom_mem(119, 0xF7,3)
 
            #Convert Sample data to string
            data = “#,temperature=”str(TempSample)+”,pressure”+str(PressureSample)+”,#,\n\r”
 
            #Write the data to Bluetooth
            Uart1.write(data)

Liste de code 2 : Exemple de script MicroPython pour initialiser UART1 et communiquer avec un dispositif externe. (Source de code : Beningo Embedded Group)

Non seulement le code d'application Python peut être facilement porté sur d'autres plateformes matérielles, mais l'application utilise également des bibliothèques et des fonctionnalités courantes qui ont déjà été mises en œuvre pour permettre aux développeurs d'accélérer leur développement. La création de l'application ci-dessus peut être effectuée en une heure voire moins, au lieu d'une semaine ou plus si le développeur avait dû commencer au niveau logiciel le plus bas puis en évoluant progressivement.

Conseils et astuces pour développer un logiciel en temps réel

Le développement d'applications embarquées à l'aide de MicroPython est aisé, mais l'obtention de performances en temps réel du système peut s'avérer plus compliqué qu'on ne le pense. Même si MicroPython offre des avantages considérables pour la simplification et la réutilisation du code, obtenir une temporisation prévisible et cohérente du système peut représenter un défi, si le développeur ne connaît pas certains faits et bibliothèques intéressants.

MicroPython inclut un récupérateur de mémoire qui s'exécute en arrière-plan et gère le tas et les autres ressources de mémoire. Le récupérateur de mémoire n'étant pas déterministe, les développeurs prévoyant un comportement déterministe risquent alors de rencontrer des problèmes, si l'exécution du récupérateur de mémoire commence dans une section soumise à des contraintes temporelles. Les développeurs doivent suivre quelques recommandations afin d'éviter cette situation.

Les développeurs peuvent importer la bibliothèque du récupérateur de mémoire (gc) et utiliser les méthodes d'activation et de désactivation pour contrôler l'activation ou la désactivation du récupérateur de mémoire. Le développeur peut désactiver le récupérateur de mémoire avant une section critique, puis l'activer par la suite, comme indiqué ci-après (Liste de code 3).

Copy
import gc
 
gc.disable()
 
#My time critical code
 
gc.enable()

Liste de code 3 : désactivation du récupérateur de mémoire MicroPython avant une section de code soumise à des contraintes temporelles. (Source de code : Beningo Embedded Group)

Le développeur peut également commander manuellement le processus de récupération de mémoire. Lorsqu'un développeur crée et supprime des objets, il alloue la mémoire à un tas. Le récupérateur de mémoire s'exécute et libère l'espace inutilisé. Comme ce processus est réalisé à intervalles irréguliers, les développeurs peuvent utiliser la méthode de récupération pour régulièrement exécuter la récupération de mémoire afin de garantir que l'espace du tas ne soit pas plein. Après avoir pris cette mesure, les récupérations de mémoire peuvent durer de 10 millisecondes à moins d'une milliseconde par exécution. Recourir manuellement à la récupération de mémoire garantit également le contrôle du code temporisé non déterministe par l'application du développeur. Cela lui permet de décider de l'heure d'exécution de la récupération de mémoire et garantit des performances en temps réel pour l'application.

Il existe également de nombreuses autres meilleures pratiques recommandées aux développeurs qui souhaitent écrire du code en temps réel, notamment :

  • Utiliser des tampons préalloués pour les canaux de communication
  • Utiliser la méthode readinto lors de l'utilisation de périphériques de communication
  • Éviter une documentation Python traditionnelle en utilisant ###
  • Limiter la création et la suppression d'objets pendant l'exécution
  • Surveiller les temps d'exécution de l'application

Les développeurs qui souhaitent en savoir plus sur les « meilleures pratiques » peuvent consulter la documentation d'optimisation MicroPython ici.

Conclusion

MicroPython est une plateforme intéressante pour les développeurs cherchant à implémenter des applications embarquées en temps réel qui sont indépendantes du matériel de microcontrôleur sous-jacent. Les développeurs peuvent écrire des scripts Python de haut niveau à l'aide des bibliothèques standard fournies dans MicroPython et les exécuter sur un microcontrôleur pris en charge. Cela offre de nombreux avantages aux développeurs, notamment :

  • Amélioration de la réutilisation de l'application
  • Commercialisation plus rapide
  • Découplage de l'application du matériel

Même si MicroPython n'est pas idéal pour toutes les applications, il connaît à ce jour une réussite dans les applications de systèmes industriels et spatiaux, et pour le prototypage rapide et les démonstrations de faisabilité.

Avertissement : les opinions, convictions et points de vue exprimés par les divers auteurs et/ou participants au forum sur ce site Web ne reflètent pas nécessairement ceux de Digi-Key Electronics ni les politiques officielles de la société.

À propos de l'auteur

Jacob Beningo

Jacob Beningo est un consultant en logiciels embarqués, et il travaille actuellement avec des clients dans plus d'une douzaine de pays pour transformer radicalement leurs activités en améliorant la qualité, les coûts et les délais de commercialisation des produits. Il a publié plus de 200 articles sur les techniques de développement de logiciels embarqués. Jacob Beningo est un conférencier et un formateur technique recherché, et il est titulaire de trois diplômes, dont un master en ingénierie de l'Université du Michigan. N'hésitez pas à le contacter à l'adresse jacob@beningo.com et sur son site Web www.beningo.com, et abonnez-vous à sa newsletter mensuelle Embedded Bytes.

À propos de l'éditeur

Rédacteurs nord-américains de Digi-Key