Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Serial protocol

Host ↔ Pico link over USB CDC-ACM, 115200 baud, using compact binary frames.

  • Firmware implementation: firmware-rust-v2/src/proto.rs (FrameParser)
  • Host implementation: client/src/haptic_skin/protocol.py (Decoder + builders)

Frame format

Every frame shares the same layout:

[start] [type] [len] [payload … (len bytes)] [xor]
FieldSizeDescription
start10xAA host → Pico, 0xBB Pico → host
type1command code (H→P) or event code (P→H)
len1payload length in bytes (0..=8)
payloadlendata
xor1XOR of type ^ len ^ payload[…] (excludes start and xor)

len is capped at 8 (MAX_PAYLOAD = MOTOR_COUNT); larger frames are rejected (BadLength). The receiver resynchronises on the next start byte if it lands mid-stream.

Commands — host → Pico (start = 0xAA)

CommandCodelenPayloadEffect
Ping0x010Device replies Pong
SetMotor0x102[idx, intensity]Motor idx (0–7) to intensity (0–255)
SetAll0x118[v0, v1, … v7]All 8 motors at once
StopAll0x120Turn every motor off

Events — Pico → host (start = 0xBB)

EventCodelenPayloadMeaning
Pong0x010Reply to Ping (handshake / heartbeat)
Error0xFF1[code]Invalid frame (BadXor, UnknownCmd, BadLength)

Examples

Ping            : AA 01 00 01
StopAll         : AA 12 00 12
SetMotor 3→200  : AA 10 02 03 C8 19      (xor = 10^02^03^C8 = 0x19)
SetAll 100…     : AA 11 08 64 64 64 64 64 64 64 64 6D
Pong (reply)    : BB 01 00 01

Design notes

  • Intensity is one byte (0–255), scaled on the device to the PWM resolution (PWM_TOP). On the host, intensity_to_pwm(0.0..1.0).
  • Power guard runs on the device after decoding: at most 6 motors on at once, each capped to 60 % duty, to stay under the 500 mA USB budget — the host need not worry about it.
  • The binary framing (vs the legacy text protocol) is compact and resynchronises cleanly; the XOR checksum catches corrupted bytes on the serial line.