14.1.1.4. Depuración del firmware¶
La depuración sobre el hardware consiste en detener el procesador, establecer puntos de interrupción en el código fuente C, ejecutar paso a paso e inspeccionar variables, memoria, registros y periféricos, todo desde dentro de VS Code. Esto requiere tres cosas: una compilación de depuración, una sonda de depuración SWD (una Segger J-Link) y la extensión Cortex-Debug que controla arm-none-eabi-gdb contra un servidor GDB de J-Link.
14.1.1.4.1. Compilar para depuración¶
Recompila siempre el destino con DEBUG=1:
make -j$(nproc) TARGET=<TARGET> DEBUG=1
Una imagen de versión (DEBUG=0) se compila con -O2; en el depurador verás <optimized out> para muchas variables, las funciones en línea se fusionan en sus llamadoras y la ejecución paso a paso salta de forma impredecible. Las compilaciones DEBUG=1 usan -Og -ggdb3, lo que las hace depurables y a la vez arrancables en la cámara. El ELF al que apuntas el depurador es:
build/<TARGET>/bin/firmware.elf
(Para el Alif AE3, depura build/OPENMV_AE3/bin/firmware_M55_HP.elf, el núcleo de alto rendimiento.)
14.1.1.4.2. El hardware: J-Link sobre SWD¶
Conecta una Segger J-Link a los pines SWD de la cámara (SWDIO, SWCLK, GND y VCC del destino como referencia; la cámara se alimenta por USB como de costumbre). Una J-Link EDU / Base / Pro funcionan todas. El lugar donde aparecen los pines de depuración difiere según la cámara; muchas placas tienen un conector JTAG/SWD dedicado, otras exponen el SWD en el cabezal de E/S o en almohadillas de prueba, así que consulta el diagrama de pines y el esquema de esa placa en la documentación de hardware de OpenMV para saber qué pines cablear. Instala el J-Link Software and Documentation Pack desde segger.com en la máquina donde está físicamente conectada la sonda. Mantenlo razonablemente actualizado, ya que el software de J-Link más antiguo no conocerá los nombres de dispositivo más nuevos (STM32N6, MIMXRT, Alif).
Cada MCU necesita su nombre de dispositivo exacto de J-Link para que la sonda cargue el cargador de memoria flash y el mapa de memoria correctos:
Cámara ( |
MCU |
|
|---|---|---|
|
STM32F427 |
|
|
STM32F765 |
|
|
STM32H743 |
|
|
STM32N657 |
|
|
MIMXRT1062 |
|
|
Alif Ensemble (M55-HP) |
|
|
STM32H747 |
|
14.1.1.4.3. Configuración de Cortex-Debug en VS Code¶
Crea .vscode/launch.json en el repositorio. El caso más sencillo, en el que VS Code, la J-Link y la compilación están todos en la misma máquina Linux / macOS, usa servertype: "jlink", lo que hace que Cortex-Debug inicie por sí mismo un servidor GDB de 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"
}
]
}
Cambia executable y device para tu placa (consulta la tabla anterior). Pulsa F5 para compilar, grabar y ejecutar hasta main y detenerte ahí.
Truco
Para recompilar automáticamente cada vez que inicies la depuración, añade una tarea de compilación a .vscode/tasks.json y haz referencia a ella desde la configuración de lanzamiento con "preLaunchTask". Por ejemplo, una tarea que ejecute make -j$(nproc) TARGET=OPENMV4 DEBUG=1, llamada "build-firmware", más "preLaunchTask": "build-firmware" en la configuración anterior, de modo que F5 recompile, grabe e inicie el depurador en un solo paso.
Advertencia
Cortex-Debug necesita arm-none-eabi-gdb. Se incluye en el SDK en ~/openmv-sdk-<version>/gcc/bin, pero no está en el PATH de forma predeterminada, por lo que la depuración falla con «GDB executable “arm-none-eabi-gdb” was not found». Soluciónalo estableciendo armToolchainPath / gdbPath como se muestra arriba, o añadiendo ~/openmv-sdk-<version>/gcc/bin a tu PATH (printenv PATH debería entonces incluirlo).
14.1.1.4.4. Vista de registros de periféricos (SVD)¶
Apunta Cortex-Debug a un archivo SVD de CMSIS para obtener una vista decodificada de los registros de periféricos (temporizadores, DMA, la interfaz de la cámara, etc.) por nombre y campo de bits:
"svdFile": "/path/to/STM32H743.svd"
Para STM32 y MIMXRT, obtén el SVD de los paquetes CMSIS de ST / NXP o del registro de SVD de Cortex-Debug. Los SVD de Alif vienen incluidos en el repositorio del firmware en lib/micropython/lib/alif_ensemble-cmsis-dfp/Debug/SVD/ (usa el ..._CM55_HP_View.svd para el núcleo HP del AE3).
14.1.1.4.5. Windows: el puente J-Link entre WSL y Windows¶
WSL 2 no puede ver directamente el dispositivo USB de la J-Link, así que la división es: Windows sirve la sonda (donde está conectada) y VS Code + gdb se ejecutan en WSL y la alcanzan a través de TCP.
En Windows, instala el paquete de Segger J-Link y conecta la J-Link a un puerto USB de Windows.
En Windows, inicia el J-Link Remote Server (se incluye con el paquete de J-Link): lánzalo con la J-Link conectada y haz clic en OK. Permítelo a través del firewall de Windows cuando se te solicite. La ventana muestra la dirección IP en la que está sirviendo la sonda; anótala.
En WSL, compila con
DEBUG=1y asegúrate de quearm-none-eabi-gdbsea accesible (establecearmToolchainPathcomo se indicó arriba).En VS Code dentro de WSL, mantén
servertype: "jlink"(el servidor GDB se ejecuta en WSL y alcanza la sonda a través del Remote Server) y añadeserverpath+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" }
Establece
ipAddressen la dirección que muestra la ventana del Remote Server. Eso es todo el puente.
Truco
Alternativa al puente del servidor GDB: usbipd-win. En lugar de ejecutar un servidor en Windows, puedes conectar el dispositivo USB de la J-Link directamente a WSL con usbipd-win. Desde un PowerShell de administrador:
winget install usbipd
usbipd list
usbipd bind --busid <busid>
usbipd attach --wsl --busid <busid>
(<busid> es el ID de bus de la J-Link obtenido de usbipd list.) La sonda aparece entonces dentro de WSL, y usas la configuración sencilla de la misma máquina servertype: "jlink" de VS Code Cortex-Debug setup sin dirección IP y sin un servidor de Windows aparte. El puente del servidor GDB requiere menos configuración para un uso ocasional; usbipd-win es más cómodo para el desarrollo rutinario.
Truco
Usa "request": "attach" para depurar el firmware tal como ya se está ejecutando sin reiniciarlo ni volver a grabarlo, ideal para capturar un cuelgue en campo. Usa "request": "launch" para reiniciar, grabar el ELF y empezar de nuevo en runToEntryPoint.
14.1.1.4.6. Depuración desde la línea de comandos con gdbrunner¶
Configurar a mano una sesión de GDB contra un destino embebido es un baile de cinco pasos: iniciar el servidor GDB de J-Link / ST-Link en una ventana con el dispositivo, el puerto y los indicadores de interfaz correctos; esperar a que imprima Waiting for GDB connection; ejecutar arm-none-eabi-gdb en una segunda ventana; escribir target remote localhost:<port>; apuntar gdb al ELF. Cuando la sesión de gdb termina, recuerda cerrar la ventana del servidor. gdbrunner es una pequeña CLI que condensa todo eso en un solo comando en primer plano. Se incluye en el entorno de Python del SDK de OpenMV, así que no hay nada que instalar; el punto de entrada habitual es el destino make debug del repositorio del firmware:
make -j$(nproc) TARGET=<TARGET> DEBUG=1 debug
Esto ejecuta gdbrunner con los argumentos del depurador de la configuración de la placa (el nombre del dispositivo J-Link y, donde sea necesario, el cargador de memoria flash externa de ST-Link) con el arm-none-eabi-gdb del SDK ya en el PATH. El backend predeterminado es J-Link; make DEBUGGER=STLINK debug funciona con una sonda ST-Link en su lugar.
gdbrunner también puede invocarse directamente (fuera del 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
El primer argumento posicional elige el backend del servidor (jlink, stlink, qemu); el resto se reenvía a ese backend, con valores predeterminados que funcionan para las cámaras OpenMV. gdbrunner --help lista el conjunto completo de indicadores por backend; la tabla de argumentos de cada backend está definida por JSON (src/gdbrunner/backends.json), así que añadir un servidor nuevo es una edición de configuración en lugar de código.
Lo que gdbrunner hace para el trabajo en la línea de comandos:
Un proceso, ciclo de vida limpio. El servidor arranca, gdb se conecta cuando el puerto está abierto y el servidor se termina limpiamente cuando gdb sale. Sin ningún
JLinkGDBServerhuérfano que sobreviva a la sesión, sin dos terminales que gestionar.Descubrimiento automático de STM32CubeProgrammer. El backend
stlinkbusca en las ubicaciones de instalación habituales (~/STM32CubeProgrammer/,/opt/st/, el árbol de complementos de STM32CubeIDE) las herramientas de STM32CubeProgrammer, de modo que no haya que escribir cada vez la larga ruta--cube-prog. El SDK incluye su propia copia en~/openmv-sdk-<version>/stcubeprog/bin; apunta--cube-progahí si no existe una instalación del sistema.Se respeta el gdbinit por proyecto. Un
.gdbiniten el directorio actual se carga con-ix(anulando el~/.gdbinitglobal del usuario), así que el scripting de gdb por proyecto (pretty-printers, macros específicas de la placa, conjuntos de puntos de interrupción) se incorpora con solo estar presente en el directorio de trabajo.make debugse ejecuta desde la raíz del repositorio, así que un.gdbinitahí se aplica.Ejecución en seco.
--dryrunimprime el comando del servidor sin ejecutarlo, útil para adaptar la invocación a un script envoltorio, copiarla en la configuración de lanzamiento de un IDE o simplemente comprobar qué argumentos está componiendo gdbrunner.Salida del servidor visible.
--show-outputmantiene visibles la stdout / stderr del servidor. El valor predeterminado las suprime (para que la interfaz de gdb quede limpia); activa el indicador cuando el problema está en el propio servidor.Backend de QEMU.
qemu-system-armdepura una compilación del firmware sin ninguna placa conectada. El destinoMPS2_AN500selecciona este backend en su configuración de placa, así quemake TARGET=MPS2_AN500 DEBUG=1 debugcompila para la máquinamps2-an500de QEMU y recorre paso a paso el código independiente de la plataforma (todo lo que no toca periféricos específicos de la cámara) en un avión. (qemu-system-armes una instalación del host, no forma parte del SDK.)
Para la ejecución paso a paso a nivel de código fuente con márgenes de puntos de interrupción y una vista de registros de periféricos, la configuración de Cortex-Debug en VS Code descrita arriba es la mejor herramienta; gdbrunner es la adecuada para todo lo que vive en la línea de comandos.
14.1.1.4.7. Uso del depurador¶
Una vez que una sesión está en marcha (con el procesador detenido en main):
Puntos de interrupción – haz clic en el margen junto a una línea de C, o en la Debug Console usa
break <file>:<line>/break <function>. Los núcleos Cortex-M tienen un número reducido de comparadores de puntos de interrupción de hardware (normalmente de 6 a 8 en M7 / H7, 8 en M55). Superar ese número en código en memoria flash falla silenciosamente; mantén modesto el número de puntos de interrupción activos.Ejecución paso a paso – F10 salta por encima (
next), F11 entra (step), Shift+F11 sale (finish), F5 continúa. La ejecución paso a paso a nivel de instrucción esstepi/nextien la Debug Console.Variables / inspección / pila de llamadas – los paneles Variables y Call Stack muestran las variables locales y el rastreo de pila; añade expresiones a Watch. Pasa el cursor sobre una variable en el código fuente para ver su valor. Cualquier cosa que muestre
<optimized out>significa que no estás en una compilaciónDEBUG=1.Puntos de observación (puntos de interrupción de datos) –
watch <expr>se detiene cuando se escribe una variable,rwatchcuando se lee,awatchen cualquiera de los dos casos. La unidad DWT de Cortex-M admite ~4 puntos de observación de hardware, lo cual es muy valioso para capturar quién corrompió una variable.Registros y periféricos – la vista Cortex Registers muestra los registros del núcleo; con
svdFileestablecido, la vista Peripherals decodifica cada registro de periférico y campo de bits (DMA, temporizadores, la interfaz de cámara / CSI, XSPI, etc.), la forma más rápida de ver por qué un controlador funciona mal.Memoria – usa el visor de memoria de Cortex-Debug o el
x/de gdb para inspeccionar directamente los búferes de fotogramas, los búferes de DMA y las estructuras.printf sin detener (SWO/RTT) – para problemas sensibles a la temporización, el RTT o el SWO de Segger ofrecen
printfcon una sobrecarga casi nula mientras el destino se ejecuta. Compila conDEBUG_PRINTF=1y añade elrttConfig(RTT) o elswoConfig(SWO, necesita el reloj del núcleo) de Cortex-Debug. Esta es la herramienta adecuada cuando un punto de interrupción cambiaría la temporización que intentas observar.Desconexión – Stop en una sesión
launchdetiene el destino; Disconnect en una sesiónattachdeja la cámara en ejecución. Apaga y enciende la cámara para devolverla al funcionamiento normal después.
14.1.1.4.8. Errores comunes en la depuración¶
Variables optimizadas y descartadas. Todo muestra
<optimized out>: compilaste conDEBUG=0. Recompila conDEBUG=1.«GDB executable not found» – el
gcc/bindel SDK no está en elPATH; establecearmToolchainPath/gdbPath.«Cannot connect» / mapa de memoria incorrecto – nombre de
deviceincorrecto o ausente; usa la cadena exacta de la tabla.Los puntos de interrupción no se alcanzan silenciosamente – demasiados puntos de interrupción de hardware en código residente en memoria flash; redúcelos.
Las rutas de los fuentes no coinciden (ELF compilado con Docker) – compila con el destino
build-firmware-devde Docker (misma ruta absoluta dentro y fuera del contenedor) o establece en gdbset substitute-path.