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

# Binho Pulsar: Protocols

> How I2C, SPI, UART, GPIO, 1-Wire, RS-485 and CAN-FD work on the Binho Pulsar, large transfers, and code examples.

How I2C, SPI, UART, GPIO, 1-Wire, RS-485 and CAN-FD work on the Binho Pulsar, with large transfers and code examples.

## I2C

Two independent buses (A and B), in controller or target role.

### Controller

| Parameter           | Value                                                                                   |
| ------------------- | --------------------------------------------------------------------------------------- |
| Voltage             | 1.2 V to 3.3 V (Multifunction Power Domain)                                             |
| Clock               | 100 kHz to 1 MHz                                                                         |
| SCL duty cycle      | 20 % to 80 % high, in 10 % steps (firmware 4.6.0 and later); also sets the exact clock  |
| Addressing          | 7-bit; 10-bit addresses can be scanned                                                   |
| Clock stretching    | Yes                                                                                      |
| Pull-up resistors   | Off, 150 Ω, 220 Ω, 330 Ω, 470 Ω, 680 Ω, 1 kΩ, 1.5 kΩ, 2.2 kΩ, 4 kΩ                       |
| Max transfer        | 1024 bytes per HID request; 16,384 bytes over the bulk interface                         |

:::caution
**The Pulsar has no 3.3 kΩ, 4.7 kΩ or 10 kΩ pull-up.** Those values are Supernova-only and the Pulsar refuses them with `INVALID_PARAMETER`. The 4 kΩ setting is the closest to 4.7 kΩ.

:::

**How the clock is rounded.** 100 kHz, 400 kHz and 1 MHz are exact on all versions. For an exact frequency, set a duty cycle: 550 kHz then runs at 549.8 kHz. Without a duty cycle:

* **Firmware 4.6.2 and later:** the clock never runs above the request (550 kHz → 500 kHz, 858 kHz → 800 kHz). A rate below 11 Hz or above 3 MHz is refused with `INVALID_PARAMETER`.
* **Firmware 4.6.1 and earlier:** some frequencies run above the request (550 kHz → 600 kHz), and some far below it (858 kHz runs at about 273 kHz). A rate above 3 MHz or below 11 Hz answers success and the bus runs at about 11 Hz. On 4.5.0 and earlier a 3.4 MHz request could make the adapter re-enumerate.

### Target

The Pulsar answers the bus from its own memory, with no round trip to the PC. Firmware 4.5.0 and later. Three emulation models:

| Model        | Behaviour                                                                                                                 |
| ------------ | ------------------------------------------------------------------------------------------------------------------------- |
| Memory       | Flat, byte-addressable memory with an auto-incrementing pointer; 8-bit or 16-bit register address                          |
| EEPROM       | 24Cxx-style EEPROM: size, page size and page-wrapped writes, write-cycle busy time (NACK or clock stretch), write protect    |
| Register map | Register file with per-region access: read/write, read-only, write-only, write-1-to-clear, reserved; 8-bit or 16-bit registers |

The target role belongs to the whole adapter, not to one bus. A controller cannot run on bus A and a target on bus B at the same time. Release the role (`release()` in PyCosmicSDK) to switch, with no power cycle. See the [target-mode guide](https://binhollc.github.io/pycosmicsdk/guides/target-mode.html).

## SPI

One bus with 4 chip selects, in controller or peripheral role.

### Controller

| Parameter       | Value                                                              |
| --------------- | ------------------------------------------------------------------ |
| Voltage         | 1.2 V to 3.3 V (Multifunction Power Domain)                        |
| Clock           | 1.53 kHz to 50 MHz                                                 |
| Modes           | 0, 1, 2 and 3                                                      |
| Data size       | 8 bits                                                             |
| Bit order       | MSB-first or LSB-first                                             |
| Chip selects    | 4 (CS0 to CS3), active high or active low                          |
| Max transfer    | 1024 bytes per HID request; 16,384 bytes over the bulk interface   |

**How the clock is set.** The SPI clock is 100 MHz divided by a whole number, so the bus runs at 50, 33.3, 25, 20, 16.7 MHz and so on. On firmware 4.6.0 and later the Pulsar picks the fastest step **at or below** the requested value: 29 MHz runs at 25 MHz. A request below 1.53 kHz is refused with `INVALID_PARAMETER` and the bus keeps its clock. Firmware 4.5.x rounded to the nearest step, so 29 MHz ran at 33.3 MHz.

:::caution
**Reads at high clocks can come back shifted by one bit while the Pulsar reports success.** Writes are not affected. The highest clean read clock depends on the wiring and the part:

| Setup                                          | Clean   | Shifted |
| ---------------------------------------------- | ------- | ------- |
| Short ribbon cable                             | 50 MHz  | None    |
| Winbond W25Q128JV on jumper wires              | 33 MHz  | 50 MHz  |
| Longer jumper wires                            | 25 MHz  | 33 MHz  |

Above that ceiling, a flash `WIP` status poll never ends, because the shifted status byte reads as "write in progress". Read data at a conservative clock and put a limit on every poll loop.

:::

### Peripheral (Target)

Firmware 4.5.0 and later. Clock 10 kHz to 16 MHz, modes 0 to 3, same voltage and transfer size as the controller. Three emulation models:

| Model        | Behaviour                                                                                                                  |
| ------------ | -------------------------------------------------------------------------------------------------------------------------- |
| Memory       | Full duplex: each byte clocked in is stored, and the byte at the same address is sent back. The pointer resets on chip select |
| Flash        | 25-series flash commands: READ, FAST_READ, PAGE_PROGRAM, RDSR, WREN, WRDI, RDID; set address width, page size, program time and JEDEC ID |
| Register map | First byte is a R/W bit plus a 7-bit register address, then auto-incrementing access                                         |

## UART

| Parameter              | Value                                       |
| ---------------------- | ------------------------------------------- |
| Voltage                | 1.2 V to 3.3 V (Multifunction Power Domain) |
| Baud rate              | 600 baud to 2 Mbaud                         |
| Parity                 | None, even, odd                             |
| Data size              | 7 or 8 bits                                 |
| Hardware flow control  | Optional (RTS, CTS)                         |

The Pulsar also appears as a USB serial port (USB CDC, firmware 4.4 and later). Open it in any serial terminal to use the UART, while I2C, SPI and GPIO keep running over HID.

## GPIO

6 pins (GPIO1 to GPIO6) on the Multifunction Port: output, input, or interrupt on the rising edge, falling edge or both. An interrupt reports the edge and the pin level. An output can start high or low (firmware 4.4 and later). The datasheet lists the 4 SPI chip-select pins as GPIO-capable; PyCosmicSDK addresses GPIO1 to GPIO6 only.

PWM output is planned and not available yet.

## 1-Wire

The Pulsar has a 1-Wire controller on pin 30 of the Multifunction Port: Standard and Overdrive speeds, at 3.3 V or 5 V. With firmware 4.4.0 and later, it works in **Nova compatibility mode** (see the table below): switch the serial port to that mode, then send the Binho Nova 1-Wire commands (`1WIRE0 BEGIN`, `WRITE`, `READ`, `SEARCH`, `WHR`; `+PWR 1WIRE0 3300` or `5000` sets the bus voltage) from a serial terminal or the Nova SDK (`binho` package). Mission Control 3, PyCosmicSDK and CosmicSDK do not offer 1-Wire.

## Virtual COM Port Modes: RS-485 and CAN-FD

The Pulsar's USB serial port can work in one of several modes. The mode is stored in flash, survives power cycles, and the Pulsar restarts when it is changed. Set it from PyCosmicSDK (`set_usb_config`, see [Examples](#examples)).

| Mode                         | What the serial port does                                                          |
| ---------------------------- | ---------------------------------------------------------------------------------- |
| UART pass-through (default)  | Bridges to the UART pins                                                           |
| RS-485                       | Bridges to the RS-485 Port, half-duplex, with automatic driver enable              |
| CAN-FD                       | SLCAN adapter on the CAN-FD Port                                                   |
| Nova compatibility           | Speaks the Binho Nova text protocol, for legacy Nova scripts. The HID protocols are off in this mode |

### RS-485

Half-duplex, 600 baud to 2 Mbaud, parity none, even or odd, 7 or 8 data bits, −7 V to +12 V common-mode input. Switch the serial port to RS-485 mode and open it at the baud rate the bus uses.

### CAN-FD

Classic CAN and CAN-FD frames, 11-bit and 29-bit IDs, bit-rate switching, payloads up to 64 bytes, listen-only mode. Use any tool that speaks SLCAN, such as the `slcan` interface of [python-can](https://python-can.readthedocs.io/) or `slcand` from `can-utils` on Linux.

| Setting                     | Values                                                         |
| --------------------------- | -------------------------------------------------------------- |
| Nominal bit rate (`S0`–`S8`) | 10, 20, 50, 100, 125, 250, 500, 800 kbps, 1 Mbps (default 500 kbps) |
| Data bit rate (`Y1`, `Y2`, `Y5`) | 1, 2, 5 Mbps (default 5 Mbps)                              |

The serial baud rate does not matter in this mode. Send `S` and `Y` before opening the channel (`O`); changes after that apply at the next open.

In CAN-FD mode, I2C, SPI, UART and GPIO keep working over HID, but only up to 1024 bytes per transfer: the 16 KB bulk transfers are unavailable.

Not supported: remote (RTR) frames, receive timestamps, error-frame and bus-state reports, acceptance filters.

## Large Transfers (16 KB)

One USB HID request carries at most 1024 bytes. With firmware 4.5.0 and later, the Pulsar also moves I2C and SPI payloads of up to **16,384 bytes** in one transfer, over a second USB interface. PyCosmicSDK and CosmicSDK use it automatically when a transfer is larger than 1024 bytes. See the [bulk transfer guide](https://binhollc.github.io/pycosmicsdk/guides/bulk.html).

## Examples

The examples use [PyCosmicSDK](https://binhollc.github.io/pycosmicsdk/getting-started.html). `bring_up()` can be run again on an adapter that is already set up.



**I2C: scan and read**

Scan the Dedicated I2C Port (bus A), then read 16 bytes from an EEPROM at `0x50`.

```python
from pycosmicsdk import Device, DeviceModel, I2cBus, I2cPullUp

with Device.open(model=DeviceModel.PULSAR) as dev:
    i2c = dev.i2c(bus=I2cBus.A)              # I2cBus.B = Multifunction Port
    i2c.set_voltage(3300)                    # VTARG in mV
    i2c.bring_up(frequency_hz=400_000, pull_up=I2cPullUp.KOHM_2_2)

    print([hex(a) for a in sorted(i2c.scan().addresses_7bit)])
    data = i2c.read(0x50, 16, subaddress=b"\x00\x00")
    print(data.hex(" "))
```

An empty bus raises `I2cError` with `FW_I2C_BUS_WITH_NO_TARGETS_CONNECTED`.



**SPI: read a flash JEDEC ID**

Read the 3-byte JEDEC ID of a SPI flash on CS0.

```python
from pycosmicsdk import (
    Device, DeviceModel, SpiBitOrder, SpiChipSelect,
    SpiChipSelectPolarity, SpiDataWidth, SpiMode,
)

with Device.open(model=DeviceModel.PULSAR) as dev:
    spi = dev.spi()
    spi.set_voltage(3300)
    spi.bring_up(
        bit_order=SpiBitOrder.MSB,
        mode=SpiMode.MODE_0,
        data_width=SpiDataWidth.BITS_8,
        chip_select=SpiChipSelect.CS_0,
        chip_select_polarity=SpiChipSelectPolarity.ACTIVE_LOW,
        frequency_hz=10_000_000,
    )
    print(spi.read(b"\x9f", 3).hex(" "))     # e.g. ef 40 18 for a W25Q128JV
```



**CAN-FD with python-can**

Switch the serial port to CAN-FD mode once. The Pulsar restarts and keeps the mode.

```python
import dataclasses
from pycosmicsdk import CdcMode, Device, DeviceModel

with Device.open(model=DeviceModel.PULSAR) as dev:
    cfg = dev.get_usb_config()               # read, change one field, write
    dev.set_usb_config(dataclasses.replace(cfg, cdc_mode=CdcMode.CANFD))
```

Then send a frame with python-can (`pip install python-can`). Use the Pulsar's serial port name (`COM7`, `/dev/ttyACM0`, …).

```python
import can

bus = can.Bus(interface="slcan", channel="/dev/ttyACM0", bitrate=500_000)
bus.send(can.Message(arbitration_id=0x123, data=[0xAA, 0xBB, 0xCC, 0xDD],
                     is_extended_id=False))
print(bus.recv(timeout=1.0))
bus.shutdown()
```

To go back, set `cdc_mode=CdcMode.UART_PASSTHROUGH` the same way.