Aquila Reloaded – Chapitre 10 : Interaction en CLI et création d’un mode « debug »

Hello world !

Dans ce chapitre, nous allons voir comment interagir avec notre Aquila, et plus généralement comment interagir avec un Arduino en général. C’est plutôt trivial donc je vais surtout me concentrer sur quelques astuces. Nous verrons comment :

  • Envoyer des informations
  • Recevoir des informations
  • Optimiser l’occupation mémoire
  • Créer un « mode debug »

Envoyer des informations depuis l’Arduino

Pour récupérer des informations, le plus simple est d’utiliser l’interface USB/Série de l’Arduino, qui sert aussi à flasher la carte. Pour cela, on va utiliser la classe « Serial ».

Tout d’abord on initialise le port, avec la méthode « begin« . Ce qui donne la commande suivante pour initialiser le port à un débit de 9600 bauds par seconde (qui est un débit très faible mais standardisé dans le milieu informatique).

Serial.begin(9600);

On peut utiliser un débit plus élevé si besoin, comme 115200 bps.

Pour l’affichage, la classe contient deux méthodes « print » et « println« . Elles font la même chose si ce n’est que « println » effectuera automatiquement un retour chariot.

Ces fonctions prennent en paramètre le texte ou la variable à afficher, et la fonction est quelque peu permissive : elle prend en charge des chaines de caractère, des nombres entiers, des flottants, des long, etc…

Si on veut afficher du texte et des nombres, il sera nécessaire de jongler en appelant plusieurs fois print et println. Par exemple :

int tempLed = 25;
void setup() {
  Serial.begin(9600);
}

void loop() {
  Serial.print("La température est de ");
  Serial.print(tempLed);
  Serial.println(" degrés");
  delay(1000);
}

Vous voyez, c’est trivial et cela permet de renvoyer des informations lisibles pour un être humain.

Recevoir et traiter des informations sur l’Arduino

Maintenant que l’on sait envoyer des informations, on va voir comment en recevoir pour traitement sur l’Arduino.

On va utiliser pour cela la méthode « read » de la classe Serial.

On commence par ouvrir le port, et on rajoute une petite boucle pour laisser le temps au port de s’ouvrir :

void setup() {
  // start serial port at 9600 bps and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
   ; // wait for serial port to connect. Needed for native USB port only
  }
}

La commande permettant ensuite de lire le contenu est « read« :

inByte = Serial.read();

Elle retourne le premier octet stocké dans le buffer. Il faut appeler cette fonction autant de fois que nécessaire pour vider le buffer.

Attention, si on utilise read et qu’aucune donnée ne se trouve dans le buffer, alors le programme sera bloqué et en attente de réception de donnée !

Heureusement, on peut vérifier au préalable que la taille du buffer est supérieure à 0, avant de lire les données, en utilisant la méthode « available« .

if(Serial.available() > 0) {
 inByte = Serial.read();
}

Attention également à la taille du buffer, qui est limité à 64 octets, il faudra le vider très régulièrement si on ne veut pas perdre d’infos !

« Au secours !!! Mon programme consomme beaucoup de mémoire ! »

Si vous insérez beaucoup de texte dans votre programme, notamment pour un mode debug, vous aurez tendance à intégrer pas mal de texte pour afficher un maximum d’informations :

void setup() {
 // put your setup code here, to run once:
 Serial.begin(9600);
 Serial.println("Lorem ipsum dolor sit amet, consectetur adipiscing elit,");
 Serial.println("sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.");
 Serial.println("Ut enim ad minim veniam, quis nostrud exercitation ullamco");
 Serial.println("laboris nisi ut aliquip ex ea commodo consequat.");
 Serial.println("Duis aute irure dolor in reprehenderit in voluptate velit");
 Serial.println("esse cillum dolore eu fugiat nulla pariatur.");
 Serial.println("Excepteur sint occaecat cupidatat non proident, sunt in culpa");
 Serial.println("qui officia deserunt mollit anim id est laborum.");
}

void loop() {
 // put your main code here, to run repeatedly:
}

Le problème est que, de part la conception du C++, une chaine de caractères n’est ni plus ni moins qu’un tableau d’octets (« bytes »), représentés par leur code ASCII. Ce tableau sera stocké sous la forme d’un pointeur, et sera donc chargé… en mémoire !

Et vu la très faible quantité de mémoire de nos Arduino, cela est quelque peu dommageable de la gaspiller juste pour afficher des informations de debug, qui ne serviront pas tous les jours !

Heureusement il existe une astuce : la fonction « F() » permet d’indiquer au compilateur de stocker cette chaine de caractères dans PROGMEM au lieu de la stocker en mémoire vive. En utilisant cette fonction pour créer nos chaines de caractères, on pourra économiser notre précieuse mémoire sans altérer la qualité et la réactivité de notre programme.

Ce qui nous donne :

void setup() {
 // put your setup code here, to run once:
 Serial.begin(9600);
 Serial.println(F("Lorem ipsum dolor sit amet, consectetur adipiscing elit,"));
 Serial.println(F("sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."));
 Serial.println(F("Ut enim ad minim veniam, quis nostrud exercitation ullamco"));
 Serial.println(F("laboris nisi ut aliquip ex ea commodo consequat."));
 Serial.println(F("Duis aute irure dolor in reprehenderit in voluptate velit"));
 Serial.println(F("esse cillum dolore eu fugiat nulla pariatur."));
 Serial.println(F("Excepteur sint occaecat cupidatat non proident, sunt in culpa"));
 Serial.println(F("qui officia deserunt mollit anim id est laborum."));
}

void loop() {
 // put your main code here, to run repeatedly:
}

Comme vous voyez, la consommation de mémoire, pour une simple chaine de caractères est passée de 30 à 9%, ce qui correspond au « payload » de notre programme sans les chaines de caractères.

Créer un mode « debug »

On sait collecter des données, on sait émettre des données, on peut donc créer un « mode debug », qui sera activable à la demande au démarrage.

Voici un morceau de code que je vous propose, il permet d’afficher un menu de debug sur appuie d’une touche au démarrage de l’Aquila.

bool debugMode = false;
void menu_main() {
 int inByte;
 bool quit = false;
 while(!quit) {
   Serial.println();
   Serial.println(F("Boot Menu"));
   Serial.println(F("========="));
   Serial.println();
   Serial.println(F(" [N]: Boot the firmware in normal mode"));
   Serial.println(F(" [D]: Boot the firmware in debug mode"));
   Serial.println();
   Serial.print(F("Enter N or D: "));
   while(Serial.available() <= 0) {
     delay(10);
   }
   inByte = Serial.read();

   // Convert lowercase to uppercase
   if(inByte >= 'a' and inByte <= 'z') {
     inByte -= ('a' - 'A');
   }
 
   switch (inByte) {
     case 'N':
       Serial.println(F("N"));
       Serial.println(F("Continue boot to normal mode"));
       debugMode = false;
       quit = true;
       break;
 
     case 'D':
       Serial.println(F("D"));
       Serial.println(F("Continue boot to debug mode"));
       debugMode = true;
       quit = true;
       break;

     default:
       Serial.println(F("Wrong key!"));
   }
 }
}

void setup() {
 Serial.begin(9600);
 Serial.print(F("Press any key to display configuration menu"));
 int counter = 20;
 while(counter > 0) {
   Serial.print(".");
   delay(100);
   counter--;
   if(Serial.available() > 0) {
     Serial.println();
     Serial.println(F("System startup aborded, running configuration menu"));
     counter = 0;
     //Clear serial buffer
     while(Serial.available() > 0) {
       Serial.read();
     }
     menu_main();
   }
 }
 Serial.println();
 Serial.println();
 Serial.println(F("System is starting..."));
}

void loop() {
  // Do what you want
  if(debugMode) {
    Serial.println("Starting loop function");
  }
  delay(1000);
}

Et voici le résultat en vidéo :

Pour aller plus loin…

Afin de saisir les données de calibration de l’Aquila, il est possible de développer une sorte de « wizard » permettant de mettre à jour toutes les valeurs stockées en mémoire EEPROM. Work in progress, stay tuned 🙂

La suite au prochain épisode !

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *