I'd like to try and use a RPI Pico as NeoPixel (WS2812B serially programmable LED).
A WS2812B has 4 pins:

VDD, VSS, DIN, DOUT
Neopixels use a 24-bit pulse width modulated encoding. Each neopixel scrapes the first 24 bits off its input serial data stream and programs its LEDs based on this 24 bit RGB value. All remaining serial data is passed on to the next Pixel.
So, I would like to try and make a PIO statemachine(s) on the pico that would operate just like a neopixel.
First I need a test data stream. My Digilient Digital Discovery can output customs bitstreams. So I'll start with that.
First I generated a WS2812B bit stream in Matlab using a 1/3rd and 2/3rds pulse width encoding ( I have tried that before and know it works).
NeoPixels = [ 1, 2, bitshift(255,16) ];
d = dec2bin(NeoPixels,24)';
d = d(:) > '0';
n = length(d);
dat = [ ones(1,n); d'; zeros(1,n) ];
dat = uint8(dat(:));
fid = fopen('wdat.dat','w');
fwrite(fid,dat);
fclose(fid);
Now in Waveforms, I enable a custom pattern generator on pin27

Load in my binary data file

Set the clock rate to be 3 times the baudrate of 800 kbits/s = 2.4 MHz (Waveforms chooses the closest available frequency of 2.381 MHz)

Now when I click the trigger button,

I get a WS2812B data stream output on pin27.
Looping it over to input0 and running the logic analyzer


Looks good. I now have a test ws2812b test bitstream and a working logic analyzer (though I wish it had a decoder for WS2812B bit streams ).
Connecting a small neopixel strip to my DD, we see

The first pixel is displaying blue at an intensity of 1/255 and the third is display green at full intensity.
I soldered two probe wires onto the first neopixel to capture the DIN and DO pins

Getting closer...

Reset or End of Frame Detection
The WS2812 signaling format signals an end of frame or reset by holding the line idle for a minimum of 50 us

So after my PIO state machine scrapes of the leading 24 bits of an incoming bit stream it needs to go into a pass through mode until the line has sat idle for 50 us.
Right now I am waiting ~16 us to signal an end of frame condition (that could of course be changed). I added some extra zeros in my matlab code to demonstrate the end of frame signal.
After seeing the line idle for greater than 16 us, the PIO re-arms for another display frame and scrapes of the first 24 bits

When the line is idle for less than 16 us, the PIO remains in a passthrough mode:

Micropython Code
# PicoPixel 2023-11-27 rev0
# Turn a RPI Pico into a NeoPixel
# Scottiebabe // sstobbe@2n3904blog.com
# https://community.element14.com/products/raspberry-pi/f/forum/54012/rpi-pico-as-a-neopixel---tinkering
# Note Pico uses 3V3 logic levels
# level translation needed for a 5V pixel string
#
# DIN : GP0
# DO : GP1
import time
from machine import mem32, Pin, PWM
from rp2 import PIO
# PIO state machine for WS2812B functionality
@rp2.asm_pio(in_shiftdir=PIO.SHIFT_LEFT,fifo_join=PIO.JOIN_RX,out_init=([PIO.OUT_LOW]))
def ws2812b():
# Load OSR with 256
set(x,1)
mov(isr,x)
in_(null,8)
mov(osr,isr)
# Start in pass through mode waiting for an idle line
jmp('passth')
wrap_target()
# Wait for rising edge
label('sync')
jmp(pin,'startsampling') # breakout of loop while line high
jmp('sync')
label('startsampling')
set(x,23) # Number of bits to scrape/sample
jmp('sample')
label('sampling')
wait(1,pin,0)
label('sample')
set(y,25)
label('delaysample')
jmp(y_dec,'delaysample')
# Sample DIN
in_(pins,1)
# Wait for falling edge
wait(0,pin,0)
# keep sampling until x = 0
jmp(x_dec,'sampling')
# Push 24-bit GBR to Rx FIFO
push(noblock)
# Passthrough DIN to DO until an end of frame
# or reset condition is seen
label('passth')
mov(y,osr) # load y with OSR=1024
label('passcount')
mov(x,pins) # Read DIN
mov(pins,x) # Write DIN value to DO
jmp(pin,'passth') # if DIN is high reset y count back to 1024
jmp(y_dec,'passcount') # wait until y decrements to zero
wrap()
# Set system clock to 125 MHz
machine.freq(125_000_000)
time.sleep(0.1)
# Statemachine Sample Rate
Fs = 62_500_000
sm = rp2.StateMachine(0, ws2812b, freq=Fs, in_base=Pin(0),jmp_pin=Pin(0),out_base=Pin(1))
sm.active(1)
# Init PWM for onboard LED
led = PWM(Pin(25))
led.duty_u16(0)
while True:
if sm.rx_fifo() > 0:
GRBreq = sm.get()
G = (GRBreq >> 16) & 0xFF
R = (GRBreq >> 8) & 0xFF
B = (GRBreq ) & 0xFF
print('R: {}, G: {}, B: {}'.format(R,G,B))
led.duty_u16(B<<15)
Posted as is, no guarantees.
Also checkout the wonderful project by workshopshed for an alternative method using a WS2811 IC: Jumbo DIY LED -- Episode 533




