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.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.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 stlink procura 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-prog não precisa ser digitado toda vez. O SDK inclui sua própria cópia em ~/openmv-sdk-<version>/stcubeprog/bin – aponte --cube-prog para lá se nenhuma instalação no sistema existir.

  • gdbinit por projeto respeitado. Um .gdbinit no diretório atual é carregado com -ix – sobrescrevendo o ~/.gdbinit global 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 debug roda a partir da raiz do repositório, então um .gdbinit ali se aplica.

  • Execução de teste (dry run). --dryrun imprime 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-output manté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-arm depura uma compilação de firmware sem nenhuma placa conectada. O alvo MPS2_AN500 seleciona esse backend em sua configuração de placa, então make TARGET=MPS2_AN500 DEBUG=1 debug compila para a máquina mps2-an500 do 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. (O qemu-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 passoF10 passa por cima (next), F11 entra (step), Shift+F11 sai (finish), F5 continua. O passo a passo em nível de instrução é stepi / nexti no 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ção DEBUG=1.

  • Watchpoints (pontos de interrupção de dados)watch <expr> para quando uma variável é escrita, rwatch na leitura, awatch em 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 svdFile definido, 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 printf com sobrecarga quase nula enquanto o alvo executa. Compile com DEBUG_PRINTF=1 e adicione o rttConfig (RTT) ou swoConfig (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.

  • DesconectandoStop em uma sessão launch para o alvo; Disconnect em uma sessão attach deixa 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 com DEBUG=0. Recompile com DEBUG=1.

  • “GDB executable not found” – o gcc/bin do SDK não está no PATH; defina armToolchainPath / gdbPath.

  • “Cannot connect” / mapa de memória incorreto – nome de device incorreto 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 defina set substitute-path no gdb.