Getting Started with the Debug Probe

CosmicSDK drives the Binho Atira debug probe (since 1.7) next to the Supernova and Pulsar host adapters. Nothing changes for host adapter users: CosmicSDK and libcosmicsdk stay as they are, and the debug probe has its own class and its own library.

What 1.9 covers

  • Target flash: program, erase and verify the target’s own flash through the vendor’s CMSIS flash algorithm (.FLM), straight from a .pack (see Flashing a target with the SDK). New in 1.9.

  • Target power on fw-atira 1.1: the signal voltage and target power as separate switches, and self-powered targets (see Connecting a target). New in 1.9.

  • SWD debug: attach, halt, run, step, hardware breakpoints, registers, memory.

  • External SPI flash: detect, read, erase, program, verify.

  • The probe’s USB serial ports.

  • Rebooting the probe into its bootloader for a firmware update.

pyOCD still works for flashing (see Flashing a target with pyOCD). Python is pycosmicsdk 1.9.0 (DebugProbe, target_flash()). JTAG, SWO and RTT are not part of 1.9.

What is in the download

The same archives and installers as Download and Installation. Next to the host adapter files, each one carries:

  • lib/: the debug probe library, libdebugprobe.so / libdebugprobe.dylib / debugprobe.dll (with debugprobe.lib), and the static debugprobe_static.

  • include/debugprobe/ and include/DebugProbe_API_c.h: the C++ and C headers.

  • examples/debugprobe/: debugprobe_demo.cpp (C++ tour), debugprobe_c_demo.c (C tour), spi_flash_example.cpp (program and verify an SPI NOR flash), and the target flash examples target_flash_example.cpp (C++), target_flash_c_example.c (C) and target_flash_pack_example.cpp (load a part from a .pack).

Minimum firmware

The probe needs firmware fw-atira v1.0 or later. From v1.0 the firmware answers with 16-bit status codes, and connect() refuses an older image: it returns false (DebugProbe_Connect returns false in C) and prints the firmware answers with an 8-bit status (before fw-atira v1.0) ... update the probe firmware on stderr. Update the probe, then connect again.

To read the version of a probe that connects:

#include "debugprobe/DebugProbe.hpp"

using namespace debugprobe;

DebugProbe probe;
auto boards = DebugProbe::listDevices();   // serial, path, vid, pid
if (boards.empty() || !probe.connect("", boards[0].serial)) {
    return 1;
}
DeviceInfo info;
if (probe.getDeviceInfo(info) == SUCCESS) {
    std::printf("firmware %u.%u.%u\n", info.fw_major, info.fw_minor, info.fw_patch);
}

In C, DebugProbe_GetDeviceInfo() fills DebugProbe_DeviceInfo with the same fw_major, fw_minor and fw_patch fields.

Every call returns a CosmicSDK result code and is checked against SUCCESS, like any CosmicSDK call.

Setting up the host

Linux. Install the udev rule shipped in the archive, then unplug and replug the probe:

sudo ./scripts/install_udev_rules.sh

The rule grants the logged-in user access (uaccess), whether or not the distribution has a plugdev group, and covers the probe’s bootloader too. Without the rule, opening the probe fails with a permission error.

Windows. No driver installation is expected. Until the probe firmware declares its bulk interface to Windows, SPI flash programming uses the HID path, which is slower (spiBulkAvailable() reports it as unavailable).

macOS. Nothing to install.

Connecting a target

These are the Atira Rev A/B connectors. Switch the signal voltage on before any SWD or SPI traffic: it powers the level shifters on the debug header. On fw-atira 1.1 that is setSignalVoltageEnabled(true), plus setTargetPowerEnabled(true) only when pin 1 has no supply; on 1.0.x it is powerSetOutput(true), which also drives pin 1 (powerSetOutput answers DEBUGPROBE_POWER_USE_SPLIT_CONTROLS on 1.1).

Debug header J4 (10-pin, 1.27 mm):

Pin

Signal

Pin

Signal

1

VDD (target)

2

SWDIO

3

GND

4

SWCLK

5

GND

6

SWO

7

key

8

TDI

9

TRST

10

nRESET

Warning

Pin 9 is TRST, not the GNDDetect of the standard Cortex debug pinout. A cable that grounds pin 9 is fine for SWD. On fw-atira 1.0.x it is also the SPI chip select, so such a cable breaks SPI there; fw-atira 1.1 moved the chip select to pin 2.

For a target with its own supply: on fw-atira 1.1, keep pin 1 connected. The probe reads the target’s supply there and sets the signal voltage to it (signalVoltageUseExternal) without powering pin 1. On 1.0.x, leave pin 1 unconnected, since powerSetOutput(true) would drive it over the target’s supply.

SPI flash also uses J4. Wire a 25-series NOR flash as /CS → 2 (fw-atira 1.1; pin 9 on 1.0.x), CLK → 4, DI (MOSI) → 6, DO (MISO) → 8, VCC → 1 and GND → 3, with /WP and /HOLD tied to VCC. SCK goes up to 8 MHz. The chip select to pass to spiControllerInit() is SPI_CHIP_SELECT_0 on 1.1 and SPI_CHIP_SELECT_2 on 1.0.x. examples/debugprobe/spi_flash_example.cpp finds it on its own.

UART bridge is on J3 (3-pin): 1 = TX (out of the probe, to the target’s RX), 2 = GND, 3 = RX. The same signals are on J2: 4 = TX, 5 = RX, 1 = GND. ⚠ J2 pin 3 is the target rail. The bridge appears as the probe’s first serial port (uartBridgePort()).

Building against the library

Link the debug probe library. A host adapter program keeps linking cosmicsdk; a program that uses both boards links both.

# Linux / macOS, from the extracted archive
c++ -std=c++17 -Iinclude my_probe_app.cpp -Llib -ldebugprobe -o my_probe_app
cc  -std=c11   -Iinclude my_probe_app.c   -Llib -ldebugprobe -o my_probe_app

The examples in examples/debugprobe/ build with these commands. Run one with the probe’s serial number to pin it on a bench with several boards:

./debugprobe_demo --serial=<iSerial>
./debugprobe_c_demo <iSerial>

Flashing a target with the SDK

From 1.9, DebugProbe programs a target’s internal flash through a CMSIS flash algorithm, the .FLM file in the vendor’s device pack. pyOCD and Ground Control are not involved. The SDK runs the algorithm on the target with debugCallFunction; the probe needs no new firmware.

The inputs are bytes in memory. The SDK takes no file paths:

  • the .FLM: targetFlashLoadAlgorithm copies it and holds it until targetFlashUnloadAlgorithm or disconnect. One algorithm per handle.

  • a RAM window that holds the algorithm, a 512-byte stack and one page buffer: from the datasheet, or RAMstart/RAMsize in the pack’s .pdsc.

  • the image, as raw bytes at an address. HEX, ELF and S-record files are not read.

flashSize comes from the FLM, not from the part. A family FLM can declare more than one part has: Nordic’s nrf52xxx.flm declares 2 MiB, the nRF52840 has 1 MiB. Keep images inside the part’s real flash (datasheet, or IROM in the .pdsc). A write past it fails with DEBUGPROBE_TFLASH_ROUTINE_FAILED. On an nRF52840 that write left the real flash unchanged (checked byte for byte).

#include "debugprobe/DebugProbe.hpp"
#include "debugprobe/TargetFlash.hpp"

DebugProbe probe;
probe.connect("", serial);
// fw-atira 1.1, unpowered target: Atira supplies it. A self-powered board and
// fw 1.0.x need other calls: see powerTarget() in target_flash_example.cpp.
probe.setSignalVoltageLevel(3300);
probe.setSignalVoltageEnabled(true);
probe.setTargetPowerEnabled(true);
DebugState st;
probe.debugAttach(false, st);

TargetFlashInfo info;                      // flash base, size, page size, sectors
probe.targetFlashLoadAlgorithm(flm, {0x20000000, 0x8000}, info);   // STM32H503 SRAM1
uint16_t rc = probe.targetFlashWrite(info.flashBase, image, /*verify=*/true,
    [](uint32_t done, uint32_t total) { return true; });            // false cancels
probe.debugRun(st);                        // the target is left halted at reset

Check every result against SUCCESS. The full programs are examples/debugprobe/target_flash_example.cpp and target_flash_c_example.c.

From a CMSIS-Pack

targetFlashListPackDevices lists a .pack’s devices, each with its <variant> part numbers; targetFlashLoadPackDevice loads one by device name in place of targetFlashLoadAlgorithm, with the .pdsc’s RAM window. Both take the pack as bytes; listing needs no handle.

Each device comes back with a support value:

  • Flashable: loads as is.

  • Needs connect steps: the pack declares vendor connect steps (for example DebugPortStart on NXP MCX parts) that the SDK does not run. The device loads only when the caller passes its steps back as confirmed, same names in the same order; otherwise the load returns DEBUGPROBE_TFLASH_PACK_NEEDS_CONFIRM. After an operation that touched the target, the report’s connectStepsSkipped names them (C: DebugProbe_TargetFlashGetLastSkippedSteps). Operations can then fail before the target answers, because the steps did not run.

  • Not supported: refused, with reason saying why.

In C, DebugProbe_TargetFlashListPackDevices and _ListPackVariants take the caller’s sizeof of the entry (entrySize); a device’s connect steps are read one at a time with DebugProbe_TargetFlashGetPackDeviceConnectStep.

Multi-core parts (multiCore in the listing) load by one rule, with no core choice:

  • The memory map is every core’s, merged. The part loads only when its flash is one contiguous block, as on the STM32H745 (CM7’s bank 1 + CM4’s bank 2). Flash split across cores is refused as several flash regions.

  • When each core declares its own copy of the .FLM over the same range, the copy of the core on AP0, the one the Atira attaches, is loaded with its RAMstart. When no single core is on AP0, the part is not listed.

Call

What it does

targetFlashLoadAlgorithm

Parse the FLM and place it in the RAM window, on the host. Touches nothing on the target.

targetFlashWrite

Erase the sectors the image touches, program, and verify if asked, in one algorithm session.

targetFlashEraseRange

Erase every sector that overlaps the range. Bytes of those sectors outside the range are lost.

targetFlashEraseChip

Erase the whole flash region: the FLM’s EraseChip when it has one, else every sector.

targetFlashProgram

Program pages padded with the erased value, skipping pages that are all erased. Does not erase.

targetFlashVerify

Read back with the core halted and compare; returns the first differing address.

targetFlashGetLastReport

What the last call did: whether it touched the target, sectors erased and the erased range, bytes programmed, the failing routine and its r0, and whether the final reset held.

targetFlashGetInfo

The loaded algorithm’s flash description.

targetFlashUnloadAlgorithm

Drop the algorithm.

The C API has the same calls as DebugProbe_TargetFlash*, with a bool (*)(uint32_t done, uint32_t total, void* user) progress callback. DebugProbe_TargetFlashReport stays 28 bytes; the erased range comes from DebugProbe_TargetFlashGetLastErasedRange.

The erased range is whole sectors, so it can reach past the requested range. It counts only erases that finished: after a failed EraseChip the whole region is in an unknown state, and after a failed EraseSector so is the next sector.

What every operation does

  • Refuses before touching the target when no algorithm is loaded, no debug session is attached, the range leaves the algorithm’s flash region, or FPB breakpoints are set. Clear breakpoints first.

  • Reset and halt first, never a plain halt, so running firmware stops at the reset vector. Then it loads the algorithm and calls Init, EraseSector, ProgramPage and UnInit. Verify reads flash back over the debug port, never through the algorithm’s Verify entry.

  • Reset and halt last, on success, failure and cancel. The target is left halted at the reset vector and the session stays attached; debugRun starts it.

  • After a failure, the SDK calls UnInit if Init ran, then resets. If finalResetOk in the report is false, the target’s state is unknown: detach and power-cycle it. When that reset was the only failure, the call returns DEBUGPROBE_TFLASH_RESET_FAILED.

  • A readback that differs returns DEBUGPROBE_TFLASH_VERIFY_MISMATCH, and the report sets verifyMismatch and firstMismatch (also after targetFlashWrite with verify, which has no out-parameter for it).

  • Each routine has a time budget taken from the FLM’s own timeouts (Init and UnInit: 2 s). A routine that runs past it returns DEBUGPROBE_DEBUG_TIMEOUT. A routine that overruns its stack returns DEBUGPROBE_TFLASH_ROUTINE_FAILED.

  • Progress counts bytes over the whole operation (erase, program, verify) on the calling thread. Returning false cancels between pages and sectors, never inside a routine; the cleanup is a failure’s and the code is DEBUGPROBE_TFLASH_CANCELLED.

  • Blocking, one operation per handle at a time.

Result codes

DebugProbeTargetFlashCodes.h, group 0x1D:

Code

Value

Meaning

DEBUGPROBE_TFLASH_NO_ALGORITHM

0x1D01

No algorithm loaded.

DEBUGPROBE_TFLASH_BAD_ALGORITHM

0x1D02

Not an ELF32 FLM, an entry missing, a bad sector table, or over a cap (8 MiB file, 512 sector-table entries).

DEBUGPROBE_TFLASH_RAM_WINDOW

0x1D03

Algorithm, stack and page buffer do not fit the window, or the window overlaps flash.

DEBUGPROBE_TFLASH_OUT_OF_RANGE

0x1D04

The range leaves the flash region. Target untouched.

DEBUGPROBE_TFLASH_LOAD_MISMATCH

0x1D05

The algorithm read back different: the window is not writable RAM, or the target does not answer coherently.

DEBUGPROBE_TFLASH_ROUTINE_FAILED

0x1D06

A routine returned non-zero, or overran its stack. Routine and r0 are in the report.

DEBUGPROBE_TFLASH_VERIFY_MISMATCH

0x1D07

Read-back differs from the image.

DEBUGPROBE_TFLASH_CANCELLED

0x1D08

The progress callback returned false.

DEBUGPROBE_TFLASH_BREAKPOINTS_ACTIVE

0x1D09

FPB breakpoints are set. Target untouched.

DEBUGPROBE_TFLASH_BAD_PACK

0x1D0A

Not a readable .pack.

DEBUGPROBE_TFLASH_PACK_NO_DEVICE

0x1D0B

The pack has no device by that name.

DEBUGPROBE_TFLASH_PACK_UNSUPPORTED

0x1D0C

The device is listed as not supported.

DEBUGPROBE_TFLASH_PACK_NEEDS_CONFIRM

0x1D0D

The device’s connect steps were not confirmed.

DEBUGPROBE_TFLASH_RESET_FAILED

0x1D0E

Written (and verified, if asked); the final reset failed.

The debug plane’s codes keep their meaning here: DEBUGPROBE_DEBUG_NOT_ATTACHED, DEBUGPROBE_DEBUG_NO_TARGET (no target answers, for example with the rail off), DEBUGPROBE_DEBUG_FAULT (for example a RAM window with no memory behind it), DEBUGPROBE_DEBUG_TIMEOUT and DEBUGPROBE_DEBUG_CALL_FAULTED.

Supported in 1.9

  • Verified part: STM32H503RB on a NUCLEO-H503RB, with STM32H503_128k_0800.FLM from ST’s STM32H5xx_DFP and RAM window 0x20000000/0x8000. Other parts are not verified.

  • Probe: Atira Rev B over SWD, target either powered from the probe’s rail or self-powered from its own USB (the release-commit run was self-powered).

  • Host: Linux. Windows and macOS are not run on hardware.

  • Not supported: running vendor connect steps or debug sequences; HEX, ELF or S-record images; several flash regions, TrustZone pairs; choosing the core of a multi-core part (one with a single contiguous flash loads, with the AP0 core’s .FLM); option bytes; readout protection; the browser (WASM). Python target flash is in pycosmicsdk 1.9.0 (DebugProbe.target_flash()).

Measured on Linux, Atira Rev B with fw-atira 1.1.0, NUCLEO-H503RB, 2026-10-20:

Image

Runs

Median, write + verify

2 KiB

10/10

1.0 s

16 KiB

10/10

4.1 s

128 KiB

10/10

29.0 s

At the release commit (2026-10-28, target self-powered from its own USB), a 128 KiB targetFlashWrite took 11.44–11.47 s (faster than the 10-20 table because of #444, which batches the flash routine calls’ register traffic and waits in the probe, and #450, which skips their register save/restore), 10/10, read back identical; erase range (5/5, 3 sectors reported), erase chip (5/5), load by pack name (3/3) and cancel (3/3) passed.

A fixture firmware written through the API booted and ran 10/10. On the same bench, each refusal returned its code: breakpoint set → BREAKPOINTS_ACTIVE; truncated or garbage FLM → BAD_ALGORITHM; small window, or a window over flash → RAM_WINDOW; window with no memory → DEBUG_FAULT; detached → NOT_ATTACHED; rail off → NO_TARGET with 0 bytes written.

Flashing a target with pyOCD

The probe is a standard CMSIS-DAP probe, so pyOCD drives it with no extra setup. Verified on Linux, programming an STM32H503RB over SWD: pyOCD 0.44.1, 11/11 program, verify and erase cycles, about 6.5 s per 128 KiB; pyOCD 0.45.1 on a Rev A/B probe, program with a byte-identical read-back.

python -m pip install pyocd
pyocd list                               # shows the probe and its serial
pyocd pack install stm32h503rb           # the device pack, from the vendor
pyocd flash -u <iSerial> -t stm32h503rbtx --base-address 0x08000000 firmware.bin
  • Close any program that holds the probe (your own SDK program, Mission Control) before running pyOCD: only one program can own the probe’s debug interface.

  • The target must be powered. The probe can power it from its own rail, or it can run from its own supply.

  • Read flash back only while the core is halted (pyocd cmd -u <iSerial> -t stm32h503rbtx -c halt -c "savemem 0x08000000 131072 out.bin"). A read taken while the target runs can return wrong data.

Verified targets

Target

Tool

Result

Host

STM32H503RB

pyOCD

11/11 program + verify

Linux

STM32H503RB

pyOCD

program + read-back

Linux (Rev A/B)

Other Cortex-M devices with a CMSIS pack should work the same way; they are not verified yet.