> Complete documentation index: [llms.txt](/llms.txt). Markdown versions of all pages are available by appending `.md` to page URLs.

# Binho Atira: Protocols

> Using the Binho Atira for SWD debug, SPI flash programming and the UART bridge: PyCosmicSDK and C++ examples, and flashing with pyOCD.

Examples of using the Binho Atira for SWD debug, SPI flash programming and the UART bridge, and of flashing a target with pyOCD.

## Examples (PyCosmicSDK)

Install the SDK:

```bash
pip install "pycosmicsdk>=1.7.0"
```

On Linux, install the udev rule first; see [Binho Atira hardware setup](/hardware/atira/setup/#3-linux-udev-rule).

### Probe info

```python
from pycosmicsdk import DebugProbe, list_debug_probes

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

with DebugProbe.open() as probe:          # first probe found; or open(serial="...")
    info = probe.info
    print(f"Serial:   {probe.serial_number}")
    print(f"Firmware: {info.fw_major}.{info.fw_minor}.{info.fw_patch}")
```

`open()` raises `UnsupportedFirmwareError` when the probe runs firmware older than 1.0, and `DeviceNotFoundError` when no probe matched, another program holds it, or the current user has no access to it.

### Target power and reading

```python
import time
from pycosmicsdk import DebugProbe

with DebugProbe.open() as probe:
    power = probe.power()
    power.set_output(True)
    time.sleep(0.5)                       # the reading lags the switch by ~0.5 s
    r = power.reading()
    print(f"{r.voltage_mv} mV, {r.current_ua / 1000:.2f} mA, {r.power_mw} mW")
    print("rail on:", power.output())
```

### SWD: attach, halt, registers, memory, breakpoint

Connect the target to J4 with a straight 10-pin cable.

```python
import struct
import time
from pycosmicsdk import DebugProbe

REG_SP, REG_PC = 13, 15                   # 0-12 = r0-r12, 13 = sp, 14 = lr, 15 = pc
SCB_VTOR = 0xE000_ED08                    # vector table base, on every Cortex-M

with DebugProbe.open() as probe:
    probe.power().set_output(True)        # the SWD level shifters run from the rail
    time.sleep(0.5)

    debug = probe.debug()
    debug.attach()
    state = debug.halt()
    print(f"halted at PC 0x{state.pc:08X}")
    print(f"SP 0x{debug.read_register(REG_SP):08X}")

    (vtor,) = struct.unpack("<I", debug.read_memory(SCB_VTOR, 4))
    print("vector table:", debug.read_memory(vtor, 64).hex())

    stepped = debug.step()                # one instruction
    bp = debug.set_breakpoint(stepped.pc) # hardware (FPB) breakpoint
    debug.run()
    deadline = time.monotonic() + 2.0
    while not (hit := debug.state()).halted and time.monotonic() < deadline:
        time.sleep(0.01)
    if hit.halted:
        print(f"breakpoint hit at 0x{hit.pc:08X} ({hit.stop_reason.name})")
    else:
        debug.halt()
    debug.clear_breakpoint(bp)

    debug.run()
    debug.detach()                        # target keeps running
```

`write_memory()` writes RAM only; program flash with pyOCD (below). A read that runs past the end of a memory region raises `DebugError`.

### SPI flash: detect, erase, program, verify

Wire the flash to J4 as in [J4 as an SPI flash header](/hardware/atira/reference/#j4-as-an-spi-flash-header), with no SWD target on J4.

```python
import time
from pycosmicsdk import DebugProbe, FlashVerifyMethod
from pycosmicsdk.enums import (
    SpiBitOrder, SpiChipSelect, SpiChipSelectPolarity, SpiDataWidth, SpiMode,
)

ADDR = 0x10_0000
image = open("firmware.bin", "rb").read()

with DebugProbe.open() as probe:
    probe.power().set_output(True)        # powers the flash on J4 pin 1
    time.sleep(0.5)

    spi = probe.spi()
    spi.initialize(
        bit_order=SpiBitOrder.MSB,
        mode=SpiMode.MODE_0,
        data_width=SpiDataWidth.BITS_8,
        chip_select=SpiChipSelect.CS_2,   # J4 pin 9
        chip_select_polarity=SpiChipSelectPolarity.ACTIVE_LOW,
        frequency_hz=8_000_000,           # maximum
    )
    try:
        flash = probe.flash()
        part = flash.detect()
        print(f"{part.name or '(unnamed)'}: {part.capacity // 1024} KiB, page {part.page_size} B")

        flash.erase(ADDR, len(image))
        flash.program(ADDR, image)
        print("transport:", flash.last_transport)

        check = flash.verify(ADDR, image, method=FlashVerifyMethod.HASH)
        if check.passed:
            print("verify OK")
        else:
            print(f"first mismatch at 0x{check.first_mismatch:X}")
    finally:
        spi.release()
```

* `program()` uses the USB bulk interface when available and falls back to HID otherwise. `flash.last_transport` shows which ran.
* `FlashVerifyMethod.HASH` compares a CRC-32 computed on the probe and reads the flash back only to locate a mismatch. `FlashVerifyMethod.READBACK` reads the whole span back.
* `erase()` erases whole sectors, so it can clear data on either side of the span passed.

### UART bridge port

```python
from pycosmicsdk import DebugProbe

with DebugProbe.open() as probe:
    print(probe.uart().bridge_port)       # e.g. /dev/ttyACM0 or COM7; empty if not found
```

Open that port with any serial library, for example pySerial: `serial.Serial(port, 115200)`. Switch the target rail on first.

### Asyncio

`AsyncDebugProbe` has the same calls with `await`:

```python
import asyncio
from pycosmicsdk import AsyncDebugProbe

async def main():
    async with AsyncDebugProbe.open() as probe:
        power = probe.power()
        await power.set_output(True)
        await asyncio.sleep(0.5)
        r = await power.reading()
        print(f"{r.voltage_mv} mV, {r.current_ua} uA")

        debug = probe.debug()
        await debug.attach()
        state = await debug.halt()
        print(f"halted at 0x{state.pc:08X}")
        await debug.run()
        await debug.detach()

asyncio.run(main())
```

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

### C++ (CosmicSDK)

```cpp
#include <cstdio>
#include "debugprobe/DebugProbe.hpp"

using namespace debugprobe;

int main() {
    DebugProbe probe;
    auto boards = DebugProbe::listDevices();
    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);
    }
    probe.powerSetOutput(true);
    return 0;
}
```

Build: `c++ -std=c++17 -Iinclude app.cpp -Llib -ldebugprobe -o app`. More in the [CosmicSDK debug probe guide](https://binhollc.github.io/CosmicSDK/getting_started/debug_probe.html).

## Flashing a target MCU with pyOCD

The Atira is a standard CMSIS-DAP probe. pyOCD programs the target's internal flash with no plugin.

1. Close any program that holds the probe (for example an SDK script). Only one program can own the probe's debug interface.
2. Switch the target rail on. pyOCD does not switch it, and the rail stays on after the script exits:

   ```bash
   python -c "from pycosmicsdk import DebugProbe; p = DebugProbe.open(); p.power().set_output(True); p.close()"
   ```

3. Install pyOCD and the target's device pack, then flash. Example for an STM32H503RB:

   ```bash
   python -m pip install pyocd
   pyocd list                                # shows the probe and its serial
   pyocd pack install stm32h503rb
   pyocd flash -u <serial> -t stm32h503rbtx --base-address 0x08000000 firmware.bin
   ```

4. Read flash back only while the core is halted:

   ```bash
   pyocd cmd -u <serial> -t stm32h503rbtx -c halt -c "savemem 0x08000000 131072 out.bin"
   ```

:::caution
A flash read taken while the target runs can return wrong data (mostly zeros on the STM32H503). Halt first.

:::

Verified: STM32H503RB on Linux with pyOCD 0.44.1, 11/11 program + verify + chip-erase cycles, about 6.5 s per 128 KiB. Other Cortex-M devices with a CMSIS pack should work the same way; they are not verified yet.