Creature Serial Control Protocol

This page covers the custom serial protocol spoken between the Linux host running the Controller software and the Creature Controller hardware (RP2040) inside each creature.

Why invent a new protocol? I couldn’t find one that seemed to work. I chose to use ASCII strings because:

  • In my heart I’m a UNIX geek and ASCII strings are the universal form for RPC.
  • It’s easy to debug with off the shelf tools.
  • I can hook up a creature to a serial terminal on my Mac and see exactly what’s going on.

Wire Format

All messages are ASCII text, delimited by newlines (\n). Fields within a message are separated by tabs (\t). The firmware also accepts \r\n line endings on input.

Outgoing commands from the host include a checksum appended as the final token:

COMMAND\tparam1\tparam2\tCS 12345

The checksum is a simple u16 sum of all characters in the message not including the checksum token itself. (How would I be able to calculate the checksum for the thing I’m writing? 😅)

Startup Sequence

The firmware goes through a strict startup handshake before it will accept any movement commands:

  1. The Creature Controller connects to the Linux host as a CDC device (serial port emulation).
  2. The firmware sends INIT with its version number.
  3. The host responds with CONFIG containing the servo configuration.
  4. The firmware sends READY to confirm it’s loaded the config.
  5. Now the host can start sending POS frames.

The firmware’s configuration is completely dynamic — the config file for the creature is read by the controller at each startup and is the only source of truth. When the CDC connection closes, the firmware wipes its configuration and waits for the connection to re-open so it can start fresh. I do this so I can make changes to the config file and iterate rapidly.

Module Addressing

Each RP2040 board inside a creature is assigned a module letter: A, B, C, or D. The host controller routes messages to the correct board through the message router. This means a single creature can have multiple controller boards working together, each managing its own set of servos.

Host → Firmware Commands

CONFIG — Servo Configuration

CONFIG\tSERVO <pin> <min_us> <max_us>\tSERVO <pin> <min_us> <max_us>\tCS ####

Sends the configuration for this session to the firmware, in response to an INIT message. Each servo gets a pin number and the minimum/maximum microsecond pulse widths it’s allowed to operate in.

For Dynamixel servos, there’s an additional profile velocity parameter:

CONFIG\tDYNAMIXEL <pin> <min_us> <max_us> <profile_velocity>\tCS ####

This is quite critical to the operation of the firmware. Great care is given to make sure that a motor does not operate outside of its normal range to avoid damaging any parts of the creature.

POS — Set Servo Positions

POS\t<pin> <ticks>\t<pin> <ticks>\tCS ####

Sets the position of the servos. Each servo is identified by its pin number and given a position in encoder ticks. For Dynamixel servos, the pin is prefixed with D:

POS\t0 1500\t1 1600\tD5 2048\tCS ####

PING — Heartbeat

PING\t<epoch_seconds>\tCS ####

Sends a ping to the firmware, which responds with a PONG. The timestamp is used to measure round-trip latency. The firmware calls tud_task() (TinyUSB) every millisecond, so response times are usually under 2ms. Given a 50Hz duty cycle (20ms frames), this is just fine.

ESTOP — Emergency Stop

ESTOP\t1\tCS ####

Immediately halts all servo movement. This is the panic button — no acknowledgment needed, no waiting around.

FLUSH BUFFER — Reset Serial Buffer

\a

A single bell character (ASCII 0x07) that signals the firmware to flush its input buffer and reset its parsing state. Useful for recovering from garbled communication without a full reconnect.

Firmware → Host Messages

INIT — Ready for Configuration

INIT\t<firmware_version>

Sent when the firmware starts up. The version number is checked by the controller — the controller will refuse to run against a firmware version it doesn’t know, and the firmware will refuse to listen to an unknown version of the controller. They must be in lock step.

READY — Configuration Loaded

READY\tCS ####

Tells the controller that the firmware has loaded its configuration and is ready to receive frames. Any POS commands sent before this message will be dropped.

PONG — Heartbeat Response

PONG\tCS ####

Response to a PING. The controller measures the round trip time to track firmware responsiveness.

LOG — Firmware Log Messages

LOG\t<timestamp>\t<level>\t<message>

Log messages from the firmware, mapped to the controller’s local log levels and displayed on the Linux side. The level tokens are: [V] Verbose, [D] Debug, [I] Info, [W] Warning, [E] Error, [F] Fatal.

Sensor Reports

The firmware periodically sends sensor data back to the host without being asked. This telemetry flows all the way through to the Creature Server via WebSocket, where it’s visible in real time on the creature detail screen in the Creature Console.

BSENSE — Board Sensors

BSENSE\tTEMP <temp>\tVBUS <voltage> <current> <power>\tMP_IN <voltage> <current> <power>\t3V3 <voltage> <current> <power>

Power rail measurements from the controller board. Temperature is in degrees, voltage in volts, current in amps, and power in watts. This is how I keep an eye on the health of the boards — I can see if something is drawing too much power or running hot.

MSENSE — Motor Sensors (PWM Servos)

MSENSE\tM0 <position> <voltage> <current> <power>\tM1 <position> <voltage> <current> <power>\t...

Per-motor telemetry for PWM servos. Reports the current position (in encoder ticks) and power consumption for up to 8 motors.

DSENSE — Motor Sensors (Dynamixel Servos)

DSENSE\tD<id> <temperature> <present_load> <voltage_mV>\tD<id> <temperature> <present_load> <voltage_mV>\t...

Telemetry from Dynamixel smart servos. Temperature is in degrees Fahrenheit, load can be negative (indicating direction), and voltage is in millivolts. Dynamixel servos report their own sensor data over their serial bus, which the firmware collects and relays.

STATS — Firmware Statistics

STATS\t<stat_name> <value>\t<stat_name> <value>\t...

Internal housekeeping stats sent periodically by the firmware. The set of stats has grown quite a bit as the system has matured:

Token Meaning
HEAP_FREE Amount of free heap on the RP2040
USB_CRECV Characters received on USB
USB_MRECV Complete messages received on USB
USB_SENT Messages sent over USB
UART_CRECV Characters received on UART
UART_MRECV Complete messages received on UART
UART_SENT Messages sent over UART
MP_RECV Messages received by the message processor
MP_SENT Messages sent by the message processor
S_PARSE Messages successfully parsed
F_PARSE Messages that failed to parse
CHKFAIL Messages discarded due to checksum mismatch
POS_PROC Position update commands processed
PWM_WRAPS PWM counter wraps (ISR invocations for servos)
TEMP Board temperature
DXL_TX Dynamixel packets transmitted
DXL_RX Dynamixel packets received
DXL_ERR Dynamixel communication errors
DXL_CRC Dynamixel CRC failures
DXL_TO Dynamixel timeouts