
Bien que non précisé dans la liste des conseils pour programmer avec méthode, car la page est bien assez remplie, ce serait dommage dans notre cheminement expérimental de ne pas aborder ce sujet, car forcément un jour ou l’autre vous risquerez d’en être la victime. Explications :
Phénomène particulièrement sournois, la collision de PILE survient brusquement sans qu’aucun signe avant-coureur ne nous prévienne. Pour comprendre de quoi il s’agit,
il faut entrer dans la vie intime du fonctionnement des microcontrôleurs. Il serait hors propos dans ces lignes d’étudier à la loupe l’agencement matériel de l’ATmega328 et d’en détailler finement le fonctionnement interne. Nous allons dans ce chapitre nous en tenir au strict minimum vital.
Fonctionnement de la mémoire vive SRAM.
La mémoire vive (256 + 2Ko) est généralement divisée en quatre zones :
• Les 256 premiers octets pour les registres généraux du microcontrôleur (Représentée en jaune sur la Fig.85) occupent « le bas » de la SRAM. (En « assembleur » c’était la Page zéro ».)
• La zone nommée BSS qui contient toutes les variables globales, allouées statiquement au moment de l’édition de lien lors de la compilation. La BSS est utilisée par de nombreux compilateurs pour désigner une zone de données contenant les variables statiques définies dans les initialisations, et les déclarations avant void loop().
• Le TAS sur lequel on entasse du bas vers le haut est destiné aux allocations dynamiques dans lequel on peut attribuer et libérer des blocs de mémoire. (Nommé HEAP) Le TAS se fragmente généralement au cours de l’évolution du programme, (Car une variable locale libérant de la place laisse « un trou » libre.) avec un risque notable de le rendre inutilisable.
Défragmenter HEAP par une séquence de code de type « Ramasse miettes » est faisable mais relativement dangereux, car si l’on déplace une variable en cours d’utilisation, les conséquences peuvent s’avérer ingérables.
• La PILE nommée STACK mémorise temporairement :
* Les paramètres associés à l’appel des fonctions et procédures,
* Les adresses de retour des fonctions et procédures,
* Les variables locales aux fonctions et procédures.
La PILE est une zone de mémoire commençant en haut de la SRAM qui se charge vers le bas de façon linéaire et continue lors des appels des fonctions ou des procédures. Elle se réduit vers le haut lors des retours. Chaque appel à une procédure empile l’adresse, chaque retour la dépile et libère la place. Au cours du programme, si un grand nombre de données sont sur le TAS qui est « très haut », et que l’on enchaîne un grand nombre d’appels à procédures sans retour, (Cas des subroutines récursives par exemple) il peut arriver que l’espace entre PILE et TAS devienne nul. C’est la collision et l’écrasement mutuel des OCTETs engendre un fonctionnement totalement imprévisible du microcontrôleur. Aussi, avant de considérer que notre programme est fiable, il faut impérativement vérifier que le risque de collision de PILE est dérisoire. L’expérience montre que lorsque toutes les initialisations de void setup() sont terminées, il est recommandé de ne pas avoir moins de 100 octets, car le risque de collision par fragmentation de la zone devient exagéré.
(@) : Sur la Fig.85 les noms des divers pointeurs sont imposés par le compilateur de l’IDE.
Prendre une assurance contre les collisions de PILE.
Invoquer du préventif dans ce domaine n’est pas une arme absolue. Observez le programme d’exploitation P15_Exploitation_pour_V4.ino dans lequel, en fin de procédure void setup() se trouve la séquence suivante qui affiche l’espace entre la PILE et le TAS à ce stade de l’exécution :

Le résultat sur la Fig.86 pour notre programme d’exploitation montre qu’avec une telle marge de sécurité, si notre logiciel se met à faire des choses étranges quand on l’a modifié, c’est que l’on a commis une erreur de logique, car ici la collision de PILE n’est pas vraisemblable. Notez au passage que le compilateur indique une place disponible pour les variables locales de 894 octets ce qui n’est pas directement lié avec la RAM réservée aux données dynamiques. Enfin, je vous recommande très très fortement de toujours effectuer ce test quand vous venez de terminer un programme. Pour un skech qui consomme la presque totalité des 30720 OCTETS disponibles, on peut manquer de place pour loger ce test. Il importe alors de supprimer un ou deux appels à procédures (Ce qui ne modifie pas l’évaluation) pour faire provisoirement de la place. Une fois la marge de sécurité vérifiée, vous repassez la ligne qui invoque SRAM_LIBRE en remarque et rétablissez l’appel aux deux procédures. Affaire classée !
Configuration qui produit la collision de PILE avec le TAS.
Concrètement on oublie royalement qu’un nombre important d’interruptions se produisent en tâche de fond. Quelquefois un codeur rotatif génère des interruptions, sans compter les procédures delay(), les fonctions telles que millis(), la PWM … une foule de ressources internes déclenche des interruptions. C’est transparent car c’est le compilateur C++ qui sur ces instructions fait sa cuisine interne. Arrive un moment, ou trop de données sont empilée sur le TAS et viennent écraser les adresses empilées. Puis le délimiteur ‘}‘ de fin d’une procédure ou d’une fonction demande au processeur de dépiler une adresse de retour. Comme cette dernière contient les résidus de la variable qui a « écrasé » les octets, le programme se « branche » strictement n’importe où. Étant alors sur du code objet incohérent, le comportement du logiciel devient totalement aléatoire.
PRÉVENTIF : Aucun programmeur n’est à l’abri d’une telle « catastrophe ». Aussi, pour minimiser les risques il faut placer le minimum de chaînes de caractères dans le programme car en réalité elles sont placées sur le TAS. Il faut également (Et surtout.) minimiser les tableaux.
CURATIF : Quand se produit le Scratchhh prouitchhh bom bring protchhhh ! c’est qu’il est trop tard. Nous avons placé plein plein plein de bavardages, alors que nous savons que ces « bla bla bla » sont entassés dans la mémoire dynamique. Notre démonstrateur comporte une foule de procédures et de fonctions qui passent des paramètres, sans compter les for (byte I=1, …) qui ne sont pas gratuits. Les variables locales des boucles for doivent aussi être logées en RAM. Enfin les tableaux sont gourmands en octets. Par exemple celui des caractères spécifiques consomme déjà 72 octets. Avouez qu’ici avec tous ces textes Série et LCD on a poussé le bouchon un peu loin … mais ça passe !
REMÈDE : Dégager impérativement de la place sur le TAS.
La suite est ici.