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]
| Field | Size | Description |
|---|---|---|
start | 1 | 0xAA host → Pico, 0xBB Pico → host |
type | 1 | command code (H→P) or event code (P→H) |
len | 1 | payload length in bytes (0..=8) |
payload | len | data |
xor | 1 | XOR 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)
| Command | Code | len | Payload | Effect |
|---|---|---|---|---|
Ping | 0x01 | 0 | — | Device replies Pong |
SetMotor | 0x10 | 2 | [idx, intensity] | Motor idx (0–7) to intensity (0–255) |
SetAll | 0x11 | 8 | [v0, v1, … v7] | All 8 motors at once |
StopAll | 0x12 | 0 | — | Turn every motor off |
Events — Pico → host (start = 0xBB)
| Event | Code | len | Payload | Meaning |
|---|---|---|---|---|
Pong | 0x01 | 0 | — | Reply to Ping (handshake / heartbeat) |
Error | 0xFF | 1 | [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.