# Atira debug probe {py:obj}`~pycosmicsdk.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 {py:obj}`~pycosmicsdk.Device`, and a probe and a Supernova or Pulsar can be open in the same process. The async twin is {py:obj}`~pycosmicsdk.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](../getting-started.md)) covers the Atira, USB ID `1fc9:8347`. Without it, `open()` fails with {py:obj}`~pycosmicsdk.DeviceNotFoundError`, naming the permission problem. ## Opening a probe ```python 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 {py:obj}`~pycosmicsdk.UnsupportedFirmwareError` when the probe answered but runs firmware older than v1.0. It raises {py:obj}`~pycosmicsdk.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()` | {py:obj}`~pycosmicsdk.debugprobe.TargetPower` | Switch the target rail, read voltage, current and power | | `probe.debug()` | {py:obj}`~pycosmicsdk.debugprobe.DebugTarget` | SWD: attach, halt, step, run, registers, memory, breakpoints | | `probe.spi()` | {py:obj}`~pycosmicsdk.debugprobe.DebugProbeSpi` | SPI controller, with {py:obj}`~pycosmicsdk.SpiController`'s names | | `probe.flash()` | {py:obj}`~pycosmicsdk.debugprobe.SpiFlash` | SPI NOR: detect, read, erase, program, verify | | `probe.uart()` | {py:obj}`~pycosmicsdk.debugprobe.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: ```python 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. ```python 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. ```python 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). ```python 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 {py:obj}`~pycosmicsdk.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. ```python 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 {py:obj}`~pycosmicsdk.AsyncDebugProbe` mirrors every call with `await`, including the sub-objects: ```python 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](../reference/debugprobe.md).