Atira debug probe

DebugProbe drives a Binho Atira: target power, SWD debug on Arm Cortex-M, an SPI controller with SPI NOR flash programming, and a USB-to-UART bridge. It is its own class rather than a Device, and a probe and a Supernova or Pulsar can be open in the same process. The async twin is AsyncDebugProbe.

The probe needs firmware fw-atira v1.0 or newer. RTT, SWO, UPDI and JTAG are not available in this release.

Setup

On Linux, the probe needs the same udev rule as the host adapters. The file install_udev_rules.sh writes (see Getting started) covers the Atira, USB ID 1fc9:8347. Without it, open() fails with DeviceNotFoundError, naming the permission problem.

Opening a probe

from pycosmicsdk import DebugProbe, list_debug_probes

for found in list_debug_probes():
    print(found.serial_number, found.path)

with DebugProbe.open(serial="3BDA1AEC98B43363374253A2CA79B756") as probe:
    info = probe.info
    print(f"fw {info.fw_major}.{info.fw_minor}.{info.fw_patch}")

open() without arguments takes the first probe found. It raises UnsupportedFirmwareError when the probe answered but runs firmware older than v1.0. It raises DeviceNotFoundError when nothing matched, when another process holds the probe, or when this user cannot open it.

Probe-wide calls live on the probe: info (cached at open; fetch_device_info() reads it again), settings(), mode() / set_mode(), pin_status(), swd_coexist_status(), enter_boot_mode(). Each function of the probe is a sub-object, returned by a call as on a Device:

Call

Returns

What it does

probe.power()

TargetPower

Switch the target rail, read voltage, current and power

probe.debug()

DebugTarget

SWD: attach, halt, step, run, registers, memory, breakpoints

probe.spi()

DebugProbeSpi

SPI controller, with SpiController’s names

probe.flash()

SpiFlash

SPI NOR: detect, read, erase, program, verify

probe.uart()

UartBridge

The bridge’s serial port name and saved line settings

Target power

J4’s I/O runs at the probe’s signal voltage, and the probe can also supply the target on pin 1. Switch the signal voltage on before an SWD or SPI session: it powers the level shifters on J4.

On fw-atira 1.1 and newer (the firmware 1.9.0 pairs with) the two are separate switches. Decide with signal_voltage_check(), which reads pin 1 and changes nothing:

from pycosmicsdk.debugprobe import SignalVoltageState

power = probe.power()
check = power.signal_voltage_check()
if check.state == SignalVoltageState.NO_SUPPLY:  # pin 1 dead: the probe powers the target
    power.set_signal_voltage_level(3300)
    power.set_signal_voltage_enabled(True)
    power.set_target_power_enabled(True)
else:  # self-powered: match its supply, no target power
    power.signal_voltage_use_external()
reading = power.reading()
print(reading.voltage_mv, "mV", reading.current_ua, "uA", reading.power_mw, "mW")

atira_power_on() in examples/helpers.py is this logic plus the fw 1.0.x path; the examples use it. Current is in µA because a sleeping target draws well under 1 mA. Decide “self-powered” with signal_voltage_check(), not with one reading(), which lags 300-600 ms after a switch-off.

The signal goes on before target power, so for a moment the I/O drives a target that has no supply yet. That matters on boards without input protection. set_target_power_enabled(True) normally answers at once, but while pin 1 still holds charge from an earlier switch-off the firmware re-reads it until it stops falling, and the reply can take up to 0.5 s with the signal already on. Switch on a while after the last off.

fw-atira 1.0.x

One switch, set_output, turns the signal voltage and 3.3 V on pin 1 on together. Each generation’s calls raise DEBUGPROBE_POWER_USE_* on the other.

import time

power.set_output(True)
time.sleep(0.5)  # the reading lags the switch by about half a second

Warning

On fw-atira 1.0.x, do not switch it on when the target has its own supply wired to J4 pin 1: two supplies end up in parallel. Leave pin 1 unconnected on a self-powered board, or update the probe to fw-atira 1.1.

Flashing the target

probe.target_flash() programs the target’s own flash through the vendor’s CMSIS flash algorithm (.FLM), from a .pack or a bare .FLM with its RAM window. It needs an attached session, and leaves the target halted.

from pycosmicsdk import TargetFlashVerifyError
from pycosmicsdk.debugprobe import TargetFlash, TargetFlashPackSupport

pack = "Keil.STM32H5xx_DFP.2.3.1.pack"
dev = next(d for d in TargetFlash.list_pack_devices(pack) if d.name == "STM32H503RBTx")
if dev.support == TargetFlashPackSupport.NOT_SUPPORTED:
    raise SystemExit(dev.reason)
if dev.support == TargetFlashPackSupport.NEEDS_CONNECT_STEPS:
    # The SDK skips these vendor steps. Pass them only after the user accepts that.
    raise SystemExit(f"{dev.name} needs vendor connect steps {dev.connect_steps}: confirm first")

probe.debug().attach()
flash = probe.target_flash()
info = flash.load_pack_device(pack, dev.name, confirmed_connect_steps=[])  # FLASHABLE
try:
    flash.write(info.flash_base, open("app.bin", "rb").read(), verify=True)
except TargetFlashVerifyError as e:
    print(f"mismatch at 0x{e.first_mismatch:08X}")
probe.debug().run()

confirmed_connect_steps has no default. A FLASHABLE part takes an empty list. A NEEDS_CONNECT_STEPS part lists vendor connect steps the SDK does not run: pass dev.connect_steps only once the user has accepted that, as MC3 asks each time. erase, erase_chip, program and verify run one step each; last_report() says what the last operation did. Only the STM32H503 is verified on hardware.

SPI and SPI NOR flash

On the Atira, SPI uses J4 with its own pin assignment. The SWD names on a debug cable do not apply:

Flash pin

J4 pin

/CS

2 (fw-atira 1.1; 9 on 1.0.x)

CLK

4

DI (MOSI)

6

DO (MISO)

8

VCC

1 (the target rail)

GND

3

Tie /WP and /HOLD to VCC if the flash breakout does not. The chip select is CS_0 on fw-atira 1.1 (CS_2 on 1.0.x), and the clock tops out at 8 MHz. Power J4 first (see Target power above).

from pycosmicsdk import FlashVerifyMethod, SpiTransport
from pycosmicsdk.enums import (
    SpiBitOrder, SpiChipSelect, SpiChipSelectPolarity, SpiDataWidth, SpiMode,
)

spi = probe.spi().initialize(
    bit_order=SpiBitOrder.MSB,
    mode=SpiMode.MODE_0,
    data_width=SpiDataWidth.BITS_8,
    chip_select=SpiChipSelect.CS_0,  # J4 pin 2 on fw-atira 1.1
    chip_select_polarity=SpiChipSelectPolarity.ACTIVE_LOW,
    frequency_hz=8_000_000,
)
print(spi.transfer(b"\x9f" + bytes(3))[1:].hex())   # JEDEC id

image = open("firmware.bin", "rb").read()
flash = probe.flash()
part = flash.detect()
flash.erase(0x10_0000, len(image))
flash.program(0x10_0000, image)
check = flash.verify(0x10_0000, image, method=FlashVerifyMethod.HASH)
if not check.passed:
    print(f"first mismatch at 0x{check.first_mismatch:X}")
spi.release()

program() uses the faster bulk interface when the host can open it and falls back to HID otherwise; flash.last_transport says which one ran. Pass transport=SpiTransport.HID or SpiTransport.BULK to choose. verify() and blank_check() both return FlashCheck. A HASH verify compares a CRC-32 computed on the probe and reads the flash back only to locate a mismatch.

UART bridge

The bridge is an ordinary USB serial port. Open it with any serial library; the probe does not proxy the bytes.

port = probe.uart().bridge_port   # e.g. /dev/ttyACM0 or COM7; empty if not found

uart().config() and uart().configure() read and save the bridge settings the probe boots with. The line coding in use is the one the host sets when it opens the port.

Asyncio

AsyncDebugProbe mirrors every call with await, including the sub-objects:

async with AsyncDebugProbe.open() as probe:
    power = probe.power()
    await power.set_signal_voltage_level(3300)   # fw 1.1, unpowered target
    await power.set_signal_voltage_enabled(True)
    await power.set_target_power_enabled(True)
    state = await probe.debug().attach()

The probe answers one command at a time, so calls on one probe never overlap.

Examples

Runnable scripts under examples/blocking_api/debugprobe/ and examples/async_api/debugprobe/:

  • get_probe_info.py: identity, supply state and pin ownership. Read-only.

  • debug_halt_and_read.py: attach, halt, registers, the vector table, a step and a breakpoint. Powers J4 with atira_power_on() (fw-atira 1.0.x or 1.1, unpowered or self-powered target).

  • flash_program_and_verify.py: detect, erase, program and verify 64 KiB at 1 MiB. Needs a W25Q on J4 and PYCOSMICSDK_ATIRA_SPI_FLASH=W25Q.

The API reference is Debug probe.