14.1.1.4. Debugging der Firmware¶
On-Hardware-Debugging bedeutet, den Prozessor anzuhalten, Breakpoints im C-Quellcode zu setzen, einzeln zu steppen und Variablen, Speicher, Register und Peripheriegeräte zu inspizieren – direkt aus VS Code heraus. Dafür sind drei Dinge erforderlich: ein Debug-Build, eine SWD-Debug-Probe (ein Segger J-Link) und die Cortex-Debug-Erweiterung, die arm-none-eabi-gdb gegen einen J-Link-GDB-Server ansteuert.
14.1.1.4.1. Build für das Debugging¶
Bauen Sie das Target immer mit DEBUG=1 neu:
make -j$(nproc) TARGET=<TARGET> DEBUG=1
Ein Release-Image (DEBUG=0) wird mit -O2 kompiliert; im Debugger sehen Sie für viele Variablen <optimized out>, inlinete Funktionen verschmelzen mit ihren Aufrufern und das Steppen springt unvorhersehbar umher. DEBUG=1 baut mit -Og -ggdb3, was debugbar ist und gleichzeitig noch auf der Kamera bootet. Die ELF-Datei, auf die Sie den Debugger richten, ist:
build/<TARGET>/bin/firmware.elf
(Für die Alif AE3 debuggen Sie build/OPENMV_AE3/bin/firmware_M55_HP.elf – den Hochleistungskern.)
14.1.1.4.2. Die Hardware: J-Link über SWD¶
Verbinden Sie einen Segger J-Link mit den SWD-Pins der Kamera (SWDIO, SWCLK, GND und Target-VCC zur Referenz; die Kamera wird wie üblich über USB mit Strom versorgt). Ein J-Link EDU / Base / Pro funktionieren alle. Wo die Debug-Pins herausgeführt sind, unterscheidet sich je nach Kamera – viele Boards haben einen dedizierten JTAG/SWD-Stecker, andere führen SWD am I/O-Header oder an Testpads heraus – prüfen Sie daher im Pinout-Diagramm und Schaltplan des jeweiligen Boards in der OpenMV-Hardwaredokumentation, welche Pins zu verdrahten sind. Installieren Sie das J-Link Software and Documentation Pack von segger.com auf dem Rechner, an dem die Probe physisch angeschlossen ist. Halten Sie es einigermaßen aktuell – ältere J-Link-Software kennt die neueren Gerätenamen (STM32N6, MIMXRT, Alif) nicht.
Jeder MCU benötigt seinen genauen J-Link-Gerätenamen, damit die Probe den richtigen Flash-Loader und die richtige Speicherkarte lädt:
Kamera ( |
MCU |
J-Link |
|---|---|---|
|
STM32F427 |
|
|
STM32F765 |
|
|
STM32H743 |
|
|
STM32N657 |
|
|
MIMXRT1062 |
|
|
Alif Ensemble (M55-HP) |
|
|
STM32H747 |
|
14.1.1.4.3. VS-Code-Cortex-Debug-Einrichtung¶
Erstellen Sie .vscode/launch.json im Repository. Der einfachste Fall – VS Code, der J-Link und der Build befinden sich alle auf demselben Linux-/macOS-Rechner – verwendet servertype: "jlink", wodurch Cortex-Debug selbst einen J-Link-GDB-Server startet:
{
"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"
}
]
}
Ändern Sie executable und device für Ihr Board (siehe die Tabelle oben). Drücken Sie F5, um zu bauen, zu flashen, bis main auszuführen und dort anzuhalten.
Tipp
Um bei jedem Start des Debuggings automatisch neu zu bauen, fügen Sie eine Build-Task zu .vscode/tasks.json hinzu und referenzieren Sie sie aus der Launch-Konfiguration mit "preLaunchTask". Zum Beispiel eine Task, die make -j$(nproc) TARGET=OPENMV4 DEBUG=1 ausführt und "build-firmware" heißt, plus "preLaunchTask": "build-firmware" in der obigen Konfiguration, sodass F5 in einem einzigen Schritt neu baut, flasht und den Debugger startet.
Warnung
Cortex-Debug benötigt arm-none-eabi-gdb. Es wird im SDK unter ~/openmv-sdk-<version>/gcc/bin mitgeliefert, befindet sich aber standardmäßig nicht im PATH, sodass das Debugging mit „GDB executable ‚arm-none-eabi-gdb‘ was not found“ fehlschlägt. Beheben Sie dies entweder, indem Sie armToolchainPath / gdbPath wie oben gezeigt setzen, oder indem Sie ~/openmv-sdk-<version>/gcc/bin zu Ihrem PATH hinzufügen (printenv PATH sollte es dann auflisten).
14.1.1.4.4. Peripherieregister-Ansicht (SVD)¶
Richten Sie Cortex-Debug auf eine CMSIS-SVD-Datei, um eine dekodierte Peripherieregister-Ansicht (Timer, DMA, die Kameraschnittstelle usw.) nach Name und Bitfeld zu erhalten:
"svdFile": "/path/to/STM32H743.svd"
Für STM32 und MIMXRT beziehen Sie die SVD aus den ST-/NXP-CMSIS-Packs oder der Cortex-Debug-SVD-Registry. Die Alif-SVDs sind im Firmware-Repository unter lib/micropython/lib/alif_ensemble-cmsis-dfp/Debug/SVD/ vendoriert (verwenden Sie die ..._CM55_HP_View.svd für den AE3-HP-Kern).
14.1.1.4.5. Windows: die WSL-↔-Windows-J-Link-Brücke¶
WSL 2 kann das USB-Gerät des J-Link nicht direkt sehen, daher die Aufteilung: Windows stellt die Probe bereit (wo sie angeschlossen ist) und VS Code + gdb laufen in WSL und erreichen sie über TCP.
Installieren Sie unter Windows das Segger-J-Link-Pack und stecken Sie den J-Link in einen Windows-USB-Port.
Starten Sie unter Windows den J-Link Remote Server (er wird mit dem J-Link-Pack mitgeliefert): starten Sie ihn mit angeschlossenem J-Link und klicken Sie auf OK. Erlauben Sie ihn durch die Windows-Firewall, wenn Sie dazu aufgefordert werden. Das Fenster zeigt die IP-Adresse an, unter der es die Probe bereitstellt – notieren Sie sie sich.
Bauen Sie in WSL mit
DEBUG=1und stellen Sie sicher, dassarm-none-eabi-gdberreichbar ist (setzen SiearmToolchainPathwie oben).Behalten Sie in WSL VS Code
servertype: "jlink"bei – der GDB-Server läuft in WSL und erreicht die Probe über den Remote Server – und fügen Sieserverpath+ipAddresshinzu:{ "name": "OpenMV J-Link (Windows host)", "type": "cortex-debug", "request": "launch", "cwd": "${workspaceFolder}", "executable": "${workspaceFolder}/build/OPENMV4/bin/firmware.elf", "servertype": "jlink", "serverpath": "/opt/SEGGER/JLink/JLinkGDBServer", "ipAddress": "192.168.x.x", "device": "STM32H743VI", "interface": "swd", "runToEntryPoint": "main", "armToolchainPath": "${env:HOME}/openmv-sdk-1.6.0/gcc/bin" }
Setzen Sie
ipAddressauf die Adresse, die das Remote-Server-Fenster anzeigt. Das ist die gesamte Brücke.
Tipp
Alternative zur GDB-Server-Brücke: usbipd-win. Statt einen Server unter Windows auszuführen, können Sie das USB-Gerät des J-Link mit usbipd-win direkt in WSL einbinden. Aus einer Administrator-PowerShell:
winget install usbipd
usbipd list
usbipd bind --busid <busid>
usbipd attach --wsl --busid <busid>
(<busid> ist die Bus-ID des J-Link aus usbipd list.) Die Probe erscheint dann innerhalb von WSL, und Sie verwenden die einfache Same-Machine-Konfiguration servertype: "jlink" aus VS Code Cortex-Debug setup ohne IP-Adresse und ohne separaten Windows-Server. Die GDB-Server-Brücke erfordert weniger Einrichtung für den gelegentlichen Gebrauch; usbipd-win ist bequemer für die routinemäßige Entwicklung.
Tipp
Verwenden Sie "request": "attach", um die Firmware im laufenden Betrieb zu debuggen, ohne sie zurückzusetzen oder neu zu flashen – ideal, um ein Hängen im Feld zu erfassen. Verwenden Sie "request": "launch", um zurückzusetzen, die ELF zu flashen und bei runToEntryPoint neu zu starten.
14.1.1.4.6. Kommandozeilen-Debugging mit gdbrunner¶
Eine GDB-Sitzung gegen ein Embedded-Target von Hand einzurichten ist ein fünfstufiger Tanz: starten Sie den J-Link-/ST-Link-GDB-Server in einem Fenster mit den richtigen Geräte-, Port- und Interface-Flags; warten Sie, bis er Waiting for GDB connection ausgibt; führen Sie arm-none-eabi-gdb in einem zweiten Fenster aus; tippen Sie target remote localhost:<port>; richten Sie gdb auf die ELF. Wenn die gdb-Sitzung endet, denken Sie daran, das Serverfenster zu beenden. gdbrunner ist ein kleines CLI, das all dies zu einem einzigen Vordergrundbefehl zusammenfasst. Es wird in der Python-Umgebung des OpenMV-SDK mitgeliefert, sodass es nichts zu installieren gibt; der übliche Einstiegspunkt ist das make debug-Target des Firmware-Repositorys:
make -j$(nproc) TARGET=<TARGET> DEBUG=1 debug
Dies führt gdbrunner mit den Debugger-Argumenten aus der Board-Konfiguration aus – dem J-Link-Gerätenamen und, wo nötig, dem ST-Link-Loader für externen Flash – wobei sich das arm-none-eabi-gdb des SDK bereits im PATH befindet. Das Standard-Backend ist J-Link; make DEBUGGER=STLINK debug funktioniert stattdessen mit einer ST-Link-Probe.
gdbrunner kann auch direkt aufgerufen werden (außerhalb des 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
Das erste Positionsargument wählt das Server-Backend (jlink, stlink, qemu); der Rest wird an dieses Backend weitergereicht, mit Standardwerten, die für die OpenMV-Cams funktionieren. gdbrunner --help listet die vollständige Flag-Liste pro Backend auf; die Argumenttabelle jedes Backends ist JSON-gesteuert (src/gdbrunner/backends.json), sodass das Hinzufügen eines neuen Servers eher eine Konfigurationsänderung als eine Codeänderung ist.
Was gdbrunner für die Kommandozeilenarbeit erledigt:
Ein Prozess, sauberer Lebenszyklus. Der Server startet, gdb hängt sich an, sobald der Port offen ist, der Server wird sauber beendet, wenn gdb beendet wird. Kein verwaister
JLinkGDBServer, der die Sitzung überlebt, keine zwei Terminals zu verwalten.STM32CubeProgrammer-Autoerkennung. Das
stlink-Backend durchsucht die üblichen Installationsorte (~/STM32CubeProgrammer/,/opt/st/, den STM32CubeIDE-Plugin-Baum) nach den STM32CubeProgrammer-Tools, sodass der lange--cube-prog-Pfad nicht jedes Mal eingetippt werden muss. Das SDK bringt seine eigene Kopie unter~/openmv-sdk-<version>/stcubeprog/binmit – richten Sie--cube-progdorthin, wenn keine Systeminstallation existiert.Pro-Projekt-gdbinit wird berücksichtigt. Eine
.gdbinitim aktuellen Verzeichnis wird mit-ixgeladen – wodurch die benutzerweite~/.gdbinitüberschrieben wird – sodass projektspezifisches gdb-Scripting (Pretty-Printer, board-spezifische Makros, Breakpoint-Sets) durch bloßes Vorhandensein im Arbeitsverzeichnis greift.make debugläuft vom Repository-Stammverzeichnis aus, sodass eine dort liegende.gdbinitangewendet wird.Trockenlauf.
--dryrungibt den Serverbefehl aus, ohne ihn auszuführen, nützlich, um den Aufruf an ein Wrapper-Skript anzupassen, ihn in eine IDE-Launcher-Konfiguration zu kopieren oder einfach zu prüfen, welche Argumente gdbrunner zusammenstellt.Serverausgabe sichtbar.
--show-outputhält die stdout-/stderr-Ausgabe des Servers sichtbar. Standardmäßig wird sie unterdrückt (damit die Benutzeroberfläche von gdb sauber bleibt); kippen Sie das Flag, wenn der Server selbst dasjenige ist, das sich falsch verhält.QEMU-Backend.
qemu-system-armdebuggt einen Firmware-Build ohne angeschlossenes Board. DasMPS2_AN500-Target wählt dieses Backend in seiner Board-Konfiguration, sodassmake TARGET=MPS2_AN500 DEBUG=1 debugfür QEMUsmps2-an500-Maschine baut und den plattformunabhängigen Code – alles, was keine cam-spezifischen Peripheriegeräte berührt – im Flug durchsteppt. (qemu-system-armist eine Host-Installation, nicht Teil des SDK.)
Für das Stepping auf Quellcodeebene mit Breakpoint-Rinnen und einer Peripherieregister-Ansicht ist die obige VS-Code-Cortex-Debug-Einrichtung das bessere Werkzeug; gdbrunner ist das richtige für alles, was auf der Kommandozeile lebt.
14.1.1.4.7. Den Debugger verwenden¶
Sobald eine Sitzung läuft (der Prozessor bei main angehalten):
Breakpoints – klicken Sie in die Rinne neben einer C-Zeile oder in der Debug-Konsole
break <file>:<line>/break <function>. Cortex-M-Kerne haben eine kleine Anzahl von Hardware-Breakpoint-Komparatoren (typischerweise 6–8 bei M7 / H7, 8 bei M55). Wird diese bei Code im Flash überschritten, schlägt es stillschweigend fehl – halten Sie die Anzahl aktiver Breakpoints bescheiden.Stepping – F10 Step Over (
next), F11 Step Into (step), Shift+F11 Step Out (finish), F5 Continue. Stepping auf Instruktionsebene iststepi/nextiin der Debug-Konsole.Variablen / Watch / Aufrufstapel – die Bereiche Variables und Call Stack zeigen lokale Variablen und den Backtrace; fügen Sie Ausdrücke zu Watch hinzu. Fahren Sie im Quellcode über eine Variable, um ihren Wert zu sehen. Alles, was
<optimized out>anzeigt, bedeutet, dass Sie nicht auf einemDEBUG=1-Build sind.Watchpoints (Daten-Breakpoints) –
watch <expr>hält an, wenn eine Variable geschrieben wird,rwatchbeim Lesen,awatchbei beidem. Die Cortex-M-DWT-Einheit unterstützt ~4 Hardware-Watchpoints – unverzichtbar, um herauszufinden, wer eine Variable beschädigt hat.Register und Peripheriegeräte – die Ansicht Cortex Registers zeigt die Kernregister; mit gesetztem
svdFiledekodiert die Ansicht Peripherals jedes Peripherieregister und Bitfeld (DMA, Timer, die Kamera-/CSI-Schnittstelle, XSPI usw.) – der schnellste Weg zu sehen, warum sich ein Treiber falsch verhält.Speicher – verwenden Sie den Speicher-Viewer von Cortex-Debug oder gdb
x/, um Framebuffer, DMA-Puffer und Strukturen direkt zu inspizieren.printf ohne Anhalten (SWO/RTT) – für timing-sensitive Probleme bietet Seggers RTT oder SWO ein
printfmit nahezu null Overhead, während das Target läuft. Bauen Sie mitDEBUG_PRINTF=1und fügen Sie Cortex-DebugsrttConfig(RTT) oderswoConfig(SWO, benötigt den Kerntakt) hinzu. Dies ist das richtige Werkzeug, wenn ein Breakpoint das Timing verändern würde, das Sie beobachten wollen.Trennen – Stop bei einer
launch-Sitzung hält das Target an; Disconnect bei einerattach-Sitzung lässt die Kamera weiterlaufen. Führen Sie danach einen Power-Cycle der Kamera durch, um sie in den Normalbetrieb zurückzuversetzen.
14.1.1.4.8. Debugging-Fallstricke¶
Wegoptimierte Variablen. Alles zeigt
<optimized out>– Sie haben mitDEBUG=0gebaut. Bauen Sie mitDEBUG=1neu.„GDB executable not found“ – das
gcc/bindes SDK befindet sich nicht imPATH; setzen SiearmToolchainPath/gdbPath.„Cannot connect“ / falsche Speicherkarte – falscher oder fehlender
device-Name; verwenden Sie die exakte Zeichenkette aus der Tabelle.Breakpoints werden stillschweigend nicht getroffen – zu viele Hardware-Breakpoints auf flash-residentem Code; reduzieren Sie sie.
Quellpfade stimmen nicht überein (Docker-gebaute ELF) – bauen Sie mit dem Docker-
build-firmware-dev-Target (gleicher absoluter Pfad innerhalb und außerhalb des Containers) oder setzen Sie gdbset substitute-path.