Le moniteur série et la mise au point
Ce que ce chapitre apporte
- Envoyer au moniteur série des valeurs lisibles, avec leur nom et leur unité.
- Présenter des mesures sous une forme que le traceur série transforme en courbes.
- Recevoir une commande envoyée depuis l'ordinateur.
- Calculer ce que coûte un affichage, et l'empêcher de fausser le rythme du programme.
- Mettre au point un programme en affichant l'état au bon endroit.
Une carte Arduino n'a ni écran ni débogueur pas à pas. Pour savoir ce que fait réellement un programme, il ne reste qu'un fil : la liaison série, qui envoie du texte à l'ordinateur et peut en recevoir. Bien employée, elle suffit à trouver la plupart des défauts. Mal employée, elle en crée, parce que chaque message prend du temps et change le rythme du programme qu'on observe. Le chapitre se termine par un aide-mémoire des fonctions de base, à garder ouvert pendant les montages.
Écrire lisiblement
Un nombre seul dans le moniteur ne dit rien : 27 peut être une température, un compteur ou une broche. Une ligne utile porte le nom de la valeur et son unité.
Serial.print(valeur, 1) fixe le nombre de décimales d'un nombre à virgule. println ajoute une fin de ligne, print non : une ligne se compose de plusieurs print et se termine par un println.
Le traceur série
L'environnement Arduino propose aussi un traceur série, qui dessine des courbes au lieu d'afficher du texte. Il attend une ligne par instant, chaque valeur précédée de son nom et de deux-points, les valeurs séparées par une virgule.
Voir une mesure franchir un seuil sur une courbe en dit plus long qu'une colonne de nombres : on y lit le bruit, les pics isolés, la vitesse de variation.
Recevoir une commande
Le moniteur série permet aussi d'envoyer du texte vers la carte. Serial.available() rend le nombre d'octets reçus en attente, Serial.read() en lit un.
1 puis Entrée envoie '1' suivi d'un ou deux caractères de fin de ligne. Un programme qui traiterait chaque caractère reçu comme une commande réagirait trois fois. Ne réagir qu'aux caractères attendus, comme ci-dessus, règle la question.
Ce que coûte un affichage
La liaison série transmet un caractère en dix bits. À 9 600 bauds, cela fait environ 960 caractères par seconde. Une ligne de vingt caractères, fin de ligne comprise, met donc près de 23 ms à partir.
Tant que les messages sont rares, ce délai passe inaperçu. Placé à chaque tour de loop(), il limite le programme à une quarantaine de tours par seconde, dès que la mémoire d'envoi est pleine et que Serial.print attend qu'elle se vide. Un anti-rebond, un clignotement réglé avec millis() changent alors de comportement, et ce changement n'existe que tant que la trace est active.
Mettre au point
Afficher au hasard produit beaucoup de texte et peu d'information. La démarche efficace est celle d'une enquête.
- Formuler une hypothèse précise : « le compteur est remis à zéro », « le bouton n'est jamais lu comme enfoncé ».
- Choisir la valeur qui confirme ou infirme cette hypothèse, et l'afficher à l'endroit exact où elle compte : juste avant le test, juste après l'affectation.
- Afficher le temps avec la valeur,
millis(), quand le défaut dépend du rythme. - Lire, conclure, retirer la trace, ou la couper par une constante, avant de passer à l'hypothèse suivante.
int, qui plafonne à 32 767, au lieu de unsigned long.
Aide-mémoire
| Structure et temps | Rôle |
|---|---|
setup() | exécutée une fois au démarrage |
loop() | exécutée indéfiniment |
delay(ms) | suspend tout le programme |
millis() | millisecondes depuis le démarrage, en unsigned long |
millis() - derniere >= intervalle | test d'intervalle, juste au retour à zéro |
| Broches | Rôle |
|---|---|
pinMode(b, OUTPUT) | broche en sortie |
pinMode(b, INPUT_PULLUP) | entrée avec tirage interne : enfoncé se lit LOW |
digitalWrite(b, HIGH) / LOW | impose 5 V ou 0 V |
digitalRead(b) | rend HIGH ou LOW |
analogRead(A0) | rend de 0 à 1023 pour 0 à 5 V |
analogWrite(b, v) | PWM de 0 à 255, broches 3, 5, 6, 9, 10, 11 |
| Calculs utiles | Formule |
|---|---|
| Lecture vers tension | tension = lecture × 5 / 1023 |
| PWM vers tension moyenne | tension = 5 × valeur / 255 |
| Changement d'échelle | map(x, 0, 1023, 0, 255), calcul entier, non borné |
| Borner | constrain(x, min, max) |
| Résistance d'une LED | R = (5 − tension de la LED) / courant |
| Types | Taille | Plage |
|---|---|---|
bool | 1 octet | true, false |
byte | 1 octet | 0 à 255 |
int | 2 octets | -32 768 à 32 767 |
unsigned long | 4 octets | 0 à environ 4,3 milliards |
float | 4 octets | 6 à 7 chiffres significatifs |
| Liaison série | Rôle |
|---|---|
Serial.begin(115200) | ouvre la liaison, même débit dans le moniteur |
Serial.print(v) / println(v) | envoie, sans ou avec fin de ligne |
Serial.print(x, 2) | un nombre à virgule avec deux décimales |
Serial.available() | nombre d'octets reçus en attente |
Serial.read() | lit un octet reçu |
Exercices type
Un programme affiche Serial.println(compteur) à chaque tour de loop(), à 9600 bauds. Les lignes font une vingtaine de caractères. Combien de tours par seconde fait loop() au plus ?
Vingt caractères et la fin de ligne font 22 caractères, soit 220 bits. À 9 600 bauds : ms par ligne. loop() est limitée à environ 44 tours par seconde, une fois la mémoire d'envoi remplie.
Passer à 115 200 bauds ramène la ligne à moins de 2 ms. Mieux encore : n'afficher qu'à intervalle régulier, avec millis().
La commande '1' allume bien la LED, mais le moniteur affiche aussi une réponse pour chaque touche Entrée. Le programme écrit else Serial.println("commande inconnue"). Pourquoi ?
Entrée envoie un ou deux caractères de fin de ligne, '\n' et parfois '\r'. Chacun est lu par Serial.read(), ne correspond ni à '1' ni à '0', et déclenche le message.
Deux corrections : régler le moniteur pour qu'il n'envoie pas de fin de ligne, ou, plus robuste, ignorer silencieusement les caractères '\n' et '\r' avant de signaler une commande inconnue.
Un clignotement réglé avec millis() fonctionne. Une trace ajoutée à chaque tour, à 9600 bauds, le rend irrégulier. Que se passe-t-il ?
Chaque ligne bloque le programme une vingtaine de millisecondes pendant l'envoi. Le test d'intervalle n'est plus évalué qu'une quarantaine de fois par seconde : un basculement prévu à 500 ms peut n'être vu qu'à 520 ms, et l'écart varie d'un cycle à l'autre.
La trace a modifié le rythme de ce qu'elle observe. On l'espace dans le temps, on augmente le débit, ou on la coupe par une constante une fois l'information obtenue.
Vérification
1.Quelle différence entre Serial.print et Serial.println ?
2.À 9600 bauds, combien de caractères passent environ par seconde ?
3.Pourquoi une trace placée à chaque tour de loop() peut-elle changer le comportement d'un programme ?
4.Que rend Serial.available() ?
5.Quel format le traceur série attend-il pour dessiner deux courbes ?
La méthode
- Ouvrir la liaison à 115 200 bauds, et régler le moniteur sur le même débit.
- Nommer chaque valeur affichée et donner son unité.
- Formuler l'hypothèse avant d'ajouter une trace, et n'afficher que la valeur qui la teste.
- Placer la trace à l'endroit exact où la valeur compte, avec
millis()si le temps est en cause. - Espacer les traces dans le temps, et les couper par une constante une fois le défaut compris.
- Ne réagir qu'aux caractères attendus dans une commande reçue.
Synthèse
Serial.begin(débit)ouvre la liaison ;printetprintlnenvoient,availableetreadreçoivent.- Une valeur affichée porte son nom et son unité ; le traceur série dessine des lignes
nom:valeur,nom:valeur. - Un caractère occupe dix bits : environ 960 caractères par seconde à 9 600 bauds.
- Une trace prend du temps : placée à chaque tour, elle ralentit le programme et peut masquer ou créer un défaut lié au rythme.
- La mise au point procède par hypothèses : une valeur, à un endroit précis, pour confirmer ou infirmer.
- La fin de ligne envoyée par le moniteur est un caractère comme un autre.