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:
- The Creature Controller connects to the Linux host as a CDC device (serial port emulation).
- The firmware sends
INITwith its version number. - The host responds with
CONFIGcontaining the servo configuration. - The firmware sends
READYto confirm it’s loaded the config. - Now the host can start sending
POSframes.
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 |