15.4.1.4. Debugging the firmware

On-hardware debugging means halting the processor, setting breakpoints in the C source, single-stepping, and inspecting variables, memory, registers, and peripherals – from inside VS Code. This needs three things: a debug build, a SWD debug probe (a Segger J-Link), and the Cortex-Debug extension driving arm-none-eabi-gdb against a J-Link GDB server.

15.4.1.4.1. Build for debugging

Always rebuild the target with DEBUG=1:

make -j$(nproc) TARGET=<TARGET> DEBUG=1

A release (DEBUG=0) image is compiled -O2; in the debugger you will see <optimized out> for many variables, inlined functions collapse into their callers, and stepping jumps around unpredictably. DEBUG=1 builds -Og -ggdb3, which is debuggable while still booting on the camera. The ELF you point the debugger at is:

build/<TARGET>/bin/firmware.elf

(For the Alif AE3, debug build/OPENMV_AE3/bin/firmware_M55_HP.elf – the high-performance core.)

15.4.1.4.3. VS Code Cortex-Debug setup

Create .vscode/launch.json in the repository. The simplest case – VS Code, the J-Link, and the build are all on the same Linux / macOS machine – uses servertype: "jlink", which makes Cortex-Debug start a J-Link GDB server itself:

{
  "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"
    }
  ]
}

Change executable and device for your board (see the table above). Press F5 to build-flash-and-run to main and stop there.

Tip

To rebuild automatically every time you start debugging, add a build task to .vscode/tasks.json and reference it from the launch config with "preLaunchTask". For example a task running make -j$(nproc) TARGET=OPENMV4 DEBUG=1, named "build-firmware", plus "preLaunchTask": "build-firmware" in the configuration above, so that F5 rebuilds, flashes, and starts the debugger in a single step.

Warning

Cortex-Debug needs arm-none-eabi-gdb. It ships in the SDK at ~/openmv-sdk-<version>/gcc/bin but is not on PATH by default, so debugging fails with “GDB executable ‘arm-none-eabi-gdb’ was not found”. Fix it either by setting armToolchainPath / gdbPath as shown above, or by adding ~/openmv-sdk-<version>/gcc/bin to your PATH (printenv PATH should then list it).

15.4.1.4.4. Peripheral register view (SVD)

Point Cortex-Debug at a CMSIS SVD file to get a decoded peripheral-register view (timers, DMA, the camera interface, etc.) by name and bitfield:

"svdFile": "/path/to/STM32H743.svd"

For STM32 and MIMXRT, get the SVD from the ST / NXP CMSIS packs or the Cortex-Debug SVD registry. The Alif SVDs are vendored in the firmware repo at lib/micropython/lib/alif_ensemble-cmsis-dfp/Debug/SVD/ (use the ..._CM55_HP_View.svd for the AE3 HP core).

15.4.1.4.6. The make debug shortcut

If the J-Link is on the same machine as the build, the repository has a shortcut that starts a GDB server with the right device automatically:

make -j$(nproc) TARGET=<TARGET> DEBUG=1 debug

This runs the SDK’s gdbrunner with the board’s J-Link arguments against build/<TARGET>/bin/firmware.elf. It is convenient for command-line gdb; for source-level stepping, the VS Code Cortex-Debug setup above is what you want.

15.4.1.4.7. Using the debugger

Once a session is running (the processor halted at main):

  • Breakpoints – click the gutter next to a C line, or in the Debug Console break <file>:<line> / break <function>. Cortex-M cores have a small number of hardware breakpoint comparators (typically 6–8 on M7 / H7, 8 on M55). Exceeding that on code in flash silently fails – keep the active breakpoint count modest.

  • SteppingF10 step over (next), F11 step into (step), Shift+F11 step out (finish), F5 continue. Instruction-level stepping is stepi / nexti in the Debug Console.

  • Variables / watch / call stack – the Variables and Call Stack panes show locals and the backtrace; add expressions to Watch. Hover a variable in the source to see its value. Anything showing <optimized out> means you are not on a DEBUG=1 build.

  • Watchpoints (data breakpoints)watch <expr> halts when a variable is written, rwatch on read, awatch on either. The Cortex-M DWT unit supports ~4 hardware watchpoints – invaluable for catching who corrupted a variable.

  • Registers and peripherals – the Cortex Registers view shows core registers; with svdFile set, the Peripherals view decodes every peripheral register and bitfield (DMA, timers, the camera / CSI interface, XSPI, etc.) – the fastest way to see why a driver is misbehaving.

  • Memory – use the Cortex-Debug memory viewer or gdb x/ to inspect framebuffers, DMA buffers, and structures directly.

  • printf without halting (SWO/RTT) – for timing-sensitive issues, Segger RTT or SWO gives near-zero-overhead printf while the target runs. Build with DEBUG_PRINTF=1 and add Cortex-Debug’s rttConfig (RTT) or swoConfig (SWO, needs the core clock). This is the right tool when a breakpoint would change the timing you are trying to observe.

  • DisconnectingStop on a launch / jlink session halts the target; Disconnect on an attach / external session leaves the camera running. Power-cycle the camera to return it to normal operation afterwards.

15.4.1.4.9. Debugging pitfalls

  • Everything is ``<optimized out>`` – you built DEBUG=0. Rebuild with DEBUG=1.

  • “GDB executable not found” – the SDK gcc/bin is not on PATH; set armToolchainPath / gdbPath.

  • “Cannot connect” / wrong memory map – wrong or missing device name; use the exact string from the table.

  • Breakpoints silently not hit – too many hardware breakpoints on flash-resident code; reduce them.

  • N6 / RT1062 / AE3 won’t flash from the debugger – they boot from external flash; use "request": "attach" against running firmware instead of launch.

  • Source paths don’t match (Docker-built ELF) – build with the Docker build-firmware-dev target (same absolute path inside and outside the container) or set gdb set substitute-path.