Enseignant: |
Jérôme Collin, responsable (local M-4013, poste 5060) |
|
Support technique supplémentaire: | Laurent Tremblay (local M-4011, poste 7181) | |
Chargés de laboratoire: | Section 1: |
Kais Fallouh (Lundi AM) Romain Lebbadi-Breteau (Mercredi PM) |
Section 2: |
Tristan Rioux (Mardi PM) Gaëtan Florio (Jeudi AM) |
|
Section 3: |
Luciano Garin-Iriarte (Lundi PM) Ely-Cheikh Abass (Jeudi PM) |
|
Section 4: |
Dorine Dantrun (Mardi AM) Meriam Ben Rabia (Vendredi AM) |
|
Section 5: |
Amélie Simard (Mercredi AM) Abdul-Wahab Chaarani (Vendredi PM) |
|
Section 6: |
Marc-Antoine Manningham (Mardi soir) Laurent Bourgon (Jeudi soir) |
INF1900
Projet initial de système embarqué
Travail pratique 8
Méthodes pratiques de débogage
Objectif: Accroître la capacité à détecter des problèmes logiciels et matériels
Durée: Une semaine
Travail préparatoire: travail pratique 7
Document à remettre: L’ajout d’un module de débogage au travail pratique 7 qui est à remettre avant le lundi 17 mars 17h00.
Présentation en classe: fichier PowerPoint de la présentation
«Programming allows you to think about thinking, and while debugging you learn learning.» |
|
- |
Nicholas Negroponte |
Introduction
Le débogage d’un programme. Un art ? Une science ? Un peu des deux ? On peut certainement dire que c’est une capacité qui se développe avec le temps et la pratique. Elle s’améliore avec la compréhension des systèmes et des langages de programmation. Donc, il y a nécessairement une forme d’expérience et de confiance impliquée dans son acquisition. Il faudra donc une certaine forme de patience avant de devenir habile à déboguer du code.
Est-ce qu’il y a au moins quelques règles ou conseils à mettre en place dès maintenant qui peuvent aider à cet aspect fondamental ? Absolument ! Ici, on montrera une technique un peu plus systématique et aussi quelques conseils de base importants. Et on laissera le temps faire le reste… On ne peut certainement pas vider la question en un seul laboratoire. On ne peut nier que le sujet s’applique de façon plus aiguë dans les projets intégrateurs cependant. La raison est que les systèmes à développer sont plus gros que dans les autres cours et qu’ils mettent en valeur des notions vues dans plusieurs autres cours. Le risque d’apparition de bogues dans le code d’un projet intégrateur augmente naturellement et la pression sur le développement de bonnes habiletés de mise au point du code s’accentue.
Cependant, on aura recours aussi à quelques points de départ tirés de la littérature. On ajoute aussi une partie qui est le fruit de nos observations des étudiants en projet de première année. On doit aussi dire que la qualité du code a une influence sur le processus...
Dispositions pour faciliter le débogage
Mais, justement, par où commencer ? En fait, on peut commencer par ce qu’on a déjà peut-être un peu accumulé comme expérience dans le cours depuis le début de la session pour intuitivement tenter une forme de réponse. Il est incontestable qu’il y a des dispositions qui facilitent l’analyse et la détection de bogues. Nécessairement, avoir un environnement calme autour de soi qui facilite la concentration est souhaitable. À l’autre extrême, verbaliser son problème au coéquipier est certainement valable également et peut mener directement à la solution.
Aussi, lire la documentation. On est toujours surpris de voir des étudiants qui travaillent à un poste sans description du travail pratique ni document (Atmel, AVRLibC, etc.) ouverts dans un fureteur ou autrement. En informatique, il faut lire… Une certaine maîtrise de l’environnement de développement, des outils de déverminage et même de Git aideront également.
Avant tout, il faut écrire un code lisible et compréhensible… Un code bien indenté et bien écrit est nécessairement plus facile à corriger. C’est indéniable, autant à court terme qu’à long terme par les membres de l’équipe. «Redresser» son code juste avant de le soumettre pour avoir ses points par le chargé de laboratoire lors de la correction n’est pas une bonne habitude à prendre. On se nuit à soi-même bien avant en étant incapable de faire ressortir clairement les opérations importantes du code. Le débogage devient substantiellement plus difficile par le fait même. Évidemment, l’utilisation de noms de variables et de fonctions significatifs va dans le même sens, tout comme l’écriture de commentaires appropriés.
Dans la même veine, on peut aussi mentionner une mauvaise utilisation de Git durant le développement. Souvent, au laboratoire, les chargés de laboratoire voient que le code développé n’est pas sous Git ou n’est pas soumis régulièrement. Git n’est pas d’abord un endroit où on soumet un devoir pour correction! C’est une façon de sauvegarder ses changements et de les partager entre membres d’une même équipe d’abord et avant tout. Un programmeur ne veut pas regarder Git comme un outil HPR! … Mais c’est peut-être aussi une partie de la réalité, il faut bien l’admettre. Chose certaine, les équipes qui ont du succès sont bien structurées et portent une attention particulière aux détails et à leurs habitudes de travail. C’est indéniable. Point.
Un autre problème comme on peut fréquemment observer en INF1900 est un code trop long, autant en «largeur» qu’en «longueur». Détaillons. Par largeur, on veut dire des lignes de code trop longues. Beaucoup de programmeurs encore aujourd’hui essaient de limiter leurs lignes à un nombre autour de 80 caractères (une veille limite des systèmes des années 1970, mais qui conserve son prestige!) Quand cette règle n’est pas respectée, il faut commencer à se poser des questions: est-ce qu’il serait préférable de déplacer le commentaire au-dessus de la ligne ? Expression trop longue et complexe en cause ? Appel de fonction avec des paramètres qui sont des expressions trop longues ou des paramètres qui sont eux-mêmes des appels de fonctions ? Il faut essayer de raccourcir les lignes pour conserver la lisibilité du code. Nos yeux regardent surtout du code de haut en bas, et peu de gauche à droite.
Le code peut aussi être trop long au sens où on écrit des lignes et des lignes sans même tester les premières lignes écrites. Souvent, on arrive devant un code fraîchement écrit dans le cadre du cours et qui fait 500 lignes et on ne sait même pas où il bloque et où est le problème. Souvent, le code ne compile même pas. C’est inutile. Avant d’aller voir si la ligne 478 est en cause, il faudrait peut-être s’assurer que les 10 premières fonctionnent. C’est simplement logique, mais il faut être capable de rapidement s’assurer de ce qui fonctionne et ne fonctionne pas avant même de chercher précisément où est le problème. Souvent, cette étape est la plus longue. Régler le problème peut aller très vite par la suite. Un principe important qui doit guider le processus de débogage est d’arriver à isoler le problème et d’être capable de le répliquer dans un code plus court pour les mêmes entrées et, si possible, par une séquence plus courte d’opérations. Le premier endroit pour commencer la recherche d’un problème vient de la liste des messages de la compilation parce que ça prend 10 secondes à regarder! Souvent, on a tendance à faire la commande «make install» directement sans regarder les «warnings» du compilateur qui peuvent révéler des subtilités à ne pas négliger. Faire simplement une commande «make clean», suivie d’une commande «make» et regarder ce que le compilateur a à nous dire. 10 secondes qui peuvent être payantes...
Le débogage, quelques principes reconnus
Mais est-ce que tous ces «petits trucs» s’inscrivent dans une théorie plus structurée sur le sujet ? L’article «Learning to Troubleshoot : A new Theory-Based Design Architecture» présente un bon tour d’horizon de la question. Ce qui suit en est pratiquement une traduction française des éléments les plus importants. On y apprend que le débogage est une catégorie de résolution de problème parmi les plus communes. Le débogage a des caractéristiques reconnues :
Il n’est pas toujours bien défini parce que la personne qui fait la mise au point doit déterminer l’information pour établir le diagnostic. Exemple : est-ce que le multimètre peut m’aider à résoudre ce problème ?
Une bonne représentation conceptuelle du système est requise. Exemple : est-ce que l’interruption de la minuterie 1 est bien ajustée dans ce code ?
Il y a généralement un seul problème, mais ceci n’exclut pas un ensemble de fautes dans le système.
Il y a une solution connue qui règle ce problème de façon assez simple. En d’autres mots, ce n’est pas toute la conception qui est à revoir, mais plutôt un ajustement. Autrement, toute la solution est à revoir et on n’est plus dans le débogage.
Il est plus difficile pour les débutants d’apprendre le débogage, car ses règles de diagnostic sont basées sur l’expérience.
Demande à un apprenant ou une apprenante de prendre une décision sur la nature du problème.
Varie beaucoup d’un système à l’autre ce qui est toujours difficile.
En progressant de débutant à expert dans le domaine, une personne en situation d’apprentissage s’enrichira de modèles conceptuels (mental) des systèmes sur lesquels elle travaille. Plus ces situations seront nombreuses, plus les solutions arriveront rapidement parce qu’il y aura eu une intégration personnelle des connaissances du domaine, d’informations contextuelles et souvenir de problèmes semblables affrontés par le passé. Souvent même, ces souvenirs deviendront pratiquement le principal outil de débogage!
Alors, sur quels axes doivent s’étendre les habiletés de débogage à développer ? Il y a quelques généralités bien identifiées :
Une bonne connaissance du domaine en général, le génie informatique et le génie logiciel dans notre cas, bien entendu.
Une connaissance du ou des systèmes impliqués (la carte et sa programmation particulière dans ce cours).
Connaître les procédures de débogage (par des tests, des mesures, des évaluations de performances, etc.) pour attaquer le problème.
Avoir une connaissance de bonnes stratégies de débogage. Nécessaire pour isoler les problèmes potentiels, tester et évaluer des hypothèses et réduire l’espace du problème.
L’apprentissage (à la dure, parfois…) par l’expérience et la pratique. On n’y échappe pas vraiment, malheureusement.
Depuis le début de la session, il y a nécessairement eu amélioration des deux premiers éléments. La démarche qu’on vous propose dans la section qui suit s’inscrit vraiment dans la troisième, connaître les procédures de débogage. En plus, elle sera suffisamment puissante et générique pour établir de bonnes stratégies de débogage par la suite.
Parmi les grandes stratégies de débogage, on peut en mentionner ces cinq :
Essaie et erreur : se passe presque d’explication tellement elle est intuitive… On change un paramètre et on regarde l’effet en sortie. On en change un autre et on recommence.
Stratégie exhaustive : si on sait qu’il peut y avoir 6 grandes situations typiques par exemple, on les essaie toutes jusqu’à trouver le problème. Appelé aussi élimination en série. Peut fonctionner pour les systèmes simples.
Par topologie : dans le code, pour nous, si on détecte un problème avec une partie de code, on essaie de suivre ce qui est appelé par ce code ou ce qui appelle ce code. On procède donc en amont (backward) ou en aval (forward).
Séparation en deux : on isole ce qui est connu pour fonctionner de ce qui peut être douteux. En itérant sur ce qui est douteux, on espère ce convaincre qu’on arrive à confirmer de plus en plus de sections comme étant fonctionnelles et que le problème sera dans une portion de moins en moins grosse et deviendra isolée, voir évidente.
Détection par irrégularité fonctionnelle : peut-être un peu plus difficile à cerner, mais peut-être aussi la plus avancée. Il vise à établir une différence entre ce qui est attendu et ce qu’on obtient comme résultat. On vise ici à trouver une série de combinaisons d’entrées qui mènent de façon répétée à cette différence. Peut rapidement devenir complexe avec les logiciels. Elle nécessite un bon doigté et de la patience, car établir la séquence parfaite peut s’avérer difficile. Par contre, le test, si bien reproductible, peut mener directement au bogue.
En général, la recherche a montré que, basé sur l’expérience, la stratégie qui vise à trouver les bogues les plus communément rencontrés est généralement payante et celle qui est aussi la plus souvent employée par les gens d’expérience. On peut ajouter que les personnes les moins bonnes à déboguer sont celles qui établissent les moins bonnes hypothèses, les poursuivent trop longtemps, ont de la difficulté à reconnaître une information critique et utile en cours de chemin, établissent moins de tests significatifs et négligent également la vérification de leurs résultats du travail effectué.
Ceci complète ce petit tour de la théorie. On peut maintenant regarder une stratégie plus précise pour le laboratoire.
Démarche plus systématique pour le code
Bien entendu, lorsqu’on se retrouve dans un processus de débogage, on se dit à peu près toujours qu’il serait utile de savoir l’état de la variable X à tel moment donné durant l’exécution du programme. Par contre, assez souvent, la paresse nous gagne, car on sait que la démarche implique d’ajouter des printf (cout ou valeurs retournées par RS232, …) dans le code pour y arriver. De plus, si on se trompe de variable, on doit tout reprendre. On sait aussi qu’on devra enlever tout ce code avant la remise puisqu’il n’est là que pour régler ce problème ponctuel.
Tous les programmeurs ont fait face à cette situation qui se présente à peu près toujours durant une démarche de déverminage de code. Ce problème a-t-il une forme générale de solution qui peut être appliquée systématiquement ? Certainement ! En fait, pour être précis, ce qui est cherché ici est une invocation de la commande make qui recompilerait le code avec des instructions de débogage pour faire sortir ces valeurs de variables à divers moments durant l’exécution. Par contre, la commande make habituellement exécutée remettrait le code tel qu’il était avant après le débogage. Cet idéal peut s’obtenir, mais il faut travailler un peu pour l’obtenir...
Avant, regardons cet extrait de code avec des «macros» du langage C/C++:
#define DEBUG // à mettre en commentaire ou non au besoin
#ifdef DEBUG
# define DEBUG_PRINT(x) printf (x) // ou par RS-232
#else
# define DEBUG_PRINT(x) do {} while (0) // code mort
#endif
Ces quelques lignes montrent qu’on peut créer une «fonction» (on aime jamais vraiment le terme lorsqu’on parle de macros…) qui serait tout simplement un printf (ou des instructions en RS232 dans notre cas) pour retourner au PC des valeurs pouvant être analysées pour aider au débogage. La même fonction pourrait aussi être remplacée par un code bidon (souvent appelé «code mort») qui sera éliminé par le compilateur avant de générer le fichier binaire. De cette façon, il n’y aura aucun surcoût à l’exécution.
Alors, comment utiliser un tel mécanisme dans le code maintenant ? Ici, selon la situation, on pourra faire preuve de créativité. Dans une situation où on utilise à la base un printf, on peut avoir beaucoup de souplesse dans l’utilisation des arguments pour un usage pouvant convenir à plusieurs contextes différents. Par exemple:
DEBUG_PRINT(("INFO: Génération de statistiques activée"));
DEBUG_PRINT(("var1: %d; var2: %d; str: %s\n", var1, var2, str));
On notera au passage les doubles parenthèses: une pour la fonction printf et une pour l’appel à la macro DEBUG_PRINT elle-même. C’est une particularité de la syntaxe dont il faut tenir compte pour éviter les ennuis. Dans un petit code de système de système embarqué sur le robot, on peut avoir une formule un peu plus simple avec seulement deux arguments fixes: une chaîne de caractères et un entier par exemple.
Le choix du comportement de DEBUG_PRINT dépend de la valeur de DEBUG. Ici, on ne fait que tester si DEBUG existe ou non. Dans des systèmes plus complexes, on peut aussi avoir une valeur possible de DEBUG de 1, 2, 3, 4 ou 5 pour augmenter l’information en sortie. On dit souvent «contrôler le niveau de verbosité» à afficher. Ce même contrôle peut aussi se faire avec une plus significative: FATAL, ERROR, WARN, INFO, DEBUG, TRACE par exemple. Dans de tels cas, il peut y avoir plusieurs définitions de la macro DEBUG_PRINT pour couvrir l’ensemble des besoins d’information pour chaque type de message à produire en sortie.
La question devient maintenant comment ajuster la valeur de DEBUG ? On peut avoir à éditer à chaque fois le fichier pour mettre ou non en commentaire la première de ces lignes pour avoir l’une ou l’autre des variantes de DEBUG_PRINT. Mais on peut être encore plus systématique en passant l’argument -DDEBUG (oui, deux «D» de suite, -D suivi de DEBUG) à l’invocation du compilateur GCC pour obtenir la définition de DEBUG ou non. L’argument sur la ligne de commande devient donc intéressant. Par contre, on ne fait que déplacer le problème puisqu’on doit maintenant éditer manuellement à chaque fois le Makefile plutôt que le fichier source… Évidemment, ce problème peut être résolu. Il suffit d’ajouter une ou quelques règles au Makefile pour y arriver et invoquer sur la ligne de commande. Ce lien montre diverses façons d’y arriver:
% make debug
En résumé, on veut placer des appels à DEBUG_PRINT en divers points stratégiquement bien choisis dans le code pour tirer de l’information pour aider au débogage. On laisse ces appels de façon permanente dans le code. On les «active» en situation de débogage (avec make debug) ou ces appels tournent en du code qui, ultimement, sera éliminé par le compilateur et ne générera donc par de surcoût à l’exécution lorsqu’on est en mode normal de fonctionnement. On peut placer la définition de DEBUG_PRINT dans notre librairie, car son potentiel de réutilisation est élevé puisqu’on peut en avoir besoin dans de très nombreuses situations de développement de code d’application quelconque.
Problèmes courants
Normalement, lorsqu’un problème logiciel se présente avec le robot, le code est la source du problème, plus souvent que le matériel. On peut, trop tenir de statistiques, mais sans trop se tromper non plus, affirmer qu’il y a quelques catégories courantes de problèmes plus fréquentes que d’autres:
Des variables non initialisées ou peut-être surtout mal réinitialisées en cours d'exécution par la suite si on réutilise la variable.
Confusion dans la direction d’un port, surtout lorsque le port en question est partagé par plusieurs périphériques (exemple pour le port D: utilisation combinée du RS-232, du bouton-poussoir et de la minuterie 1)
Débordement d’une valeur de 8 bits après incrémentation dépassant 255.
Comparaison entre une valeur de 16 bits et une de 8 bits. Dangereux…
Des situations de logique inversée. Le signal attendu est actif au niveau bas et non au niveau haut. Ceci peut se présenter aussi avec les signaux d’entrée, même avec des interruptions!
Cavalier en position (ou non) qui nuit à l’usage du port (par le périphérique ou pour l’usage général). Même les connexions dans SimulIDE doivent être vérifiées...
Voici quelques autres situations pour exercer votre œil à rapidement repérer des problèmes courants sur le robot, que ce soit dans le code ou avec le matériel. Pouvez-vous les cibler directement ?
Exercice à travailler en laboratoire
Il faut mettre un tel mécanisme en place dans votre librairie. Il faudra créer un fichier avec un nom significatif comme debug.h (et probablement aussi debug.cpp) et les incorporer à votre librairie. Ces deux nouveaux fichiers n’ont pas pour buts d’aider le débogage de la librairie elle-même, mais plutôt de l’application qui utilisera la librairie éventuellement. Il est plus rare qu’on ait à analyser ce qui se passe dans une librairie en tant que développeur d’application. Par contre, c’est vrai, rien n’empêcherait d’ajuster le mécanisme pour être capable de mettre au point une librairie en développement, mais c’est un peu hors de notre propos. En conséquence, on ne compilera pas la librairie avec la commande «make debug». La seule modification à faire au Makefile du code de librairie est de prendre en charge les nouveaux fichiers debug.h et debug.cpp.
Du côté de l’application qui utilisera la librairie, il faudra par contre ajuster le Makefile pour qu’il puisse régler la valeur de DEBUG et la passer correctement au compilateur (mais pas à l’éditeur de liens, car, on le sait, ce serait inutile). Ce mécanisme deviendra générique et utile dans pratiquement toutes les situations (et pas seulement sur le robot!), d’où son intérêt à l’intégrer à même sa propre librairie. Il sera fort utile pour le reste du projet, bien évidemment.
On vous suggère de reprendre le code de la machine à états logicielle du travail pratique 2 pour renvoyer au PC par RS232 la valeur de la variable qui maintient l’état de la machine. Cet exercice permettra de vérifier que votre mécanisme de débogage fonctionne correctement.
Pour la remise, ne pas créer de répertoire tp/tp8 mais ajouter simplement vos quelques fichiers dans le répertoire tp/tp7.
Qualité du logiciel
Comme il y a moins de code C++ à écrire cette semaine, on n’introduit aucune nouvelle règle de qualité du code avec ce travail pratique. Par contre, le lien entre la qualité du code a une influence évidente du le débogage du code, tel qu’expliqué plus haut. Il n’est donc jamais trop tard, puisqu’on continue de travailler sur la librairie de la semaine précédente, de modifier le code de l’équipe pour le rendre de meilleure qualité et de continuer de progresser dans l’établissement de bonnes habitudes de travail en développement de code.
Pouvez-vous identifier le problème rapidement ?
Juste quelques petites «faciles» (hummm…) en passant...
Problèmes logiciels
Quelques extraits de code où vous devriez être capable de rapidement repérer le problème ou profiter de conseils supplémentaires...
Extrait de programme 1:
. . .
switch (etat) {
case INIT:
if ( (PIND & 0x04) == 0 ) {
etat = ETAT1;
break;
}
case ETAT1:
if ( (PIND & 0x04) != 0 ) {
etat = ETAT2;
}
break;
case ETAT2:
if ( (PIND & 0x04) == 0 ) {
etat = INIT;
}
break;
default:
etat = INIT;
}
. . .
Très subtile comme erreur… Pouvez-vous le voir rapidement ?
Extrait de programme 2:
. . .
int main()
{
DDRA = 0xFF;
char vec[5] = { 'H', 'e', 'l', 'l', 'o' };
uint16_t i = 0;
initialisationUART();
for(;;) {
PORTA = 0x01;
vec[i++] = i;
transmissionUART ( vec[i] );
// on devrait quitter ce programme éventuellement... mais non...
if (i == 50) {
PORTA = 0x00;
return 0;
}
}
return 0;
}
Oui, il y a bien entendu débordement du vecteur. C’est intentionnel. Sur un PC, ce genre de code risque de peut-être provoquer un message d’erreur assez rapidement ou, au moins, les sorties générées risquent de provoquer un questionnement plus rapidement dans la tête du programmeur. Sur la carte mère, sans système d’exploitation, aucun message n’est généré. Dans des cas comme celui-ci, le fait d’éventuellement dépasser certaines zones de mémoire risque de provoquer un «reset» du processeur ce qui donne l’impression de ne jamais sortir de la boucle. Ou pire, d’en sortir seulement de temps à autre sans qu’on soit capable d’en reproduire les conditions à coup sûr… Très difficile à détecter de façon générale.
Le même symptôme peut se présenter lorsqu’une interruption est activée, mais n’est pas prise en charge par une routine identifiée par le bon type d’interruption. Pas mauvais à savoir...
Extrait de programme 3:
Ceci n’est pas une ligne fautive au sens où elle s’exécute correctement, mais elle devient très longue… (133 caractères!) Simplement le fait de placer le commentaire au-dessus de la ligne de programmation rendrait la lecture du code beaucoup plus simple. Simplifier l’expression de test de boucle est évidemment aussi à considérer.
while (((adresse/16) != nbInstruction ) && (instruction != fin)){ //Lire et executer toutes les instructions jusqu'a rencontrer une fin
Cette ligne de code a le même problème, mais il peut être réglé différemment. En effet, il est toujours possible de place un «\» en fin de ligne en C/C++ pour permettre de poursuivre sur la ligne suivante. Cette ligne pourrait donc être présentée plus lisiblement sur quelques lignes sans changer quoi que ce soit à l’initialisation du vecteur.
uint16_t vect[] = {11, 16, 12, 11, 1239, 147, 132, 465, 975, 85, 3196, 6208, 220141, 23, 22947, 26122, 2, 29422, 31, 31, 3999, 4470, 39222, 4, 40, 66, 944, 52123, 5324, 57, 61212, 959, 1740, 84, 0, 12880};
Extrait de programme 4:
uint8_t OCR0A_500HZ = uint8_t((F_CPU / (2.0 * 256 * 500)) – 1);
Cette ligne ne présente pas vraiment, un problème en soit, mais elle fait un grand calcul avec des valeurs flottantes. Pensez un peu à votre pauvre petit processeur de 8 bits sans unité matérielle de calcul point flottant… Dans un contexte de système embarqué, demandez-vous s’il n’y a pas moyen de faire les choses autrement. Combien de fois est fait ce calcul ? À quel moment dans l’exécution du programme ? Est-ce que certaines valeurs pourraient être précalculées ailleurs ?
Problèmes matériels
À partir de ces photographies, sans même prendre en considération le code, il devrait y avoir un problème évident associé au matériel dans chaque cas. Quel est-il ?
Dans le cas de la dernière image, il n’y a pas vraiment de problème comme tel, mais le fait de laisser un périphérique branché aux ports B5, B6 et B7 durant que la carte est programmée (make install), peut engendrer un conflit avec le ATmega8 qui travaille à placer le code dans la flash du ATmega324PA en utilisant ces mêmes signaux. Souvent, la conséquence est que le ATmega324PA perd sa configuration (les «fuses» ne sont plus aux bonnes valeurs). La manifestation visible sera que la carte paraîtra 8 fois plus lente, car le ATmega324PA utilisera son horloge interne à 1 MHz plutôt que l’horloge externe à 8 MHz. Les instructions de délais seront donc faussées, mais on pourra mettre bien du temps avant de s’en apercevoir...