Passer à un RTOS sur le RP2040
(blog.brixit.nl)- Un contrôleur vidéo basé sur Raspberry Pi Pico s'est mis à exiger beaucoup de travaux concurrents, au point que la structure logicielle ne tenait plus avec le seul pico-sdk et une séparation en double cœur
- Le contrôleur doit gérer à la fois RS-485 VISCA, Ethernet, 9 boutons RGB, un joystick et un écran, avec en plus DHCP, mDNS et le traitement du protocole ATEM sur UDP
- FreeRTOS a permis d'implémenter une partie du système grâce à son ordonnanceur et à la communication entre tâches, mais les blocages de
printfet l'absence d'abstraction matérielle ont compliqué le débogage et la réutilisation du code - Apache NuttX apportait un shell, un système de fichiers, une abstraction des périphériques comme
/dev/i2c0et une configuration via Kconfig, mais les problèmes I2C ont ensuite été attribués à une erreur de configuration, ce qui peut invalider une bonne partie de l'évaluation faite à l'époque - Zephyr s'est heurté à un dépôt de 5 Go, à son SDK, aux définitions de cartes et à la complexité de la build, ce qui a finalement fait pencher la balance vers la poursuite des essais avec FreeRTOS, plus simple à intégrer dans l'environnement existant
Pourquoi un RTOS est devenu nécessaire
- Plusieurs petits projets sur microcontrôleur sont en cours de développement autour de la carte Raspberry Pi Pico
- Pico facilite l'intégration dans un IDE grâce à un SDK correct, un matériel peu coûteux et la prise en charge du débogage gdb/openocd
- Le projet actuel est un contrôleur matériel destiné à piloter plusieurs équipements vidéo
- Il contrôle deux caméras PTZ motorisées, une caméra fixe et l'équipement de commutation vidéo connecté
- Le contrôleur PTZ existant est un panneau sans marque qui ressemble au Marshall VS-PTC-200
- Il coûtait 650 € il y a quelques années, mais le ressenti des boutons et la qualité du joystick analogique sont médiocres
- Beaucoup de boutons ne fonctionnent pas avec les caméras actuellement utilisées et le produit semble optimisé pour des caméras de surveillance
- La liaison avec les caméras se fait par bus RS-485
- Le contrôle du mélangeur vidéo ATEM se fait actuellement uniquement via un panneau logiciel sur ordinateur
- Les panneaux matériels de Blackmagic Design sont très chers
Configuration matérielle du contrôleur
- La conception minimale retenue comprend 9 boutons, un joystick et un écran pour l'interface utilisateur
- Après plusieurs itérations matérielles sur un an, le PCB a été finalisé
- 9 boutons RGB
- le joystick à 10 $ déjà présent sur le panneau cloné de Marshall
- un TP8485E pour communiquer avec les caméras PTZ en RS-485
- un module Wiznet W5500 pour communiquer avec le mélangeur vidéo en Ethernet
- Une fois les corrections de carte terminées, tous les composants matériels fonctionnaient, mais la partie la plus difficile restait le logiciel
Un logiciel devenu trop lourd pour le seul pico-sdk
- Le projet a commencé comme les autres projets RP2040 existants, en important le pico-sdk dans un projet cmake
- Pour obtenir une version fonctionnelle, le deuxième cœur du Pico a été affecté au traitement du module Wiznet, tandis que le premier gérait les E/S de l'interface utilisateur
- Il a été possible d'aller jusqu'au clignotement d'une LED et à l'implémentation d'un client DHCP exécuté sur le deuxième cœur
- Le reste du système est vite devenu bien plus complexe
- Le nombre de tâches à exécuter en parallèle a rapidement augmenté
- dessiner une interface utilisateur relativement fluide sur l'écran
- envoyer des commandes VISCA via l'interface RS-485
- réagir aux entrées des boutons
- maintenir la pile réseau avec plusieurs connexions
- La partie réseau impose aussi des traitements en arrière-plan dédiés
- Une prise en charge DHCP conforme au standard doit suivre les temps d'expiration et échanger occasionnellement avec le serveur DHCP pour maintenir le bail
- mDNS est nécessaire pour découvrir automatiquement l'adresse IP du mélangeur vidéo ATEM, et il serait aussi utile d'annoncer la présence du panneau de contrôle
- Le protocole ATEM est simple, mais il arrive de recevoir des données dépassant la taille des tampons du module Wiznet, et si l'envoi des datagrammes UDP s'interrompt, le délai avant déconnexion est très court
- Dans ces conditions, il semblait plus pertinent de répartir le travail avec un RTOS que d'empiler encore des boucles maison
FreeRTOS : simple, mais avec peu d'abstraction
- FreeRTOS est techniquement inclus dans le pico-sdk, mais les tutoriels consultés utilisent le téléchargement d'une nouvelle copie, ce qui a été suivi tel quel
- C'était le RTOS qui semblait le plus simple parmi ceux étudiés, fournissant surtout un ordonnanceur et de la communication entre tâches
- Les tâches sont créées avec
xTaskCreate, puis l'ordonnanceur est lancé avecvTaskStartScheduler - Il est possible d'utiliser des IPC via des files, par exemple pour transmettre l'état des boutons à une tâche LED
- Les tâches sont créées avec
- Après quelques jours d'utilisation, la base de code était déjà découpée en plusieurs tâches alors même que les fonctionnalités réelles restaient limitées
buttonsTask: interroge un expandeur GPIO en I2C pour lire les boutons et place des messages dans une file de boutonsledTask: règle la couleur RGB d'un bouton donné à partir des messages deledQueuemainTask: exécute la boucle principale qui met à jour l'état du projet selon les entrées des boutonsnetworkTask: communique avec le module WiznetdhcpTask: créée parnetworkTasklors de la connexion du câble réseaumdnsTask: créée pardhcpTaskaprès l'obtention d'une adresse IPatemTask: créée quand mDNS reçoit une réponse d'un équipement ATEMviscaTask: ne fait encore rien, mais devra envoyer des données sur le port RS-485
- Le nombre de tâches est vite devenu élevé alors que le matériel ne faisait encore rien d'autre qu'apparaître sur le réseau
- Le problème le plus gênant est que
printfprovoque un blocage à chaque fois- Le débogueur gdb fonctionne, mais il n'est pas adapté à une vérification basée sur l'affichage du trafic DHCP
- FreeRTOS ne fournit pas d'abstraction matérielle, ce qui rend difficile la réutilisation du code écrit pour communiquer avec différents circuits
- Un nouveau projet FreeRTOS propre a ensuite été recréé pour y migrer les fonctionnalités, mais l'inconfort d'un débogage à l'aveugle sans sortie série a conduit à examiner d'autres options
Apache NuttX : une structure de type Unix et des erreurs de configuration
- Apache NuttX semblait plus proche d'un système d'exploitation généraliste, en traitant le microcontrôleur comme un système Unix
- Les tutoriels expliquent qu'il faut importer le pico-sdk et définir des variables d'environnement
- Le SDK était déjà présent dans
/usr/shareet les variables existaient déjà, mais la build a échoué sur un problème de droits lorsque NuttX a essayé d'écraser le fichierversion.hdu pico-sdk
- Le SDK était déjà présent dans
- Après la compilation d'un firmware NuttX minimal et une connexion au port série, un vrai shell est apparu
- Des commandes comme
uptime,unameetuname -afonctionnent - La version affichée est
NuttX 12.5.1et la cible estarm raspberrypi-pico
- Des commandes comme
- En suivant l'approche Unix, il semblait possible d'écrire des applications puis de les lancer automatiquement au démarrage
- Il y a un système de fichiers et le matériel est exposé via des abstractions comme
/dev/i2c0ou/dev/adc0
- Il y a un système de fichiers et le matériel est exposé via des abstractions comme
- L'aspect le plus séduisant était la configuration basée sur menuconfig/Kconfig
- C'est une approche familière du développement Linux
- Il existe un véritable système de pilotes matériels, et le pilote du circuit expandeur GPIO utilisé pour les boutons est déjà disponible
- Le réglage du multiplexage des broches RP2040 peut aussi se faire dans menuconfig, ce qui évite de maintenir des constantes de numéros de broches séparées ou d'écrire beaucoup de code d'initialisation du bus I2C
- Des utilitaires de test I2C peuvent également être intégrés au firmware
- Au départ, il semblait que l'I2C de base ne fonctionnait pas
- Une mise à jour ultérieure a indiqué qu'en réalité cela fonctionnait, et que le bus I2C avait été cassé par une erreur de configuration
- Il est explicitement précisé que le reste de l'évaluation de cette section sur NuttX est probablement en grande partie invalide
- À l'époque, il n'avait pas été possible de comprendre comment indiquer à NuttX que les boutons GPIO étaient derrière un expandeur GPIO, ni comment relier cet expandeur au bus I2C
- Après un échec de
configure.sh, le dépôt s'est retrouvé dans un état incohérent, au point qu'il a fallu recloner le dépôt NuttX à plusieurs reprisesdistcleana lui aussi parfois cessé de fonctionner correctement pour la même raison
- L'approche de type Unix semblait séduisante au début, mais il n'y a pas envie de manipuler de faux chemins de système de fichiers sur un microcontrôleur
- Un shell n'est pas nécessaire dans un système de production : il suffit que le code s'exécute
Zephyr : un SDK volumineux et l'obstacle des définitions de cartes
- Le choix suivant s'est porté sur Zephyr, qui fournit des utilitaires Python pour configurer le projet
- Dès la première étape, environ 5 Go de dépôts Git ont été récupérés
- Cela inclut des bibliothèques HAL pour de nombreuses puces
- Cela affecte aussi la configuration cmake globale de l'utilisateur
- Les tutoriels exigent l'installation du SDK Zephyr
- Le SDK Zephyr comprend les toolchains, compilateurs, assembleurs, éditeurs de liens et outils de build pour chaque architecture prise en charge
- Il inclut aussi des outils hôte comme QEMU et OpenOCD
- Comme plusieurs toolchains ARM sont déjà installées, il n'y avait pas envie de compiler ou de télécharger des compilateurs pour toutes les architectures
- Des options de cross-compilation ont donc été configurées pour construire sans le SDK Zephyr
ZEPHYR_TOOLCHAIN_VARIANT=cross-compileCROSS_COMPILE=/usr/bin/arm-none-eabi-west build -p always -b sparkfun_pro_micro_rp2040 samples/basic/blinky
- Le Raspberry Pi Pico n'est en réalité pas pris en charge, seules d'autres cartes utilisant le même SoC le sont
- Le travail a donc continué en considérant qu'une carte avec le même SoC était, en pratique, assez proche
- Un problème est apparu immédiatement : la démo
blinkyne compilait pas- La démo exige une définition
led0pour la LED à faire clignoter - Or la Sparkfun Pro Micro RP2040 n'a pas une simple LED GPIO, mais une LED adressable WS2812B
- La démo exige une définition
- La suite s'est faite en suivant le manuel des cartes personnalisées et en copiant la définition d'une autre carte, mais même après correction des erreurs et avertissements de build, il n'a pas été possible de compiler avec succès pour la carte cible
Finalement, retour à FreeRTOS
- Parmi les trois RTOS, FreeRTOS est le seul avec lequel une partie réelle de l'application a pu être compilée
- Le problème de
printfdevra probablement être résolu en le remplaçant par une autre implémentation, comme le suggèrent diverses ressources en ligne, puis en appelant une autre fonction aux endroits nécessaires - L'idée est désormais de continuer avec FreeRTOS
- C'était le seul choix pouvant s'intégrer relativement simplement dans l'environnement existant, plutôt que d'imposer d'adapter l'environnement de développement au RTOS
1 commentaires
Avis de Hacker News
L’auteur semble considérer un RTOS comme équivalent à l’environnement Arduino, ou comme quelque chose qu’on peut bricoler vite fait en s’attendant à ce que ça marche. La plupart ne le sont pas.
De nos jours, beaucoup d’Arduino intègrent mbed ou FreeRTOS en interne, avec aussi des moyens de les exposer ; cela aurait peut-être mieux convenu au style de l’auteur.
Zephyr est facile à utiliser et bien pris en charge par CLion, mais on ne peut pas s’attendre à ce que tout fonctionne sans installer la toolchain. Le Pi Pico est lui aussi clairement pris en charge, et je n’ai pas eu de problème en l’utilisant moi-même.
Pour résumer simplement, FreeRTOS est pris en charge presque partout, mais les pilotes sont généralement propres au SoC ou au périphérique, donc pénibles, et l’API n’est pas très accueillante, même si on peut s’y habituer. Pour utiliser Bluetooth, il faut trouver soi-même une stack.
Zephyr fournit une véritable abstraction matérielle et prend en charge la plupart des SoC, mais il peut falloir faire un peu de travail côté carte. La stack Bluetooth est fournie, et il peut être nécessaire d’ajouter un peu de prise en charge HCI.
NuttX n’a pas un support extraordinaire, mais si vous arrivez à le faire fonctionner, c’est une option assez élégante. Le soutien de l’industrie n’est pas encore très fort. Il y a aussi mbed, mais je le laisse de côté ici.
Dans le monde réel des RTOS, on choisit généralement ce que le fournisseur du SoC prend en charge. Si c’est Nordic, Zephyr ; si c’est NXP, FreeRTOS, etc., parce que c’est ainsi qu’on obtient le meilleur support.
Je n’ai jamais vu un projet de firmware pour appareil OEM où les développeurs ne se soient pas débattus avec Zephyr. Je n’ai pas encore rencontré non plus de développeur travaillant sur le firmware réel d’un produit commercialisé qui estime que l’abstraction matérielle de Zephyr l’aide.
Cela ne veut pas dire que ces personnes n’existent pas, mais je n’en ai pas croisé au cours des cinq dernières années environ.
À mes yeux, Zephyr a des supports marketing très jolis. Mais derrière ce vernis, il y a une lourdeur excessive, des compilations très lentes et un environnement difficile à prendre en main.
Installer la toolchain à l’échelle du système, à la manière UNIX traditionnelle, est pénible et, franchement, pas particulièrement malin.
C’est acceptable quand on est seul et que tout fonctionne, mais quand plusieurs développeurs travaillent sur plusieurs projets avec des cibles différentes, on passe beaucoup de temps à diagnostiquer des problèmes de build et de configuration.
Continuer à utiliser Python pour les outils n’aide pas non plus. Je ne comprends pas pourquoi on tient tant à utiliser un langage qui apporte ses propres problèmes de versions et se comporte différemment d’une machine de développeur à l’autre.
Je fais du développement embarqué comme loisir et comme métier depuis une dizaine d’années, et je ne comprends pas l’attitude qui consiste à passer une semaine à aligner l’environnement de tout le monde sur un projet sans y voir un problème.
C’est un vrai problème, c’est agaçant, cela fait perdre du temps et c’est inutile.
Les outils devraient être des binaires liés statiquement. Peu importe qu’ils soient écrits en Rust, Go, C ou C++, mais j’aimerais qu’on privilégie des outils robustes, dont on peut être sûr qu’ils fonctionneront de la même façon indépendamment de ce qui est installé sur l’ordinateur, plutôt que du bricolage temporaire.
Python ne permet pas cela, et se mettre en colère ou sur la défensive au lieu de prendre ce problème plus au sérieux n’aide pas.
Cela dit, des outils comme PlatformIO vont dans la bonne direction. Je sais que c’est un projet Python et que cela pose parfois problème, mais moins que d’autres outils, et l’idée est la bonne.
Les toolchains, SDK et bibliothèques doivent être gérés, la configuration des projets doit rester simple, et les builds doivent être reproductibles partout et à tout moment.
J’aimerais que l’industrie de l’embarqué prenne davantage conscience de la valeur d’un effort commun de structuration. Je connais beaucoup de gens qui travaillent chez de grands fabricants de MCU, et je suis toujours déçu de les voir, dans l’ensemble, si absorbés par la résolution de leurs problèmes immédiats qu’ils semblent avoir une vision étroite des besoins des développeurs.
J’ai un projet de clavier qui tourne sur RP2040, avec un firmware écrit en Rust. En partant uniquement du dépôt, sans toolchain Rust installée, la procédure pour flasher est la suivante :
(installer rustup)
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh$ rustup target add thumbv6m-none-eabi$ cargo install elf2uf2-rs* basculer le clavier en mode bootloader *$ cargo run --releaseIci, rustup et Cargo prennent en charge l’essentiel de la gestion de la toolchain et du build, et ils sont vraiment excellents. Les projets Python n’arrivent pas à leur cheville.
Il suffit d’exécuter une seule commande pour que tout soit configuré et fonctionne exactement de la même façon, en local comme en CI, avec les mêmes versions des outils de build et des bibliothèques.
Du coup, chaque fois que je dois revenir à CMake et Make et me battre pour installer des bibliothèques, c’est douloureux. C’est tellement différent d’une approche du type
compile 'library-name-here'.Ils le choisissent plutôt pour son écosystème de bibliothèques. Pour l’analyse de données, la visualisation et le calcul scientifique, il n’y a pas d’alternative comparable dans d’autres langages.
Ce sont des domaines qu’on ne veut pas réimplémenter depuis zéro. Il est facile d’y introduire des bugs dans des aspects comme la stabilité numérique, avec des résultats corrects 99 % du temps, puis 1 % de résultats plausibles mais complètement faux.
Personnellement, j’ai commencé à migrer mes projets RP2040 vers Rust et Embassy.
Rust a demandé un peu d’adaptation, mais il me plaît beaucoup. Ce n’est pas un RTOS, mais il répond à beaucoup des besoins qui finissent par rendre un RTOS nécessaire.
Rust et Cargo éliminent les douleurs liées au build et au flash sur RP2040, ou sur STM32. C’est l’environnement embarqué le plus agréable que j’aie utilisé jusqu’ici.
Beaucoup d’applications n’utilisent pas de MPU. Il faut aussi prendre en compte la sûreté mémoire de Rust et la complexité globale du firmware réduite quand on se passe de RTOS.
L’asynchrone de l’executor Embassy fonctionne aussi très bien, ce qui enlève une bonne partie des douleurs liées à la conception avec RTOS.
Ajoutez à cela l’interface PIO pour RP2040, et le code devient très simple et élégant, avec une configuration difficile à obtenir sur d’autres processeurs.
Le Pi Pico est pris en charge à 100 % par Zephyr. N’ont-ils pas consulté la documentation https://github.com/zephyrproject-rtos/zephyr/tree/main/board... ? https://docs.zephyrproject.org/latest/boards/raspberrypi/rpi...
Par ailleurs, dans de nombreux cas, l’usage prévu n’est pas d’avoir une seule installation Zephyr “main”, mais d’inclure les modules externes nécessaires dans le
west.ymldu projet.Que plusieurs projets partagent la même installation Zephyr est une autre discussion, mais installer toutes les chaînes d’outils et tous les HAL possibles n’est pas la seule méthode.
ThreadX, qui est désormais open source, n’a pas été mentionné.
https://github.com/eclipse-threadx/threadx/
https://github.com/RIOT-OS/RIOT
La comparaison des options de RTOS était bonne.
Personnellement, je vois MicroPython comme une voie plus simple. Le multitâche coopératif basé sur
async/awaitme convient bien.Dans un projet récent, j’ai géré six moteurs pas à pas, plusieurs LED et le scan de boutons, et cela paraissait temps réel pour l’utilisateur.
Il est étonnant que beaucoup de gens ne réalisent toujours pas à quel point les ressources dont nous disposions alors étaient limitées, et que nous utilisions pourtant des langages de haut niveau.
Quand j’ai utilisé MicroPython sur ESP32, j’ai aimé la façon très Python dont les gestionnaires d’interruptions étaient pris en charge. Je suppose que le RP2040 est pris en charge de manière similaire.
Dans mon microscope, le contrôle presque temps réel des moteurs pas à pas est assuré par une carte FluidNC, et je la pilote via un protocole série léger.
Cela dit, je regarde https://pypi.org/project/micropython-stepper/, et cela semble utiliser des timers matériels et du code Python.
J’aimerais essayer Hubris sur un vrai projet (https://hubris.oxide.computer/reference/).
Son approche architecturale correspond assez bien à ce vers quoi je tends dans l’embarqué. Sauf que moi, je le fais de manière plus douloureuse en C.
Ce n’est pas si différent de ce qu’on fait avec Erlang/Elixir dans un environnement hébergé.
Quand la mémoire est plus limitée et qu’on ne peut pas se permettre plusieurs piles, Embassy semble être un bon choix.
Tout nouveau projet embarqué devrait toujours, vraiment toujours, commencer dans une machine virtuelle. Il ne faut pas mélanger les outils sur le même système.
Dans mes projets commerciaux, c’était la première cause de problèmes de qualité.
Si vous démarrez un projet avec un nouveau chipset ou un nouveau fournisseur, créez une nouvelle VM, installez uniquement les outils du fournisseur dans cette VM, puis compilez là-dedans.
Le développement expérimental peut se faire sur votre machine locale non VM. Mais les releases doivent absolument être faites dans une VM. Et, par pitié, gardez la VM et le poste de développement synchronisés.
En ce moment, je souffre énormément à devoir gérer un build de firmware qu’il faut impérativement corriger alors que le développeur d’origine est en vacances. Personne n’a accès à son poste de travail, la VM préparée a six mois de retard, et le client demande pourquoi il doit payer toute une équipe pour quelque chose qu’un seul programmeur spécial sur Terre semble capable de faire.
À mon avis, les releases devraient être compilées par le système de CI. On pose un tag de release dans git, et après passage des tests, un binaire est produit.
En choisissant FreeRTOS, il est difficile de se tromper lourdement. À ce stade, c’est de fait le standard du secteur.
printf()?Ça a l’air assez pénible.
J’ai eu une expérience similaire à celle de l’article d’origine.
J’ai donc créé moi-même un simple timer à green threads.
Il ne prend pas en charge la gestion réelle des processus comme un vrai noyau et n’offre aucune garantie, mais il m’a permis d’aller plus loin que l’ordonnancement bare metal et d’éviter le bazar qu’est un RTOS.
On peut le voir comme quelque chose du genre callback de timer JavaScript recevant en C une structure de contexte optionnelle.
J’ai pu interroger différents capteurs, traiter les signaux entrants, prendre des décisions de contrôle et émettre des commandes, chacun à des cadences différentes.
Avant de vous gâcher la vie avec ce genre d’architecture lente et abstraite, je recommande vivement d’essayer d’abord cette approche.