Skip to content
binho.io (opens in a new tab)Contact Us

Binho Pulsar: Protocols

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

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

ParameterValue
Voltage1.2 V to 3.3 V (Multifunction Power Domain)
Clock100 kHz to 1 MHz
SCL duty cycle20 % to 80 % high, in 10 % steps (firmware 4.6.0 and later); also sets the exact clock
Addressing7-bit; 10-bit addresses can be scanned
Clock stretchingYes
Pull-up resistorsOff, 150 Ω, 220 Ω, 330 Ω, 470 Ω, 680 Ω, 1 kΩ, 1.5 kΩ, 2.2 kΩ, 4 kΩ
Max transfer1024 bytes per HID request; 16,384 bytes over the bulk interface

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.

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:

ModelBehaviour
MemoryFlat, byte-addressable memory with an auto-incrementing pointer; 8-bit or 16-bit register address
EEPROM24Cxx-style EEPROM: size, page size and page-wrapped writes, write-cycle busy time (NACK or clock stretch), write protect
Register mapRegister 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.

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

ParameterValue
Voltage1.2 V to 3.3 V (Multifunction Power Domain)
Clock1.53 kHz to 50 MHz
Modes0, 1, 2 and 3
Data size8 bits
Bit orderMSB-first or LSB-first
Chip selects4 (CS0 to CS3), active high or active low
Max transfer1024 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.

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:

ModelBehaviour
MemoryFull duplex: each byte clocked in is stored, and the byte at the same address is sent back. The pointer resets on chip select
Flash25-series flash commands: READ, FAST_READ, PAGE_PROGRAM, RDSR, WREN, WRDI, RDID; set address width, page size, program time and JEDEC ID
Register mapFirst byte is a R/W bit plus a 7-bit register address, then auto-incrementing access
ParameterValue
Voltage1.2 V to 3.3 V (Multifunction Power Domain)
Baud rate600 baud to 2 Mbaud
ParityNone, even, odd
Data size7 or 8 bits
Hardware flow controlOptional (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.

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.

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.

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).

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

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.

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 or slcand from can-utils on Linux.

SettingValues
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.

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.

The examples use PyCosmicSDK. 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.

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.

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.

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, …).

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.