Achieving ±25 ns STEP Pulse Timing with PIO State Machines on the RP2350
Every CNC machine builder eventually confronts the same timing problem. The step-direction interface is simple in concept — a STEP pulse for each motor increment, a DIR signal for direction — but the quality of that pulse matters enormously. Jitter on the STEP edge translates directly to velocity ripple, which translates to surface finish artifacts on machined parts and mechanical stress on drive components.
Software-timed STEP pulses — generated by an interrupt service routine toggling a GPIO — have inherent jitter caused by interrupt latency. On a bare-metal microcontroller running at 200 MHz, that jitter is typically ±1–5 µs. Under load (multiple axes, USB activity, motion planning), it can reach ±10 µs. This is not a flaw in the firmware; it is a fundamental property of interrupt-driven output.
The RP2350's PIO (Programmable I/O) state machines eliminate this problem entirely. In our 8-Axis CNC Motion Controller, we achieve ±25 ns STEP pulse jitter — 40 to 400 times tighter than software-timed alternatives — using the PIO hardware on the dual RP2350 (Raspberry Pi Pico 2) platform.
What is PIO?
PIO is a block of dedicated hardware on the RP2040 and RP2350 that is best described as a programmable state machine for I/O. Each RP2350 has two PIO blocks, each with four state machines, for a total of eight independent PIO state machines per chip.
Each PIO state machine runs a short program (up to 32 instructions) from its own instruction memory. The program executes independently of the ARM Cortex-M33 cores — it does not share the main clock domain, does not compete for the ARM instruction pipeline, and is not affected by interrupt latency. The state machine clock can be set to any integer or fractional divisor of the system clock.
For STEP pulse generation, the key property is this: the PIO state machine asserts and deasserts the STEP GPIO with a precision of one state machine clock cycle. At 125 MHz (the default system clock divided by 1 for PIO), that is one cycle = 8 ns. Our STEP program runs at 125 MHz and produces transitions with ±1 clock cycle variation — hence ±8 ns jitter. The ±25 ns figure we publish is conservative, including PCB trace skew and oscilloscope measurement uncertainty.
The STEP/DIR PIO Program
The PIO program for STEP pulse generation is eleven instructions. The ARM core writes a pulse count to the PIO FIFO; the state machine pulls the count, asserts STEP, waits the programmed pulse width, deasserts STEP, waits the step period remainder, and loops.
; PicoPLC STEP generator — simplified for illustration
; PIN 0 = STEP, PIN 1 = DIR
; OSR loaded with: [31:16] = step_period, [15:0] = dir_bit | pulse_count
.program step_gen
.side_set 1 ; side-set controls DIR pin
pull block side 0 ; wait for new command word
out pins, 1 side 0 ; write DIR bit to DIR pin
out x, 15 side 0 ; x = pulse_count (15-bit)
step_loop:
nop side 1 ; STEP high — rising edge
nop side 1 ; hold for min pulse width (2 cycles = 16ns)
nop side 0 ; STEP low — falling edge
jmp x-- step_loop side 0 ; decrement count, loop if not zero
irq set 0 side 0 ; signal core: burst complete The actual production program is more complex — it handles DIR setup time (minimum 200 ns before first STEP), configurable pulse width, and step rate limiting — but this illustrates the principle. The entire waveform is generated in hardware. The ARM core only writes a word to the FIFO; everything else is deterministic hardware.
Architecture: Dual RP2350 for 8 Axes
The RP2350 has 8 PIO state machines (2 blocks × 4 machines). With two machines per axis (one for STEP timing, one for quadrature encoder decoding), a single RP2350 can handle 4 axes in pure PIO. Our 8-Axis CNC Controller uses two RP2350 boards, each handling 4 axes, connected via a synchronisation bus that ensures all axes start and end coordinated moves simultaneously.
The division of responsibilities:
| RP2350 Board | Core 0 | Core 1 | PIO |
|---|---|---|---|
| Primary (Axes 1–4) | Motion planner, G-code parser, lookahead buffer | Safety monitor, fault log, USB/serial host interface | 4× STEP gen + 4× quadrature decode |
| Secondary (Axes 5–8) | Trajectory follower (receives setpoints from Primary) | Secondary safety monitor, secondary watchdog | 4× STEP gen + 4× quadrature decode |
The Primary board performs motion planning and lookahead; the Secondary board receives pre-computed step burst descriptors over a dedicated SPI link and executes them in synchrony. A hardware SYNC line ensures simultaneous step initiation across all 8 axes to within one PIO clock cycle (8 ns).
Quadrature Encoder Decoding in PIO
Closed-loop axes require real-time quadrature encoder decoding. Reading A/B encoder channels in firmware typically requires either interrupt-per-edge (which struggles above ~50 kHz per axis) or a DMA trick that wastes bandwidth.
PIO decodes quadrature in hardware. A four-instruction PIO program reads A and B simultaneously, detects transitions, and increments or decrements a 32-bit position counter in SRAM via DMA — at speeds exceeding 10 MHz (20 million edges per second) per axis, with zero CPU involvement. With 1250 CPR encoders (5000 PPR quadrature), this gives us >50 kHz sampling rate with headroom to spare.
Measured Results
| Metric | Software-Timed (ISR) | PIO State Machine |
|---|---|---|
| STEP edge jitter | ±1–10 µs | ±25 ns |
| Max step rate (single axis) | ~80 kHz | >2 MHz |
| CPU load at 80 kHz × 4 axes | ~60% | <1% |
| Jitter under USB load | degrades to ±50 µs | unchanged (±25 ns) |
| Jitter under motion planning load | degrades significantly | unchanged |
The last two rows are particularly important for real-world CNC applications. Motion planning — calculating smooth trajectories for 8 axes simultaneously — is computationally intensive. A software-timed approach is forced to make a trade-off between planning quality and step pulse fidelity. PIO eliminates this trade-off entirely.
Implications for Machine Performance
At 5000 PPR quadrature decoding with a 5mm pitch ball screw, one encoder count = 1 µm of linear travel. STEP jitter of ±25 ns at a nominal step rate of 100 kHz translates to velocity uncertainty of approximately 0.0025% — negligible for any practical machining operation.
Compare this to ±5 µs jitter at 100 kHz: the velocity uncertainty is ±0.5%, which on a high-speed finishing pass (say, 5000 mm/min) produces measurable velocity ripple and can cause chatter on thin walls.
PIO-driven STEP pulses are not a luxury for high-end machines. They are the right way to drive stepper and servo axes on the RP2350, and our 8-Axis CNC Controller is designed from the ground up to exploit this capability.