14.1.1.4. Depurando o firmware¶
A depuração em hardware significa parar o processador, definir pontos de interrupção no código-fonte C, executar passo a passo e inspecionar variáveis, memória, registradores e periféricos – tudo a partir do VS Code. Isso requer três coisas: uma compilação de depuração, uma sonda de depuração SWD (um Segger J-Link) e a extensão Cortex-Debug controlando o arm-none-eabi-gdb contra um servidor GDB do J-Link.
14.1.1.4.1. Compilando para depuração¶
Sempre recompile o alvo com DEBUG=1
make -j$(nproc) TARGET=<TARGET> DEBUG=1
Uma imagem de lançamento (DEBUG=0) é compilada com -O2; no depurador você verá <optimized out> para muitas variáveis, funções inline colapsam dentro de seus chamadores e o passo a passo salta de forma imprevisível. DEBUG=1 compila com -Og -ggdb3, que é depurável e ainda assim inicializa na câmera. O ELF para o qual você aponta o depurador é:
build/<TARGET>/bin/firmware.elf
(Para o Alif AE3, depure build/OPENMV_AE3/bin/firmware_M55_HP.elf – o núcleo de alto desempenho.)
14.1.1.4.2. O hardware: J-Link via SWD¶
Conecte um Segger J-Link aos pinos SWD da câmera (SWDIO, SWCLK, GND e VCC do alvo como referência; a câmera é alimentada via USB normalmente). Um J-Link EDU / Base / Pro funcionam igualmente. O local onde os pinos de depuração aparecem difere por câmera – muitas placas têm um conector JTAG/SWD dedicado, outras expõem o SWD no header de E/S ou em pads de teste – então verifique no diagrama de pinagem e no esquemático daquela placa, na documentação de hardware da OpenMV, quais pinos conectar. Instale o J-Link Software and Documentation Pack de segger.com na máquina em que a sonda está fisicamente conectada. Mantenha-o razoavelmente atualizado – versões mais antigas do software J-Link não conhecerão os nomes de dispositivos mais novos (STM32N6, MIMXRT, Alif).
Cada MCU precisa de seu nome de dispositivo J-Link exato para que a sonda carregue o flash loader e o mapa de memória corretos:
Câmera ( |
MCU |
|
|---|---|---|
|
STM32F427 |
|
|
STM32F765 |
|
|
STM32H743 |
|
|
STM32N657 |
|
|
MIMXRT1062 |
|
|
Alif Ensemble (M55-HP) |
|
|
STM32H747 |
|
14.1.1.4.3. Configuração do Cortex-Debug no VS Code¶
Crie o arquivo .vscode/launch.json no repositório. O caso mais simples – VS Code, J-Link e a compilação todos na mesma máquina Linux / macOS – usa servertype: "jlink", o que faz o Cortex-Debug iniciar ele mesmo um servidor GDB do 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"
}
]
}
Altere executable e device para a sua placa (veja a tabela acima). Pressione F5 para compilar-gravar-e-executar até main e parar ali.
Dica
Para recompilar automaticamente toda vez que você iniciar a depuração, adicione uma tarefa de compilação a .vscode/tasks.json e referencie-a na configuração de inicialização com "preLaunchTask". Por exemplo, uma tarefa que execute make -j$(nproc) TARGET=OPENMV4 DEBUG=1, chamada "build-firmware", mais "preLaunchTask": "build-firmware" na configuração acima, de modo que F5 recompile, grave e inicie o depurador em uma única etapa.
Aviso
O Cortex-Debug precisa do arm-none-eabi-gdb. Ele vem no SDK em ~/openmv-sdk-<version>/gcc/bin, mas não está no PATH por padrão, então a depuração falha com “GDB executable ‘arm-none-eabi-gdb’ was not found”. Corrija isso definindo armToolchainPath / gdbPath como mostrado acima, ou adicionando ~/openmv-sdk-<version>/gcc/bin ao seu PATH (printenv PATH deve então listá-lo).
14.1.1.4.4. Visualização de registradores de periféricos (SVD)¶
Aponte o Cortex-Debug para um arquivo SVD CMSIS para obter uma visualização decodificada dos registradores de periféricos (timers, DMA, a interface da câmera etc.) por nome e campo de bits:
"svdFile": "/path/to/STM32H743.svd"
Para STM32 e MIMXRT, obtenha o SVD nos pacotes CMSIS da ST / NXP ou no registro de SVDs do Cortex-Debug. Os SVDs da Alif estão incluídos no repositório do firmware em lib/micropython/lib/alif_ensemble-cmsis-dfp/Debug/SVD/ (use o ..._CM55_HP_View.svd para o núcleo HP do AE3).
14.1.1.4.5. Windows: a ponte J-Link entre WSL ↔ Windows¶
O WSL 2 não consegue enxergar o dispositivo USB do J-Link diretamente, então a divisão é: o Windows serve a sonda (onde ela está conectada) e o VS Code + gdb rodam no WSL e a alcançam via TCP.
No Windows, instale o pacote Segger J-Link e conecte o J-Link a uma porta USB do Windows.
No Windows, inicie o J-Link Remote Server (ele vem com o pacote J-Link): execute-o com o J-Link conectado e clique em OK. Permita-o através do firewall do Windows quando solicitado. A janela mostra o endereço IP no qual ela está servindo a sonda – anote-o.
No WSL, compile com
DEBUG=1e certifique-se de que oarm-none-eabi-gdbesteja acessível (definaarmToolchainPathcomo acima).No VS Code do WSL, mantenha
servertype: "jlink"– o servidor GDB roda no WSL e alcança a sonda através do Remote Server – e adicioneserverpath+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" }
Defina
ipAddresscom o endereço que a janela do Remote Server mostra. Essa é toda a ponte.
Dica
Alternativa à ponte do servidor GDB: usbipd-win. Em vez de rodar um servidor no Windows, você pode anexar o dispositivo USB do J-Link diretamente ao WSL com o usbipd-win. A partir de um PowerShell de administrador:
winget install usbipd
usbipd list
usbipd bind --busid <busid>
usbipd attach --wsl --busid <busid>
(<busid> é o ID de barramento do J-Link obtido com usbipd list.) A sonda então aparece dentro do WSL, e você usa a configuração servertype: "jlink" simples, de mesma máquina, descrita em Configuração do Cortex-Debug no VS Code, sem endereço IP e sem um servidor Windows separado. A ponte do servidor GDB exige menos configuração para uso ocasional; o usbipd-win é mais conveniente para o desenvolvimento rotineiro.
Dica
Use "request": "attach" para depurar o firmware enquanto ele já está em execução, sem reiniciá-lo nem regravá-lo – ideal para capturar um travamento em campo. Use "request": "launch" para reiniciar, gravar o ELF e começar do zero em runToEntryPoint.
14.1.1.4.6. Depuração via linha de comando com o gdbrunner¶
Configurar manualmente uma sessão GDB contra um alvo embarcado é uma dança de cinco passos: iniciar o servidor GDB do J-Link / ST-Link em uma janela com o dispositivo, a porta e as flags de interface corretos; esperar até ele imprimir Waiting for GDB connection; executar o arm-none-eabi-gdb em uma segunda janela; digitar target remote localhost:<port>; apontar o gdb para o ELF. Quando a sessão do gdb termina, lembre-se de encerrar a janela do servidor. O gdbrunner é uma pequena CLI que condensa tudo isso em um único comando em primeiro plano. Ele vem no ambiente Python do SDK da OpenMV, então não há nada a instalar; o ponto de entrada usual é o alvo make debug do repositório do firmware:
make -j$(nproc) TARGET=<TARGET> DEBUG=1 debug
Isso executa o gdbrunner com os argumentos do depurador definidos na configuração da placa – o nome de dispositivo J-Link e, quando necessário, o flash loader externo da ST-Link – com o arm-none-eabi-gdb do SDK já no PATH. O backend padrão é o J-Link; make DEBUGGER=STLINK debug funciona com uma sonda ST-Link em vez disso.
O gdbrunner também pode ser invocado diretamente (fora do 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
O primeiro argumento posicional escolhe o backend do servidor (jlink, stlink, qemu); o restante é encaminhado para esse backend, com padrões que funcionam para as câmeras OpenMV. gdbrunner --help lista a lista completa de flags por backend; a tabela de argumentos de cada backend é orientada por JSON (src/gdbrunner/backends.json), então adicionar um novo servidor é uma edição de configuração em vez de código.
O que o gdbrunner faz pelo trabalho via linha de comando:
Um processo, ciclo de vida limpo. O servidor inicia, o gdb se conecta quando a porta está aberta e o servidor é encerrado de forma limpa quando o gdb sai. Nenhum
JLinkGDBServerórfão sobrevivendo à sessão, nenhum par de terminais para gerenciar.Descoberta automática do STM32CubeProgrammer. O backend
stlinkprocura nos locais de instalação usuais (~/STM32CubeProgrammer/,/opt/st/, a árvore de plugins do STM32CubeIDE) pelas ferramentas do STM32CubeProgrammer, de modo que o longo caminho--cube-prognão precisa ser digitado toda vez. O SDK inclui sua própria cópia em~/openmv-sdk-<version>/stcubeprog/bin– aponte--cube-progpara lá se nenhuma instalação no sistema existir.gdbinit por projeto respeitado. Um
.gdbinitno diretório atual é carregado com-ix– sobrescrevendo o~/.gdbinitglobal do usuário – de modo que o scripting de gdb por projeto (pretty-printers, macros específicas da placa, conjuntos de pontos de interrupção) entra em ação apenas por estar presente no diretório de trabalho.make debugroda a partir da raiz do repositório, então um.gdbinitali se aplica.Execução de teste (dry run).
--dryrunimprime o comando do servidor sem executá-lo, útil para adaptar a invocação a um script wrapper, copiá-la para a configuração de inicialização de uma IDE ou apenas verificar quais argumentos o gdbrunner está compondo.Saída do servidor visível.
--show-outputmantém o stdout / stderr do servidor visível. O padrão o suprime (para que a interface do gdb permaneça limpa); ative a flag quando o próprio servidor for o que está se comportando mal.Backend QEMU. O
qemu-system-armdepura uma compilação de firmware sem nenhuma placa conectada. O alvoMPS2_AN500seleciona esse backend em sua configuração de placa, entãomake TARGET=MPS2_AN500 DEBUG=1 debugcompila para a máquinamps2-an500do QEMU e executa passo a passo o código independente de plataforma – tudo o que não toca em periféricos específicos da câmera – em pleno voo. (Oqemu-system-armé uma instalação do host, não faz parte do SDK.)
Para o passo a passo em nível de código-fonte com marcadores de pontos de interrupção na margem e uma visualização de registradores de periféricos, a configuração do Cortex-Debug no VS Code acima é a melhor ferramenta; o gdbrunner é a ferramenta certa para tudo o que vive na linha de comando.
14.1.1.4.7. Usando o depurador¶
Uma vez que uma sessão esteja em execução (o processador parado em main):
Pontos de interrupção – clique na margem ao lado de uma linha C, ou no Debug Console use
break <file>:<line>/break <function>. Os núcleos Cortex-M têm um pequeno número de comparadores de pontos de interrupção de hardware (tipicamente 6–8 no M7 / H7, 8 no M55). Exceder esse número em código na flash falha silenciosamente – mantenha modesto o número de pontos de interrupção ativos.Passo a passo – F10 passa por cima (
next), F11 entra (step), Shift+F11 sai (finish), F5 continua. O passo a passo em nível de instrução éstepi/nextino Debug Console.Variáveis / watch / pilha de chamadas – os painéis Variables e Call Stack mostram as variáveis locais e o backtrace; adicione expressões ao Watch. Passe o mouse sobre uma variável no código-fonte para ver seu valor. Qualquer coisa exibindo
<optimized out>significa que você não está em uma compilaçãoDEBUG=1.Watchpoints (pontos de interrupção de dados) –
watch <expr>para quando uma variável é escrita,rwatchna leitura,awatchem qualquer uma delas. A unidade DWT do Cortex-M suporta ~4 watchpoints de hardware – inestimável para capturar quem corrompeu uma variável.Registradores e periféricos – a visualização Cortex Registers mostra os registradores do núcleo; com
svdFiledefinido, a visualização Peripherals decodifica cada registrador de periférico e campo de bits (DMA, timers, a interface da câmera / CSI, XSPI etc.) – a forma mais rápida de ver por que um driver está se comportando mal.Memória – use o visualizador de memória do Cortex-Debug ou o
x/do gdb para inspecionar diretamente framebuffers, buffers de DMA e estruturas.printf sem parar (SWO/RTT) – para problemas sensíveis ao tempo, o RTT ou SWO da Segger oferece
printfcom sobrecarga quase nula enquanto o alvo executa. Compile comDEBUG_PRINTF=1e adicione orttConfig(RTT) ouswoConfig(SWO, requer o clock do núcleo) do Cortex-Debug. Esta é a ferramenta certa quando um ponto de interrupção alteraria o tempo que você está tentando observar.Desconectando – Stop em uma sessão
launchpara o alvo; Disconnect em uma sessãoattachdeixa a câmera em execução. Reinicie a câmera (power-cycle) para devolvê-la à operação normal depois.
14.1.1.4.8. Armadilhas da depuração¶
Variáveis otimizadas (optimized-out). Tudo mostra
<optimized out>– você compilou comDEBUG=0. Recompile comDEBUG=1.“GDB executable not found” – o
gcc/bindo SDK não está noPATH; definaarmToolchainPath/gdbPath.“Cannot connect” / mapa de memória incorreto – nome de
deviceincorreto ou ausente; use a string exata da tabela.Pontos de interrupção não atingidos silenciosamente – pontos de interrupção de hardware demais em código residente na flash; reduza-os.
Os caminhos de origem não coincidem (ELF compilado com Docker) – compile com o alvo Docker
build-firmware-dev(mesmo caminho absoluto dentro e fora do contêiner) ou definaset substitute-pathno gdb.