rp2040_pio_stepper
rp2040_pio_stepper
rp2040_pio_stepper — Real-Time Ethernet CNC Controller for LinuxCNC Using RP2040 PIO and W5500
Tags: W5500 · W5500-EVB-Pico · RP2040 · PIO · LinuxCNC · CNC · Stepper Motor · UDP · Real-Time
Overview
rp2040_pio_stepper is an open-source, Ethernet-connected stepper motor controller for LinuxCNC, built on the RP2040 microcontroller and WIZnet W5500 Ethernet chip. It bridges a gap that has long existed in the CNC hobbyist and light-industrial world: GRBL is cheap but offers no real-time feedback path to the host and no encoder support, while industrial-grade motion control cards (Mesa cards and similar) deliver real performance but at significant cost and complexity.
rp2040_pio_stepper occupies the space between them — low-cost open hardware with a real-time UDP Ethernet link to LinuxCNC, deterministic step pulse generation through the RP2040's PIO state machines, and a clean HAL driver that fits LinuxCNC's existing toolchain with no parallel port required.
The verified reference hardware is the WIZnet W5500-EVB-Pico — a dev board that integrates the RP2040 and W5500 on a single board. Up to 8-axis operation has been tested and confirmed on this platform.
Hardware Platform
| Component | Description |
|---|---|
| MCU | RP2040 (dual-core Cortex-M0+, 133 MHz, 264 KB SRAM) |
| Ethernet | WIZnet W5500 (hardware TCP/IP offload, SPI-connected) |
| Reference board | W5500-EVB-Pico (RP2040 + W5500 integrated) |
| GPIO expander | MCP23017 (I2C, up to 8 devices × 16 pins) |
| Spindle interface | RS-485 VFD (Huanyang, Fuling, Weiken) |
| Host connection | Point-to-point Ethernet to LinuxCNC PC NIC |
How WIZnet W5500 is Used
The W5500 is the sole communication path between the RP2040 firmware and the LinuxCNC host. It is the reason the project can eliminate the parallel port entirely. The W5500 provides a complete hardware UDP socket abstraction over SPI — handling Ethernet framing, checksums, and ARP internally — so the RP2040's two cores are left completely free to handle real-time motion tasks.
W5500 Role: Real-Time UDP Link for CNC Motion Control
Every servo period (typically 1 ms), the LinuxCNC HAL driver (hal_rp2040_eth.so) on the PC side:
- Serialises the current joint positions, velocities, and GPIO states into one or more
MSG_*packets - Stamps a sequence number (
seq-out) for latency monitoring - Sends the packet to the RP2040 over UDP via the W5500
The RP2040's Core0 blocks on recv() through the W5500, dispatches each message to the appropriate handler, then immediately serialises a reply containing position feedback and diagnostics and sends it back through the same W5500 UDP socket. The round-trip sequence number difference (seq-out − seq-in) is used directly to calculate the machine's safe following error (FERROR) setting for LinuxCNC.
This is a hardware-offload UDP use case in the strictest sense: the RP2040 writes and reads raw UDP payloads over SPI, and the W5500 handles all Ethernet-layer work autonomously.
Why UDP over TCP for CNC?
CNC motion control requires bounded, low-latency communication. TCP's retransmission and flow control mechanisms introduce variable latency that would cause LinuxCNC to fault on following error. UDP gives the HAL driver direct control over timing — if a packet is late or dropped, LinuxCNC handles the fault condition itself rather than waiting for a TCP retransmission. The W5500's hardware UDP socket makes this pattern trivially implementable without a software networking stack on the RP2040.
Network Configuration
LinuxCNC PC NIC (192.168.12.1)
│
│ Point-to-point Ethernet (no switch)
│
W5500-EVB-Pico RP2040 (192.168.12.2) UDP port 5002A direct point-to-point connection is recommended — every switch hop adds latency and jitter to the servo loop. NIC interrupt coalescing is disabled on the host side via a udev rule to further minimise round-trip latency.
Dual-Core Architecture
The RP2040's two cores have clearly divided responsibilities, made possible by the W5500 handling all Ethernet work independently:
Core0 — Networking and Clock Synchronisation
Core0 blocks on UDP receive through the W5500. On each incoming packet it:
- Dispatches each
MSG_*message to the appropriate config update function - Increments
packet_generationto signal Core1 that fresh data is available - Runs
recover_clock()to keep the hardware alarm driving Core1 in phase with LinuxCNC's servo period - Serialises position feedback and diagnostics into a reply packet
- Sends the reply back over UDP through the W5500
The clock synchronisation step is critical: LinuxCNC sends packets at a fixed servo period, and Core0 continuously adjusts the RP2040's internal hardware alarm to align Core1's step generation tick with the incoming packet cadence. This keeps step pulses phase-coherent with LinuxCNC's motion planner.
Core1 — Step Generation
Core1 waits for the hardware alarm tick fired by Core0's clock-sync logic, then waits for packet_generation to advance — guaranteeing it always processes the freshest position command before generating steps. It then calls do_steps() for each enabled joint, converting the requested velocity into a pulse length and pushing step commands to PIO0's TX FIFO.
PIO Step Generation: The Core Technique
The RP2040's Programmable I/O (PIO) state machines are what make this project's step generation deterministic and CPU-independent. Each axis uses two PIO state machines:
step_gen (PIO0) — Step Pulse Output
Reads a packed word from the TX FIFO encoding pulse length and direction, then generates a precision square-wave step pulse on the step GPIO pin. The theoretical ceiling is 6.65 MHz per joint at 133 MHz PIO clock, with a practical limit of ~500 kHz per joint (bounded by stepper driver minimum pulse width of 1–2 µs).
step_count (PIO1) — Position Feedback
Counts rising edges on the step pin, increments or decrements a 32-bit counter based on the direction pin, and pushes the result to the RX FIFO. This gives LinuxCNC real-time step position feedback without any CPU polling — the counter is always current.
Four joints occupy four state machines on each PIO block, supporting up to 8 joints across both PIO instances.
Performance Specifications
| Property | Value |
|---|---|
| Maximum joints (axes) | 8 |
| Step rate — theoretical max | 6.65 MHz per joint (PIO @ 133 MHz) |
| Step rate — practical max | ~500 kHz per joint |
| Step command modes | Position or velocity (configurable per joint) |
| Position feedback | Step counter via second PIO state machine |
| Servo period | ~1 ms (LinuxCNC default) |
| GPIO channels | 32 default (compile-time, expandable) |
| MCP23017 expanders | 4 default, up to 8 |
| Spindle control | 1× RS-485 VFD (Huanyang, Fuling, Weiken) |
| Ethernet chip | W5500 or W5100S (compile-time flag) |
LinuxCNC HAL Driver
The project ships a complete LinuxCNC realtime module (hal_rp2040_eth.so) that compiles and installs via halcompile. The driver:
- Allocates all HAL pins at startup
- Opens a UDP socket to the W5500 firmware address
- Each servo period: sends position/velocity/GPIO commands, receives position feedback
- Exposes
seq-out/seq-infor round-trip latency monitoring - Provides
ferror-suggestper joint — a computed minimum safeFERRORvalue based on current latency
Ready-to-run HAL and INI configurations are included for 3, 4, 6, and 8-axis machines in the config/ directory, covering XYZA through XYZABCUV kinematic configurations.
Build System
The build exposes two key CMake variables:
cmake -B build_rp -S . -DBUILD_RP=ON -DWIZNET_CHIP=W5500 -DMAX_JOINT=6 make -C build_rp stepper_control| Variable | Default | Description |
|---|---|---|
WIZNET_CHIP | W5500 | Ethernet chip — W5500 or W5100S |
MAX_JOINT | 8 | Number of stepper joints (1–8) |
The output is a .uf2 image that flashes over USB via BOOTSEL mode on the W5500-EVB-Pico. No dedicated programmer needed.
Why W5500 for Real-Time CNC?
The parallel port — long the standard LinuxCNC interface — is increasingly absent from modern PC hardware. USB-based motion controllers exist but introduce latency and jitter incompatible with LinuxCNC's real-time servo loop.
The W5500 solves this cleanly. Its hardware UDP offload means the RP2040 never runs a software TCP/IP stack, and both cores are available exclusively for real-time motion work. The W5500-EVB-Pico integrates everything on a single board at a fraction of the cost of commercial Ethernet motion control cards, while still delivering the wired Ethernet determinism that CNC control requires.
The result is a motion controller that sits genuinely between hobby GRBL and industrial Mesa hardware — real LinuxCNC integration, real Ethernet latency characteristics, and completely open source.
WIZnet Chip Roadmap: W6100 and W6300 Support Planned
The project's Ethernet backend is currently W5500 (or W5100S), but the author has filed two feature issues — both on May 17, 2026 — to expand support across WIZnet's full Hardwired TCP/IP chip family.
Issue #35 — W6100 Support
The W6100 is WIZnet's dual-stack (IPv4 + IPv6) successor to the W5500. It shares a compatible SPI interface and a similar register layout, making it a natural upgrade path for this project. The scope of this issue is:
- Abstract the current W5500 HAL into a chip-agnostic layer that can swap drivers at compile time
- Integrate the W6100 ioLibrary driver and verify that UDP socket semantics match W5500 behaviour
- Update CMake to a new
-DETH_CHIP=W6100scheme - Document W6100-based boards (e.g. W6100-EVB-Pico) as supported targets
- IPv6 as a stretch goal — initial target is IPv4 parity with W5500
Because the W6100's SPI register layout is close to the W5500's, the abstraction work done for W6100 directly enables the W6300 port as well.
Issue #36 — W6300 Support
The W6300 is WIZnet's highest-capability Hardwired TCP/IP controller. Compared to the W5500/W6100, it adds:
- Larger socket buffers — up to 8 sockets with configurable RX/TX buffer sizes, which can reduce jitter in the CNC servo loop by allowing larger UDP receive windows
- Dual-bus interface — SPI mode and parallel bus mode, giving board designers flexibility
- Full dual-stack — native IPv4 + IPv6
The scope for W6300 mirrors W6100: integrate the ioLibrary driver, verify UDP datagram delivery matches W5500 semantics, and document W6300-based boards (e.g. W6300-EVB-Pico).
What This Means Architecturally
Both issues are being designed together. The W5500 HAL will be refactored into a chip abstraction layer shared across W5500, W6100, and W6300 — adding a new WIZnet chip will require only implementing the abstraction interface, with no changes to motion control code. The CMake build will select the Ethernet backend at compile time:
# Current (already supported) cmake ... -DWIZNET_CHIP=W5500 # or W5100S # Planned cmake ... -DETH_CHIP=W6100
cmake ... -DETH_CHIP=W6300For the CNC use case specifically, the W6300's larger configurable socket buffers are the most compelling upgrade: more RX buffer headroom means fewer dropped UDP packets under CPU load spikes, directly improving servo loop reliability without any change to firmware logic.
Application Fields
rp2040_pio_stepper is applicable wherever deterministic multi-axis motion control over Ethernet is needed:
- Hobby and semi-professional CNC routers — parallel-port replacement for LinuxCNC machines
- 3D printers with closed-loop feedback — position counter feedback enables error detection
- Laser cutters and plasma tables — multi-axis coordination with spindle/relay control
- Light industrial automation — low-cost Ethernet motion controller for stepper-based stages
- Robotics — multi-joint coordinated motion with real-time host communication
- Educational CNC platforms — fully documented open hardware for teaching motion control
FAQ
Q: What does this project use the W5500 for? A: The W5500 provides the entire real-time communication link between the RP2040 firmware and LinuxCNC on the host PC. The firmware receives joint position/velocity commands every servo period over UDP and sends back position feedback — all through the W5500's hardware UDP socket over SPI.
Q: Why is the W5500-EVB-Pico the verified platform? A: The W5500-EVB-Pico integrates the RP2040 and W5500 on a single board, eliminating any wiring uncertainty between the MCU and the Ethernet chip. The project also supports W5100S via compile flag, but W5500 is the primary tested configuration.
Q: Does the RP2040 run a software TCP/IP stack? A: No. The W5500 handles all Ethernet framing, checksums, and ARP in hardware. The RP2040 reads and writes raw UDP payloads over SPI. Both cores are entirely free for motion control tasks.
Q: How does timing accuracy compare to a parallel port? A: The point-to-point Ethernet path with interrupt coalescing disabled achieves round-trip latencies compatible with LinuxCNC's 1 ms servo period. The ferror-suggest HAL pin reports measured latency in real time so the machine's following error limit can be set accurately.
