; ============================================================================ ; ws2812.pio — WS2812B NZR protocol driver for RP2040 PIO ; ============================================================================ ; Written from scratch for the color_switcher project. ; ; WS2812 protocol: each bit is a fixed-length pulse (~1.25 us at 800 kHz). ; "1" bit: long high (~875 ns) + short low (~375 ns) ; "0" bit: short high (~375 ns) + long low (~875 ns) ; ; Timing: 10 PIO cycles per bit at 8 MHz PIO clock (sysclk 125 MHz / 15.625). ; "1" bit: high 7 cycles, low 3 cycles ; "0" bit: high 3 cycles, low 7 cycles ; ; Data: 24-bit GRB, MSB first, left-justified in 32-bit FIFO word. ; Autopull at 24 bits, shift left. Side-set pin drives the data line. ; ============================================================================ .program ws2812 .side_set 1 .wrap_target bitloop: out x, 1 side 0 [2] ; shift 1 bit into X, drive LOW, 3 cycles total jmp !x do_zero side 1 [1] ; if bit=0 jump, drive HIGH, 2 cycles total do_one: jmp bitloop side 1 [4] ; bit=1: stay HIGH 5 more (total HIGH=7), loop do_zero: nop side 0 [4] ; bit=0: drive LOW 5 more (total LOW from next out=3+5) .wrap ; ============================================================================ ; C SDK helper — emitted into ws2812.pio.h by pico_generate_pio_header. ; Configures the state machine for WS2812 output on a single GPIO pin. ; ; Parameters: ; pio — PIO instance (pio0 or pio1) ; sm — state machine index (0-3) ; offset — instruction memory offset where the program was loaded ; pin — GPIO pin connected to the WS2812 data line ; freq — bit frequency in Hz (800000.0f for standard WS2812) ; ============================================================================ % c-sdk { #include "hardware/clocks.h" static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin, float freq) { /* Configure the GPIO pin for PIO output */ pio_gpio_init(pio, pin); pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); /* Get the default config (sets wrap points and sideset count from the .pio) */ pio_sm_config c = ws2812_program_get_default_config(offset); /* Side-set pin = the WS2812 data line */ sm_config_set_sideset_pins(&c, pin); /* Shift left, autopull at 24 bits (GRB = 3 bytes). * Data must be left-justified in the 32-bit FIFO word (bits [31:8]). */ sm_config_set_out_shift(&c, false, true, 24); /* Join both FIFOs into a single 8-entry TX FIFO for deeper buffering */ sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); /* Clock divider: 10 PIO cycles per bit, so PIO freq = bit_freq * 10. * clkdiv = sysclk / (freq * 10). E.g., 125 MHz / 8 MHz = 15.625 */ sm_config_set_clkdiv(&c, clock_get_hz(clk_sys) / (freq * 10.0f)); /* Apply the config and start the state machine */ pio_sm_init(pio, sm, offset, &c); pio_sm_set_enabled(pio, sm, true); } %}