Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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()