[Tuto] Programmation non bloquante pour Arduino & Co

  • Nico! C’est quoi le programme du jour ?
  • Heu… T’es sûr de vouloir le savoir?
  • Bah oui, ça fait trop longtemps, j’en peux plus d’attendre!
  • Ok, alors si on parlait Arduino et temps réel? Je vais te montrer comment on programme des événements récurrents de façon non bloquante!

Une petite contextualisation me semble nécessaire. En ce moment je travaille sur une interface MIDI, pour synchroniser un microcontrôleur (Teensy) avec des instruments de musique. Pour la faire courte, la Teensy va donner un tempo en BPM, et les instruments MIDI vont se caler dessus.

La Teensy est géniale dans ce contexte, parce qu’elle peut se faire passer pour un périphérique MIDI standard et son API dispose de toute une batterie de fonctions pour envoyer des instructions MIDI à qui veut bien les écouter.

Mais pour l’instant, pas besoin de rentrer dans le protocole d’horloge midi, on va se contenter d’afficher du texte dans le moniteur série, et après si vous êtes sages, on fera clignoter des LEDs. Houlala, mais quel programme trépidant!

1. Approche naïve pour gérer un événement récurrent

Si vous êtes en train de lire ceci, il y a de forte chances pour que vous ayez testé votre première Arduino avec l’exemple blink.

Rien de choquant, on est tous passé par là :).

Affichons plutôt des messages, ce sera plus pratique pour la suite:

Si j’ai maintenant envie d’afficher « Paf » toutes les 500ms et « Pif » toutes les 3 secondes… Comment faire?
On pourrait utiliser un compteur, mettre un delay(500), à chaque tour afficher Paf, et tous les 6 tours afficher Pif.

Ça peut vite devenir très compliqué l’histoire. Et d’autant plus que pendant l’appel à delay(), il ne se passe rien, le programme est bloqué pendant 500ms.
C’est la méga-cagade si on doit faire d’autres traitements en parallèle (lecture d’entrées, affichage…).

2. Approche non bloquante

Pour remédier à cela, on doit avoir une approche non bloquante, basée sur l’heure actuelle (ou plus précisément sur le nombre de millisecondes (ou microsecondes) écoulées depuis le démarrage du programme).
On va utiliser les fonction millis() ou micros(), selon la précision désirée (respectivement à la milliseconde ou à la microseconde près).

L’exemple proposé dans l’IDE Arduino est BlinkWithoutDelay, et on peut l’adapter ainsi pour notre exemple:

Dans ce cas, à chaque tour de loop(), on va regarder si la durée nécessaire est passée, et si ce n’est pas le cas on laisse filer (et du coup on peut faire autre chose plutôt que d’attendre bêtement).

3. Approche plus encapsulée

On est déjà bien mieux que dans le tout premier exemple, mais il y a des variables globales qui se baladent, des grosses conditions dans les if(), ce n’est pas encore très élégant.

Pour mon projet, j’ai encapsulé ce fonctionnement dans une classe C++ histoire de faciliter l’utilisation:

Pour reprendre notre exemple précédent, on aurait donc:

C’est plus léger. Plus de variables globales pour stocker des états dont on se fout royalement dans le programme principal.
Vous noterez que l’heure (ici en microsecondes) est lue une seule fois, puis passée en paramètre aux méthodes de test. Ça permet d’éviter des décalages de temps causés par l’exécution du code qui peut être « longue » (~100µs pour un analogRead() par exemple). En faisant comme ça, on travaille sur la même référence de temps pour toutes les opérations.

4. Application : Faire clignoter une LED de façon contrôlée et non bloquante

Je dis LED, mais le concept s’applique évidemment au contrôle de moteurs pas à pas, à l’envoi de signaux d’horloge MIDI, et que sais-je encore.

Voici l’interface de la classe :

Si je veux faire clignoter une LED à 3Hz, pendant 5 impulsions, il me suffit d’écrire:

Si je veux faire 3 clignotements à 5 Hz chaque seconde, il me suffit d’écrire le programme suivant:

Les deux projets sont dispo sur GitHub et dans les bibliothèques Arduino avec des exemples!

https://github.com/toxnico/DMTimer

https://github.com/toxnico/DMOscillator

Tests unitaires en C++

Monsieur Jourdain: Adieu Sganarelle, quoi de prévu pour aujourd’hui?

Sganarelle: Les tests unitaires.

Monsieur Jourdain: Ah… Mais vous êtes au courant que même si l’on sait que c’est bien, on n’aime pas faire ça…

Sganarelle: Ouais, mais là j’ai un argument choc: le développement sur microcontrôleurs!

Monsieur Jourdain: …

Ça annonce du lourd, n’est-ce pas?

J’ai eu très récemment besoin d’écrire une classe de pilotage de moteur pas à pas pour un microcontrôleur type Arduino (Teensy pour être exact, mais là n’est pas la question).

La classe en question doit gérer l’accélération et décélération du moteur en début et fin de mouvement.

Vous allez me dire, il existe déjà des bibliothèques pour ça. Hé bien oui, mais vous commencez à me connaitre, et j’étais très curieux de développer mon propre contrôleur de moteur. C’était d’ailleurs très instructif de se replonger dans les maths de terminale S pour calculer tout ce bordel; notamment réapprendre la notion d’intégrale pour calculer la distance parcourue pendant la durée du mouvement, avec une accélération et décélération constantes en début et fin de mouvement.

J’en ai aussi profité pour découvrir des outils mathématiques en ligne extrêmement pratiques, comme Desmos ça, c’est la courbe de vitesse par rapport au temps

En résumé, j’ai dû écrire une série de fonctions de calcul purement mathématiques pour calculer la durée d’accélération, le nombre de pas selon l’accélération et la vitesse max, ce genre de trucs.

J’aurais pu y aller empiriquement, et débugger avec des traces dans la console Arduino, à base de Serial.println(), mais franchement… franchement… C’est super relou de débugger sur ce genre de plateforme…

Alors dans un premier temps, j’ai écrit ces fonctions en Python (oui, je m’y suis mis et je kiffe pas mal), et comme Python dispose en standard d’un environnement de tests unitaires, c’était cool.

Puis, une fois mes fonctions validées en Python, je les ai transposées en C++ dans une classe statique. Mais là, c’est méga chiant, parce que les langages sont quand même très différents :/

J’ai cherché un environnement de tests unitaires en C++ (j’en avais de mauvais souvenirs en termes de dépendance, et c’est essentiellement pour ça que je suis passé par Python). Et j’ai trouvé la perle: Catch

C’est HYPER simple à mettre en place. Aucun héritage, ni nommage conventionnel à faire.
Juste UN fichier d’en-tête à inclure, et une syntaxe à la cool pour écrire les tests 🙂

Imaginons que j’aie à tester une classe qui permet d’effectuer des opérations très complexes, telles qu’ajouter deux nombres, calculer une puissance ou pire, diviser deux nombres (float).

Pour info, REQUIRE arrête l’exécution des tests en cas d’erreur, alors que CHECK indique l’erreur et continue avec les autres tests. Pour plus de détails (notamment sur l’approximation des nombres à virgule flottante), voir la documentation officielle.

Et qu’est-ce qu’il dit?

Créer des templates dans Atom

Salut les barbus!

Vous qui programmez (et je sais qu’il y en a, pas la peine vous cacher!), peut-être utilisez-vous l’éditeur Atom?

C’est ce que j’utilise, en tout cas pour développer en C++ sur mes différents joujoux (choux, cailloux, genoux, hiboux, joujoux, poux… oui c’est bien un x… Mais c’est bizarre à lire quand même), Arduino, ESP8266 et Teensy.

Et s’il y a bien une chose qui me fatigue, c’est de devoir toujours taper la même chose à chaque fois que je crée une nouvelle classe:

maclassequitue.h:

maclassequitue.cpp:

Ce n’est pas grand chose, mais c’est monstre rébarbatif de devoir se taper ça à chaque fois.

J’ai trouvé un plugin Atom permettant de créer des templates pour générer ce qu’on veut en deux coups de cuillère à pot. Il s’appelle Atom Smart Templates.

Une fois installé, voici comment j’ai créé ma template pour générer les deux fichiers à partir d’un nom de classe:

  • Menu Packages/Smart Templates/Open templates folder
  • Dupliquer le répertoire de la template de base (BaseTemplate), et l’appeler cpp (nom totalement arbitraire)
  • Virer tout sauf le fichier index.js (qui contient la configuration de notre template)
  • Créer deux fichiers: header.template et implementation.template (noms tout aussi arbitraires)

Le plus gros est fait 🙂

Voici le contenu de mon index.js:

La variable params contient la liste des paramètres à demander à l’utilisateur pour pouvoir générer les fichiers.

Chaque item généré dans la fonction rules prend en source nos deux fichiers .template, et construit leurs noms finaux en fonction du paramètre ClassName qui a été saisi.

Ensuite, nous devons créer notre fichier header.template:

Et le fichier implementation.template:

Facile, non?
Et surtout maintenant, quand je veux créer une nouvelle classe, je n’ai qu’à faire un clic droit dans le répertoire concerné, Create files from template, clic sur C++ class, je rentre le nom de ma classe et paf!