14.1.1.4. De firmware debuggen

Debuggen op hardware betekent dat je de processor stilzet, breakpoints in de C-broncode plaatst, stap voor stap doorloopt en variabelen, geheugen, registers en randapparaten inspecteert – allemaal vanuit VS Code. Hiervoor zijn drie dingen nodig: een debug-build, een SWD-debugprobe (een Segger J-Link) en de Cortex-Debug-extensie die arm-none-eabi-gdb aanstuurt tegen een J-Link GDB-server.

14.1.1.4.1. Bouwen om te debuggen

Bouw het target altijd opnieuw met DEBUG=1

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

Een release-image (DEBUG=0) wordt gecompileerd met -O2; in de debugger zie je voor veel variabelen <optimized out>, geïnlinede functies worden samengevoegd met hun aanroepers en het stappen springt onvoorspelbaar rond. DEBUG=1 bouwt met -Og -ggdb3, wat debugbaar is en toch nog opstart op de camera. De ELF waar je de debugger op richt is:

build/<TARGET>/bin/firmware.elf

(Voor de Alif AE3 debug je build/OPENMV_AE3/bin/firmware_M55_HP.elf – de high-performance core.)

14.1.1.4.3. VS Code Cortex-Debug-instelling

Maak .vscode/launch.json aan in de repository. Het eenvoudigste geval – VS Code, de J-Link en de build draaien allemaal op dezelfde Linux-/macOS-machine – gebruikt servertype: "jlink", waardoor Cortex-Debug zelf een J-Link GDB-server start:

{
  "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"
    }
  ]
}

Pas executable en device aan voor jouw board (zie de tabel hierboven). Druk op F5 om te bouwen, flashen en uitvoeren tot main en daar te stoppen.

Tip

Om automatisch opnieuw te bouwen elke keer dat je begint met debuggen, voeg je een buildtaak toe aan .vscode/tasks.json en verwijs je ernaar vanuit de launch-config met "preLaunchTask". Bijvoorbeeld een taak die make -j$(nproc) TARGET=OPENMV4 DEBUG=1 uitvoert, met de naam "build-firmware", plus "preLaunchTask": "build-firmware" in de configuratie hierboven, zodat F5 in één stap opnieuw bouwt, flasht en de debugger start.

Waarschuwing

Cortex-Debug heeft arm-none-eabi-gdb nodig. Dit wordt meegeleverd in de SDK op ~/openmv-sdk-<version>/gcc/bin maar staat standaard niet in PATH, dus debuggen mislukt met “GDB executable ‘arm-none-eabi-gdb’ was not found”. Los dit op door armToolchainPath / gdbPath in te stellen zoals hierboven getoond, of door ~/openmv-sdk-<version>/gcc/bin aan je PATH toe te voegen (printenv PATH zou het dan moeten vermelden).

14.1.1.4.4. Weergave van randapparaatregisters (SVD)

Richt Cortex-Debug op een CMSIS-SVD-bestand om een gedecodeerde weergave van randapparaatregisters te krijgen (timers, DMA, de camera-interface, enz.) op naam en bitveld:

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

Voor STM32 en MIMXRT haal je de SVD uit de ST-/NXP-CMSIS-packs of het Cortex-Debug SVD-register. De Alif-SVD’s zijn meegeleverd in de firmware-repo op lib/micropython/lib/alif_ensemble-cmsis-dfp/Debug/SVD/ (gebruik de ..._CM55_HP_View.svd voor de AE3 HP-core).

14.1.1.4.6. Debuggen vanaf de opdrachtregel met gdbrunner

Met de hand een GDB-sessie opzetten tegen een embedded target is een dans in vijf stappen: start de J-Link-/ST-Link GDB-server in één venster met de juiste apparaat-, poort- en interfacevlaggen; wacht tot deze Waiting for GDB connection afdrukt; voer arm-none-eabi-gdb uit in een tweede venster; typ target remote localhost:<port>; richt gdb op de ELF. Wanneer de gdb-sessie eindigt, vergeet dan niet het servervenster af te sluiten. gdbrunner is een kleine CLI die dat alles samenvoegt tot één foreground-opdracht. Het wordt meegeleverd in de Python-omgeving van de OpenMV SDK, dus er valt niets te installeren; het gebruikelijke startpunt is het make debug-target van de firmware-repository:

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

Dit voert gdbrunner uit met de debugger-argumenten uit de configuratie van het board – de J-Link-apparaatnaam en, waar nodig, de externe ST-Link-flashloader – met de arm-none-eabi-gdb van de SDK al in PATH. De standaardbackend is J-Link; make DEBUGGER=STLINK debug werkt in plaats daarvan met een ST-Link-probe.

gdbrunner kan ook rechtstreeks worden aangeroepen (buiten de 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

Het eerste positionele argument kiest de serverbackend (jlink, stlink, qemu); de rest wordt doorgegeven aan die backend, met standaardwaarden die werken voor de OpenMV-camera’s. gdbrunner --help toont de volledige lijst met vlaggen per backend; de argumententabel van elke backend is JSON-gestuurd (src/gdbrunner/backends.json), zodat het toevoegen van een nieuwe server een configuratiewijziging is in plaats van code.

Wat gdbrunner doet voor werk vanaf de opdrachtregel:

  • Eén proces, schone levenscyclus. De server start, gdb koppelt zich aan wanneer de poort open is, de server wordt netjes beëindigd wanneer gdb afsluit. Geen verweesde JLinkGDBServer die de sessie overleeft, geen twee terminals om te beheren.

  • Automatische detectie van STM32CubeProgrammer. De stlink-backend doorzoekt de gebruikelijke installatielocaties (~/STM32CubeProgrammer/, /opt/st/, de STM32CubeIDE-pluginboom) naar de STM32CubeProgrammer-tools, zodat het lange --cube-prog-pad niet telkens hoeft te worden ingetypt. De SDK bundelt zijn eigen kopie op ~/openmv-sdk-<version>/stcubeprog/bin – richt --cube-prog daarop als er geen systeeminstallatie bestaat.

  • Per-project gdbinit gerespecteerd. Een .gdbinit in de huidige map wordt geladen met -ix – waarmee de gebruikersbrede ~/.gdbinit wordt overschreven – zodat per-project gdb-scripting (pretty-printers, board-specifieke macro’s, breakpointsets) eenvoudig wordt ingevoegd door aanwezig te zijn in de werkmap. make debug draait vanuit de repository-root, dus een .gdbinit daar is van toepassing.

  • Droogtest. --dryrun drukt de serveropdracht af zonder die uit te voeren, handig om de aanroep aan te passen aan een wrapperscript, om hem in een IDE-launcher-config te kopiëren, of gewoon om te controleren welke argumenten gdbrunner samenstelt.

  • Serveruitvoer zichtbaar. --show-output houdt de stdout / stderr van de server zichtbaar. De standaard onderdrukt die (zodat de UI van gdb schoon blijft); zet de vlag om wanneer de server zelf zich misdraagt.

  • QEMU-backend. qemu-system-arm debugt een firmware-build zonder dat er een board is aangesloten. Het MPS2_AN500-target selecteert deze backend in zijn boardconfiguratie, zodat make TARGET=MPS2_AN500 DEBUG=1 debug bouwt voor de mps2-an500-machine van QEMU en de platformonafhankelijke code stapsgewijs doorloopt – alles wat geen camera-specifieke randapparaten aanraakt – zonder hardware. (qemu-system-arm is een host-installatie, geen onderdeel van de SDK.)

Voor stappen op bronniveau met breakpointmarges en een weergave van randapparaatregisters is de VS Code Cortex-Debug-instelling hierboven de betere tool; gdbrunner is de juiste voor alles wat zich op de opdrachtregel afspeelt.

14.1.1.4.7. De debugger gebruiken

Zodra een sessie draait (de processor stilgezet bij main):

  • Breakpoints – klik in de marge naast een C-regel, of in de Debug Console break <file>:<line> / break <function>. Cortex-M-cores hebben een klein aantal hardware-breakpointcomparators (doorgaans 6–8 op M7 / H7, 8 op M55). Dit overschrijden voor code in flashgeheugen mislukt stilzwijgend – houd het aantal actieve breakpoints bescheiden.

  • StappenF10 stap eroverheen (next), F11 stap erin (step), Shift+F11 stap eruit (finish), F5 doorgaan. Stappen op instructieniveau gebeurt met stepi / nexti in de Debug Console.

  • Variabelen / watch / call stack – de panelen Variables en Call Stack tonen lokale variabelen en de backtrace; voeg expressies toe aan Watch. Beweeg de muis over een variabele in de broncode om de waarde te zien. Alles wat <optimized out> toont betekent dat je niet op een DEBUG=1-build zit.

  • Watchpoints (data-breakpoints)watch <expr> stopt wanneer een variabele wordt geschreven, rwatch bij lezen, awatch bij beide. De Cortex-M DWT-eenheid ondersteunt ~4 hardware-watchpoints – onmisbaar om te betrappen wie een variabele heeft beschadigd.

  • Registers en randapparaten – de weergave Cortex Registers toont de core-registers; met svdFile ingesteld decodeert de weergave Peripherals elk randapparaatregister en bitveld (DMA, timers, de camera-/CSI-interface, XSPI, enz.) – de snelste manier om te zien waarom een driver zich misdraagt.

  • Geheugen – gebruik de geheugenviewer van Cortex-Debug of gdb x/ om framebuffers, DMA-buffers en structuren rechtstreeks te inspecteren.

  • printf zonder stil te zetten (SWO/RTT) – voor timinggevoelige problemen geven Segger RTT of SWO een printf met vrijwel nul overhead terwijl het target draait. Bouw met DEBUG_PRINTF=1 en voeg de rttConfig (RTT) of swoConfig (SWO, vereist de core-klok) van Cortex-Debug toe. Dit is de juiste tool wanneer een breakpoint de timing zou veranderen die je probeert te observeren.

  • Verbinding verbrekenStop bij een launch-sessie zet het target stil; Disconnect bij een attach-sessie laat de camera draaien. Schakel de camera daarna uit en weer in om hem terug te brengen naar normale werking.

14.1.1.4.8. Valkuilen bij het debuggen

  • Wegge-optimaliseerde variabelen. Alles toont <optimized out> – je hebt gebouwd met DEBUG=0. Bouw opnieuw met DEBUG=1.

  • “GDB executable not found” – de gcc/bin van de SDK staat niet in PATH; stel armToolchainPath / gdbPath in.

  • “Cannot connect” / verkeerde geheugenmap – verkeerde of ontbrekende device-naam; gebruik de exacte string uit de tabel.

  • Breakpoints worden stilzwijgend niet geraakt – te veel hardware-breakpoints op code die in flashgeheugen staat; verminder ze.

  • Bronpaden komen niet overeen (met Docker gebouwde ELF) – bouw met het Docker-build-firmware-dev-target (zelfde absolute pad binnen en buiten de container) of stel gdb set substitute-path in.