Recopier un exemple ne demande rien. Le comprendre est ce qui permet ensuite de le modifier, de le corriger et de le réutiliser. Or un programme ne se lit pas comme un texte, de la première ligne à la dernière : on y entre par des points de repère, dans un ordre qui n’est pas celui de l’écriture. Cette fiche donne une méthode d’entrée dans un programme C++ inconnu, les mécanismes du langage qui reviennent d’un exemple à l’autre, et une lecture commentée d’un programme du wiki pris tel quel. Elle clôt le parcours C++ : la syntaxe est supposée acquise, on apprend ici à s’en servir pour lire.
À quoi ça sert ?
En projet, la plupart du code qu’on manipule n’a pas été écrit par soi : un exemple de bibliothèque, le sketch d’un camarade, la reprise d’un projet de l’an dernier. Trois situations reviennent constamment, et elles supposent toutes de savoir lire avant d’écrire :
« Cet exemple fait presque ce que je veux — qu’est-ce que je change ? » ;
« Mon binôme a écrit cette partie, je dois brancher la mienne dessus » ;
« Ce code marchait la semaine dernière, quelqu’un l’a modifié. »
Lire n’est pas déboguer. Ici, on ne suppose aucune panne : le programme est réputé fonctionner, l’objectif est de savoir ce qu’il fait. Quand un comportement est faux et qu’on cherche pourquoi, c’est l’autre métier — voir déboguer un programme Arduino. Les deux se suivent : on ne débogue pas utilement un code qu’on n’a pas lu.
Par où entrer dans un programme inconnu
Quatre temps, dans cet ordre. Le premier réflexe à désapprendre est de commencer en haut et de descendre.
1. Repérer les bornes
En C++ Arduino, tout programme a la même charpente : setup() s’exécute une fois au démarrage, loop()en boucle ensuite, indéfiniment. Cherchez ces deux fonctions d’abord, avant même de lire une ligne : elles découpent le fichier en trois zones, l’en-tête (avant setup()), l’initialisation, et le corps répété.
Cette découpe répond déjà à une question de fond : ce qui est dans setup() est un réglage, ce qui est dans loop() est un comportement. Si vous cherchez « pourquoi la LED clignote comme ça », c’est dans loop(). Si vous cherchez « à quelle vitesse le port série est ouvert », c’est dans setup().
2. Lire l’en-tête : branché, réglé, mémorisé
L’en-tête tient en général en vingt lignes et dit trois choses distinctes. Apprendre à les séparer d’un coup d’œil fait gagner l’essentiel du temps de lecture.
Les #include — de quoi le programme dépend. Une bibliothèque incluse annonce une capacité (un afficheur, un servo, une carte SD) avant tout autre indice ;
les const et les #define, généralement en majuscules — les réglages. Ce sont les valeurs qu’on peut changer sans toucher à la logique : numéros de broches, durées, seuils. C’est ici qu’on intervient en premier pour adapter un exemple ;
les variables globales en minuscules, souvent initialisées à 0 ou false — ce que le programme mémorise d’un tour de loop() au suivant. Elles sont peu nombreuses et ce sont elles qui portent tout le comportement (voir variables locales et globales).
La convention typographique n’est pas garantie par le langage, mais elle est suivie partout dans le monde Arduino et dans tout ce wiki : majuscules = ne change jamais, minuscules = change. Un nom en majuscules qu’on voit affecté dans loop() est un signal que quelque chose cloche dans le code qu’on lit.
3. Faire l’inventaire des états
Prenez les deux ou trois variables globales en minuscules repérées à l’étape précédente et, pour chacune, cherchez tous les endroits où elle est affectée. La fonction de recherche de l’éditeur suffit. Un programme embarqué typique n’a que trois à cinq variables d’état, et savoir où chacune change, c’est avoir compris la logique : le reste du code n’est que du câblage autour.
4. Jouer un tour, une seule fois
Enfin, parcourez un tour complet de loop() en vous racontant un scénario concret : « le bouton n’est pas appuyé, il s’est écoulé 3 secondes depuis le démarrage. Que vaut chaque variable, quelle branche est prise ? ». Un seul tour, joué sérieusement, en apprend plus que trois lectures survolées.
Astuce
Le test de la phrase unique. Vous avez lu un programme quand vous pouvez répondre à deux questions sans rouvrir le fichier : que fait ce programme, en une phrase ? et qu’est-ce qui change entre deux tours de boucle ? Tant qu’une des deux résiste, la lecture n’est pas finie. À l’inverse, comprendre chaque ligne n’est pas l’objectif : on peut parfaitement lire un programme sans savoir ce que fait un appel de bibliothèque précis.
Les mécanismes du C++ qu’on retrouve partout
Les points suivants ne sont pas de la syntaxe à apprendre (elle est déjà au parcours) mais des indices de lecture : ce que la présence d’un mot-clé ou d’une forme d’écriture vous apprend sur le programme, avant même d’en comprendre le détail.
Les accolades délimitent, l’indentation ne fait pas foi
En C++, un bloc est ce qui se trouve entre { et }. Le décalage à gauche n’est qu’une convention de présentation : le compilateur l’ignore. Un if sans accolades ne gouverne que la ligne qui le suit immédiatement, même si plusieurs lignes semblent alignées dessous.
if (mesure > SEUIL) digitalWrite(LED, HIGH); // dans le if compteur++; // PAS dans le if — s'exécute toujours
Réflexe de lecture : dès qu’un if, un for ou un while n’ouvre pas d’accolade, ne considérez que la ligne suivante comme lui appartenant, quelle que soit la mise en page. C’est exactement l’inverse de MicroPython, où le décalage est la structure.
Le type dit la taille — et prévient le débordement
Le type d’une variable n’est pas une décoration : il fixe la plage de valeurs qu’elle peut prendre. Une seule règle de lecture est vraiment rentable en embarqué : toute date issue de millis() se stocke en unsigned long. Voir unsigned long tDebut est normal. Voir int tDebut sur une variable de temps est une anomalie à signaler, car un int sur Uno déborde au bout de 32 secondes.
Une condition composée se lit « ou bien … et que »
Quand un test mêle && et ||, la lecture à voix haute lève l’ambiguïté. Le point à connaître : && est prioritaire sur ||, donc A || B && C se lit « A, ou bien B et que C » — et non « A ou B, et que C ».
Ce test se lit : « on sort si le temps plein est écoulé, ou bien si un piéton a demandé et que le minimum est atteint ». Un || au premier niveau ouvre toujours deux chemins de sortie. Les repérer, c’est comprendre la logique de la branche.
volatile : la variable est modifiée ailleurs
Le mot-clé volatile devant une variable globale est un panneau indicateur : cette variable est écrite en dehors du flux normal du programme, presque toujours par une routine d’interruption. Sa présence vous dit de chercher l’ISR qui la modifie avant d’essayer de comprendre le loop(). Attention à ne pas surinterpréter : volatile empêche seulement le compilateur d’optimiser les lectures, il ne protège pas l’accès concurrent (voir les interruptions sur Arduino).
Ce qu’on ouvre, on le ferme
En C++, la libération d’une ressource est à la charge du programmeur. Un fichier ouvert avec SD.open() doit être refermé par close(). Une transmission ouverte par beginTransmission() doit être conclue par endTransmission(). Réflexe de lecture : à chaque ouverture repérée, cherchez la fermeture correspondante. Son absence n’est pas un détail de style. Sans close(), un fichier peut rester vide sur la carte.
C’est le point où les deux langages du wiki divergent le plus franchement : côté MicroPython, le bloc with ferme tout seul, et l’absence de close() n’y est donc pas un oubli.
Une table de tâches : struct et pointeur de fonction
Certains programmes remplacent une pile de tests répétés par un tableau qu’on parcourt. La forme revient assez souvent pour valoir la peine d’être reconnue :
struct Tache { void (*fonction)(); // l'adresse d'une fonction à appeler unsigned long intervalle; unsigned long dernier;};
Trois choses s’y lisent. Une structregroupe plusieurs informations sous un seul nom, ici tout ce qui décrit une tâche. Le champ void (*fonction)() est un pointeur de fonction : une case qui ne contient pas un nombre mais l’adresse d’une fonction, ce qui permet de l’appeler plus tard sans savoir laquelle. Et la boucle qui suit, for (Tache &t : taches), est un for à plage : elle parcourt les éléments du tableau un à un, sans indice à gérer. Le sens général se lit sans maîtriser les pointeurs : le programme tient une liste de choses à faire et la parcourt. Le détail est développé dans la programmation non bloquante sur Arduino.
Lecture commentée : le feu tricolore
Voici la méthode appliquée de bout en bout au programme de machine à états, repris tel quel. Suivez les quatre temps sans regarder le commentaire du wiki.
Les bornes.setup() configure six broches et date le départ. loop() contient un if puis un switch à trois branches. Tout le comportement est donc dans le switch.
L’en-tête. Aucun #include : le programme n’utilise que le cœur Arduino. Les majuscules donnent les réglages — six numéros de broches, quatre durées en millisecondes. Trois minuscules donnent la mémoire : etat, tDebut, demandePieton. On sait déjà que le programme mémorise où il en est, depuis quand, et si quelqu’un a appuyé.
L’inventaire des états.etat est affecté dans les trois branches du switch, jamais ailleurs. C’est le cycle vert → jaune → rouge → vert. tDebut est réaffecté à millis() exactement aux mêmes endroits, donc à chaque changement d’état : il redate l’entrée dans la branche, ce qui fait repartir les comparaisons de durée de zéro. demandePieton passe à true en tête de loop(), hors du switch (donc l’appui est écouté en permanence, quel que soit l’état) et revient à false en entrant dans ROUGE, une fois la demande satisfaite.
Un tour. Scénario : trois secondes après le démarrage, personne n’a appuyé. digitalRead(BOUTON) vaut HIGH (la broche est en INPUT_PULLUP, donc au repos elle est à HIGH), le premier if est faux. Le switch entre dans case VERT, rallume les cinq sorties dans la bonne configuration, puis teste la condition composée : millis() - tDebut vaut 3000, ce qui est inférieur à DUREE_VERT (6000) — faux. demandePieton est faux, donc la seconde branche l’est aussi. Rien ne change, le tour se termine, et le suivant recommence à l’identique.
Le programme se résume alors en une phrase : un feu qui tourne sur des durées fixes, et qui abrège son vert si un piéton a appuyé, sans jamais descendre sous un vert minimal. Ce que le tour a montré, c’est le rôle des majuscules dans le test. Il suffit de changer DUREE_VERT pour retimer tout le feu, sans toucher à une seule ligne de logique.
Se faire aider sans se faire remplacer
Faire expliquer un programme par un assistant conversationnel est un usage légitime, à une condition : que ce qu’il affirme soit vérifiable à l’écran. Le critère est le même que pour la simulation et ne dépend pas du sens de l’usage.
« La variable demandePieton est modifiée dans le premier if de loop() » est une affirmation vérifiable : une recherche dans le fichier la confirme ou la dément en trois secondes. « Ce test déborde au bout de 32 secondes si la variable est un int » se vérifie en regardant la déclaration. Ces affirmations-là font gagner du temps sans rien retirer au travail : ce sont des hypothèses, et vous gardez la charge de les valider.
« Ce code est correct » n’est pas de cette nature. C’est la conclusion : le seul livrable de la lecture, et le seul qui ne se délègue pas. Un assistant peut vous montrer où regarder. Décider que vous avez compris reste votre part.
Pièges fréquents
Lire de haut en bas. Un programme n’est pas un texte : commencer par setup() et loop(), puis remonter à l’en-tête pour éclairer ce qu’on y a vu.
Chercher à tout comprendre. Un appel de bibliothèque obscur n’empêche pas de lire le programme. Notez-le, continuez, revenez-y si la question se repose.
Se fier à l’indentation. Elle est décorative en C++. Seules les accolades font foi, et un if sans accolades est le piège classique.
Confondre réglage et état. Une valeur en majuscules ne change jamais en cours d’exécution. La confondre avec une variable d’état fait chercher une logique là où il n’y a qu’un paramètre.
Sauter l’inventaire des variables globales. C’est l’étape la plus rentable des quatre, et celle qu’on est le plus tenté d’écourter.
Exercices
Exercice 1 — Deux chemins de sortie
Dans le programme du feu tricolore ci-dessus, la branche case VERT peut se terminer de deux façons. Décrivez chacune en une phrase, puis dites ce qui se passe si un piéton appuie une seconde après le passage au vert.
Corrigé de l'exercice 1
Les deux sorties sont les deux membres du || :
le vert a duré son temps plein — millis() - tDebut >= DUREE_VERT, soit 6 secondes ;
un piéton a appelé et le minimum est écoulé — demandePieton est vrai etmillis() - tDebut >= DUREE_VERT_MIN, soit 2 secondes.
Si l’appui a lieu à la première seconde, demandePieton passe à true immédiatement, mais la seconde condition est encore fausse (1000 < 2000). Le feu reste au vert jusqu’à la deuxième seconde, puis bascule au jaune sans attendre les six secondes. C’est précisément le rôle de DUREE_VERT_MIN : empêcher qu’un appui coupe un vert qui vient de démarrer.
Exercice 2 — Trois indices, trois conclusions
Sans chercher le programme complet, dites ce que chacune de ces trois lignes vous apprend, isolément, sur le code dont elle est extraite.
volatile unsigned long impulsions = 0;File f = SD.open("mesures.csv", FILE_WRITE);const unsigned long PERIODE = 250;
Corrigé de l'exercice 2
volatile unsigned long impulsions — le volatile annonce une variable écrite par une interruption : il existe donc une ISR ailleurs dans le fichier, et c’est par elle qu’il faut commencer. Le unsigned long indique un compteur destiné à monter haut, ou une date.
File f = SD.open(...) — une ressource est ouverte : il doit exister un f.close() plus loin, et son absence serait un défaut. Accessoirement, la ligne annonce qu’une bibliothèque SD est incluse en tête et que le programme journalise des mesures.
const unsigned long PERIODE = 250 — majuscules et const : c’est un réglage, pas un état. Il ne changera jamais en cours d’exécution, et c’est un bon candidat pour adapter le programme à un autre usage.
Raccrochage projet
Étape 4 de la phase de preuve de concept — la plupart des briques logicielles d’un PoC partent d’un exemple existant qu’on adapte. La qualité de la lecture initiale décide du temps perdu ensuite.
Revue de code — relire le code d’un camarade suppose d’abord de savoir le lire. C’est le prérequis direct de la routine collective.