Table of Contents
Today we are going to make use of the Raspberry Pi Pico microcontrollers PIO feature to make some colorful displays with a NeoPixel light ring.
Pico PIO Feature
When Raspberry Pi announced their new microcontroller, the Raspberry Pi Pico, they also announced a new MCU chip, the RP2040. This chip was designed specifically for Raspberry Pi, and it is now in use in a number of microcontrollers made by other manufacturers like Adafruit and Arduino.
The RP2040 is a pretty capable, and inexpensive chip – individually they go for about a dollar each. But aside from features like a 12-bit analog to digital converter and a bunch of capable inputs, outputs, and GPIO pins, it has another feature, called “PIO”.
PIO, or Programmable Input/Output, allows you to create additional hardware interfaces or even develop new types of interfaces. It is useful when you need to add another UART or I2C connection, or to interface with a video monitor, for example.
Because PIO manipulates bits on an individual basis, it can be used when a precisely timed stream of data is required. This is why it is a great choice for driving NeoPixel, or addressable LED, displays.
In addition to the main processor, a dual-core Cortex M0, the RP2040 has two PIO controllers.
Each one of these controllers has four cores, called StateMachines. These StateMachines are like very simple microcontrollers that only have nine instructions, all of them for manipulating bits of data.
Data flows in and out of each StateMachine using FIFO (First In, First Out) pipes, and each StateMachine has two of them. The data inputs and outputs are connected to the main Cortex M0 process through buffers.
When programming the RP2040, we can use a language like MicroPython or C++ to program the main Cortex M0 processor. We then use assembly language to program the StateMachines in the PIO.
By doing this, we can relive the main processor of tedious “bit-banging” tasks, freeing it up to perform more important or complex functions.
Today we will use an example that was provided by Raspberry Pi to use the PIO feature to drive a ring of NeoPixels.
Building the Display
Constructing the display is very simple, as it only requires three connections. We will power the display using the Pico, if you decide to modify the design and use a bigger NeoPixel ring or a string of NeoPixels then you should use a separate 5-volt supply for the display, as we are just about pushing the Raspberry Pi Pico to its limit with 16 addressable LEDs.
We only need two parts, assuming you aren’t using a bigger display:
- A Raspberry Pi Pico microcontroller.
- A 16 element NeoPixel Ring. I used the Adafruit 1463.
You’ll need to solder some wires onto the NeoPixel ring, you can solder the other ends directly to the Pico or you can do what I did and solder pins onto the Pico and use a solderless breadboard.
NeoPixel Display Hookup
As I said, there are only three connections, power, ground, and the signal into the NeoPixel ring. Here is the hookup diagram:
Coding the Display
We will be using some MicroPython code that Raspberry Pi provides on their GitHub page to drive our NeoPixels using PIO.
In order to use MicroPython on the Pico, we will need to install it.
We will also need a computer that is running a suitable editor, an ideal one for the task is the Thonny IDE. And as Thonny is a standard feature on a Raspberry Pi, I decided to use a Raspberry Pi 4 as my host machine.
First, let’s install MicroPython.
Hold down the BOOTSEL switch on the Pico, it’s easy to find as it’s the only switch on the Pico! Now plug in the MicroUSB cable to which the other end has already been connected to the host computer, in my case a Raspberry Pi 4.
Now release the BOOTSEL switch. As soon as you d another drive will appear on the ghost computer.
In the drive, you will find two files, INDEX.HTM and INFO_UF2.txt. The HTM file will open a web browser that leads to a Raspberry Pi page.
On that page, navigate to the latest version of MicroPython for the Pico and download it. Now drag the file you downloaded into the Pico folder.
Once it finishes copying, the folder will close and MicroPython will be installed on your Pico.
We can get the code we need from the Raspberry Pi GitHub page, it is also repeated here:
# Example using PIO to drive a set of WS2812 LEDs.
import array, time
from machine import Pin
# Configure the number of WS2812 LEDs.
NUM_LEDS = 16
PIN_NUM = 6
brightness = 0.2
@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=True, pull_thresh=24)
T1 = 2
T2 = 5
T3 = 3
out(x, 1) .side(0) [T3 - 1]
jmp(not_x, "do_zero") .side(1) [T1 - 1]
jmp("bitloop") .side(1) [T2 - 1]
nop() .side(0) [T2 - 1]
# Create the StateMachine with the ws2812 program, outputting on pin
sm = rp2.StateMachine(0, ws2812, freq=8_000_000, sideset_base=Pin(PIN_NUM))
# Start the StateMachine, it will wait for data on its FIFO.
# Display a pattern on the LEDs via an array of LED RGB values.
ar = array.array("I", [0 for _ in range(NUM_LEDS)])
dimmer_ar = array.array("I", [0 for _ in range(NUM_LEDS)])
for i,c in enumerate(ar):
r = int(((c >> 8) & 0xFF) * brightness)
g = int(((c >> 16) & 0xFF) * brightness)
b = int((c & 0xFF) * brightness)
dimmer_ar[i] = (g<<16) + (r<<8) + b
def pixels_set(i, color):
ar[i] = (color<<16) + (color<<8) + color
for i in range(len(ar)):
def color_chase(color, wait):
for i in range(NUM_LEDS):
# Input a value 0 to 255 to get a color value.
# The colours are a transition r - g - b - back to r.
if pos < 0 or pos > 255:
return (0, 0, 0)
if pos < 85:
return (255 - pos * 3, pos * 3, 0)
if pos < 170:
pos -= 85
return (0, 255 - pos * 3, pos * 3)
pos -= 170
return (pos * 3, 0, 255 - pos * 3)
for j in range(255):
for i in range(NUM_LEDS):
rc_index = (i * 256 // NUM_LEDS) + j
pixels_set(i, wheel(rc_index & 255))
BLACK = (0, 0, 0)
RED = (255, 0, 0)
YELLOW = (255, 150, 0)
GREEN = (0, 255, 0)
CYAN = (0, 255, 255)
BLUE = (0, 0, 255)
PURPLE = (180, 0, 255)
WHITE = (255, 255, 255)
COLORS = (BLACK, RED, YELLOW, GREEN, CYAN, BLUE, PURPLE, WHITE)
for color in COLORS:
for color in COLORS:
Lines 12 through 36 are the assembly language code used to drive the NeoPixel displays, which are referred to by the generic name of WS2812 LEDs.
Lines 83 through 91 define an array named COLORS, which we will cycle through to create our rainbow display.
Lines 93 through 104 are the displays we will send to the NeoPixels.
Load the code into Thonny and make sure you are connected to the Raspberry Pi Pico, you can see the current connection displayed in the lower right side of the editor. Then click the Run button and watch your NeoPixels dance!
As the code is currently written it will cycle once then end. But you can easily modify it to run forever.
On line 92 insert the following statement:
Then highlight all the code under that statement and press the Tab key to move it over. Save it, and press the Run button.
You should now see the display work its way through the three sequences continuously.
And if you want to make the Pico run this code automatically when it is booted up just change the name of the code to main.py.
PIO is an advanced feature available in the RP2040, and learning to use it will really expand your coding ability and will allow you to interface with a wealth of devices.
We have only scratched the surface of understanding PIO here, I urge you to explore it further. You can do much more than just flash some pretty lights, but then again, flashing NeoPixels can be a lot of fun!
Here are some components that you might need to complete the experiments in this article. Please note that some of these links may be affiliate links, and the DroneBot Workshop may receive a commission on your purchases. This does not increase the cost to you and is a method of supporting this ad-free website.
This article is part of a series of 10 Projects for the Raspberry Pi. Check out the other articles in this series.
Raspberry Pi – 10 Years & 10 Projects
Music Everywhere with Balena Sound
Extend your USB with VirtualHere