A hands-on look at PIO on the Raspberry Pi Pico
The Raspberry Pi Pico took the microcontroller world by storm when it was released in early 2021. As the first MCU developed and released by the Raspberry Pi Foundation, the world immediately took note (especially given the lineup’s undeniable success Raspberry Pi single board computers).
As we pulled the covers off the Pico, we quickly learned that it wasn’t just another MCU. Specifically, it included support for a feature called Programmable I / O (PIO).
Before we dive into PIO on the Raspberry Pi Pico, let’s take a giant step back.
What is the Raspberry Pi Pico?
Remember when Apple made a splash with the introduction of Apple Silicon? In what I suppose to be an ironic move, the Raspberry Pi Foundation linked Apple to the first version of its own chip developed in-house under the guise of “Raspberry Silicon”. This chip is the RP2040, and the Pico is the official development kit for the RP2040 from the Raspberry Pi Foundation:
Priced at a budget of $ 4, the Pico is a great microcontroller for beginners and experts alike. Officially known and distributed as the RP2040, the Pico includes hardware and firmware features such as:
- A dual-core Arm Cortex-M0 + processor;
- 264 KB of on-chip RAM;
- Built-in support for MicroPython;
- A wide range of I / O options, including this thing called Programmable I / O (PIO).
Let’s take a high-level look at what PIO is when you plan to use it, and finally, how you would use it.
What is Programmable I / O (PIO)?
All MCUs and SBCs include support for communication protocols such as I2C and SPI. The RP2040 is no different, with 2 UART controllers, 2 x SPI and 2 x I2C. This allows the Pico to easily communicate with a wide variety of standard peripherals.
However, many of us have come across scenarios where we are building a solution around existing technology or even using multiple SPI devices with a single MCU. This is where the RP2040’s PIO support comes in.
Using PIO, you can build your interfaces from scratch. In theory, you could even create entirely new interfaces that haven’t even been imagined yet!
On a slightly more technical level, an instance of a PIO is comparable to a small processor that runs code separately from the main Cortex-M0 +. So, what was previously accomplished by bit-banging (and consuming CPU cycles) protocols, PIO does independently of the CPU.
Here is the diagram for a single PIO block:
On the RP2040, each PIO instance includes four state machines which can each execute instructions stored in the shared instruction memory. This memory can contain 32 instructions, and each state machine can use any of said instructions. Each state machine can also control one of Pico’s GPIO pins.
Programming a PIO instance is easier than expected. Since it relies on special assembly instructions, you can write code in any editor (instead of a proprietary “Raspberry Pi Pico” IDE, for example).
Speaking of writing code to program a PIO instance, the PIO language consists of nine and only nine instructions:
- to push()
- irq ()
- jmp ()
At first glance, this may not seem like much, but they offer a wide variety of features.
Let’s move on to a more practical question: when would I use PIO? That’s a valid question, because in most designer projects, built-in support for I2C, UART, and SPI will be plentiful. However, there are times when PIO can be of critical benefit.
When would I use PIO?
Have you ever come across a scenario where you need more UART connections available on a card? What about direct output to DVI video? Or maybe you are trying to communicate with a legacy piece of hardware via serial without any supporting library available.
These are all valid scenarios for diving deeper into PIO on the Raspberry Pi Pico.
While not related to the Raspberry Pi Pico, a Hackster project accomplishes the above without using PIO (but maybe the author wants it). Remotely control the Nintendo ROB robot via cell phone.
How to use PIO on the Pico?
If you’re like me, you can read blog posts all day and not learn much. And yes, I realize that this comment is very meta.
I learn best by doing, and I start “doing” by copying and pasting code and going line by line.
When working with Pico and MicroPython I have found an endless amount of use of the Pico MicroPython examples repository on GitHub.
Concrete example, in the
pio directory, we can find a relatively simple PIO example which:
- Binds a single GPIO to the
- Use delays to flash an LED;
- Instantiates a state machine to execute instructions and flash the built-in LED.
The full example is available here (slightly modified to remove comments):
import time import rp2 from machine import Pin @rp2.asm_pio(set_init=rp2.PIO.OUT_LOW) def blink(): wrap_target() set(pins, 1)  nop()  nop()  nop()  nop()  set(pins, 0)  nop()  nop()  nop()  nop()  wrap() sm = rp2.StateMachine(0, blink, freq=2000, set_base=Pin(25)) sm.active(1) time.sleep(3) sm.active(0)
What’s going on in this sample code?
The PIO program is located in the
blink() function (and note the
wrap() methods create a loop. The
set() function accepts the target (in this case
1 indicates the setting of the high spindle (and
0 bottom later).
nop() functions create an artificial delay of about 20 cycles each.
So, we set the GPIO pin high (illuminating the LED), pause for a few cycles, and then set the pin low (turning off the LED). This causes rapid blinking visible to the human eye:
PIO on the Raspberry Pi Pico can be beneficial when connecting to non-standard devices. That’s not to say it’s for everyone, but if you have to connect through unknown or unavailable communication protocols, you’ll quickly fall in love!
Good hack with the Raspberry Pi Pico!