Board v2 · Firmware in developmentDraft

USB protocol

Binary protocol of the motion MCU over USB-C and UART — framing, CRC, request and response, events, telemetry and the simulator.

View .mdllms.txt

The motion MCU speaks one binary protocol to every host: a PC over USB-C, and the network module of the device over an internal UART. Messages are Protocol Buffers defined in upan.proto, wrapped in small frames with a version byte and a CRC. Every request gets exactly one response with the same sequence number; events and telemetry arrive on their own. Use this page if you write your own host or library. For a ready tool, use upan-cli or the Python library; for network clients, use the Network API instead.

Note: The protocol is version 0 of the schema and the firmware is in development. Frame format and the rules below are fixed for protocol version 1 of the frame (ver = 1); messages may still gain fields. Every message, field and enum is listed in the API reference.

Transports

Transport Where Reported as
USB CDC (virtual serial port) USB-C on the device, to a PC TRANSPORT_USB
UART between the network module and the motion MCU, inside the device TRANSPORT_UART_MODULE

Both carry the same frames and messages, so a host behaves the same over a cable and over Wi-Fi. InfoGet returns Info.transport: the channel the request came in on.

Some commands depend on the transport: HardConfigSet is accepted only over USB (see Configuration). Over the UART it is refused with ERROR_TRANSPORT_NOT_ALLOWED.

Framing

COBS( ver | protobuf | crc16 ) 0x00
Part Size Content
ver 1 byte frame version, currently 1
protobuf up to 400 bytes one HostMessage (host → device) or DeviceMessage (device → host)
crc16 2 bytes CRC over ver + protobuf, little-endian (low byte first)
delimiter 1 byte 0x00 after the COBS-encoded block

COBS removes all zero bytes from the frame, so 0x00 marks the end of a frame and a receiver can resynchronise after any line error by waiting for the next zero.

CRC

CRC-16/CCITT-FALSE:

Parameter Value
Polynomial 0x1021
Initial value 0xFFFF
Reflect input / output no
Final XOR none
Check value ("123456789") 0x29B1
def crc16_ccitt_false(data: bytes) -> int:
    crc = 0xFFFF
    for b in data:
        crc ^= b << 8
        for _ in range(8):
            crc = ((crc << 1) ^ 0x1021) if crc & 0x8000 else (crc << 1)
            crc &= 0xFFFF
    return crc

assert crc16_ccitt_false(b"123456789") == 0x29B1

Invalid frames

Received Device reaction
empty frame, bad COBS, wrong ver, CRC mismatch dropped silently
valid frame, protobuf does not decode or the message body is empty response ERROR_MALFORMED; seq is what could be read, otherwise 0
unknown command for this firmware response ERROR_UNSUPPORTED

Requests and responses

The host sends HostMessage: a seq and exactly one command in the body oneof. The device sends DeviceMessage: a response, telemetry, an event or a heartbeat.

  • The host increments seq for each request. seq = 0 is reserved.
  • Every HostMessage gets exactly one Response with the same seq.
  • error = ERROR_NONE means accepted (ACK); any other code means refused (NACK) — see ErrorCode.
  • A response to a motion command means the command was accepted, not that the motion is finished. Completion comes as an event (EVENT_MOVE_DONE, EVENT_HOME_DONE, EVENT_PROGRAM_DONE).
  • *Get commands return their data in the response (info, soft_config, hard_config).
  • A MoveLinear across a module stack is answered once every axis has confirmed it is ready (up to about 100 ms).

A refusal tells the host what to change:

Field Filled for
limit_violation ERROR_LIMIT_EXCEEDED: which limit, the requested value and the nearest allowed value, in base units
program_instruction_index program errors: which instruction
axis multi-axis moves: which module of the stack refused (0 = Core)

For a refused MoveRel, the allowed value is a distance, not a position.

Commands

Group Messages
Motion MoveAbs, MoveRel, Jog, Stop, EStop, Home, SetPosition, MoveLinear
Programs ProgramBegin, ProgramChunk, ProgramCommit, ProgramRun, ProgramPause, ProgramResume, ProgramAbort
Configuration SoftConfigGet, SoftConfigSet, HardConfigGet, HardConfigSet
I/O OutputSet
Service InfoGet, TelemetryConfig, FaultClear, Heartbeat, BootloaderEnter

Values are integers in base units: µm, µm/s, µm/s² on a linear axis, millidegrees on a rotary axis. A velocity or acceleration of 0 means "use the SoftConfig default". See Configuration.

Program upload

A program is uploaded in pieces: ProgramBegin (instruction count and CRC-32) → ProgramChunk… → ProgramCommit.

  • The CRC-32 is the zlib CRC-32 ("123456789" → 0xCBF43926) over the canonical protobuf encoding of each Instruction, in order.
  • Chunks must arrive in order; offset is the number of instructions already accepted.
  • A chunk holds up to 16 instructions and must fit one frame (400 bytes of protobuf). A multi-axis step can take about 90 bytes, so split by size, not only by count.
  • A program holds up to 256 instructions and 6 KB encoded.

The checks on Commit and Run are described in Programs.

Telemetry and events

Telemetry (Telemetry) is sent periodically: state, homed flag, position, velocity, target, encoder position and following error, inputs and outputs, limit and e-stop inputs, motor current, StallGuard value, supply voltage, driver flags, and the current program instruction and repeat. Set the rate with TelemetryConfig.rate_hz (0 = off). The device caps it at 100 Hz, separately for each transport.

Events (Event) are sent asynchronously: move, home or program done, program aborted, limit hit, stall, following error, fault, e-stop pressed or released, input changed, jog timeout, presence window open. An event carries the position at that moment, an error code for faults and aborts, a detail (for example an input number or an instruction index) and the stack axis it concerns.

Message Goes to
Response only the transport the request came from
Event all transports (USB and UART)
Telemetry each transport at its own configured rate
  • Jog runs only while the host keeps it alive. The host repeats Jog (or sends Heartbeat) more often than timeout_ms; otherwise the device decelerates on its own and sends EVENT_JOG_TIMEOUT. timeout_ms is capped by SoftConfig.jog_timeout_max_ms.
  • A loaded program keeps running when the connection drops. After reconnecting, read the state from telemetry.
  • The hardware stop input works independently of the protocol. EStop is a software stop in addition to it.

Simulator

The simulator is the real motion firmware core compiled for a PC with simulated hardware. It runs in real time and exchanges the same frames over standard input and output. upan-cli starts it with --port sim:

upan-cli -p sim status
upan-cli -p sim --sim-arg=--homed move-to 20mm -v 5mm/s

Each upan-cli call starts a fresh simulator; state is not kept between calls. Options passed with --sim-arg:

Option Simulates
--homed start homed at position 0
--presence presence button pressed (allows config set --hard)
--supply-mv 5000 USB power only: motion is refused with ERROR_NO_MOTOR_POWER
--lim-min-at N, --lim-max-at N a limit switch at position N (in steps)
--estop e-stop pressed

For a longer session, keep one simulator open from Python:

from upan.client import Device, EV
from upan.transport import SimTransport
from upan.v1 import upan_pb2 as pb

with Device(SimTransport(["--homed"])) as dev:
    dev.request(move_abs=pb.MoveAbs(target=5000))  # 5 mm in µm
    print(dev.wait_event({EV.EVENT_MOVE_DONE}, timeout=5))

Note: How the host library, upan-cli and the simulator are distributed to users is not final yet.

Firmware update

BootloaderEnter exists in the schema, but the current firmware answers it with ERROR_UNSUPPORTED. Signed updates and how signatures are checked are not final yet.