Skip to the content
PCBKitNew board
4 layers30 × 30 mmController, the printed circuit board in 3D

Low-cost 10-axis sine-microstepping controller for small steppers on the RP2350 (PIO + DMA). WIP.

Size
30 × 30 mm
Copper layers
4
Thickness
1 mm
Parts
63
Nets
94
Pads
337
Tracks, vias
1,082, 199
Pours
3
Routed
Yes
KiCad
KiCad 10.0 (file 20260206)
Created
6 Oct 2026
Updated
24 Oct 2026

Overview

Controller is an open-source printed circuit board by andrewsharmon, from the andrewsharmon/rp2350-stepper-controller repository on GitHub, published under the MIT licence. It is a 4-layer board of 30 × 30 mm with 63 parts on 94 nets, routed with 1,082 tracks and 199 vias.

It is built around the RP2354B, SN74AHCT1G125DBVR(UMW) and ME6211C33M5G-N, with the TPD2E2U06DRLR-TP, 0402CG150J500NT and DF40C-40DP-0.4V(51) among its other parts. Its parts include 24 capacitors, 14 resistors, 5 connectors, 4 ICs and 3 switches. It belongs with the robots and motor control boards and the RP2040 and RP2350 boards of this library.

Main components

12

README

README.md

RP2350 coordinated stepper controller

Work in progress. Firmware runs on a Raspberry Pi Pico 2 bench setup. The custom board designs are an unreviewed feasibility first pass: schematics only, no PCB layout, not checked against datasheets or built. Don't fabricate them as-is.

A very low-cost, compact controller for up to 10 small stepper motors (8 mm micro steppers, 28BYJ-48 converted to bipolar, about 0.2 A per coil or less). It's aimed at kinetic art first, then lab and robotics use.

There are no stepper-driver ICs. The RP2350 generates sine-PWM microstepping itself (voltage mode), using PIO state machines fed by DMA, into cheap DRV8833-class dual H-bridges (AT8833).

Status

#MilestoneState
1Hardware spec and KiCad schematics for all three boardsfirst pass, not reviewed (open part picks in hardware/README.md)
2One motor: sine PWM via PIO + DMAdone, verified on hardware
310-axis PWM, ADC monitor, e-stop/button ladder, WS2812done on Pico 2; ladder buttons not yet bench-wired
4Trajectory generators and profiles, jog modes, telemetrydone (text telemetry; binary framing comes with milestone 5)
5Coordinated groups and planner, USB protocol, host tooldone
6I²C target, cam/LED shows, flash config, standalone modedone; I²C target written for the RP2354B board but untested (no free pins or I²C master on the bench)
7PCB layout, fab, bring-up of the board stack

What works today (measured on a Pico 2 + DRV8833 + 8 mm stepper):

  • PWM: 20 kHz, four-segment H-bridge PWM with slow-decay brake off-time and 1/256-step sine microstepping. Duties below the driver's 0.5 µs minimum pulse are error-diffused.
  • 10 axes: each has its own position, velocity or jog mode on a 1 kHz trajectory tick. Drive amplitude depends on speed, with lower holding current at standstill. Core 1 is about 33-35% loaded, core 0 about 0.5%.
  • Coordinated groups: up to 4 groups, each owning a set of axes. A group follows a queue of straight lines and arcs in joint space, with every axis starting and finishing together. Lookahead keeps it moving through gentle corners and chord-split arcs, and no axis exceeds its own limits. On the Pico 2, a 300-step-radius circle ran at a constant 800 steps/s and closed exactly.
  • Shows: keyframe tracks for axes and LED pixels on one timeline, written as JSON, compiled and uploaded to one of 4 flash slots. Axes pass exactly through every keyframe (played as PVT), LED colours fade between keys, and shows can loop. A show is refused if any segment would exceed an axis's speed or acceleration limit. On the Pico 2, a looping 10-axis wave with 9 LED tracks hit all 360 keyframes checked exactly over 2.5 loops.
  • Standalone mode: a boot show (saved in the configuration) starts at power-up with no computer attached. Btn1 clears a latched stop once the e-stop is closed again, or starts / stops the selected show; Btn2 selects the next stored show, switching to it if one is playing.
  • Configuration in flash: per-axis limits, motion profile, automatic amplitude curve (run low/high, hold, corner speeds) and wiring fixes (reverse direction, swap coils) survive power cycles. Two sectors are written alternately with a generation counter and CRC, so an interrupted save keeps the previous configuration.
  • Streamed PVT: the host sends position/velocity/time points, and each axis follows a cubic through them. A 10-axis wave streamed at 20 ms per point tracked the ideal curve within 0.0005 steps. Running out of points while moving brakes safely.
  • Telemetry: up to 1 kHz with selectable axes and fields (position, speed, mode, amplitude, PVT queue, plus motor voltage, ladder, stop state and the last applied command). Each line is numbered so a host can detect drops. Event lines report finished moves, PVT underruns and stops. At 1 kHz with every field on 10 axes, no lines were dropped and core 0 was 39% busy.
  • Electronic cams: an axis can follow a cam table, so its position is a function of a leader's position: another axis, or one of two virtual leaders with their own speed ramps. Because it's position-based, followers retrace exactly when the leader reverses, at any speed. Cyclic tables can carry a net rise per cycle. Engaging blends in smoothly, and is refused if the follower couldn't keep up at the leader's limits. On the Pico 2, followers matched an independent evaluation of the cam within 0.0013 steps through forward and reverse runs. save also stores the loaded cam tables in flash; they are restored at boot.
  • Motion profiles per axis: trap, scurve (the default: jerk-limited by a moving average, Tj = 30 ms), smooth (two moving averages), cosine and quintic. Every profile lands exactly on its target within the speed and acceleration limits, and moves can be redirected mid-move without jumps.
  • Safety: an e-stop or driver fault on the analog ladder cuts all outputs in about 30 µs. A watchdog on core 0 drops outputs to coast if core 1 stalls.
  • Status LEDs: a WS2812 chain shows overall status plus each axis's speed and direction.

How it works, from trajectory planning to PIO pin toggling: docs/THEORY.md.

Repository layout

text
tools/stepperctl/    host library + command line (binary protocol)
tools/shows/         example shows (JSON; format in tools/stepperctl/show.py)
tools/cams/          example cam tables (JSON: {"cyclic": true, "points": [[x, y], ...]})
tools/pvt_demo.py    host demo: streams a PVT wave over USB serial
firmware/            Pico SDK (C) firmware
  src/               motor PWM (PIO/DMA), microstepping, ADC monitor, ladder, LEDs
  boards/            board header for the custom RP2354B controller
  test/test_host.c   host-side tests (PWM encoder, microstep math, ladder, motion)
  tools/             sine table generator
hardware/            spec, KiCad schematics and BOMs (generated by hardware/gen)

Building the firmware

Requirements:

  • Pico SDK 2.1.1 (set PICO_SDK_PATH);
  • an arm-none-eabi GCC toolchain;
  • CMake and Ninja.
bash
# Custom RP2354B controller (default board)
cmake -S firmware -B firmware/build -G Ninja
cmake --build firmware/build

# single_sh_proto prototype board
cmake -S firmware -B firmware/build-proto -G Ninja -DPICO_BOARD=single_sh_proto
cmake --build firmware/build-proto

# Raspberry Pi Pico 2 bench setup
cmake -S firmware -B firmware/build-pico2 -G Ninja -DPICO_BOARD=pico2
cmake --build firmware/build-pico2

Options: -DNUM_MOTORS=1..10 (default 10), -DLADDER_BYPASS=ON (bench without a ladder wired; never use with motors you can't stop by hand), -DBOOT_TRACE=ON.

Flash by copying stepper.uf2 to the board in BOOTSEL mode, or with picotool:

bash
picotool load -x firmware/build-pico2/stepper.uf2

Host tests (no hardware needed):

bash
cd firmware/test
cc -std=c11 -O1 -Wall -Wextra -DCONFIG_HOST_TEST -I../src test_host.c ../src/hbridge_encode.c \
   ../src/microstep.c ../src/sine_lut.c ../src/ladder.c ../src/motion.c ../src/group.c \
   ../src/frame.c ../src/config.c ../src/show.c ../src/standalone.c ../src/cam.c ../src/cam_store.c \
   -lm -o test_host && ./test_host

Pico 2 bench setup

Pico 2Connects to
GP0-GP3motor 1: DRV8833 module AIN1, AIN2, BIN1, BIN2 (motors 2-5 on GP4-GP19)
GP20WS2812 chain data in (pixel 0 status, pixels 1-10 axes)
GP26ladder: 10k to 3V3, 20k to GND (idle); see hardware/README.md
GP28board ID (optional)
VBUS / GNDDRV8833 VM and GND; pull the module's sleep pin (EEP) high

Motors 6-10 have no pins on the Pico 2, but their state machines and DMA still run, so the full 10-axis load is exercised.

The USB serial console takes line commands (type help). Some examples:

text
m 1 500          move axis 1 to 500 full steps
mr * -100        move every axis back 100 steps
j 3 400          jog axis 3 at 400 steps/s (stops unless repeated within 300 ms)
lim 1 1200 4000  axis 1 limits: 1200 steps/s, 4000 steps/s^2
prof * quintic   minimum-jerk moves on every axis (axes must be at rest)
cam 1 cyclic 0,0 100,40 200,50   cam table 1 (leader x, follower y); +50 per 200-step cycle
vl 1 lim 150 150 virtual leader 1 limits; ce 3 1 v1 makes axis 3 follow cam 1 behind it
vl 1 vel 100     run the virtual leader (negative reverses; followers retrace)
g 1 1,2 800      group 1 = axes 1 and 2, path speed 800 steps/s
gl 1 400 400     group line to (400, 400); ga 1 -300 0 360: full circle around (-300, 0)
p 2 150 300 20   PVT point: axis 2 reaches 150 steps at 300 steps/s, 20 ms after the last
pgo 1,2          start axes 1 and 2 following their queued points on the same tick
t 100            100 telemetry lines/s (header line names the columns)
tf pos,vel,mode  per-axis telemetry fields; ta 1,2 picks axes, ts adds system fields
te on            event lines: E <tick> done <ax> <pos>, underrun, stop, clear
echo off         for scripts: no echo or prompt
drive 1 40 60 25 axis 1 amplitude: 40% at low speed, 60% at high speed, 25% holding
dir 2 rev        reverse axis 2 (coils 2 ba swaps its coils)
cfg / save       show the configuration / write it to flash (axes at rest)
?                status, loads and per-axis table

Host tool (binary protocol)

The same USB port also speaks a framed binary protocol (COBS + CRC-16; the message spec is firmware/src/protocol.h). Frames and the text console coexist: binary frames are wrapped in 0x00 bytes, which the console never sends. tools/stepperctl is a standard-library Python client and command line:

bash
python3 tools/stepperctl ping
python3 tools/stepperctl move 1,2 250 --wait
python3 tools/stepperctl group 1 create 1,2 --feed 800
python3 tools/stepperctl group 1 arc -200 0 360
python3 tools/stepperctl telem --hz 200 --axes 1,2 --seconds 5 > log.csv
python3 tools/stepperctl show upload tools/shows/wave.json --slot 2
python3 tools/stepperctl show run 2          # show list / show stop
python3 tools/stepperctl boot 2 && python3 tools/stepperctl save   # run show 2 at power-up
python3 tools/stepperctl cam load 1 tools/cams/rise_dwell.json
python3 tools/stepperctl cam engage 3 1 v1 && python3 tools/stepperctl vl 1 vel 100
python
from stepperctl import Client          # run from tools/, or put tools/ on sys.path
with Client() as c:
    seq = c.move([1], 500)
    c.wait_settled([1], after=seq)

Command-line tool tests (no hardware needed): python3 -m unittest discover -s tools/stepperctl -p 'test_*.py' -t tools

I²C (Qwiic) — untested

On the RP2354B board the Qwiic port (I2C1, GPIO 46/47) is an I²C target at address 0x42 carrying the same requests as the binary protocol, without COBS framing: write [type][payload…], then read [n][type][reply payload…] (n = 0 means not ready yet, read again). See firmware/src/i2c_target.h. It is compiled out on the Pico 2 bench build and has not been run on hardware yet.

tools/pvt_demo.py streams a travelling wave across every axis as PVT points (standard-library Python, macOS/Linux):

bash
tools/pvt_demo.py --amp 200 --hz 0.5 --seconds 15

Notes

  • Sine table: the SDK's float sinf returned wrong values near multiples of π/2 on the RP2350, which produced reverse pulses at each coil peak. The sine table is generated offline (firmware/tools/gen_sine_lut.py), and the motion code uses its own polynomials (firmware/src/trig.h).
  • Endless DMA: on the RP2350, endless-mode DMA (TRANS_COUNT mode 0xF) still needs a non-zero count, or the channel completes immediately.

License

MIT, covering both firmware and hardware design files.

Parts

63
QtyValueFootprintReferences
16100nCapacitor_SMD:C_0402_1005MetricC1, C2, C3, C4, C5, C6, C7, C8, C9, C11, C12, C13, C20, C22, C23, C24
310k 1%Resistor_SMD:R_0402_1005MetricR10, R11, R14
34.7uCapacitor_SMD:C_0402_1005MetricC10, C14, C15
215pCapacitor_SMD:C_0402_1005MetricC16, C17
21kResistor_SMD:R_0402_1005MetricR2, R13
21uCapacitor_SMD:C_0402_1005MetricC19, C21
227Resistor_SMD:R_0402_1005MetricR3, R4
24.7kResistor_SMD:R_0402_1005MetricR8, R9
25.1kResistor_SMD:R_0402_1005MetricR5, R6
2BTB 40P headerstack:Hirose_DF40C-40DP_HeaderJ10, J11
2QwiicConnector_JST:JST_SH_BM04B-SRSS-TB_1x04-1MP_P1.00mm_VerticalJ2, J3
1+3V3TestPoint:TestPoint_Pad_D1.0mmTP5
11.5A holdFuse:Fuse_1812_4532MetricF1
1100kResistor_SMD:R_0402_1005MetricR7
110uCapacitor_SMD:C_0805_2012MetricC18
112MHzCrystal:Crystal_SMD_3225-4Pin_3.2x2.5mmY1
13.3k 1%Resistor_SMD:R_0402_1005MetricR12
13.3uInductor_SMD:L_Cenker_CKCS201610L1
133Resistor_SMD:R_0402_1005MetricR1
174AHCT1G125Package_TO_SOT_SMD:SOT-23-5U4
1AO3401APackage_TO_SOT_SMD:SOT-23Q1
1BOOTSELButton_Switch_SMD:SW_Push_1P1T_XKB_TS-1187ASW3
1BTN1Button_Switch_SMD:SW_SPST_TS-1088-xR020SW1
1BTN2Button_Switch_SMD:SW_SPST_TS-1088-xR020SW2
1GNDTestPoint:TestPoint_Pad_D1.0mmTP4
1I2C_PUJumper:SolderJumper-2_P1.3mm_Bridged_RoundedPad1.0x1.5mmJP2
1ME6211C33M5GPackage_TO_SOT_SMD:SOT-23-5U3
1QWIIC_SUPPLYJumper:SolderJumper-2_P1.3mm_Open_RoundedPad1.0x1.5mmJP1
1RP2354BPackage_DFN_QFN:QFN-80-1EP_10x10mm_P0.4mm_EP3.4x3.4mmU1
1RUNTestPoint:TestPoint_Pad_D1.0mmTP3
1SS54Diode_SMD:D_SMAD1
1SWCLKTestPoint:TestPoint_Pad_D1.0mmTP1
1SWDIOTestPoint:TestPoint_Pad_D1.0mmTP2
1TPD2E2U06Package_TO_SOT_SMD:SOT-553U2
1USB-CConnector_USB:USB_C_Receptacle_HRO_TYPE-C-31-M-12J1
1XL-2020RGBC-WS2812BLED_SMD:LED_WS2812B-2020_PLCC4_2.0x2.0mmD2

Questions

How big is the Controller PCB?

It measures 30 × 30 mm and has 4 copper layers, 1 mm thick.

What parts does the Controller use?

63 parts, built around the RP2354B, SN74AHCT1G125DBVR(UMW), ME6211C33M5G-N and TPD2E2U06DRLR-TP. The full list of its parts is on this page, with links to each part's symbol and footprint where PCBKit's library has them.

Where can I download the Controller KiCad files?

From the andrewsharmon/rp2350-stepper-controller repository on GitHub, at the commit this page was made from: the board is hardware/controller/controller.kicad_pcb and its schematic hardware/controller/controller.kicad_sch. PCBKit opens the board in the browser, with nothing to install.

Can I use the Controller design in my own project?

Its repository publishes it under the MIT licence: you may reuse it on that licence's terms (keep the author's notice, and for a copyleft licence share your changes alike).

More from andrewsharmon/rp2350-stepper-controller

2
  • 4 layers150 × 30 mmSingle sh proto, the printed circuit board in 3D

    Single sh proto

    andrewsharmon/rp2350-stepper-controller

    Low-cost 10-axis sine-microstepping controller for small steppers on the RP2350 (PIO + DMA). WIP.

    163 partsMIT
  • 4 layers122 × 20 mmSingle sh, the printed circuit board in 3D

    Single sh

    andrewsharmon/rp2350-stepper-controller

    Low-cost 10-axis sine-microstepping controller for small steppers on the RP2350 (PIO + DMA). WIP.

    151 partsMIT

Similar boards

  • 4 layers96.9 × 88.1 mmDigichiver, the printed circuit board in 3D

    Digichiver

    frindaddy/digichiver

    Archival hardware/firmware for the MM54104 Digitalker chip

    121 partsMIT
  • 4 layers38.9 × 23.3 mmOWL - Rotor v2, the printed circuit board in 3D

    OWL - Rotor v2

    sedlak477/owl-hardware

    Hardware design files and build instructions for the OWL platform

    118 partsCERN-OHL-P-2.0
  • 2 layers34 × 28 mmHyperion, the printed circuit board in 3D

    Hyperion

    adamhrnciar/hyperion-controller

    Open-source controller for 150 g antweight combat robots: RP2354A, 2x DRV8231A, 2-4S, ELRS/CRSF/SBUS/iBUS/PPM input, IMU. KiCad 10, CERN-OHL-S-2.0.

    90 partsCERN-OHL-S-2.0
  • 4 layers50 × 70 mmRp569, the printed circuit board in 3D

    Rp569

    kelu124/rp569

    RP569 - PIOs to usb3 ( rp2350 x ch569 )

    146 parts3
  • 4 layers82 × 55 mmSpark, the printed circuit board in 3D

    Spark

    ratmuds/spark

    The PCB files for this project. The gerber and other PCBA files are in the production folder.

    202 parts1
  • 4 layers50 × 36 mmMinicontrol, the printed circuit board in 3D

    Minicontrol

    Jotrorox/minicontrol

    RP2354A + 868 MHz LoRa (Ai-Thinker Ra-01SH) USB-C controller board, designed in KiCad 10.

    66 partsBSD-2-Clause
  • 2 layers30.5 × 68.6 mm16bit-5v, the printed circuit board in 3D

    16bit-5v

    bread-modular/bread-modular

    Modular Synths for Everyone

    62 parts67MIT
  • 4 layers21 × 51 mmEasy2350, the printed circuit board in 3D

    Easy2350

    sequoia-hope/ultraroute

    PCB autorouting experiments around copperroute: route jobs, leaderboards, board morphing

    58 parts1MIT