PIOLib: A userspace library for PIO control
Dip your toes into the world of PIO on Raspberry Pi 5 using PIOLib
The launch of Raspberry Pi 5 represented a significant change from previous models. Building chips that run faster and use less power, while continuing to support 3.3V I/O, presents real, exciting challenges. Our solution was to split the main SoC (System on Chip) in two — the compute half, and the I/O half — and put a fast interconnect (4-lane PCIe Gen 3) between them. The SoC on Raspberry Pi 5 is the Broadcom BCM2712, and the I/O processor (which used to be known in the PC world as the ‘southbridge’) is Raspberry Pi RP1.

Along with all the usual peripherals — USB, I2C, SPI, DMA, and UARTs — RP1 included something a bit more interesting. One of RP2040’s distinguishing features was a pair of PIO blocks, deceptively simple bits of Programmable I/O capable of generating and receiving patterns on a number of GPIOs. With sufficient cunning, users have been able to drive NeoPixel LEDs and HDMI displays, read from OneWire devices, and even connect to an Ethernet network.
RP1 is blessed with a single PIO block — almost identical to the two that RP2040 has — as well as four state machines and a 32-entry instruction memory. However, apart from a few hackers out there, it has so far lain dormant; it would be great to make this resource available to users for their own projects, but there’s a catch.
Need for speed
The connection between RP1’s on-board ARM M3 microcontrollers and the PIO hardware was made as fast as possible, but at the cost of making the PIO registers inaccessible over PCIe; the only exceptions are the state machine FIFOs — the input and output data pipes — that can be reached by DMA (direct memory access). This makes it impossible to control PIO directly from the host processors, so an alternative is required. One option would be to allow the uploading of code to run on the M3 cores, but there are a number of technical problems with that approach:
1. We need to “link” the uploaded code with what is already present in the firmware — think of it as knitting together squares to make a quilt (or a cardigan for Harry Styles). For that to work, the firmware needs a list of the names and addresses of everything the uploaded code might want to access, something that the current firmware doesn’t have.
2. Third-party code running on M3 cores presents a security risk — not in the sense that it might steal your data (although that might be possible…), but that by accident or design it could disrupt the operation of your Raspberry Pi 5.
3. Once the M3s have been opened up in that way, we can’t take it away, and that’s not a step we’re prepared to take.
Not like that, like this
For these reasons, we took a different path.

The latest RP1 firmware implements a mailbox interface: a simple mechanism for sending messages between two parties. The kernel has corresponding mailbox and firmware drivers, and an rp1-pio driver that presents an ioctl() interface to user space. The end result of adding all this software is the ability to write programs using the PIO SDK that can run in user space or in kernel drivers.
Latency trade-off
Most of the PIOLib functions cause a message to be sent to the RP1 firmware, which performs the operation — possibly just a single I/O access — and replies. Although this makes it simple to run PIO programs on Raspberry Pi 5 (and the rest of the Raspberry Pi family), it does come at a cost. All that extra software adds latency; most PIOLib operations take at least 10 microseconds. For PIO software that just creates a state machine and then reads or writes data, this is no problem — the WS2812 LED and PWM code are good examples of this. But anything that requires close coupling between the state machine and driver software is likely to have difficulties.
The first official use of PIOLib is the new pwm-pio kernel driver. It presents a standard Linux PWM interface via sysfs, and creates a very stable PWM signal on any GPIO on the 40-pin header (GPIOs 0 to 27). You can configure up to four of these PWM interfaces on Raspberry Pi 5; you are limited by the number of state machines. Like many peripherals, you create one with a Device Tree overlay:
dtoverlay=pwm-pio,gpio=7
One feature absent from this first release is interrupt support. RP1 provides two PIO interrupts, which can be triggered by the PIO instruction IRQ (interrupt request), and these could be used to trigger actions on the SoC.
Over time, we may discover that there are some common usage patterns — groups of the existing PIOLib functions that often appear together. Adding those groups to the firmware as single, higher-level operations may allow more complex PIO programs to run. These and other extensions are being considered.
Let me play!
If you’d like to try PIOLib, you will need:
- The library (and examples)
- The latest kernel (
sudo apt update;sudo apt upgrade) - The latest EEPROM (see the ‘Advanced Options’ section of
raspi-config)
I’ll leave you with a video of some flashing lights — two strings of WS2812 LEDs being driven from a Raspberry Pi 5. It’s beginning to look a bit festive!
31 comments
Mark Tomlin
I would really, really, really love to see Rust supported as a first class citizen in the Raspberry Pi foundation. I’m glad the PIOs are going to be more open for everyone to use. It would be nice to have the C PIOLib have a rust counterpart from Raspberry Pi on crates.io.
Raspberry Pi Staff PhilE — post author
I would have a go at writing one, but as My First Rust Project, you enthusiasts would want to claw your eyes out after reading it.
HankB
Do I read that the IO functions are accessed via sysfs and ioctl() calls? In that case all languages are on the same footing WRT access. Particularly with sysfs, it is onlyt necessary to have file IO and that’s pretty universal for languages usable on Linux.
Raspberry Pi Staff PhilE — post author
Yes for
ioctl(), but no to sysfs – I’m not sure how that would work, except slowly, and sysfs APIs are so last decade.Mark Tomlin
@PhilE we, the rust community at large, would welcome any contribution to the ecosystem. It doesn’t have to be perfectly idiomatic. A contribution by the Raspberry Pi Foundation, supporting their own hardware in the Rust ecosystem would be a huge step forward and would be universally welcomed. I appreciate the concern, but we don’t bite. Some have been known to nibble. This seems like a reasonable place to start. I’m willing to help in any way that I can.
Raspberry Pi Staff PhilE — post author
I appreciate the vote of confidence, but realistically it would be much better for someone more skilled in the art to write a Rust veneer over the published ioctl interface.
Anders
I don’t think Foundation are too much involved in the development of these libraries? But as part of their educational function, Rust could fulfil some role as it uses some different ways such as borrowing, to support memory safety.
Raspberry Pi Staff PhilE — post author
You are correct about the lack of involvement of the Foundation, but I think programming in Rust is too demanding for their target audience.
CooliPi
“I don’t think Foundation are too much involved in the development of these libraries?” – unfortunately, The biggest benefit of RPIs is the integrated I/O. But we are struggling to port programs from even one RPI generation to the next, because the underlying libraries are not developed for the newer models.RPF should take care of library development _under common, unified interface_ for all RPI generations. Much in the same way they support OpenGL. It’s frustrating to have two unsupported Python libraries, different ones for Rust, some of them requiring suid root executables, others access to /dev/mem* , on differenet RPI generations differently. What a mess.
For this reason, GPIO support was omitted from Home Assistant official plugins – it was unreliable (so says it’s author).
I’m tempted to say, that the 2/3 of RPF customers (industrial ones) could pay for this universal, software interface they would be using in the first place. Disgusted, worn out private developers doing homeworks for RPF (that they haven’t finished) won’t cut it.
Raspberry Pi Staff Gordon Hollingworth
That’s surprising, I’ll have to email the developers. The biggest problem with GPIO is that there hasn’t been a good linux kernel interface to control them until libgpiod and that took a very long time to happen. Even then using libgpiod is not the easiest API and therefore scares away users. But, it is the “recommended way to directly control GPIO” for simple usage. For more complex usage, a linux driver is often the right solution and for timing sensitive applications it’s PIO (on Pi 5 at least!)
Milliways
This is regularly claimed by your engineers. The libgpiod in Raspberry Pi OS is 4 years old and superceded. There are no examples using it. There is no man file.
Anders
The point here was even after many years, people are still pointing the finger at Raspberry Pi Foundation when the Foundation are not involved with the development of these products or software.
PicoOwner
It looks like the example *.pio source files are missing. Maybe also add a link to pioasm:
https://github.com/raspberrypi/pico-sdk-tools
Raspberry Pi Staff PhilE — post author
I was deliberately keeping the dependencies to a minimum, but I agree that a pointer to the Pico SDK (https://github.com/raspberrypi/pico-sdk), examples (https://github.com/raspberrypi/pico-examples) and tools (https://github.com/raspberrypi/pico-sdk-tools) would be helpful.
Mike
This is very cool, and will eventually be lifeline for designs that are constrained by the CM5’s (and CM4’s) limited GPIO pins and features. We often have to make compromises due to lack GPIO pins or alternate function conflicts. The PWM example will help us add features like a buzzer and backlight dimmer to our designs without having to reserve certain pins for those features. Please continue to add more examples (I2C, UART, SPI, etc.). The PIO I2C example was crucial to our last Pico solution.
Raspberry Pi Staff Gordon Hollingworth
I2C is a lot more complex, it requires that the processor in RP1 is involved within the process. But we don’t really want to provide an API for that because in future devices we might change it…
Instead we’ve been thinking and scheming other ways of doing it which are even more interesting…
CooliPi
“But we don’t really want to provide an API for that because in future devices we might change it…” – and that’s exactly what many people complain about. Hardware features without software support are not usable.
That’s the difference between RPIs and other SBCs. RPI ecosystem is mostly documented, with sources, while the other SBCs lack documentation, examples, consultation services (aka forums), etc. But regarding GPIO, we _need_ some common interface, which is readily available in any programming or scripting language. And not dependent on a model of RPI you have. OpenGL has isolated us from meddling with GPU registers, under an API. When it has shown it’s shortcomings, Vulkan was developed by AMD and given for free (as an API). But this hasn’t happened with GPIO, which is way simpler. The library can report HW abilities to a program and allow to map events to different GPIO pins, or modulators (PWM, delta-sigma if ever, digital I/O, serial, 1wire, I2C, SPI…)
If there aren’t free, standardized interfaces, proprietary ones arise. That was the case with x86/ARM/Sparc/MIPS/RISC ISA flavours. So the worst ones – x86 and its software sibling MS Windows with its APIs – are abusing the world even today.
We don’t know what’s coming from RPF, nor do we have time to create these standards.
AndrewS
“But regarding GPIO, we _need_ some common interface, which is readily available in any programming or scripting language. And not dependent on a model of RPI you have.”
See Gordon’s previous comment about libgpiod – and read more about it at https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about/
Nate
Just a few hours ago, my coworker and I were wondering if anyone’s implemented a quadrature encoder capture peripheral in the RP1 yet, but it looks like that’s a solid “maybe someday, you have the hardware but we’re not letting you use it yet”.
Ouch.
Raspberry Pi Staff PhilE — post author
Really? You could run the pico-example today. In fact, I just did: https://github.com/raspberrypi/utils/commit/1b9cf7c2224641ff9bb08c9beaa1cdc30aad2a00
This version takes an optional command line parameter to choose the base pin number – the default is still 10. It also goes to the trouble of switching the pins into PIO mode – the original didn’t.
stan423321
I have a vague memory of the information being mentioned somewhere when RP1 was introduced, but I cannot see where the docs for differences from RP2040’s PIOs are. Am I just missing them?
AndrewS
You might be thinking of https://www.raspberrypi.com/news/rp1-the-silicon-controlling-raspberry-pi-5-i-o-designed-here-at-raspberry-pi/#comment-1594782 and https://www.raspberrypi.com/news/introducing-raspberry-pi-5/#comment-1594112
tcmichals
Did I understand correctly that the PIO (RX/TX) FIFOs could be connected to the Host DMA? I’m looking for a way to use dual/quad SPI slave mode @ 25 Mhz as a high speed interface to get data into the Host.
If this is correct, any pointers on how to get the DMA to interface to FIFO and access via user mode driver?
Raspberry Pi Staff PhilE — post author
That’s what the pio_sm_config_xfer and pio_sm_xfer_data functions do. Take a look at the piotest example: https://github.com/raspberrypi/utils/blob/master/piolib/examples/piotest.c#L52
Andrew Gordon
PIOlib really needs at least a little bit of documentation – even comments in the .h file would do, but currently there’s nothing at all. Sure, the vast majority of the entry points can just say “same as Pico SDK”, but the newly added ones need their purpose and characteristics explained. What’s the function of pio_sm_config_xfer() when pio_sm_config_xfer() seems to have parameters to fully specify a transfer? Which ones are blocking/non-blocking? Are transfer sizes required to be fixed in advance or can I get however many bytes happen to have arrived?
Mykola
By looking at the diagram, DMA means PCIe bus mastering to mailbox on the other side initiated by “firmware”, and not bulk-copy-engine sort of transfers.
Chris Stmm
Thank you for sharing this great article as well as the GitHub repo! It’s a great way to start and I was looking for that for a while. As already mentioned, I would also appreciate a more detailed description on how to configure the DMA controllers that transfer data to the PIO. For instance, is there an option to repeat the transfer? Like the “set_ring” option for the rp2040/rp2350. For me the “pio_sm_xfer_data” command seems to be blocking, as it takes always the time required to shift out all the rgb bits for the test pio example. But it would be pretty neat if that could be changed somehow to be async. Looking forward to your response and thanks in advance!
Marian Vittek
I join this request. It would be nice to get some kind of asynchronous DMA transfer. Just splitting ‘pio_sm_xfer_data’ into two functions would be appreciated. There could be one function to start the transfer (say ‘pio_sm_xfer_data_start’) and one to check if the transfer is done (say ‘pio_sm_xfer_data_done’). This would allow to read/write from/to all 4 state machines simultaneously.
Steve Bollinger
The driver does not return the pin to its previous function when closed. I can’t find any way to do it other than rebooting. I could use pinctrl but that’s “for development only”.
There should be a call to save the pin muxing at the start and one to return it to that function after completion. Or have it done automatically.
libgpiod cannot remux pins so I can’t do it that way. I could make it a GPO, but actually I want it to return to being a SPI pin. See, I used pin 10 (and 20) for my outputs so that I can send SPI signals using the SPI block if I want or send WS2812 signals using the RP1 if I want. This way I can run APA102 or WS2812 lights from the same output board.
Some documentation on how I should select the size of the buffers and the number of buffers for config_xfer would be nice. What are they even used for?
Bob
Do you have any more info on how to use the pwm-pio driver? You said “It presents a standard Linux PWM interface via sysfs” but when I enable it I don’t see anything new in /sys/class/pwm… not sure where else to look. Thanks!
Charles
I am using the firmware version 2025-06-09 and I cannot see the pio0 in the device tree. Any advice on what I may be doing wrong? I really love the PIO blocks in the Pico series. To have one on a R Pi 5 would open a lot of opportunities. Thanks.
Comments are closed