# RP2xxxPIO: writing PIO programs inline in Swift

**URL:** <https://forums.swift.org/t/rp2xxxpio-writing-pio-programs-inline-in-swift/90125>\
**Category:** Community Showcase\
**Tags:** raspberrypi, embedded, rp-pico, rp2040\
**Created:** [October 17, 2026, 5:23am UTC](https://forums.swift.org/t/rp2xxxpio-writing-pio-programs-inline-in-swift/90125 "2026-10-17T05:23:36Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![Gonzalo\_Larralde](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/gonzalo_larralde/32/1005_2.png) [@Gonzalo\_Larralde](https://forums.swift.org/u/Gonzalo_Larralde)\
**Post date:** [October 17, 2026, 5:23am UTC](https://forums.swift.org/t/rp2xxxpio-writing-pio-programs-inline-in-swift/90125/1 "2026-10-17T05:23:36Z")

</div>

Hi everyone!

I’d like to share [RP2xxxPIO](https://github.com/gonzalolarralde/swift-rp2xxx-pio), a Swift DSL for writing PIO programs directly inside your Embedded Swift firmware.

PIO is programmable I/O hardware on Raspberry Pi’s RP2040 and RP2350 microcontrollers. Think of it as small processors dedicated to reading and writing pins with precise timing, independently of the main CPU. It’s useful for things like custom serial interfaces, LED drivers, and digital audio.

Instead of writing a separate assembly file, you can write this:

```swift
import RP2xxxPIO

struct Pulse: PIOProgram {
    var clockDivider: Double = 1
    var pins: PIOPinMapping = .init()
    let setCount: Int? = 1

    let code = #constPIO {
        @PIOLabel var loop

        Label(loop)
        Set(.pins, 1).delay(7)
        Set(.pins, 0).delay(7)
        Jmp(to: loop)
    }
}

```

This describes a program that repeatedly drives an output high and low. The instructions are compiled at build time, while the pin mapping and clock divider can vary between instances:

```swift
let pulse = Pulse(
    clockDivider: 100,
    pins: .init(set: 2)
)

```

The optional CPicoSDK integration provides the program descriptor and state-machine configuration. Your application still explicitly loads the instructions, initializes the GPIOs, and starts the hardware. Declaring a Swift value doesn’t quietly claim resources or touch registers.

## Labels that feel like Swift

Labels are one part of the API I’m particularly happy with. `loop` is an ordinary Swift identifier—not an assembly symbol stored in a string, or an instruction address you have to maintain.

The result builder captures the sequence of instructions and label markers. A label marks a position without emitting an instruction; the assembler resolves jumps to the resulting program-relative offsets. Moving instructions around or jumping forward doesn’t require manually updating addresses.

A useful distinction fell out of that design: a label’s identity isn’t its address. The offset belongs to a particular program. Declare a label outside the builder, and you can also use it from the surrounding Swift code to find an entry point.

## Constant when possible, dynamic when needed

There are two compilation macros:

- `try #pio` compiles what it can at build time and supports runtime customization.
- `#constPIO` requires the entire program and its encoding options to be known at build time.

For fully constant programs, both generate the same PIO instruction words without runtime assembly. `#constPIO` is the reassurance: accidentally introduce something that requires runtime assembly, and the build fails. `#pio` keeps its throwing interface even when the program is constant.

Runtime customization looks like ordinary Swift:

```swift
func makeProgram(value: Int) throws(PIOAssemblyError) -> PIOProgramData {
    try #pio {
        Set(.x, value)
        Nop()
        Jmp(to: 0)
    }
}

```

Eligible constant instructions are precompiled; runtime-dependent instructions and final layout are handled on the device. Invalid dynamic operands throw an error. Definitely invalid constant programs produce compiler diagnostics.

It’s closer to partial evaluation than a JIT: the interesting question is how much work can disappear before the firmware reaches the device.

 ![Screenshot 2026-10-16 at 1.55.55 AM](https://global.discourse-cdn.com/swift/original/3X/7/e/7e7f3450d0418b6e9e62f9c21bec19eb9868f4a2.jpeg)

## Does moving that work help?

For a matched eight-instruction program, I measured CPU instructions executed from construction until the program was ready for the SDK. The warmed, uncaptured `#constPIO` case took 59 instructions, compared with 14,460 for the Swift runtime assembler. With one dynamic operand, `#pio` used about 10% fewer instructions than runtime assembly; fully dynamic programs matched it.

Those are emulator measurements, not hardware cycle counts. The harness uses software floating-point helpers and deterministic entropy; first-use initialization and captured-label bookkeeping are measured separately. And this only changes construction costs—identical PIO words execute identically once loaded.

This started as a result-builder experiment and gradually became something I could use in firmware. There are now reference-assembler comparisons, SDK configuration checks for both chips, and a complete Pico 2 audio example.

It’s still experimental, and the API may change. The project is MIT-licensed, and the README starts with usage rather than internals. I’d love feedback from people working with Embedded Swift—or exploring how far ordinary Swift syntax can take a small, hardware-specific language.
