Dynamixel Servo Tester

The Dynamixel Servo Tester is the firmware target I reach for whenever I’m working with Dynamixel servos — usually setting up a brand-new one before it goes into a creature. It’s an interactive shell for talking to Dynamixel XC330 servos over Protocol 2.0: ping them, scan the bus, read and write registers, change IDs and baud rates, and drive them around to make sure they work.

Like the other bench tools, it presents as a USB serial device with a shell running on the board, so no host software is needed. The neat part is that it’s built on the same Dynamixel library the main controller firmware uses — so if a servo behaves under the tester, I know it’ll behave in a creature.

Hardware setup

The tester talks to servos over a single GPIO pin using a PIO-based half-duplex UART. The XC330 is 3.3 V TTL, the same as the RP2350, so no level shifter is needed.

  • Data pin: GPIO 15 (configurable in src/dynamixel_tester/config.h)
  • Default baud rate: 1,000,000 bps
  • PIO instance: pio0 (uses 2 of 4 state machines)

There are just three connections between the board and the servo:

RP2350 Dynamixel
GPIO 15 DATA
GND GND
VIN (external 5 V supply)

The servo needs its own 5 V power; the data line connects directly to the GPIO pin.

Building and flashing

cmake -B build .
cmake --build build --target dynamixel-tester-rp2350-arm-s-hw3

That produces dynamixel-tester-rp2350-arm-s-hw3.uf2. Flash it by holding BOOTSEL, plugging in the board, and copying the .uf2 onto the drive that appears.

Connecting

The tester shows up as a USB CDC serial device, so connect with any serial terminal — screen, minicom, PuTTY, whatever you like. USB CDC ignores the host baud rate, so any speed works:

screen /dev/tty.usbmodem* 115200

Type H and press Enter to see the help menu, which includes the current bus baud rate.

Commands

General

Command Description
H Show help menu (includes current bus baud rate)
I System info as JSON (version, free heap, uptime, data pin, baud rate)
R Reboot the tester

Servo discovery

Command Description
P <id> Ping a servo. Returns model number and firmware version
S [start] [end] Scan for servos in an ID range (default 0–253)

Servo configuration

These write to EEPROM registers, so torque must be off (T <id> 0) before changing EEPROM settings.

Command Description
ID <old> <new> Change a servo’s ID
BR <id> <index> Set servo baud rate (see the baud rate table below)
FR <id> <option> Factory reset: 0 = all, 1 = keep ID, 2 = keep ID + baud
RB <id> Reboot servo

Motion control

Command Description
T <id> <0|1> Torque disable/enable (must enable before moving)
M <id> <position> Move to position (0–4095, center is 2048)
SM <id:pos> [id:pos] … Sync Write goal positions to multiple servos atomically (e.g. SM 1:2048 2:1024)
V <id> <velocity> Set profile velocity (0 = max speed)
L <id> <0|1> LED off/on

Status and diagnostics

Command Description
ST <id> Read full status: position, temperature, voltage, load, moving, hardware errors
SS <id1> [id2] … Sync Read status from multiple servos in one bus transaction
RR <id> <addr> <len> Read any register (len: 1, 2, or 4 bytes)
RW <id> <addr> <len> <val> Write any register

Register shortcuts

These read the register when called without a value, or write it when a value is provided.

Command Register Size
OM <id> [mode] Operating mode 1 byte
PA <id> [accel] Profile acceleration 4 bytes
HO <id> [offset] Homing offset 4 bytes
MN <id> [min] Min position limit 4 bytes
MX <id> [max] Max position limit 4 bytes

Bus configuration

Command Description
CB <rate> Change the PIO bus baud rate (in bps)

Broadcast

Any command that takes an <id> also accepts 254 as the broadcast ID, which is sent to every servo on the bus at once. Since no servo replies to a broadcast, these are transmit-only (no status back) — handy for turning all LEDs on or off, or enabling and disabling torque everywhere at once. Read commands (P, ST, RR, OM, and friends) won’t return anything useful when broadcast.

Baud rate table

Use the index value with the BR command to change a servo’s baud rate.

Index Baud rate
0 9,600
1 57,600 (factory default)
2 115,200
3 1,000,000
4 2,000,000
5 3,000,000
6 4,000,000
7 4,500,000

Index 3 (1 Mbps) is what I use in production. Indices 4–7 work but are sensitive to wire length and signal integrity.

Operating modes

Use the mode value with the OM command.

Value Mode Description
0 Current Torque (current) control only
1 Velocity Continuous rotation
3 Position Standard position control (0–4095, default)
4 Extended Position Multi-turn position control
5 Current-based Position Position control with current limit
16 PWM Direct PWM control

Common workflows

First-time servo setup

Factory-default servos communicate at 57,600 bps. The tester boots at 1 Mbps, so lower the bus rate first:

CB 57600           # Lower bus rate to match factory default
S                  # Scan to find the servo (factory default ID is 1)
P 1                # Ping to verify communication
T 1 0              # Torque off (required for EEPROM changes)
ID 1 10            # Change ID from 1 to 10
BR 10 3            # Set baud rate to 1Mbps
CB 1000000         # Raise bus back to 1Mbps
P 10               # Verify communication at new settings

Moving a servo

T 1 1              # Enable torque
V 1 50             # Set profile velocity (lower = slower)
M 1 2048           # Move to center position
M 1 0              # Move to minimum
M 1 4095           # Move to maximum
T 1 0              # Disable torque when done

Checking servo health

ST 1               # Read full status
OM 1               # Check operating mode
MN 1               # Check min position limit
MX 1               # Check max position limit
RR 1 144 2         # Read input voltage (register 144, 2 bytes)

Multi-servo sync operations

SS 1 2 3           # Read status from servos 1, 2, 3 in one bus transaction
SM 1:2048 2:1024   # Move servo 1 to 2048 and servo 2 to 1024 atomically

Sync Read (SS) reports position, temperature, voltage, load, and timing for each servo. Servos that don’t respond are listed as “no response.”

Broadcast to all servos

L 254 1            # Turn on LED on all servos
T 254 1            # Enable torque on all servos
M 254 2048         # Move all servos to center
T 254 0            # Disable torque on all servos
L 254 0            # Turn off all LEDs

Changing baud rate

The servo’s baud rate lives in EEPROM (factory default 57,600). The bus baud rate resets to 1,000,000 on every firmware boot. To bring a factory-default servo up to match:

  1. CB 57600 — temporarily lower the bus rate to talk to the servo
  2. T 1 0 — torque off
  3. BR 1 3 — set the servo to 1 Mbps (index 3)
  4. CB 1000000 — change the bus back to 1 Mbps

If you lose communication after step 3, the servo is already at the new rate but the bus isn’t — just run CB 1000000 to reconnect.

Architecture

The tester is built on a shared Dynamixel library (src/dynamixel/) that the main controller firmware reuses:

src/dynamixel/                  # Shared library
  dynamixel_registers.h         # XC330 control table addresses
  dynamixel_protocol.h/c        # Protocol 2.0 packet layer (CRC16, byte stuffing)
  dynamixel_hal.h/c             # PIO half-duplex UART with DMA
  dynamixel_servo.h/c           # High-level servo operations

src/dynamixel_tester/           # Tester application
  config.h                      # Pin assignments, buffer sizes
  main.c                        # Entry point
  shell.h/c                     # Interactive command processor
  usb.h/c                       # TinyUSB CDC setup
  usb_descriptors.c             # USB device identity

A few details worth knowing about the protocol layer:

  • PIO UART: two state machines on the same GPIO pin handle TX and RX, with only one active at a time (half-duplex).
  • DMA receive: bytes transfer from the PIO RX FIFO straight into a memory buffer, freeing the CPU during receive.
  • Byte stuffing: Protocol 2.0 requires stuffing FF FF FD sequences in packet parameters — handled transparently when building and parsing packets.
  • CRC16: every packet carries a CRC16 checksum that’s verified on receive.

Common register addresses

For use with the RR and RW commands. See the XC330 e-Manual for the complete control table.

Address Size Name Access
7 1 ID RW
8 1 Baud Rate RW
10 1 Drive Mode RW
11 1 Operating Mode RW
20 4 Homing Offset RW
48 4 Max Position Limit RW
52 4 Min Position Limit RW
64 1 Torque Enable RW
65 1 LED RW
80 2 Position D Gain RW
82 2 Position I Gain RW
84 2 Position P Gain RW
108 4 Profile Acceleration RW
112 4 Profile Velocity RW
116 4 Goal Position RW
126 2 Present Load RO
128 4 Present Velocity RO
132 4 Present Position RO
144 2 Present Input Voltage RO
146 1 Present Temperature RO

The full command reference and more detail live in the tester docs on GitHub.