board_status
Rail current/voltage, faults, protection limits, and every channel's name, mode, travel limits, speed and current position (0 = off).
无需输入参数。

io.github.Lukasz-Juranek由 GitHub 账号 Lukasz-Juranek 发布v0.4.1更新于 Oct 30, 2026
Drive 12 hobby servos on an open-hardware USB-C controller: moves, poses, sequences, rail current
面向 12 通道 USB-C 舵机控制板的 MCP 服务器:可移动舵机、执行姿态与序列,并读取舵机电源电流与电压。
Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。
参照 仓库 中的启动说明。
[Buy on Tindie] [Product page]
12-channel USB-C servo controller, Pololu Maestro compatible, with an MCP server so AI agents (Claude, etc.) can drive servos. $18 on Tindie · product page.
[Video: USB-C servo controller driving an MG995 servo]
[USB Servo Controller board, top view]
▶ 2-minute video: wiring, current monitoring, GUI and the controller driving a real MG995
Open-source alternative to the Pololu Mini Maestro 12 that speaks the Maestro serial protocols, so Maestro libraries work unchanged — plus USB-C, servo-rail current and voltage sensing, on-board scripts. Built for JLCPCB turnkey assembly, ≈ $4.43 per board at 100 units against $29.95 for the Maestro.
tools/servo_mcp.py is an MCP server that lets an AI agent move servos,
run poses and sequences, and read rail current/voltage, with guardrails: per-channel limits,
ramped moves, overcurrent stop. pip install -r tools/requirements.txt, then:
Claude Code
Claude Desktop (claude_desktop_config.json)
Add "--port", "COM5" to pick a port, or "--mock" to try it without hardware.
Close the GUI first: only one program can hold the serial port.
The server is also on PyPI, so any MCP client can run it without cloning the repo:
command uvx, args ["ucan-servo-mcp"] (plus --config path/to/servogui.json
to use the limits, poses and sequences saved by the GUI).
Downloads: firmware images and the prebuilt GUI for Windows and Linux are on the
Releases page.
To build the GUI yourself: python tools/build_release.py → tools/dist/.
The hard requirement was a USB bootloader in ROM, so a blank chip can be flashed over USB-C with no programmer. That rules out the STM32F103 the design started on (USART-only bootloader). Of LQFP-48 parts that have one and are stocked at JLCPCB:
Cheapest part with real inventory, a third the price of the STM32F072, and pin-compatible with the STM32F103 layout (32 of 33 connected pins identical).
Costs of the choice:
VSRV=VIN block.Reserved: PA4/PA5 rail sensing · PA11/PA12 USB · PA13/PA14 SDI · PD0/PD1 crystal · PC13 LED · PB2 BOOT1. LQFP-48 has only 10 ADC pins, and PA4/PA5 went to sensing, so analog is 8 of 12 — the Maestro 12 has all twelve.
High side keeps the ground plane unbroken. It measures total rail current, not per channel. The ADC reference is VDDA = the LDO output (no VREF+ on LQFP-48), so readings are good for thresholds, not calibrated metering. The firmware trips all channels off after 5 bad readings (~50 ms): 5 A default overcurrent, and undervoltage chosen automatically (2S → 6.4 V, 3S → 9.6 V, anything else off). Details in USER.md §4.4.
The 24 A terminal is not the limit; the copper behind it is.
2 × 1 oz, 1.6 mm, 0.2 mm min trace/space, 0.3/0.6 mm vias. GND is a solid pour on both layers; SERVO_V+ is carried by three priority-ordered top-layer zones. Headers along the bottom edge, USB-C right, screw terminal left, MCU centre.
⚠️ F8 (Update PCB from Schematic) deletes H1–H4 — they have no schematic symbol. Untick Delete footprints with no symbols, or re-add them at (16, 4), (61.5, 4), (16, 33), (61.5, 33).
⚠️ HT7533-1 is not pin-compatible with the AMS1117 it replaced (SOT-89: GND / VIN+tab / VOUT). The tab is VIN.
Class: B Basic · P Preferred (both fee-free) · E Extended ($3.07 feeder fee if SMT).
J9 (SDI) is not fitted. Shunt caps (C100114, two per board) cannot be fitted
by JLCPCB — order them loose, ≈ $1.80 per 100.
Excludes shipping and duties. Prices are LCSC qty-100; stock checked 2026-09-19/20 (CH32V203 the thinnest at 8,643) — re-verify before ordering.
What drives cost:
embedded-ch32/)RISC-V, WCH ch32v20x HAL and USB device library, xPack riscv-none-elf-gcc
15.2 (make GCC_PATH=... to override). Version USBServoController 0.4.
Layout lives in User/flash_layout.h and both Link.ld files.
Blank chip or recovery: make full, fit JP2, plug in USB,
wchisp flash build/USBServoController-ch32-full.bin, remove JP2, replug.
Jumping to the ROM bootloader from firmware does not work on this chip (tested),
which is why the board has its own.
Timing: 96 MHz timers, PSC 47 → 0.5 µs ticks, ARR 39999 → 20 ms frame. 0.25 µs would need a 4 MHz tick, which only fits 16 bits at ≥ 100 Hz.
Protocol: one ASCII command per line, one OK / OK <value> / ERR <reason>
reply. Servo (S G E X A), rail sensing (I U F L P C Z N), stored script
(QC QA QS QI QG QR QX), system (V BOOT). Full reference: USER.md §4.3.
Maestro protocols (User/maestro.c): bytes of 0x80 and above are Pololu
Maestro commands — Compact, Pololu (device 12) and Mini SSC framings, with
on-board speed and acceleration ramps in the Maestro's units. Both protocols
share the port. Reference: USER.md §4.3b.
Script engine (User/script.c): up to 254 instructions — move with ramp
time, wait, sync, loop, jump, analog wait/branch — compiled from text by
tools/servoscript.py, runs without a PC, optionally at power-up.
interrupt("WCH-Interrupt-fast") is silently dropped by upstream GCC —
one warning, then ISRs end in ret instead of mret. Use
interrupt("machine").bool is a keyword; WCH's usb_type.h
typedefs it. Built with -std=gnu11 and a patched header.-march=rv32imac_zicsr_zifencei — GCC 15 split CSR and fence.i out.printf at runtime. A WCH example printf in the USB suspend hook hung
the ISR at bring-up, and PA9 (USART1 TX) is a servo output.Modified vendor files: usb_endp.c (OUT endpoint → parser), usb_prop.c
(line coding via User/cdc_glue.c), ch32v20x_it.c (TIM2 drives servos).
Other known gaps: no reverse-polarity protection on J2 — a reversed pack
kills U4 and can vent C15/C16 (a P-FET ideal diode, ≈ $0.19, was deliberately
left out); the current-sense zero (Z) is lost at power-off.
Maestro software splits in two. Runtime control — nearly every community
library (FRC4564/Maestro, maestro-servo, ROS 1/2 drivers, Node, C++) — opens
the CDC port and speaks the Pololu/Compact/Mini SSC byte protocols without
checking USB IDs. Configuration (Control Center, UscCmd) uses control
transfers bound to Pololu's VID 0x1FFB and can never work here.
Firmware 0.4 implements those serial protocols, so the runtime libraries work
unchanged: verified on hardware with an unmodified FRC4564 maestro.py
(targets, speed, acceleration, position, moving state). One dispatch on the
first byte serves everything: 0xAA Pololu, 0xFF Mini SSC, 0x80–0xFE
Compact, printable ASCII the existing protocol.
Still different from a Maestro: 0.5 µs output steps (0.25 µs needs ≥ 100 Hz
frames), a fixed 50 Hz rate, no home positions, CRC or serial timeout.
Per-channel travel limits (64–4080 µs) and start-up speed/acceleration are
stored on the board with W — the GUI's gear button sets the limits. Next steps: configurable frame rate →
serial-timeout failsafe → home positions.
JP1 is a header, not a power-mux IC. Automatic muxing above 6 V needs an ideal-diode controller plus FETs (LM5050 +$2.12/board); TPS2116 stops at 5.5 V. The diode-OR costs $0.03 and JP1 carries only the ~15 mA logic current. A header and shunt cap ($0.05) beat slide switches on price and have no voltage limit of their own.
JP2 is a header too, so the one jumper every board needs for its first flash needs no soldering iron.
HT7533-1 replaced the AMS1117, which was 66× oversized, drew 5 mA quiescent and wanted 22 µF tantalum. The HT7533 is cheaper, takes 30 V, and is stable with the existing 10 µF ceramics.
The Fabrication Toolkit reads
LCSCfrom footprints, not symbols. After a schematic sync, checkgrep -c 'property "LCSC"' usbservocontroller.kicad_pcb(58 today) — a missing field shows as "part not found" at JLCPCB.
Checked against the files on 2026-09-29: ERC 0, DRC 11 errors, all the inherent header courtyard overlaps (three rows on a 2.54 mm grid), 0 unconnected.
BOOT0_SEL instead
of JP2, so DRC reports JP2 missing plus an extra footprint, and fab files
would carry the wrong designator. Run F8 (with Delete footprints with no
symbols unticked).EXCLUDE DNP in fabrication-toolkit-options.json does nothing.production/ — it dates from 2026-09-20, before the last
board edit. Then check that H1–H4 and J9 are gone from bom.csv and
positions.csv.Re-run the checks with kicad-cli sch erc usbservocontroller.kicad_sch and
kicad-cli pcb drc --schematic-parity usbservocontroller.kicad_pcb.
MIT — hardware design files, firmware and host tools.
Files carrying a WCH (Nanjing Qinheng Microelectronics) copyright header — the
ch32v20x HAL, USB library, startup code and the WCH-derived files in
embedded-ch32/User/ — remain under WCH's own terms, which limit their use to
WCH microcontrollers.
来源:README.md,提交 6facde3
Rail current/voltage, faults, protection limits, and every channel's name, mode, travel limits, speed and current position (0 = off).
无需输入参数。
Move one or more servos and wait until they arrive. positions: {channel number or name: pulse width in us}, e.g. {"0": 1200}. duration_s: time for all to arrive together (0 = each channel's own speed). Positions are clamped to each channel's limits. Turns channels on if off.
positions必填duration_sStop driving the given channels (numbers or names); the servos go limp.
channels必填Panic stop: every channel off immediately.
无需输入参数。
Poses and the sequence saved from the GUI (servogui.json).
无需输入参数。
Move to a saved pose (see list_poses) and wait until it arrives.
name必填duration_sPlay the GUI's saved sequence of poses (move and hold times as saved), repeated `loops` times. Stops early on a fault; capped at 120 s.
loopsRead channel 0-7 as an analog input (0-4095 counts, 0-3.3 V).
channel必填Sample the servo-rail current continuously; returns peak, mean and min mA.
secondsSet the rail trip limits (0 disables either). Overcurrent is capped at 7500 mA: the board's continuous budget is ~5 A. Resets at power-up.
overcurrent_ma必填undervoltage_mvClear a latched overcurrent/undervoltage fault so channels can move again.
无需输入参数。
Compile a servo script and store it in the board's flash, where the firmware runs it without a PC (autorun: also start at every power-up). One statement per line; channels by number or name; times in ms or '1.5s': label: jump/loop target move <ch> <us> [<time>] ramp there; does not wait: moves on different channels run together, a repeat move of the same channel waits for the previous one pose <name> [<time>] move every channel of a GUI pose sync wait until all moves arrive wait <time> off <ch>|all loop <label> [<n>] block runs n times in total; no n = forever jump <label> waitin <ch> >|< <0-4095> wait for analog input ch (0-7) ifin <ch> >|< <0-4095> run next statement only if true end Positions are clamped to the channel limits. Max 254 instructions.
runscript必填autorunWhat script is stored on the board, and whether it is running.
无需输入参数。
Start the stored script from the top.
无需输入参数。
Stop the stored script; servos keep their positions (stop_all to let go).
无需输入参数。
Close the serial port now (servos keep holding) so the GUI or flash.py can use the board. It reopens automatically on the next tool call.
无需输入参数。
已满足适用检查项的 90%
该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。
请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。
claude mcp add servo -- python /path/to/UsbServoController/tools/servo_mcp.py{ "mcpServers": { "servo": { "command": "python", "args": ["/path/to/UsbServoController/tools/servo_mcp.py"] } }}pip install -r tools/requirements.txtpython tools/servogui.py # GUI (or --mock with no hardware)python tools/servoctl.py set 0 1500 # CLIcd embedded-ch32 && make flash # build + flash over USB, ~3 s, no jumper U4 INA180A2 (Kelvin across R18) ─► PA5 I_SENSEJ2 Servo V+ ── R18 5mΩ ──┬────────► servo header V+ rail (unfused) (VSRV_RAW) ├─ R19..R22 ÷4 ─► PA4 V_SENSE └─ JP1 ──D2──┐ ├─► HT7533-1 ─► 3V3 ─► CH32V203, LEDUSB-C VBUS 5V ─────────────D1──────────┘GND: one continuous plane, common to both suppliesmake # build/USBServoController-ch32.{elf,hex,bin}make flash # BOOT command -> bootloader -> write, CRC-32 check, restart (~3 s)make hosttest # on the PC: sense.c protection (21 supply scenarios), maestro.c protocol, ramps and limits (11)python tools/verify_pinmap_ch32.py # schematic <-> pin table <-> firmware