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 :
- Vérifier l'alimentation et les niveaux logiques à l'oscilloscope.
- Vérifier le clock système (PLL, prescalers) via un GPIO toggle dans
main(). - Activer les handlers de fault (
HardFault,MemManage,BusFault) avec décodage du stack frame. - Utiliser
uxTaskGetStackHighWaterMark()sur toutes les tâches RTOS. - Activer les assertions (
configASSERTen 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.