14.1.1.4. Debug del firmware¶
Il debug su hardware significa fermare il processore, impostare breakpoint nel codice sorgente C, eseguire l’avanzamento passo-passo e ispezionare variabili, memoria, registri e periferiche – dall’interno di VS Code. Questo richiede tre cose: una build di debug, una sonda di debug SWD (un Segger J-Link) e l’estensione Cortex-Debug che pilota arm-none-eabi-gdb contro un server GDB J-Link.
14.1.1.4.1. Compilare per il debug¶
Ricompila sempre il target con DEBUG=1
make -j$(nproc) TARGET=<TARGET> DEBUG=1
Un’immagine di release (DEBUG=0) viene compilata con -O2; nel debugger vedrai <optimized out> per molte variabili, le funzioni inline collassano nei loro chiamanti e l’avanzamento passo-passo salta in modo imprevedibile. DEBUG=1 compila con -Og -ggdb3, che è debuggabile pur continuando ad avviarsi sulla camera. L’ELF a cui puntare il debugger è:
build/<TARGET>/bin/firmware.elf
(Per l’Alif AE3, esegui il debug di build/OPENMV_AE3/bin/firmware_M55_HP.elf – il core ad alte prestazioni.)
14.1.1.4.2. L’hardware: J-Link su SWD¶
Collega un Segger J-Link ai pin SWD della camera (SWDIO, SWCLK, GND e VCC del target come riferimento; la camera è alimentata via USB come al solito). Vanno bene un J-Link EDU / Base / Pro. La posizione in cui emergono i pin di debug varia da camera a camera – molte schede hanno un connettore JTAG/SWD dedicato, altre espongono SWD sull’header di I/O o su piazzole di test – quindi controlla il diagramma del pinout e lo schema di quella scheda nella documentazione hardware di OpenMV per sapere quali pin cablare. Installa il J-Link Software and Documentation Pack da segger.com sulla macchina a cui la sonda è fisicamente collegata. Mantienilo ragionevolmente aggiornato – una versione più vecchia del software J-Link non conoscerà i nomi dei dispositivi più recenti (STM32N6, MIMXRT, Alif).
Ogni MCU ha bisogno del suo esatto nome dispositivo J-Link affinché la sonda carichi il flash loader e la mappa di memoria corretti:
Camera ( |
MCU |
|
|---|---|---|
|
STM32F427 |
|
|
STM32F765 |
|
|
STM32H743 |
|
|
STM32N657 |
|
|
MIMXRT1062 |
|
|
Alif Ensemble (M55-HP) |
|
|
STM32H747 |
|
14.1.1.4.3. Configurazione di Cortex-Debug in VS Code¶
Crea .vscode/launch.json nel repository. Il caso più semplice – VS Code, il J-Link e la build si trovano tutti sulla stessa macchina Linux / macOS – usa servertype: "jlink", che fa sì che Cortex-Debug avvii da solo un server 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"
}
]
}
Modifica executable e device per la tua scheda (vedi la tabella sopra). Premi F5 per compilare, flashare ed eseguire fino a main e fermarti lì.
Suggerimento
Per ricompilare automaticamente ogni volta che avvii il debug, aggiungi un task di build a .vscode/tasks.json e fai riferimento ad esso dalla configurazione di avvio con "preLaunchTask". Per esempio un task che esegue make -j$(nproc) TARGET=OPENMV4 DEBUG=1, denominato "build-firmware", più "preLaunchTask": "build-firmware" nella configurazione precedente, in modo che F5 ricompili, flashi e avvii il debugger in un unico passaggio.
Avvertimento
Cortex-Debug ha bisogno di arm-none-eabi-gdb. Viene fornito nell’SDK in ~/openmv-sdk-<version>/gcc/bin ma non è presente nel PATH per impostazione predefinita, quindi il debug fallisce con «GDB executable “arm-none-eabi-gdb” was not found». Risolvi il problema impostando armToolchainPath / gdbPath come mostrato sopra, oppure aggiungendo ~/openmv-sdk-<version>/gcc/bin al tuo PATH (printenv PATH dovrebbe quindi elencarlo).
14.1.1.4.4. Vista dei registri delle periferiche (SVD)¶
Punta Cortex-Debug a un file SVD CMSIS per ottenere una vista decodificata dei registri delle periferiche (timer, DMA, l’interfaccia della camera, ecc.) per nome e campo di bit:
"svdFile": "/path/to/STM32H743.svd"
Per STM32 e MIMXRT, ottieni l’SVD dai pacchetti CMSIS ST / NXP o dal registro SVD di Cortex-Debug. Gli SVD Alif sono inclusi nel repository del firmware in lib/micropython/lib/alif_ensemble-cmsis-dfp/Debug/SVD/ (usa ..._CM55_HP_View.svd per il core HP dell’AE3).
14.1.1.4.5. Windows: il bridge J-Link tra WSL ↔ Windows¶
WSL 2 non può vedere direttamente il dispositivo USB del J-Link, quindi la suddivisione è la seguente: Windows serve la sonda (dove è collegata) e VS Code + gdb girano in WSL e la raggiungono tramite TCP.
Su Windows, installa il pacchetto Segger J-Link e collega il J-Link a una porta USB di Windows.
Su Windows, avvia il J-Link Remote Server (viene fornito con il pacchetto J-Link): avvialo con il J-Link collegato e clicca su OK. Consentilo attraverso il firewall di Windows quando richiesto. La finestra mostra l”indirizzo IP su cui sta servendo la sonda – annotalo.
In WSL, compila con
DEBUG=1e assicurati chearm-none-eabi-gdbsia raggiungibile (impostaarmToolchainPathcome sopra).In VS Code su WSL, mantieni
servertype: "jlink"– il server GDB gira in WSL e raggiunge la sonda attraverso il Remote Server – e aggiungiserverpath+ipAddress{ "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" }
Imposta
ipAddressall’indirizzo mostrato dalla finestra del Remote Server. Questo è tutto il bridge.
Suggerimento
Alternativa al bridge tramite server GDB: usbipd-win. Invece di eseguire un server su Windows, puoi collegare il dispositivo USB del J-Link direttamente in WSL con usbipd-win. Da un PowerShell con privilegi di amministratore:
winget install usbipd
usbipd list
usbipd bind --busid <busid>
usbipd attach --wsl --busid <busid>
(<busid> è il bus ID del J-Link ottenuto da usbipd list.) La sonda appare quindi all’interno di WSL e usi la normale configurazione servertype: "jlink" per la stessa macchina descritta in Configurazione di Cortex-Debug in VS Code senza indirizzo IP e senza un server Windows separato. Il bridge tramite server GDB richiede meno configurazione per un uso occasionale; usbipd-win è più comodo per lo sviluppo abituale.
Suggerimento
Usa "request": "attach" per eseguire il debug del firmware mentre è già in esecuzione senza resettarlo o riflasharlo – ideale per intercettare un blocco sul campo. Usa "request": "launch" per resettare, flashare l’ELF e ripartire da capo da runToEntryPoint.
14.1.1.4.6. Debug da riga di comando con gdbrunner¶
Impostare a mano una sessione GDB contro un target embedded è una danza in cinque passaggi: avvia il server GDB J-Link / ST-Link in una finestra con il dispositivo, la porta e i flag dell’interfaccia corretti; attendi che stampi Waiting for GDB connection; esegui arm-none-eabi-gdb in una seconda finestra; digita target remote localhost:<port>; punta gdb all’ELF. Quando la sessione gdb termina, ricordati di chiudere la finestra del server. gdbrunner è una piccola CLI che condensa tutto ciò in un singolo comando in foreground. Viene fornita nell’ambiente Python dell’SDK di OpenMV, quindi non c’è nulla da installare; il punto di ingresso abituale è il target make debug del repository del firmware:
make -j$(nproc) TARGET=<TARGET> DEBUG=1 debug
Questo esegue gdbrunner con gli argomenti del debugger ricavati dalla configurazione della scheda – il nome dispositivo J-Link e, dove necessario, il flash loader esterno ST-Link – con arm-none-eabi-gdb dell’SDK già presente nel PATH. Il backend predefinito è J-Link; make DEBUGGER=STLINK debug funziona invece con una sonda ST-Link.
gdbrunner può anche essere invocato direttamente (al di fuori dell’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
Il primo argomento posizionale seleziona il backend del server (jlink, stlink, qemu); il resto viene inoltrato a quel backend, con valori predefiniti che funzionano per le camere OpenMV. gdbrunner --help elenca la lista completa dei flag per ogni backend; la tabella degli argomenti di ciascun backend è guidata da JSON (src/gdbrunner/backends.json), quindi aggiungere un nuovo server è una modifica alla configurazione anziché al codice.
Cosa fa gdbrunner per il lavoro da riga di comando:
Un solo processo, ciclo di vita pulito. Il server si avvia, gdb si collega quando la porta è aperta, il server viene terminato in modo pulito quando gdb esce. Nessun
JLinkGDBServerorfano che sopravvive alla sessione, nessun doppio terminale da gestire.Rilevamento automatico di STM32CubeProgrammer. Il backend
stlinkcerca nelle consuete posizioni di installazione (~/STM32CubeProgrammer/,/opt/st/, l’albero dei plugin di STM32CubeIDE) gli strumenti di STM32CubeProgrammer, in modo da non dover digitare ogni volta il lungo percorso--cube-prog. L’SDK include una propria copia in~/openmv-sdk-<version>/stcubeprog/bin– punta--cube-proglì se non esiste un’installazione di sistema.Rispetto del gdbinit per progetto. Un
.gdbinitnella directory corrente viene caricato con-ix– prevalendo sul~/.gdbinitvalido per l’intero utente – in modo che lo scripting gdb specifico del progetto (pretty-printer, macro specifiche della scheda, set di breakpoint) si inserisca semplicemente essendo presente nella directory di lavoro.make debugviene eseguito dalla radice del repository, quindi un.gdbinitlì presente viene applicato.Dry run.
--dryrunstampa il comando del server senza eseguirlo, utile per adattare l’invocazione a uno script wrapper, per copiarla in una configurazione di avvio di un IDE, o semplicemente per verificare quali argomenti gdbrunner sta componendo.Output del server visibile.
--show-outputmantiene visibili lo stdout / stderr del server. L’impostazione predefinita li sopprime (così l’interfaccia di gdb resta pulita); attiva il flag quando è il server stesso a comportarsi male.Backend QEMU.
qemu-system-armesegue il debug di una build del firmware senza alcuna scheda collegata. Il targetMPS2_AN500seleziona questo backend nella sua configurazione di scheda, quindimake TARGET=MPS2_AN500 DEBUG=1 debugcompila per la macchinamps2-an500di QEMU ed esegue passo-passo il codice indipendente dalla piattaforma – tutto ciò che non tocca le periferiche specifiche della camera – anche in volo. (qemu-system-armè un’installazione sull’host, non fa parte dell’SDK.)
Per l’avanzamento passo-passo a livello di sorgente con i margini per i breakpoint e una vista dei registri delle periferiche, la configurazione di Cortex-Debug in VS Code descritta sopra è lo strumento migliore; gdbrunner è quello giusto per tutto ciò che vive sulla riga di comando.
14.1.1.4.7. Usare il debugger¶
Una volta che una sessione è in esecuzione (il processore fermo a main):
Breakpoint – clicca sul margine accanto a una riga C, oppure nella Debug Console
break <file>:<line>/break <function>. I core Cortex-M hanno un piccolo numero di comparatori di breakpoint hardware (tipicamente 6–8 su M7 / H7, 8 su M55). Superare quel limite su codice in flash fallisce silenziosamente – mantieni modesto il numero di breakpoint attivi.Avanzamento passo-passo – F10 step over (
next), F11 step into (step), Shift+F11 step out (finish), F5 continua. L’avanzamento a livello di istruzione èstepi/nextinella Debug Console.Variabili / watch / stack delle chiamate – i pannelli Variables e Call Stack mostrano le variabili locali e il backtrace; aggiungi espressioni a Watch. Passa il mouse su una variabile nel sorgente per vederne il valore. Qualsiasi cosa che mostra
<optimized out>significa che non stai usando una buildDEBUG=1.Watchpoint (breakpoint sui dati) –
watch <expr>si ferma quando una variabile viene scritta,rwatchin lettura,awatchin entrambi i casi. L’unità DWT del Cortex-M supporta circa 4 watchpoint hardware – preziosissimi per intercettare chi ha corrotto una variabile.Registri e periferiche – la vista Cortex Registers mostra i registri del core; con
svdFileimpostato, la vista Peripherals decodifica ogni registro di periferica e campo di bit (DMA, timer, l’interfaccia camera / CSI, XSPI, ecc.) – il modo più veloce per capire perché un driver si comporta male.Memoria – usa il visualizzatore di memoria di Cortex-Debug o
x/di gdb per ispezionare direttamente frame buffer, buffer DMA e strutture.printf senza fermarsi (SWO/RTT) – per problemi sensibili alla temporizzazione, RTT o SWO di Segger forniscono un
printfa overhead pressoché nullo mentre il target è in esecuzione. Compila conDEBUG_PRINTF=1e aggiungirttConfig(RTT) oswoConfig(SWO, richiede il clock del core) di Cortex-Debug. Questo è lo strumento giusto quando un breakpoint cambierebbe la temporizzazione che stai cercando di osservare.Disconnessione – Stop su una sessione
launchferma il target; Disconnect su una sessioneattachlascia la camera in esecuzione. Spegni e riaccendi la camera per riportarla al normale funzionamento successivamente.
14.1.1.4.8. Insidie del debug¶
Variabili ottimizzate via. Tutto mostra
<optimized out>– hai compilato conDEBUG=0. Ricompila conDEBUG=1.«GDB executable not found» – la
gcc/bindell’SDK non è nelPATH; impostaarmToolchainPath/gdbPath.«Cannot connect» / mappa di memoria errata – nome
deviceerrato o mancante; usa la stringa esatta dalla tabella.Breakpoint silenziosamente non raggiunti – troppi breakpoint hardware su codice residente in flash; riducili.
I percorsi dei sorgenti non corrispondono (ELF compilato con Docker) – compila con il target Docker
build-firmware-dev(stesso percorso assoluto dentro e fuori dal container) oppure impostaset substitute-pathin gdb.