Microcontroller Debugging Techniques

Les microcontrôleurs vous font-ils peur ? Domptez-les !


Le microcontrôleur (µC) est une invention brillante, mais avant qu’il fasse exactement et seulement ce que vous voulez, il peut circuler bien du courant dans ses lignes d’alimentation. La plupart des problèmes se ramènent à l'une des deux causes suivantes : vous avez mal compris une caractéristique du µC ou vous avez laissé passer une erreur dans le code. Eh oui, c’est forcément toujours de votre faute !
Quand ça coince, on aimerait tellement ouvrir comme le font les chirurgiens. Regarder à l'intérieur, c’est le rêve utopique, ça n’existe pas. Il existe en revanche d’excellents outils de diagnostic, des interfaces de débogage intégrées qui permettent à la fois de programmer les µC (flasher) et de les déboguer. Le matériel de débogage de base réside sur des cartes de développement bon marché. C’est bien, mais ça ne résout pas toujours tout. Voici donc quelques ficelles de débogage des microcontrôleurs.

Faire clignoter une LED ou basculer une broche

Pour arriver à savoir si une section de code, une routine d'interruption par exemple, est exécutée, il est utile d'insérer un bout de code de diagnostic qui allume une LED. Ainsi vous saurez que le µC est bien passé par là. Vous pouvez faire ça à différents endroits précis du code, mais très vite cette méthode perd de son efficacité, car il faut pouvoir distinguer avec certitude la section du code qui a modifié l'état de la LED.
 
pinMode(LED_BUILTIN, OUTPUT);

digitalWrite(LED_BUILTIN, HIGH);

digitalWrite(LED_BUILTIN, LOW);

Premier exemple de code  : La LED standard des cartes Arduino peut être commandée à l'aide des fonctions digitalWrite et pinMode.

Une autre difficulté vient du fait que le code s'exécute si rapidement que l'allumage et l'extinction de la LED restent imperceptibles. Si vous avez un oscilloscope ou un analyseur logique à portée de main, vous pouvez, au lieu de commander une LED, simplement faire basculer une broche d'entrée/sortie polyvalente (GPIO) libre comme témoin de l’exécution d’un bout de code. Différentes broches peuvent ainsi être attribuées à des sections spécifiques du code ou à différentes routines d'interruption. 
 
Certains problèmes sont liés à des questions de chronologie des signaux. Le fait d’insérer quelques lignes de code de diagnostic peut suffire à résoudre parce que ces lignes modifient un peu la chronologie. Dans ce cas, le problème serait résolu, mais vous ne comprendriez pas pourquoi ! Pour éviter d’influencer la chronologie des signaux, efforcez-vous de ne jamais insérer qu’un code de diagnostic aussi court que possible, en utilisant l'assembleur en ligne et des commandes simples comme le basculement de bit (bit-toggle).
 
__asm__ volatile ("btg latb, #0");

Deuxième exemple de code : Pour faire basculer une broche, une instruction de basculement de bit à cycle unique avec un assembleur en ligne utilisant le compilateur XC16 de Microchip Technology PIC24/dsPIC

Sortie sérielle

La méthode la plus confortable pour obtenir des informations de l'état du code consiste à  utiliser l'interface série appelée UART. La célèbre commande printf() peut être couplée à une interface UART disponible, mais le code sous-jacent sollicite assez fortement le processeur, surtout si les valeurs des données doivent être formatées dans la chaîne de sortie.
 
La mise en œuvre du programme Arduino Serial constitue un compromis entre formatage des chaînes et charge du processeur. Elle copie rapidement le texte ou les valeurs numériques souhaitées dans une mémoire tampon en SRAM, en utilisant les interruptions UART pour transférer les données sur le PC du concepteur. C’est moins rapide que le basculement des GPIO et l’impact sur le temps d'exécution du code est sensible. Cependant, la richesse des informations reçues peut apporter plus de clarté sur le problème à résoudre, notamment lorsque le doute porte sur une variable ou le contenu d’un registre.
 
Serial.begin(115200);

Serial.println(“Button pressed...”);

Serial.println(“Value: ”);
Serial.write(dataByte);

Troisième exemple de code : Les chaînes alphanumériques donnent un meilleur aperçu de l'exécution du code, rendu possible grâce à la bibliothèque Serial sur les cartes Arduino.

Interfaces de débogage

La plupart des µC modernes disposent d’un module de débogage sur puce. Celui-ci permet d'accéder à tous les circuits internes, y compris la mémoire, les registres de l'unité centrale et tous les périphériques. Il peut même démarrer et arrêter l'exécution du code ou réinitialiser l'appareil. Le logiciel de conception sur le PC peut également être utilisé pour définir des points d'arrêt, en marquant une ligne de code spécifique avec un drapeau pour le débogueur. Lorsque cette ligne de code est atteinte, l'exécution s'arrête et l'état de toutes les mémoires et de tous les registres est conservé. À partir de là, le concepteur peut vérifier si toutes les variables et tous les paramètres correspondent à ses attentes.
 
Une grande partie du code s'exécute sans qu’il se passe quoi que ce soit de mesurable en dehors des entrailles du µC. Vous doutez par exemple de l’appel d’une routine d'interruption après une temporisation. Dans ce cas, créer une variable et en incrémenter la valeur permet de déterminer la fréquence, si tant est qu'il y en ait une, à laquelle le code est exécuté. Il suffira de vérifier le contenu de la variable lorsqu'un point d'arrêt est atteint ou que le µC est arrêté par un autre moyen. L’impact d’un tel code de diagnostic sur le temps d’exécution devrait rester négligeable, car il se réduit généralement à une simple instruction d'incrémentation en assembleur. Pour que cela fonctionne, pensez à définir la variable de comptage comme volatile, car les compilateurs ont tendance à optimiser les variables dépourvues de but précis, notamment dans les routines d'interruption.
 
volatile unsigned int counter = 0;

ISR(TIMER0_OVF_vect) {
        counter ++;

}

Quatrième exemple de code : création d'une variable de comptage dans une routine d'interruption sur un µC AVR.

Code de traçage

La ficelle de débogage la plus efficace est le traçage. Il combine l'interface de débogage et éventuellement d'autres broches spécifiques pour suivre à la trace toutes les instructions exécutées et en informer le logiciel de conception. Les variantes de cette fonction sont nombreuses. Le résultat obtenu permet littéralement au concepteur de reconstituer le trajet du processeur à travers le code, y compris les interruptions, et de voir l’ordre dans lequel les fonctions ont été appelées. L'interface Aurora d'Infineon sur les µC Aurix peut transférer ces informations à des vitesses de l'ordre du gigabit.
 
Quand on ne dispose pas de cette fonction de traçage matériel, il reste la possibilité de l’imiter par instrumentation. Cela ressemble à l’émission déjà évoquée de chaînes de texte via l'UART, mais il faut en réduire l’impact sur le temps d'exécution. Certains µC, ainsi que MPLAB X, prennent en charge cette fonction de traçage, en utilisant les interfaces de débogage ou SPI pour transférer les messages.
 
TRACE(id);
LOG(id, variable);

Cinquième exemple de code : MPLAB X prend en charge le traçage instrumenté et l'enregistrement des valeurs de variables et de registres.

Le débogage des microcontrôleurs simplifié

En fait, la recherche des causes du dysfonctionnement du code sur un µC peut se révéler très gratifiante. Les méthodes utilisées ne doivent pas avoir un impact sur le code au point de faire disparaître le problème comme par magie. Les µC les plus évolués, avec du matériel de débogage intégré, fournissent le meilleur aperçu de ce qui s’y passe, mais ils coûtent plus chers et il faut du temps pour apprendre à bien s’en servir. Si vous n’avez pas les moyens, vous trouverez toujours une modeste broche GPIO et une LED qui peuvent être tout aussi efficaces pour résoudre bien des problèmes.