Généralement, sur un gros projet, je joins un petit livret contenant les explications détaillées sur le logiciel, avec organigramme des procédures principales. Pour cette fois, je ne suis pas certain que ce soit vraiment utile, car le but n’est pas d’apprendre à programmer Arduino. Toutefois, je ne dois pas exclure que certaines ou certains puissent avoir envie de modifier le programme pour ajouter une ou plusieurs petites fonctions. La première aide réside dans la façon dont je développe un programme. Très inspiré du langage le plus « propre » que j’ai pratiqué il y a longtemps, le PASCAL, j’en ai gardé des réflexes lors du codage en C++. Par exemple toutes les constantes et les variables sont déclarées en tête de listage et sont inscrites par ordre alphabétique. Sauf exception j’évite comme une plaie les procédures ou les fonctions qui comportent un nombre trop grand de lignes d’instructions. J’estime qu’il ne faut pas dépasser en « hauteur » la taille disponible dans la fenêtre de l’IDE pour avoir intégralement le listage sous les yeux. Quand une procédure invoque une autre procédure, l’appelée est située avant, du coup vous savez où la chercher. Je m’impose l’utilisation d’identificateurs évocateurs de façon à ce qu’en lisant le nom de la procédure vous comprenez immédiatement à quoi elle sert. Enfin, dans les procédures je repère certaines séquences spécifiques par des remarques de séparation. Je saupoudre un maximum de remarques pour préciser certaines instructions ou justifier des choix effectués qui peuvent sembler non optimisés. Si pour une quelconque raison je dois souvent modifier une constante, pour la retrouver facilement dans le listage je la jalonne avec une remarque visuelle du type //@@@@@@@@@@@@@@@@@@.
L’optimisation du programme.
Généralement, quand j’engage mes heures de loisir sur un gros projet contenant du logiciel, je cherche à en optimiser le code à l’OCTET prés. C’est un réflexe conditionné de programmeur qui dans la majorité de sa vie a utilisé l’ASSEMBLEUR. Dans ce programme comme dans tous les autres, j’optimise à outrance le choix du type des données. Habituellement j’optimise intensément le codage des procédures. Par exemple, pour traiter un BIP() sonore je préfère réaliser une petite boucle avec deux delay() au lieu d’utiliser la facilité d’un tone (). Toutefois, je me suis rendu compte assez rapidement que j’aurais plus de place qu’il n’en faut pour satisfaire toutes mes envies. Aussi, j’ai dilapidé de façon scandaleuse la place disponible pour le programme dans l’ATmega328. Le programme Machine_elementaire.ino est l’exemple typique de ce qu’il ne faut pas faire. En effet, ce qui gaspille le plus de place dans un programme C++ ce sont les textes que l’on fait afficher. Non seulement ce programme est le plus « bavard » de tous ceux que j’ai concocté jusqu’à présent, mais il est truffé d’accentués. Hors chaque accentué oblige à couper le Serial.print(F( » Texte à afficher) »)) en deux appels à procédure, mais il faut en intercaler une de plus pour l’accentué sous la forme Serial.print(char(224)). Par ailleurs, au lieu de minimiser les textes affichés, j’ai profité de l’abondance pour étaler des phrases absolument pas « compactées ».
Méga radin le Nulentout.
L’ATmega328 laisse disponible 30720 octets de disponibles au programmeur pour loger son logiciel. Si on en n’utilise que la moitié, le reste ne nous sera jamais remboursé ! Aussi, je les ai payés … je les utilise ! Bon, ce n’est pas vraiment ma motivation. Personnellement, j’y vois un aspect d’élégance, c’est à dire faire traiter un maximum de choses au programme. Quand vous compilez le sketch, vous obtenez un résultat qui ressemble à celui de la Fig.50 sur laquelle on peut vérifier que j’ai consommé la totalité de l’espace disponible. C’est à mon sens le summum de l’élégance informatique au point de vue de la « rentabilité » du programme. Notez au passage qu’il n’y a aucun risque et aucune pénalité à utiliser la totalité de la place disponible pour le programme.
Aussi, tant que le compilateur ne râle pas, vous pouvez en tartiner des couches et des couches. Ce n’est que si le compilateur se fâche tout rouge comme sur la Fig.51 qu’il faut faire profil bas ! Le premier moyen et de loin le plus facile consiste à modifier les textes. Ceci dit, comme je savais que le manque de place ne serait pas un obstacle, j’ai gaspillé sans compter. En particulier il y a une foule de séquence qui se répètent. Chaque fois je me suis contenté de les coder directement, au lieu de faire une procédure utilisé plusieurs fois. J’insiste : Pour les donnée, c’est du bon travail, pour le reste j’ai donné libre cours à une paresse intellectuelle condamnable …
La PILE : Tout programmeur doit s’en préoccuper.
Visitons un court instant l’intimité du fonctionnement d’un programme. Chaque fois que le logiciel fait appel à une procédure, le microcontrôleur sauvegarde sur la PILE l’adresse de retour pour pouvoir continuer quand cette dernière est terminée. Si une procédure en appelle une deuxième, qui en invoque une troisième, toutes ces adresses de retour s’empilent les unes sur les autres. Puis, en retour de « subroutine » elles sont dépilées et l’emplacement sur la PILE libéré. Chaque fois qu’une procédure utilise une variable locale, on réserve de la place sur le TAS. Plus il y a de variables locales dans les procédures qui s’invoquent les une et les autres, plus on va entasser des octets de données. Naturellement, en retour de subroutine on libère la place sur le TAS. La Fig.52 schématise le fonctionnement interne du microcontrôleur pour la gestion des variables dynamiques. Le haut de la zone en vert est réservé à la PILE qui progresse « vers le bas ». Par exemple en 1 on a empilé deux adresses de retour. Quand la deuxième subroutine se termine, son adresse de retour est récupérée et l’emplacement libéré en 3. En 2 ont été entassées deux variables locales. En retour d’une subroutine, en 4 la place de la variable qui n’est plus utilisée est libérée. Au cours du programme, la zone non utilisée jaune se rétréci à chaque appel de procédure ou de fonction, et se réduit pour chaque variable locale. Si l’on a trop de procédures qui appellent des procédures et qui entassent des donnée locales trop volumineuses, arrive un moment ou il y a, comme en 5, COLLISION de PILE. Le TAS et la PILE se rencontrent, l’un écrasant les données de l’autre. Le programme devient totalement incohérent et se met à faire n’importe quoi. C’est un problème particulièrement sournois, car strictement rien ne prévient le programmeur. Par exemple dans une procédure vous remplacez une variable locale qui était un byte par un int. Imaginons que le contexte à empilé et entassé tellement de données qu’il ne restait plus de place. Maintenant la variable locale fait un octet de plus. Sur un RUN se produit alors la collision tant destructrice. Votre programme se met à faire n’importe quoi. Vous avez beau analyser, analyser et analyser encore, le croquis est propre et en aucun cas ne peut expliquer le comportement du logiciel.
CONCLUSION : Il faut impérativement s’assurer que ça n’arrivera pas.
Prendre une assurance vie pour la PILE.
Parer le risque de COLLISION de PILE est tellement important, que le C++ d’Arduino met à notre disposition un outil spécial pour évaluer les risque encourus. Aussi, chaque fois que je termine un gros logiciel qui invoque des subroutines à la pelle, avec des variables locales nombreuses, avant de considérer qu’il est fiable j’en teste la pile, ou plus exactement l’amplitude de la zone jaune qui reste quand le programme a fait son RESET. Le petit programme qui effectue ce travail consomme environ 140 octets. Aussi, sur un logiciel avec aucun gaspillage il n’y a pas la place. Je me contente de passer en remarque certains affichages, et valide la procédure. Puis elle repasse en remarques et je restitue les affichages. Dans notre application, vu que je gaspille à outrance, l’outil d’évaluation de la PILE est resté actif et de plus encombre le MENU des OPTIONS.
MANIPULATIONS :
1) Faire un RESET puis « o » pour passer dans le Menu des OPTIONS.
2) Commande « w » et obtenir le résultat de la Fig.53 qui annonce la place qui reste dans la zone jaune à ce stade précis du déroulement du programme. (Adresse de retour de la procédure de « calcul » comprise et entassée dans la PILE.)
Une petite astuce logicielle.
Comme déjà précisé, la rédaction d’un livret décrivant les routines principales n’est pas estimé pertinent pour cette application. Toutefois, et si mes libertés ludiques le permettent, j’envisage de développer une deuxième version qui sera totalement autonome et qui n’imposera plus l’utilisation d’un ordinateur pour dialoguer avec la machine. Ce sera une application plus importante car il faudra ajouter un afficheur, une clavier et créer un coffret bien plus complexe. Une telle réalisation si elle voit le jour méritera un document informatique de type livret. Il est clair que toutes les routines de l’interface HOMME / MACHINE seront à créer. Mais celles propres au traitement des données de la Machine de Turing seront reprises sans modification. On peut donc croire que dans un avenir pas trop lointain le manque de description logicielle sera comblé.
Transformer une lettre minuscule en majuscule est très fréquent quand on programme des applications qui impliquent du dialogue HOMME/MACHINE. Hors, je n’ai pas trouvé d’instruction qui en C++ d’Arduino effectuerai ce transcodage. Une telle modification est prévue pour des chaînes de caractères, (Supposées codées en ASCII.) mais pas pour des données de type char. Il existe toutefois un moyen très simple d’y parvenir. En effet, quand on observe une table de codage en ASCII on constate que la seule différence entre lettre MAJuscule et lettre MINuscule se situe sur le sixième BIT, celui de poids décimal 32. La technique consiste à enlever 32 au code ASCII du caractère par l’entremise d’un ET logique. Exemple :
if ((Caractere > 96) && (Caractere < 123)) Caractere = Caractere & 223;
Caractere >= a Caractere >= z Caractere = Caractere – 32;
La suite est ici.