Bibliothèque assert

Comme dans la majeure partie des bibliothèques, l'AVR Libc définit une macro assert qui prend en paramètre l'expression à évaluer :

#define assert(expression)

La macro évalue l'expression. Si le résultat est vrai, l'exécution du programme continue. Si le résultat est faux, elle est interrompue. Un message de diagnostic est écrit sur la sortie d'erreur standard et la fonction abort() est appelée.

La fonction abort() désactive les interruptions puis se termine par une boucle infinie.

La plupart des applications utilisant l'AVR Libc ne disposent pas de sortie d'erreur standard. Par conséquent, aucun message imprimable n'est généré par l'assertion par défaut. Il est toutefois possible de modifier ce comportement en définissant la macro __ASSERT_USE_STDERR juste avant l'inclusion du fichier d'entête assert.h :

#define __ASSERT_USE_STDERR

#include <assert.h>

Pour récupérer les informations de diagnostic produites par l'assertion, il est nécessaire de définir une fonction conforme au prototype de fonction externe déclaré dans le fichier assert.h :

extern void __assert(const char *__func, const char *__file, int __lineno, const char *__sexp);

Par exemple :

void __assert(const char *__func, const char *__file, int __lineno, const char *__sexp) {
    // Process diagnostic informations.
    // ...

    // Abort program execution.
    abort();
}

Plutôt que de rester silencieuse, l'assertion redirige les informations de diagnostic vers la fonction _assert.

Les informations de diagnostic disponibles sont :

  • __func, le nom de la fonction dans laquelle une assertion a échoué ;
  • __file, le nom du fichier contenant la fonction ;
  • __lineno, le numéro de la ligne où se trouve l'assertion ayant échoué ;
  • __sexp, l'expression évaluée par l'assertion.

Parce que le rôle des assertions est d'aider au débogage lors des phases de développement et de test, il est possible de les désactiver en prodution en définissant la macro NDEBUG :

#define NDEBUG

Ou à l'aide de l'option de compilation -DNDEBUG.

Mise en œuvre

Le point de départ de cette mise en œuvre est le sketch blink livré avec l'environnement Arduino. On propose de provoquer l'échec d'une assertion et de transmettre les informations de diagnostic via la liaison série.

On définit la macro __ASSERT_USE_STDERR afin de récupérer les informations de diagnostic (ligne 1). Juste avant d'inclure le fichier d'entête de la bibliothèque assert (ligne 3).

#define __ASSERT_USE_STDERR
 
#include <assert.h>

On initialise la liaison série (ligne 15).

// Pin 13 has an LED connected on most Arduino boards.
// give it a name:
int led = 13;

// the setup routine runs once when you press reset:
void setup() {                
    // initialize the digital pin as an output.
    pinMode(led, OUTPUT);  

    // initialize serial communication at 9600 bits per second.
    Serial.begin(9600);  
}

La LED va clignoter 3 fois puis on provoque un échec de l'assertion (ligne 27).

// the loop routine runs over and over again forever:
void loop() {
    for (uint8_t i = 0; i < 3; i++) {
        digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
        delay(1000);               // wait for a second
        digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
        delay(1000);               // wait for a second
    }
    
    assert(false);
}

On récupère les informations de diagnostic et on les transmet via la liaison série (lignes 32 à 36). Puis on interrompt l'exécution du programme en appelant la fonction abort() (ligne 37).

// handle diagnostic informations given by assertion and abort program execution:
void __assert(const char *__func, const char *__file, int __lineno, const char *__sexp) {
    Serial.println(__func);
    Serial.println(__file);
    Serial.println(__lineno, DEC);
    Serial.println(__sexp);
    Serial.flush();
    abort();
}

Le moniteur série affiche les informations transmises par l'Arduino :

assert.png

Une assertion a échoué dans la fonction loop, ligne 31 du fichier assert.cpp.

À noter que la ligne indiquée par l'assertion n'est pas celle du sketch mais celle du fichier source généré par le processus de compilation Arduino. En effet celui ajoute systématiquement l'inclusion du fichier d'entête Arduino.h ainsi que les prototypes des fonctions setup, loop et _assert.

[Télécharger le sketch complet]

Conclusion

En environnement embarqué, le débogage est souvent rendu difficile par le manque de visibilité. L'utilisation des assertions permet d'être un peu moins aveugle en vérifiant des conditions aux différents endroits susceptibles du poser problème.

Si cet exemple propose de transmettre les informations de diagnostic via liaison série, cette solution n'est pas optimale. En effet, le traitement d'une assertion doit être le moins intrusif possible. L'état du système étant à priori critique, il est inutile d'ajouter de nouveaux problèmes potentiels...

Un autre solution consisterait à enregistrer ces informations en mémoire EEPROM pour ensuite les lire à l'aide d'un outil tel qu'AVR-DUDE.