Aller au contenu

Debug et instrumentation

Trouver le bug qui ne se reproduit qu'en production à 3h du matin — les outils et méthodes du debug embarqué professionnel.


La spécificité du debug embarqué

Contrairement au debug logiciel classique où l'on peut attacher un debugger à n'importe quel processus, le debug embarqué nécessite une interface matérielle dédiée entre la machine de développement (host) et le système sous test (target). Le code tourne sur un silicium souvent inaccessible physiquement, avec des ressources limitées et des contraintes temps réel qui interdisent de s'arrêter trop longtemps sur un point d'arrêt.

La maîtrise du debug embarqué est un différenciateur clé entre un développeur junior et un ingénieur firmware senior.


JTAG — le standard universel

JTAG (Joint Test Action Group, IEEE 1149.1) est le standard de debug et de test des circuits intégrés depuis 1990. Il définit un protocole série synchrone sur 4 fils minimum (TCK, TMS, TDI, TDO) formant une chaîne de scan traversant tous les composants du circuit.

Signaux JTAG :

Signal Rôle
TCK Clock — cadence le protocole
TMS Test Mode Select — navigation dans la machine d'états
TDI Test Data In — données envoyées vers la cible
TDO Test Data Out — données lues depuis la cible
TRST Test Reset (optionnel) — réinitialisation asynchrone

Connecteurs standardisés :

  • ARM Cortex 20 broches (0.1" pitch) : le standard historique, présent sur les cartes de développement.
  • ARM Cortex 10 broches (0.05" pitch) : format compact pour la production.
  • TAG-Connect : connecteur sans en-tête, brochage direct sur les pads de la PCB — économise l'espace et le coût.

Chaîne JTAG multi-composants :

graph LR
    Host --> JLink["J-Link"]
    JLink -->|TCK/TMS/TDI| MCU["MCU"]
    MCU -->|TDO| FPGA["FPGA"]
    FPGA -->|TDO| GND["GND"]

    style MCU stroke-dasharray: 5 5
    style FPGA stroke-dasharray: 5 5

    linkStyle 1 stroke:#e67e22
    linkStyle 2 stroke:#e67e22
    linkStyle 3 stroke:#e67e22

OpenOCD (Open On-Chip Debugger) est le serveur de debug open source qui parle JTAG/SWD et expose une interface GDB standard.


SWD — Serial Wire Debug

SWD (Serial Wire Debug) est une interface ARM CoreSight sur 2 fils seulement (SWDIO + SWDCLK), plus un optionnel SWDOUT pour la trace. Elle est fonctionnellement équivalente à JTAG pour le debug de MCU ARM Cortex-M mais ne supporte pas le boundary scan.

Avantages de SWD sur JTAG pour les MCU ARM :

  • Seulement 2 fils — économise des broches GPIO précieuses sur les petits packages.
  • Plus robuste aux interférences en raison de la topologie point-à-point.
  • Support du SWO (Serial Wire Output) pour la trace ITM sans fil supplémentaire.
  • Standard sur tous les kits Nucleo, Discovery et la majorité des cartes ARM modernes.

Probes de debug courantes

Probe Interface Protocoles Cible Coût Particularité
J-Link (Segger) USB JTAG, SWD ARM, RISC-V, RX 500–1000 € Le plus rapide, RTT intégré
ST-Link v3 USB JTAG, SWD STM32 20 € Intégré sur kits ST
CMSIS-DAP / DAPLink USB JTAG, SWD ARM universel 10–50 € Firmware open source
ESP-Prog USB JTAG ESP32 15 € Officiel Espressif
Raspberry Pi Debug Probe USB SWD, UART RP2040 + ARM 12 € Open hardware
MPLAB PICkit USB JTAG, ICSP PIC, dsPIC 90 € Spécifique Microchip

Analyseur logique

Un analyseur logique capture des signaux numériques (niveaux haut/bas) sur plusieurs canaux simultanément et les décode selon les protocoles (UART, SPI, I2C, CAN, 1-Wire).

Saleae Logic est la référence commerciale : 8 à 16 canaux, 500 MHz d'échantillonnage, logiciel Logic 2 avec décodeurs intégrés. Indispensable pour debugger un bus I2C récalcitrant ou vérifier le timing d'un protocole propriétaire.

PulseView / sigrok est l'alternative open source, compatible avec une cinquantaine d'adaptateurs USB à bas coût (5 à 30 €). La qualité d'acquisition est inférieure aux Saleae mais suffisante pour la majorité des cas.

Cas d'usage pratiques :

  • Vérifier que l'adresse I2C est correcte et que l'ACK est reçu.
  • Mesurer le timing entre une interruption GPIO et la réponse du firmware.
  • Capturer une trame CAN et décoder ses identifiants.
  • Vérifier le protocole SPI sur 4 fils simultanément (MOSI, MISO, CLK, CS).

Oscilloscope

L'oscilloscope mesure les signaux analogiques dans le temps. En embarqué, il révèle ce qu'un analyseur logique ne peut pas voir : les glitches sur l'alimentation, les niveaux de tension marginaux, les problèmes d'impédance sur les lignes différentielles.

Paramètres critiques :

  • Bande passante : au minimum 5× la fréquence du signal à mesurer. Pour un signal SPI à 10 MHz, il faut au moins 50 MHz de bande passante.
  • Taux d'échantillonnage : au moins 5× la bande passante. Un scope 100 MHz doit échantillonner à 500 MS/s minimum.
  • Déclenchement : sur front, sur niveau, sur pattern — la maîtrise du trigger est l'art du debugging oscilloscope.

Oscilloscopes embarqués courants : Rigol DS1054Z (4 canaux, 50 MHz, 150 €), Siglent SDS1104X-E (4 canaux, 100 MHz, 400 €), Keysight DSOX1204G (4 canaux, 70 MHz, 700 €).


Méthodes de debug print

Le printf() est souvent la première réaction face à un bug, mais il n'est pas anodin en embarqué. Il peut perturber le timing, consommer de la RAM stack, et bloquer si le périphérique UART est lent.

flowchart LR
    A[printf\nUART bloquant] --> B[Semihosting\nvia debug probe]
    B --> C[ITM\nSWO non bloquant]
    C --> D[Segger RTT\nplus rapide, non intrusif]

    style A fill:#6e1a1a,color:#fff
    style D fill:#1a6e3a,color:#fff
Méthode Interface Débit Intrusivité Coût matériel Cas d'usage
printf UART Série UART Faible (115200 bps) Haute (bloquant) Nul (UART libre) Prototypage simple
Semihosting JTAG/SWD Très faible Très haute (halte CPU) Probe debug Debug initial, rare
ITM (SWO) SWD pin dédié ~2 Mbps Faible Probe avec SWO Debug non intrusif Cortex-M
Segger RTT JTAG/SWD RAM ~1 Mo/s Quasi nulle J-Link Production, performance

Segger RTT (Real-Time Transfer) est la méthode la plus professionnelle : le firmware écrit dans un buffer RAM circulaire, le J-Link lit ce buffer en fond de tâche sans interrompre le CPU. L'application JLinkRTTViewer affiche les logs en temps réel.


Profiling

Mémoire — surveiller heap et stack

// FreeRTOS : watermark stack d'une tâche
UBaseType_t watermark = uxTaskGetStackHighWaterMark(NULL);
// Si watermark < 50 mots → risque d'overflow, augmenter la taille

// Heap disponible
size_t heapLibre = xPortGetFreeHeapSize();
size_t heapMin   = xPortGetMinimumEverFreeHeapSize(); // minimum historique

En bare-metal, le linker script définit les sections .heap et .stack. Placer une magic word à la fin de la zone stack et la vérifier périodiquement dans un watchdog task est la technique classique.

CPU — cycle counting

Sur Cortex-M, le registre DWT_CYCCNT (Data Watchpoint and Trace Cycle Count) incrémente à chaque cycle horloge. Il permet de mesurer avec précision le temps d'exécution d'une section de code.

// Activation du compteur de cycles (Cortex-M)
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL  |= DWT_CTRL_CYCCNTENA_Msk;

uint32_t debut = DWT->CYCCNT;
// --- code à profiler ---
traiterDonnees();
// --- fin code à profiler ---
uint32_t cycles = DWT->CYCCNT - debut;
float duree_us = (float)cycles / (float)(SystemCoreClock / 1000000);

Sampling profiler

Un profiler par échantillonnage déclenche une interruption périodique (timer), capture la valeur du PC (Program Counter), et construit un histogramme de fréquence. Les fonctions les plus représentées dans l'histogramme sont les goulots d'étranglement. Segger SystemView et Percepio Tracealyzer offrent cette fonctionnalité avec visualisation temporelle des tâches RTOS.


Chaîne de debug complète

flowchart LR
    A[IDE\nVS Code / CLion\nSTM32CubeIDE] --> B[GDB Client\narm-none-eabi-gdb]
    B --> C[Serveur GDB\nOpenOCD / pyOCD\nJ-Link GDB Server]
    C --> D[Probe\nJ-Link / ST-Link\nDAPLink]
    D --> E[SWD / JTAG\n2 ou 4 fils]
    E --> F[MCU\nCortex-M / RISC-V]
    F --> G1[ITM → SWO\nlogs non intrusifs]
    F --> G2[RTT → RAM\nlogs ultra-rapides]
    G1 --> H[Host\nJLinkSWOViewer\nOpenOCD swo]
    G2 --> H

    style A fill:#2d4a6e,color:#fff
    style F fill:#1a6e3a,color:#fff
    style H fill:#4a2d6e,color:#fff

Stratégie de debug en pratique

Règle d'or : isoler avant d'instrumenter. Réduire le problème au plus petit cas reproductible avant d'ajouter des logs ou des points d'arrêt — chaque outil de debug est potentiellement intrusif.

Checklist de premier debug :

  1. Vérifier l'alimentation et les niveaux logiques à l'oscilloscope.
  2. Vérifier le clock système (PLL, prescalers) via un GPIO toggle dans main().
  3. Activer les handlers de fault (HardFault, MemManage, BusFault) avec décodage du stack frame.
  4. Utiliser uxTaskGetStackHighWaterMark() sur toutes les tâches RTOS.
  5. Activer les assertions (configASSERT en FreeRTOS) en mode debug.

Ce qu'il faut retenir

  • JTAG et SWD sont les interfaces de debug standard — JTAG pour les chaînes multi-composants, SWD pour les MCU ARM seuls (2 fils).
  • Segger RTT est la méthode de logging la moins intrusive — à préférer sur les systèmes de production.
  • L'analyseur logique décode les protocoles ; l'oscilloscope révèle les problèmes analogiques et de timing.
  • Le DWT_CYCCNT est le profiler hardware intégré sur Cortex-M — précision au cycle près, coût nul.
  • Stack overflow et inversion de priorité sont les deux bugs RTOS les plus fréquents — instrumenter proactivement.

Chapitre suivant : Tests embarqués — Unity, HIL, QEMU et CI pour valider le firmware avant de le déployer.