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.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.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 JLinkGDBServer huérfano que sobreviva a la sesión, sin dos terminales que gestionar.

  • Descubrimiento automático de STM32CubeProgrammer. El backend stlink busca 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-prog ahí si no existe una instalación del sistema.

  • Se respeta el gdbinit por proyecto. Un .gdbinit en el directorio actual se carga con -ix (anulando el ~/.gdbinit global 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 debug se ejecuta desde la raíz del repositorio, así que un .gdbinit ahí se aplica.

  • Ejecución en seco. --dryrun imprime 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-output mantiene 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-arm depura una compilación del firmware sin ninguna placa conectada. El destino MPS2_AN500 selecciona este backend en su configuración de placa, así que make TARGET=MPS2_AN500 DEBUG=1 debug compila para la máquina mps2-an500 de 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-arm es 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 pasoF10 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 es stepi / nexti en 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ón DEBUG=1.

  • Puntos de observación (puntos de interrupción de datos)watch <expr> se detiene cuando se escribe una variable, rwatch cuando se lee, awatch en 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 svdFile establecido, 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 printf con una sobrecarga casi nula mientras el destino se ejecuta. Compila con DEBUG_PRINTF=1 y añade el rttConfig (RTT) o el swoConfig (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ónStop en una sesión launch detiene el destino; Disconnect en una sesión attach deja 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 con DEBUG=0. Recompila con DEBUG=1.

  • «GDB executable not found» – el gcc/bin del SDK no está en el PATH; establece armToolchainPath / gdbPath.

  • «Cannot connect» / mapa de memoria incorrecto – nombre de device incorrecto 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-dev de Docker (misma ruta absoluta dentro y fuera del contenedor) o establece en gdb set substitute-path.