PIO
micro/pio provides access to the RP2040’s Programmable IO (PIO) state machines. PIO is a small, fixed-instruction-set processor for bit-banging custom protocols at high speed without CPU involvement.
Overview
PIO is unique to the RP2040 (and RP2350). It lets you implement custom protocols (WS2812 LEDs, stepper motor control, custom serial protocols) at deterministic speeds without relying on the CPU.
Flint compiles PIO programs from .pio files during the build, alongside your Flint source.
PIO Source Files
PIO programs are written in PIO assembly and live anywhere under src/ with a .pio extension:
src/
main.fl
drivers/
ws2812.pio
stepper.pio
Flint assembles these during the build. PIO errors (syntax, constraint violations) appear as normal Flint diagnostics pointing at the .pio file.
Loading a PIO Program
use micro/pio
use micro/gpio
let program = pio.program_file("drivers/ws2812.pio")?
let loaded = pio.block(0).load(program)?
program_file(...) takes a compile-time string literal path under src/. The compiler resolves and assembles the PIO file during the build.
Configuring a State Machine
let mut sm = pio.block(0)
.state_machine(0)
.program(loaded)
.set_pin(gpio.pin(25))
.set_pin_count(1)
.clock_div(256) // slow clock for visible blink
.init()?
sm.start()
Builder methods configure pin routing, clock divisor, FIFO behavior, and shift settings. init() is the fallible step. start() and stop() are infallible once initialized.
Inline PIO Programs
For small examples or tests, you can write PIO inline as a raw string:
use micro/pio
let program = pio.program("""
.program blink
set pins, 1 [31]
set pins, 0 [31]
""")?
The string is assembled at compile time, not runtime.
Writing to the TX FIFO
sm.put(0xFF_FF_FF_00u32)? // write word to TX FIFO
Reading from the RX FIFO:
let word = sm.get()? // blocking read from RX FIFO
Example: WS2812 LED Strip
use micro/gpio
use micro/pio
use std/time
fn main() -> never {
let prog = pio.program_file("drivers/ws2812.pio")?
let loaded = pio.block(0).load(prog)?
let mut sm = pio.block(0)
.state_machine(0)
.program(loaded)
.out_pin(gpio.pin(0))
.out_pin_count(1)
.clock_div(10)
.init()?
sm.start()
loop {
// GRB format for WS2812
sm.put(0x00_FF_00_00u32)? // green
time.sleep_ms(500)
sm.put(0xFF_00_00_00u32)? // red
time.sleep_ms(500)
}
}
Do and Don’t
// Do: use pio.program_file() for production code (compile-time path checking)
let prog = pio.program_file("drivers/ws2812.pio")?
// Do: use pio.program() with raw strings for quick experiments
let prog = pio.program("""...""")?
// Avoid: runtime-assembled PIO programs; not supported
// Avoid: assuming PIO block or state machine is available
// load() is fallible because instruction memory has limited capacity
// Avoid: tight-looping on sm.put() without backpressure
// The TX FIFO has limited depth; handle the Result from put()