14.1.1.4. Depanarea firmware-ului

Depanarea pe hardware înseamnă oprirea procesorului, plasarea punctelor de întrerupere în codul sursă C, execuția pas cu pas și inspectarea variabilelor, a memoriei, a regiștrilor și a perifericelor – din interiorul VS Code. Pentru aceasta sunt necesare trei lucruri: o compilare de depanare, o sondă de depanare SWD (un Segger J-Link) și extensia Cortex-Debug care controlează arm-none-eabi-gdb printr-un server GDB J-Link.

14.1.1.4.1. Compilarea pentru depanare

Recompilați întotdeauna ținta cu DEBUG=1

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

O imagine de versiune finală (DEBUG=0) este compilată cu -O2; în depanator veți vedea <optimized out> pentru multe variabile, funcțiile inline se contopesc în apelanții lor, iar execuția pas cu pas sare imprevizibil. DEBUG=1 compilează cu -Og -ggdb3, ceea ce permite depanarea în timp ce firmware-ul pornește în continuare pe cameră. Fișierul ELF către care îndreptați depanatorul este:

build/<TARGET>/bin/firmware.elf

(Pentru Alif AE3, depanați build/OPENMV_AE3/bin/firmware_M55_HP.elf – nucleul de înaltă performanță.)

14.1.1.4.3. Configurarea Cortex-Debug în VS Code

Creați .vscode/launch.json în depozit. Cazul cel mai simplu – VS Code, J-Link și compilarea se află toate pe aceeași mașină Linux / macOS – folosește servertype: "jlink", ceea ce face ca Cortex-Debug să pornească el însuși 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ți executable și device pentru placa dumneavoastră (vedeți tabelul de mai sus). Apăsați F5 pentru a compila-flasha-și-rula până la main și a vă opri acolo.

Sfat

Pentru a recompila automat de fiecare dată când porniți depanarea, adăugați o sarcină de compilare în .vscode/tasks.json și faceți referire la ea din configurația de lansare prin "preLaunchTask". De exemplu, o sarcină care rulează make -j$(nproc) TARGET=OPENMV4 DEBUG=1, numită "build-firmware", plus "preLaunchTask": "build-firmware" în configurația de mai sus, astfel încât F5 să recompileze, să flasheze și să pornească depanatorul într-un singur pas.

Atenționare

Cortex-Debug are nevoie de arm-none-eabi-gdb. Acesta este livrat în SDK la ~/openmv-sdk-<version>/gcc/bin, dar nu se află pe PATH în mod implicit, astfel încât depanarea eșuează cu „GDB executable «arm-none-eabi-gdb» was not found”. Remediați acest lucru fie setând armToolchainPath / gdbPath așa cum se arată mai sus, fie adăugând ~/openmv-sdk-<version>/gcc/bin la PATH (printenv PATH ar trebui apoi să-l listeze).

14.1.1.4.4. Vizualizarea regiștrilor periferici (SVD)

Îndreptați Cortex-Debug către un fișier SVD CMSIS pentru a obține o vizualizare decodată a regiștrilor periferici (temporizatoare, DMA, interfața camerei etc.) după nume și câmp de biți:

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

Pentru STM32 și MIMXRT, obțineți fișierul SVD din pachetele CMSIS ale ST / NXP sau din registrul SVD al Cortex-Debug. Fișierele SVD pentru Alif sunt incluse în depozitul de firmware la lib/micropython/lib/alif_ensemble-cmsis-dfp/Debug/SVD/ (folosiți ..._CM55_HP_View.svd pentru nucleul HP al AE3).

14.1.1.4.6. Depanare din linia de comandă cu gdbrunner

Configurarea manuală a unei sesiuni GDB împotriva unei ținte încorporate este un dans în cinci pași: porniți serverul GDB J-Link / ST-Link într-o fereastră cu opțiunile corecte de dispozitiv, port și interfață; așteptați să afișeze Waiting for GDB connection; rulați arm-none-eabi-gdb într-o a doua fereastră; tastați target remote localhost:<port>; îndreptați gdb către fișierul ELF. Când sesiunea gdb se încheie, nu uitați să închideți fereastra serverului. gdbrunner este un mic CLI care reduce toate acestea la o singură comandă în prim-plan. Este livrat în mediul Python al SDK-ului OpenMV, deci nu este nimic de instalat; punctul de intrare obișnuit este ținta make debug a depozitului de firmware:

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

Aceasta rulează gdbrunner cu argumentele depanatorului din configurația plăcii – numele de dispozitiv J-Link și, acolo unde este necesar, încărcătorul de flash extern ST-Link – cu arm-none-eabi-gdb al SDK-ului deja pe PATH. Backend-ul implicit este J-Link; make DEBUGGER=STLINK debug funcționează în schimb cu o sondă ST-Link.

gdbrunner poate fi invocat și direct (în afara SDK-ului, 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

Primul argument pozițional alege backend-ul de server (jlink, stlink, qemu); restul sunt transmise acelui backend, cu valori implicite care funcționează pentru camerele OpenMV. gdbrunner --help listează lista completă de opțiuni per backend; tabelul de argumente al fiecărui backend este bazat pe JSON (src/gdbrunner/backends.json), astfel încât adăugarea unui server nou este o modificare de configurație, nu de cod.

Ce face gdbrunner pentru lucrul din linia de comandă:

  • Un singur proces, ciclu de viață curat. Serverul pornește, gdb se atașează când portul este deschis, serverul este terminat curat când gdb iese. Niciun JLinkGDBServer orfan care să supraviețuiască sesiunii, niciun al doilea terminal de gestionat.

  • Descoperire automată STM32CubeProgrammer. Backend-ul stlink caută în locațiile uzuale de instalare (~/STM32CubeProgrammer/, /opt/st/, arborele de plugin-uri STM32CubeIDE) instrumentele STM32CubeProgrammer, astfel încât calea lungă --cube-prog să nu trebuiască tastată de fiecare dată. SDK-ul include propria copie la ~/openmv-sdk-<version>/stcubeprog/bin – îndreptați --cube-prog acolo dacă nu există o instalare la nivel de sistem.

  • Fișierul gdbinit per proiect este respectat. Un fișier .gdbinit din directorul curent este încărcat cu -ix – suprascriind fișierul ~/.gdbinit la nivel de utilizator – astfel încât scripturile gdb specifice proiectului (pretty-printer-e, macrouri specifice plăcii, seturi de puncte de întrerupere) intră în vigoare doar prin prezența lor în directorul de lucru. make debug rulează din rădăcina depozitului, deci un .gdbinit de acolo se aplică.

  • Rulare de probă. --dryrun afișează comanda serverului fără a o rula, util pentru adaptarea invocării la un script wrapper, pentru copierea ei într-o configurație de lansare a unui IDE sau pur și simplu pentru a verifica ce argumente compune gdbrunner.

  • Ieșirea serverului vizibilă. --show-output menține vizibile stdout / stderr ale serverului. Implicit acestea sunt suprimate (astfel încât interfața gdb să rămână curată); activați opțiunea atunci când serverul însuși este cel care se comportă greșit.

  • Backend QEMU. qemu-system-arm depanează o compilare de firmware fără nicio placă conectată. Ținta MPS2_AN500 selectează acest backend în configurația sa de placă, astfel încât make TARGET=MPS2_AN500 DEBUG=1 debug compilează pentru mașina mps2-an500 a QEMU și parcurge pas cu pas codul independent de platformă – tot ce nu atinge perifericele specifice camerei – în zbor. (qemu-system-arm este o instalare pe gazdă, nu face parte din SDK.)

Pentru execuția pas cu pas la nivel de sursă cu marcaje pentru punctele de întrerupere în marginea ferestrei și o vizualizare a regiștrilor periferici, configurația Cortex-Debug din VS Code de mai sus este instrumentul mai bun; gdbrunner este cel potrivit pentru tot ce trăiește în linia de comandă.

14.1.1.4.7. Utilizarea depanatorului

Odată ce o sesiune rulează (procesorul oprit la main):

  • Puncte de întrerupere – faceți clic în marginea de lângă o linie C sau, în Debug Console, break <file>:<line> / break <function>. Nucleele Cortex-M au un număr mic de comparatoare hardware pentru puncte de întrerupere (de obicei 6–8 pe M7 / H7, 8 pe M55). Depășirea acestui număr pe cod aflat în flash eșuează în tăcere – mențineți modest numărul de puncte de întrerupere active.

  • Execuție pas cu pasF10 peste (next), F11 în interior (step), Shift+F11 ieșire (finish), F5 continuare. Execuția pas cu pas la nivel de instrucțiune este stepi / nexti în Debug Console.

  • Variabile / urmărire / stivă de apeluri – panourile Variables și Call Stack afișează variabilele locale și urma de execuție; adăugați expresii în Watch. Treceți cu mouse-ul peste o variabilă din sursă pentru a-i vedea valoarea. Orice afișează <optimized out> înseamnă că nu vă aflați pe o compilare DEBUG=1.

  • Puncte de urmărire (puncte de întrerupere pe date)watch <expr> se oprește când o variabilă este scrisă, rwatch la citire, awatch la oricare dintre ele. Unitatea DWT a Cortex-M acceptă ~4 puncte de urmărire hardware – de neprețuit pentru a surprinde cine a corupt o variabilă.

  • Regiștri și periferice – vizualizarea Cortex Registers afișează regiștrii nucleului; cu svdFile setat, vizualizarea Peripherals decodifică fiecare registru și câmp de biți periferic (DMA, temporizatoare, interfața camerei / CSI, XSPI etc.) – cea mai rapidă modalitate de a vedea de ce un driver se comportă greșit.

  • Memorie – folosiți vizualizatorul de memorie Cortex-Debug sau x/ din gdb pentru a inspecta direct tampoanele de cadre, tampoanele DMA și structurile.

  • printf fără oprire (SWO/RTT) – pentru probleme sensibile la sincronizare, RTT sau SWO de la Segger oferă printf cu suprasarcină aproape nulă în timp ce ținta rulează. Compilați cu DEBUG_PRINTF=1 și adăugați rttConfig (RTT) sau swoConfig (SWO, are nevoie de frecvența nucleului) din Cortex-Debug. Acesta este instrumentul potrivit atunci când un punct de întrerupere ar modifica sincronizarea pe care încercați să o observați.

  • DeconectareStop într-o sesiune launch oprește ținta; Disconnect într-o sesiune attach lasă camera să ruleze. Reporniți alimentarea camerei pentru a o readuce ulterior la funcționarea normală.

14.1.1.4.8. Capcane în depanare

  • Variabile optimizate. Totul afișează <optimized out> – ați compilat cu DEBUG=0. Recompilați cu DEBUG=1.

  • „GDB executable not found” – directorul gcc/bin al SDK-ului nu se află pe PATH; setați armToolchainPath / gdbPath.

  • „Cannot connect” / hartă de memorie greșită – nume device greșit sau lipsă; folosiți șirul exact din tabel.

  • Puncte de întrerupere care nu sunt atinse în tăcere – prea multe puncte de întrerupere hardware pe cod rezident în flash; reduceți-le.

  • Căile sursă nu se potrivesc (ELF compilat cu Docker) – compilați cu ținta Docker build-firmware-dev (aceeași cale absolută în interiorul și în afara containerului) sau setați în gdb set substitute-path.