Aller au contenu principal

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é.

Arduino
void setup() {
Serial.begin(115200);
}
void loop() {
float temperature = analogRead(A0) * (5.0 / 1023.0) * 100.0; // capteur LM35
Serial.print("temperature = ");
Serial.print(temperature, 1); // une décimale
Serial.println(" degres");
delay(1000);
}

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.

Arduino
const int SEUIL = 600;
void setup() {
Serial.begin(115200);
}
void loop() {
Serial.print("lumiere:");
Serial.print(analogRead(A0));
Serial.print(",seuil:");
Serial.println(SEUIL); // deux courbes : la mesure et le seuil
delay(50);
}

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.

Arduino
const int LED = 13;
void setup() {
Serial.begin(115200);
pinMode(LED, OUTPUT);
}
void loop() {
if (Serial.available() > 0) {
char commande = Serial.read(); // un seul caractère
if (commande == '1') {
digitalWrite(LED, HIGH);
Serial.println("LED allumee");
} else if (commande == '0') {
digitalWrite(LED, LOW);
Serial.println("LED eteinte");
}
// tout autre caractère, dont la fin de ligne, est ignoré
}
}
La fin de ligne est aussi un caractère
Selon le réglage du moniteur, taper 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.

Le défaut qui disparaît quand on l'observe
Ajouter une trace ralentit le programme ; retirer la trace le rend à nouveau rapide. Un défaut lié au temps peut ainsi apparaître ou disparaître selon qu'on l'observe. Deux précautions limitent ce risque : un débit élevé, 115 200 bauds, qui divise le coût par douze, et un affichage espacé dans le temps plutôt qu'à chaque tour.
Arduino
const bool TRACE = true; // passer à false pour couper toutes les traces
unsigned long derniereTrace = 0;
int compteur = 0;
void setup() {
Serial.begin(115200);
}
void loop() {
compteur++; // le travail réel du programme
if (TRACE && millis() - derniereTrace >= 500) { // une trace par demi-seconde au plus
derniereTrace = millis();
Serial.print("t=");
Serial.print(millis());
Serial.print(" compteur=");
Serial.println(compteur);
}
}

Mettre au point

Afficher au hasard produit beaucoup de texte et peu d'information. La démarche efficace est celle d'une enquête.

  1. Formuler une hypothèse précise : « le compteur est remis à zéro », « le bouton n'est jamais lu comme enfoncé ».
  2. 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.
  3. Afficher le temps avec la valeur, millis(), quand le défaut dépend du rythme.
  4. Lire, conclure, retirer la trace, ou la couper par une constante, avant de passer à l'hypothèse suivante.
Un compteur qui devient négatif
Un programme mesure une durée et affiche une valeur absurde après une trentaine de secondes. Une trace de la variable d'horodatage montre 32 000, puis 32 500, puis -32 536. L'hypothèse se confirme immédiatement : l'horodatage a été déclaré int, qui plafonne à 32 767, au lieu de unsigned long.

Aide-mémoire

Structure et tempsRô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 >= intervalletest d'intervalle, juste au retour à zéro
BrochesRôle
pinMode(b, OUTPUT)broche en sortie
pinMode(b, INPUT_PULLUP)entrée avec tirage interne : enfoncé se lit LOW
digitalWrite(b, HIGH) / LOWimpose 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 utilesFormule
Lecture vers tensiontension = lecture × 5 / 1023
PWM vers tension moyennetension = 5 × valeur / 255
Changement d'échellemap(x, 0, 1023, 0, 255), calcul entier, non borné
Bornerconstrain(x, min, max)
Résistance d'une LEDR = (5 − tension de la LED) / courant
TypesTaillePlage
bool1 octettrue, false
byte1 octet0 à 255
int2 octets-32 768 à 32 767
unsigned long4 octets0 à environ 4,3 milliards
float4 octets6 à 7 chiffres significatifs
Liaison sérieRô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 : 220/960023220 / 9600 \approx 23 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

Vérification rapideon peut se reprendre

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

  1. Ouvrir la liaison à 115 200 bauds, et régler le moniteur sur le même débit.
  2. Nommer chaque valeur affichée et donner son unité.
  3. Formuler l'hypothèse avant d'ajouter une trace, et n'afficher que la valeur qui la teste.
  4. Placer la trace à l'endroit exact où la valeur compte, avec millis() si le temps est en cause.
  5. Espacer les traces dans le temps, et les couper par une constante une fois le défaut compris.
  6. Ne réagir qu'aux caractères attendus dans une commande reçue.

Synthèse

  • Serial.begin(débit) ouvre la liaison ; print et println envoient, available et read reç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.