14.1.1.4. Debugowanie oprogramowania układowego¶
Debugowanie na sprzęcie oznacza zatrzymywanie procesora, ustawianie punktów wstrzymania w kodzie źródłowym C, wykonywanie krokowe oraz inspekcję zmiennych, pamięci, rejestrów i urządzeń peryferyjnych – wszystko z poziomu VS Code. Wymaga to trzech rzeczy: kompilacji debugowej, sondy debugowej SWD (Segger J-Link) oraz rozszerzenia Cortex-Debug sterującego arm-none-eabi-gdb względem serwera GDB J-Link.
14.1.1.4.1. Kompilacja do debugowania¶
Zawsze przebudowuj cel z DEBUG=1
make -j$(nproc) TARGET=<TARGET> DEBUG=1
Obraz wydaniowy (DEBUG=0) jest kompilowany z -O2; w debuggerze zobaczysz <optimized out> dla wielu zmiennych, funkcje wstawione w miejsce wywołania zostają zwinięte do swoich wywołujących, a wykonywanie krokowe przeskakuje w nieprzewidywalny sposób. DEBUG=1 buduje z -Og -ggdb3, co umożliwia debugowanie, a jednocześnie pozwala na uruchomienie na kamerze. Plik ELF, na który wskazujesz debugger, to:
build/<TARGET>/bin/firmware.elf
(Dla Alif AE3 debuguj build/OPENMV_AE3/bin/firmware_M55_HP.elf – rdzeń wysokiej wydajności.)
14.1.1.4.2. Sprzęt: J-Link przez SWD¶
Podłącz Segger J-Link do pinów SWD kamery (SWDIO, SWCLK, GND oraz VCC celu jako odniesienie; kamera jest zasilana przez USB jak zwykle). Działa J-Link EDU / Base / Pro. Sposób wyprowadzenia pinów debugowych różni się w zależności od kamery – wiele płytek ma dedykowane złącze JTAG/SWD, inne wyprowadzają SWD na listwie I/O lub na padach testowych – sprawdź więc diagram pinów oraz schemat danej płytki w dokumentacji sprzętowej OpenMV, aby ustalić, które piny połączyć. Zainstaluj J-Link Software and Documentation Pack ze strony segger.com na maszynie, do której fizycznie podłączona jest sonda. Utrzymuj go względnie aktualnym – starsze oprogramowanie J-Link nie zna nowszych nazw urządzeń (STM32N6, MIMXRT, Alif).
Każdy mikrokontroler potrzebuje swojej dokładnej nazwy urządzenia J-Link, aby sonda załadowała właściwy loader pamięci flash oraz mapę pamięci:
Kamera ( |
MCU |
J-Link |
|---|---|---|
|
STM32F427 |
|
|
STM32F765 |
|
|
STM32H743 |
|
|
STM32N657 |
|
|
MIMXRT1062 |
|
|
Alif Ensemble (M55-HP) |
|
|
STM32H747 |
|
14.1.1.4.3. Konfiguracja Cortex-Debug w VS Code¶
Utwórz .vscode/launch.json w repozytorium. Najprostszy przypadek – VS Code, J-Link i kompilacja znajdują się na tej samej maszynie z systemem Linux / macOS – używa servertype: "jlink", co sprawia, że Cortex-Debug samodzielnie uruchamia serwer 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"
}
]
}
Zmień executable oraz device dla swojej płytki (zobacz tabelę powyżej). Naciśnij F5, aby skompilować, wgrać i uruchomić do main i zatrzymać się tam.
Wskazówka
Aby automatycznie przebudowywać przy każdym uruchomieniu debugowania, dodaj zadanie budowania do .vscode/tasks.json i odwołaj się do niego z konfiguracji uruchamiania za pomocą "preLaunchTask". Na przykład zadanie uruchamiające make -j$(nproc) TARGET=OPENMV4 DEBUG=1, nazwane "build-firmware", plus "preLaunchTask": "build-firmware" w powyższej konfiguracji, tak aby F5 przebudowywało, wgrywało i uruchamiało debugger w jednym kroku.
Ostrzeżenie
Cortex-Debug potrzebuje arm-none-eabi-gdb. Jest on dostarczany w SDK pod ścieżką ~/openmv-sdk-<version>/gcc/bin, ale domyślnie nie znajduje się w PATH, więc debugowanie kończy się niepowodzeniem z komunikatem „GDB executable «arm-none-eabi-gdb» was not found”. Napraw to, ustawiając armToolchainPath / gdbPath jak pokazano powyżej, albo dodając ~/openmv-sdk-<version>/gcc/bin do swojego PATH (printenv PATH powinno wtedy go wymieniać).
14.1.1.4.4. Widok rejestrów urządzeń peryferyjnych (SVD)¶
Wskaż Cortex-Debug plik SVD standardu CMSIS, aby uzyskać zdekodowany widok rejestrów urządzeń peryferyjnych (liczniki czasu (timery), DMA, interfejs kamery itp.) według nazwy i pola bitowego:
"svdFile": "/path/to/STM32H743.svd"
Dla STM32 i MIMXRT pobierz SVD z paczek CMSIS firmy ST / NXP lub z rejestru SVD Cortex-Debug. Pliki SVD dla Alif są dołączone w repozytorium oprogramowania układowego pod ścieżką lib/micropython/lib/alif_ensemble-cmsis-dfp/Debug/SVD/ (użyj ..._CM55_HP_View.svd dla rdzenia HP AE3).
14.1.1.4.5. Windows: mostek J-Link między WSL ↔ Windows¶
WSL 2 nie widzi bezpośrednio urządzenia USB J-Link, więc podział jest następujący: Windows obsługuje sondę (gdzie jest podłączona), a VS Code + gdb działają w WSL i sięgają do niej przez TCP.
W systemie Windows zainstaluj paczkę Segger J-Link i podłącz J-Link do portu USB systemu Windows.
W systemie Windows uruchom J-Link Remote Server (dostarczany z paczką J-Link): uruchom go z podłączonym J-Link i kliknij OK. Pozwól mu przejść przez zaporę systemu Windows, gdy pojawi się monit. Okno wyświetla adres IP, na którym udostępnia sondę – zanotuj go.
W WSL zbuduj z
DEBUG=1i upewnij się, żearm-none-eabi-gdbjest osiągalny (ustawarmToolchainPathjak powyżej).W VS Code w WSL pozostaw
servertype: "jlink"– serwer GDB działa w WSL i sięga do sondy przez Remote Server – oraz dodajserverpath+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" }
Ustaw
ipAddressna adres wyświetlany w oknie Remote Server. To cały mostek.
Wskazówka
Alternatywa dla mostka serwera GDB: usbipd-win. Zamiast uruchamiać serwer w systemie Windows, możesz podłączyć urządzenie USB J-Link bezpośrednio do WSL za pomocą usbipd-win. Z poziomu PowerShell uruchomionego jako administrator:
winget install usbipd
usbipd list
usbipd bind --busid <busid>
usbipd attach --wsl --busid <busid>
(<busid> to identyfikator magistrali J-Link z polecenia usbipd list.) Sonda pojawia się wtedy wewnątrz WSL i używasz zwykłej konfiguracji servertype: "jlink" dla tej samej maszyny z sekcji Konfiguracja Cortex-Debug w VS Code bez adresu IP i bez osobnego serwera Windows. Mostek serwera GDB wymaga mniej konfiguracji do sporadycznego użycia; usbipd-win jest wygodniejsze przy rutynowym programowaniu.
Wskazówka
Użyj "request": "attach", aby debugować oprogramowanie układowe gdy już działa, bez resetowania ani ponownego wgrywania – idealne do wychwytywania zawieszenia w terenie. Użyj "request": "launch", aby zresetować, wgrać plik ELF i rozpocząć od nowa w runToEntryPoint.
14.1.1.4.6. Debugowanie z wiersza poleceń za pomocą gdbrunner¶
Ręczne skonfigurowanie sesji GDB względem celu wbudowanego to pięcioetapowy taniec: uruchom serwer GDB J-Link / ST-Link w jednym oknie z właściwym urządzeniem, portem i flagami interfejsu; poczekaj, aż wypisze Waiting for GDB connection; uruchom arm-none-eabi-gdb w drugim oknie; wpisz target remote localhost:<port>; wskaż gdb plik ELF. Gdy sesja gdb się kończy, pamiętaj o zamknięciu okna serwera. gdbrunner to małe narzędzie CLI, które zwija to wszystko do jednego polecenia pierwszoplanowego. Jest dostarczane w środowisku Python SDK OpenMV, więc nie trzeba nic instalować; zwykłym punktem wejścia jest cel make debug repozytorium oprogramowania układowego:
make -j$(nproc) TARGET=<TARGET> DEBUG=1 debug
Uruchamia to gdbrunner z argumentami debuggera z konfiguracji płytki – nazwą urządzenia J-Link oraz, w razie potrzeby, zewnętrznym loaderem pamięci flash ST-Link – z arm-none-eabi-gdb z SDK już obecnym w PATH. Domyślnym zapleczem jest J-Link; make DEBUGGER=STLINK debug działa z sondą ST-Link.
gdbrunner można też wywołać bezpośrednio (poza 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
Pierwszy argument pozycyjny wybiera zaplecze serwera (jlink, stlink, qemu); reszta jest przekazywana do tego zaplecza, z wartościami domyślnymi działającymi dla kamer OpenMV. gdbrunner --help wymienia pełną listę flag dla każdego zaplecza; tabela argumentów każdego zaplecza jest sterowana przez JSON (src/gdbrunner/backends.json), więc dodanie nowego serwera to edycja konfiguracji, a nie kodu.
Co gdbrunner robi dla pracy z wiersza poleceń:
Jeden proces, czysty cykl życia. Serwer uruchamia się, gdb podłącza się, gdy port jest otwarty, a serwer jest czysto kończony, gdy gdb się zamyka. Żadnego osieroconego
JLinkGDBServerprzeżywającego sesję, żadnych dwóch terminali do zarządzania.Automatyczne wykrywanie STM32CubeProgrammer. Zaplecze
stlinkprzeszukuje typowe lokalizacje instalacji (~/STM32CubeProgrammer/,/opt/st/, drzewo wtyczek STM32CubeIDE) w poszukiwaniu narzędzi STM32CubeProgrammer, więc długiej ścieżki--cube-prognie trzeba wpisywać za każdym razem. SDK dołącza własną kopię pod ścieżką~/openmv-sdk-<version>/stcubeprog/bin– wskaż tam--cube-prog, jeśli nie istnieje instalacja systemowa.Uwzględniony gdbinit dla projektu. Plik
.gdbinitw bieżącym katalogu jest ładowany z-ix– nadpisując ogólnoużytkownikowy~/.gdbinit– więc skrypty gdb dla projektu (pretty-printery, makra specyficzne dla płytki, zestawy punktów wstrzymania) trafiają do akcji po prostu przez obecność w katalogu roboczym.make debugdziała z katalogu głównego repozytorium, więc.gdbinittam umieszczony obowiązuje.Przebieg próbny.
--dryrunwypisuje polecenie serwera bez jego uruchamiania, przydatne przy dostosowywaniu wywołania do skryptu opakowującego, kopiowaniu go do konfiguracji uruchamiania IDE lub po prostu sprawdzaniu, jakie argumenty gdbrunner składa.Widoczne wyjście serwera.
--show-outpututrzymuje widoczne stdout / stderr serwera. Domyślnie jest ono tłumione (aby interfejs gdb pozostał czysty); przełącz flagę, gdy to sam serwer się źle zachowuje.Zaplecze QEMU.
qemu-system-armdebuguje kompilację oprogramowania układowego bez podłączonej płytki. CelMPS2_AN500wybiera to zaplecze w swojej konfiguracji płytki, więcmake TARGET=MPS2_AN500 DEBUG=1 debugbuduje dla maszynymps2-an500QEMU i wykonuje krokowo kod niezależny od platformy – wszystko, co nie dotyka urządzeń peryferyjnych specyficznych dla kamery – w locie. (qemu-system-armto instalacja hosta, a nie część SDK.)
Do wykonywania krokowego na poziomie kodu źródłowego z marginesami punktów wstrzymania i widokiem rejestrów urządzeń peryferyjnych lepszym narzędziem jest powyższa konfiguracja Cortex-Debug w VS Code; gdbrunner jest właściwy dla wszystkiego, co żyje w wierszu poleceń.
14.1.1.4.7. Korzystanie z debuggera¶
Gdy sesja jest uruchomiona (procesor zatrzymany w main):
Punkty wstrzymania – kliknij margines obok linii C lub w konsoli debugowania
break <file>:<line>/break <function>. Rdzenie Cortex-M mają niewielką liczbę sprzętowych komparatorów punktów wstrzymania (zwykle 6–8 na M7 / H7, 8 na M55). Przekroczenie tej liczby dla kodu w pamięci flash kończy się cichym niepowodzeniem – utrzymuj umiarkowaną liczbę aktywnych punktów wstrzymania.Wykonywanie krokowe – F10 krok ponad (
next), F11 krok do wnętrza (step), Shift+F11 krok na zewnątrz (finish), F5 kontynuuj. Wykonywanie krokowe na poziomie instrukcji tostepi/nextiw konsoli debugowania.Zmienne / obserwacja / stos wywołań – panele Variables i Call Stack pokazują zmienne lokalne oraz ślad wywołań; dodawaj wyrażenia do Watch. Najedź na zmienną w kodzie źródłowym, aby zobaczyć jej wartość. Wszystko, co pokazuje
<optimized out>, oznacza, że nie jesteś na kompilacjiDEBUG=1.Punkty obserwacji (punkty wstrzymania na danych) –
watch <expr>zatrzymuje przy zapisie zmiennej,rwatchprzy odczycie,awatchprzy obu. Jednostka DWT Cortex-M obsługuje ~4 sprzętowe punkty obserwacji – nieocenione przy wychwytywaniu, kto uszkodził zmienną.Rejestry i urządzenia peryferyjne – widok Cortex Registers pokazuje rejestry rdzenia; z ustawionym
svdFilewidok Peripherals dekoduje każdy rejestr urządzenia peryferyjnego i pole bitowe (DMA, liczniki czasu (timery), interfejs kamery / CSI, XSPI itp.) – najszybszy sposób, by zobaczyć, dlaczego sterownik źle się zachowuje.Pamięć – użyj przeglądarki pamięci Cortex-Debug lub gdb
x/, aby bezpośrednio badać bufory ramki, bufory DMA i struktury.printf bez zatrzymywania (SWO/RTT) – przy problemach wrażliwych na czas Segger RTT lub SWO zapewnia
printfo niemal zerowym narzucie podczas działania celu. Buduj zDEBUG_PRINTF=1i dodajrttConfig(RTT) lubswoConfig(SWO, wymaga zegara rdzenia) Cortex-Debug. To właściwe narzędzie, gdy punkt wstrzymania zmieniłby czas, który próbujesz obserwować.Odłączanie – Stop w sesji
launchzatrzymuje cel; Disconnect w sesjiattachpozostawia kamerę działającą. Następnie wyłącz i włącz zasilanie kamery, aby przywrócić jej normalne działanie.
14.1.1.4.8. Pułapki debugowania¶
Zmienne usunięte przez optymalizację. Wszystko pokazuje
<optimized out>– zbudowałeś zDEBUG=0. Przebuduj zDEBUG=1.„GDB executable not found” –
gcc/binz SDK nie jest wPATH; ustawarmToolchainPath/gdbPath.„Cannot connect” / błędna mapa pamięci – błędna lub brakująca nazwa
device; użyj dokładnego ciągu z tabeli.Punkty wstrzymania cicho nietrafiane – zbyt wiele sprzętowych punktów wstrzymania na kodzie rezydującym w pamięci flash; zmniejsz ich liczbę.
Ścieżki źródłowe nie pasują (ELF zbudowany w Dockerze) – buduj z celem Docker
build-firmware-dev(ta sama ścieżka bezwzględna wewnątrz i na zewnątrz kontenera) lub ustaw w gdbset substitute-path.