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