14.1.1.4. Débogage du micrologiciel

Le débogage sur matériel consiste à arrêter le processeur, à poser des points d’arrêt dans le code source C, à exécuter pas à pas et à inspecter les variables, la mémoire, les registres et les périphériques – le tout depuis VS Code. Cela nécessite trois éléments : une version de débogage, une sonde de débogage SWD (une Segger J-Link) et l’extension Cortex-Debug pilotant arm-none-eabi-gdb face à un serveur GDB J-Link.

14.1.1.4.1. Compiler pour le débogage

Recompilez toujours la cible avec DEBUG=1

make -j$(nproc) TARGET=<TARGET> DEBUG=1

Une image de production (DEBUG=0) est compilée en -O2 ; dans le débogueur, vous verrez <optimized out> pour de nombreuses variables, les fonctions en ligne se fondent dans leurs appelants et l’exécution pas à pas saute de façon imprévisible. Les versions DEBUG=1 sont compilées en -Og -ggdb3, ce qui les rend débogables tout en démarrant correctement sur la caméra. L’ELF que vous indiquez au débogueur est

build/<TARGET>/bin/firmware.elf

(Pour l’Alif AE3, déboguez build/OPENMV_AE3/bin/firmware_M55_HP.elf – le cœur haute performance.)

14.1.1.4.3. Configuration de Cortex-Debug dans VS Code

Créez .vscode/launch.json dans le dépôt. Le cas le plus simple – VS Code, la J-Link et la version compilée sont tous sur la même machine Linux / macOS – utilise servertype: "jlink", ce qui fait que Cortex-Debug démarre lui-même un serveur GDB J-Link

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "OpenMV J-Link",
      "type": "cortex-debug",
      "request": "launch",
      "cwd": "${workspaceFolder}",
      "executable": "${workspaceFolder}/build/OPENMV4/bin/firmware.elf",
      "servertype": "jlink",
      "device": "STM32H743VI",
      "interface": "swd",
      "runToEntryPoint": "main",
      "armToolchainPath": "${env:HOME}/openmv-sdk-1.6.0/gcc/bin",
      "gdbPath": "${env:HOME}/openmv-sdk-1.6.0/gcc/bin/arm-none-eabi-gdb"
    }
  ]
}

Modifiez executable et device selon votre carte (voir le tableau ci-dessus). Appuyez sur F5 pour compiler-flasher-et-exécuter jusqu’à main et vous y arrêter.

Astuce

Pour recompiler automatiquement à chaque démarrage du débogage, ajoutez une tâche de compilation à .vscode/tasks.json et référencez-la depuis la configuration de lancement avec "preLaunchTask". Par exemple une tâche exécutant make -j$(nproc) TARGET=OPENMV4 DEBUG=1, nommée "build-firmware", plus "preLaunchTask": "build-firmware" dans la configuration ci-dessus, afin que F5 recompile, flashe et démarre le débogueur en une seule étape.

Avertissement

Cortex-Debug a besoin de arm-none-eabi-gdb. Il est fourni dans le SDK sous ~/openmv-sdk-<version>/gcc/bin mais ne figure pas dans le PATH par défaut, si bien que le débogage échoue avec « GDB executable “arm-none-eabi-gdb” was not found ». Corrigez cela soit en définissant armToolchainPath / gdbPath comme indiqué ci-dessus, soit en ajoutant ~/openmv-sdk-<version>/gcc/bin à votre PATH (printenv PATH devrait alors le lister).

14.1.1.4.4. Vue des registres de périphériques (SVD)

Faites pointer Cortex-Debug vers un fichier SVD CMSIS pour obtenir une vue décodée des registres de périphériques (minuteurs, DMA, l’interface caméra, etc.) par nom et par champ de bits

"svdFile": "/path/to/STM32H743.svd"

Pour STM32 et MIMXRT, récupérez le SVD depuis les packs CMSIS de ST / NXP ou depuis le registre SVD de Cortex-Debug. Les SVD Alif sont fournis dans le dépôt du micrologiciel sous lib/micropython/lib/alif_ensemble-cmsis-dfp/Debug/SVD/ (utilisez le ..._CM55_HP_View.svd pour le cœur HP de l’AE3).

14.1.1.4.6. Débogage en ligne de commande avec gdbrunner

Mettre en place une session GDB face à une cible embarquée à la main est une chorégraphie en cinq étapes : démarrer le serveur GDB J-Link / ST-Link dans une fenêtre avec les bons indicateurs de périphérique, de port et d’interface ; attendre qu’il affiche Waiting for GDB connection ; lancer arm-none-eabi-gdb dans une deuxième fenêtre ; taper target remote localhost:<port> ; faire pointer gdb vers l’ELF. À la fin de la session gdb, ne pas oublier de fermer la fenêtre du serveur. gdbrunner est un petit utilitaire en ligne de commande qui condense tout cela en une seule commande au premier plan. Il est fourni dans l’environnement Python du SDK OpenMV, il n’y a donc rien à installer ; le point d’entrée habituel est la cible make debug du dépôt du micrologiciel

make -j$(nproc) TARGET=<TARGET> DEBUG=1 debug

Cela exécute gdbrunner avec les arguments du débogueur issus de la configuration de la carte – le nom de périphérique J-Link et, lorsque c’est nécessaire, le chargeur de mémoire flash externe ST-Link – avec le arm-none-eabi-gdb du SDK déjà dans le PATH. Le backend par défaut est J-Link ; make DEBUGGER=STLINK debug fonctionne plutôt avec une sonde ST-Link.

gdbrunner peut aussi être invoqué directement (en dehors du SDK, pip install gdbrunner)

gdbrunner jlink --device STM32H743VI build/OPENMV4/bin/firmware.elf
gdbrunner stlink build/OPENMV4/bin/firmware.elf
gdbrunner qemu --machine mps2-an500 build/MPS2_AN500/bin/firmware.elf

Le premier argument positionnel sélectionne le backend serveur (jlink, stlink, qemu) ; le reste est transmis à ce backend, avec des valeurs par défaut qui conviennent aux caméras OpenMV. gdbrunner --help liste l’ensemble des indicateurs par backend ; le tableau d’arguments de chaque backend est piloté par JSON (src/gdbrunner/backends.json), de sorte qu’ajouter un nouveau serveur relève d’une modification de configuration plutôt que de code.

Ce que gdbrunner fait pour le travail en ligne de commande :

  • Un seul processus, cycle de vie propre. Le serveur démarre, gdb s’y attache lorsque le port est ouvert, le serveur est arrêté proprement à la sortie de gdb. Aucun JLinkGDBServer orphelin ne survit à la session, aucun deuxième terminal à gérer.

  • Découverte automatique de STM32CubeProgrammer. Le backend stlink recherche les emplacements d’installation habituels (~/STM32CubeProgrammer/, /opt/st/, l’arborescence du greffon STM32CubeIDE) pour trouver les outils STM32CubeProgrammer, de sorte que le long chemin --cube-prog n’a pas à être saisi à chaque fois. Le SDK fournit sa propre copie sous ~/openmv-sdk-<version>/stcubeprog/bin – faites pointer --cube-prog vers cet emplacement s’il n’existe aucune installation système.

  • Prise en compte du gdbinit par projet. Un fichier .gdbinit situé dans le répertoire courant est chargé avec -ix – ce qui remplace le ~/.gdbinit global de l’utilisateur – de sorte qu’un script gdb propre au projet (pretty-printers, macros spécifiques à la carte, jeux de points d’arrêt) s’intègre simplement par sa présence dans le répertoire de travail. make debug s’exécute depuis la racine du dépôt, un .gdbinit présent à cet endroit s’applique donc.

  • Exécution à blanc. --dryrun affiche la commande du serveur sans l’exécuter, utile pour adapter l’invocation à un script enveloppant, la copier dans la configuration de lancement d’un IDE, ou simplement vérifier quels arguments gdbrunner compose.

  • Sortie du serveur visible. --show-output conserve la visibilité de la sortie stdout / stderr du serveur. Le comportement par défaut la supprime (afin que l’interface de gdb reste épurée) ; activez l’indicateur lorsque c’est le serveur lui-même qui se comporte mal.

  • Backend QEMU. qemu-system-arm débogue une version compilée du micrologiciel sans aucune carte branchée. La cible MPS2_AN500 sélectionne ce backend dans sa configuration de carte, de sorte que make TARGET=MPS2_AN500 DEBUG=1 debug compile pour la machine mps2-an500 de QEMU et exécute pas à pas le code indépendant de la plateforme – tout ce qui ne touche pas aux périphériques spécifiques à la caméra – en plein vol. (qemu-system-arm est une installation côté hôte, ne faisant pas partie du SDK.)

Pour une exécution pas à pas au niveau du code source avec marges de points d’arrêt et une vue des registres de périphériques, la configuration Cortex-Debug dans VS Code décrite ci-dessus est le meilleur outil ; gdbrunner est l’outil idéal pour tout ce qui se passe en ligne de commande.

14.1.1.4.7. Utiliser le débogueur

Une fois qu’une session est en cours d’exécution (le processeur étant arrêté à main) :

  • Points d’arrêt – cliquez dans la marge à côté d’une ligne C, ou dans la console de débogage break <file>:<line> / break <function>. Les cœurs Cortex-M disposent d’un petit nombre de comparateurs de points d’arrêt matériels (typiquement 6 à 8 sur M7 / H7, 8 sur M55). Dépasser ce nombre sur du code en mémoire flash échoue silencieusement – gardez un nombre de points d’arrêt actifs modeste.

  • Exécution pas à pasF10 pas par-dessus (next), F11 pas à l’intérieur (step), Shift+F11 sortie de la fonction (finish), F5 continuer. L’exécution pas à pas au niveau instruction se fait avec stepi / nexti dans la console de débogage.

  • Variables / surveillance / pile d’appels – les volets Variables et Call Stack affichent les variables locales et la trace d’appels ; ajoutez des expressions à Watch. Survolez une variable dans le code source pour voir sa valeur. Tout ce qui affiche <optimized out> signifie que vous n’êtes pas sur une version DEBUG=1.

  • Points de surveillance (points d’arrêt sur données)watch <expr> s’arrête lorsqu’une variable est écrite, rwatch à la lecture, awatch dans les deux cas. L’unité DWT des Cortex-M prend en charge environ 4 points de surveillance matériels – précieux pour déterminer qui a corrompu une variable.

  • Registres et périphériques – la vue Cortex Registers affiche les registres du cœur ; lorsque svdFile est défini, la vue Peripherals décode chaque registre de périphérique et chaque champ de bits (DMA, minuteurs, l’interface caméra / CSI, XSPI, etc.) – le moyen le plus rapide de comprendre pourquoi un pilote se comporte mal.

  • Mémoire – utilisez la visionneuse de mémoire de Cortex-Debug ou la commande x/ de gdb pour inspecter directement les tampons d’image, les tampons DMA et les structures.

  • printf sans arrêt (SWO/RTT) – pour les problèmes sensibles au timing, le RTT ou le SWO de Segger fournissent un printf à surcharge quasi nulle pendant que la cible s’exécute. Compilez avec DEBUG_PRINTF=1 et ajoutez le rttConfig (RTT) ou le swoConfig (SWO, qui nécessite l’horloge du cœur) de Cortex-Debug. C’est l’outil adéquat lorsqu’un point d’arrêt modifierait le timing que vous cherchez à observer.

  • DéconnexionStop sur une session launch arrête la cible ; Disconnect sur une session attach laisse la caméra en fonctionnement. Effectuez ensuite un cycle d’alimentation de la caméra pour la ramener à un fonctionnement normal.

14.1.1.4.8. Pièges du débogage

  • Variables optimisées. Tout affiche <optimized out> – vous avez compilé en DEBUG=0. Recompilez avec DEBUG=1.

  • « GDB executable not found » – le gcc/bin du SDK n’est pas dans le PATH ; définissez armToolchainPath / gdbPath.

  • « Cannot connect » / plan mémoire erroné – nom de device erroné ou manquant ; utilisez la chaîne exacte issue du tableau.

  • Points d’arrêt silencieusement non atteints – trop de points d’arrêt matériels sur du code résidant en mémoire flash ; réduisez-les.

  • Les chemins source ne correspondent pas (ELF compilé via Docker) – compilez avec la cible Docker build-firmware-dev (même chemin absolu à l’intérieur et à l’extérieur du conteneur) ou définissez set substitute-path dans gdb.