Neopixel LED panely nebo pásky, či samostatné LED jsou soustavy mikročipů WS2812B (nebo WS2811) s 3 LED diodami zabalené do jednoho pouzdra. Pomocí poměrně jednoduchého protokolu se dá nastavit libovolná barva z 16777216 možných na libovolném čipu. Čipy jsou zapojeny jeden za druhým.

Schéma zapojení

kaskada ws2812b

Matice 16x16 WS2812B

neopixel matice16x16

Jedna LED WS2811

led 8mm rgb ws2811 adresovane 10ks

Protokol komunikace s čipem WS2812B

Nastavení barvy u čipu se provádí tak, že se do DIN (data input), pošle sekvence 24 bitů, kde každých 8 bitů představuje jednu barevnou složku GRB (green, red, blue). Hodnoty R,G,B mohou být v rozsahu 0 až 255.

Sestavení 24bitů

sestaveni 24bitu dat

Jednotlivé bity časově vypadají takto:

jednotlive bity

Tabulka 1. Časový průběh impulsů
bit vysoká úrověň nízká úrověň celkem

0

\(0.35 \mu s\) (T0H)

\(0.9 \mu s\) (T0L)

\(1.25 \mu s \pm 150 ns\)

1

\(0.9 \mu s\) (T1H)

\(0.35 \mu s\) (T1L)

\(1.25 \mu s \pm 150 ns\)

reset

\(\gt 50 \mu s\)

Zjednodušeně řečeno, každý bit začíná vysokou úrovní a končí nízkou a trvá celkem \(1.25 \mu s \pm 150 ns\).
Pokud je vysoká úroveň kratší než nízká, je to bit 0.
Pokud je vysoká úroveň delší než nízká, je to bit 1.
Nízká úroveň delší než \(50 \mu s\) znamená konec přenosu (reset).

Frekvenci přenosu jednotlivých bitů spočítáme takto: \(f = \frac{1}{1.25 \mu s} = \frac{1}{0.00000125} = 800000 Hz = 800 kHz\). Tuto hodnotu si zapamatujeme, budeme ji potřebovat při nastavení děliče frekvence PIO.

Příklad oranžové barvy

Budeme chtít poslat do LED oražovou barvu o barevné hodnotě RGB = 0xffa500. Musíme poslat GRB = 0xa5ff00, což bude bitově 0b10100101_1111111_00000000. Impulsy budou vypadat takto:

Impulsy pro oranžovou barvu 0xffa500

oranzova impulsy

Kaskáda LED

Čipy jsou zapojeny za sebou spojením DOUT a DIN. Pošleme data prvnímu čipu a bez resetu pošleme další data. Data z prvního čipu se přemístí do druhého čipu. Pošleme bez resetu další data: data druhého čipu se přemístí do třetího čipu, data prvního čipu se přemístí do druhého. A tak budeme pokračovat až do konce naší kaskády.

Pokud jsou čipy WD2812B zapojeny do pásku, poslední LEDce posíláme data nejdříve a první LEDce nejpozději.

U čtverové matice je to trochu složitější, čipy jsou propojeny takto:

Tabulka 2. Zapojení hada, který dělá matici; šipky naznačují propojení DIN a DOUT
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

15 DOUT

\(U_{256}\)

\(\gets U_{255}\)

\(\gets U_{254}\)

\(\gets U_{253}\)

\(\gets U_{252}\)

\(\gets U_{251}\)

\(\gets U_{250}\)

\(\gets U_{249}\)

\(\gets U_{248}\)

\(\gets U_{247}\)

\(\gets U_{246}\)

\(\gets U_{245}\)

\(\gets U_{244}\)

\(\gets U_{243}\)

\(\gets U_{242}\)

\(\gets U_{241}\)

14

\(U_{225} \to\)

\(U_{226} \to\)

\(U_{227} \to\)

\(U_{228} \to\)

\(U_{229} \to\)

\(U_{230} \to\)

\(U_{231} \to\)

\(U_{232} \to\)

\(U_{233} \to\)

\(U_{234} \to\)

\(U_{235} \to\)

\(U_{236} \to\)

\(U_{237} \to\)

\(U_{238} \to\)

\(U_{239} \to\)

\(U_{240} \uparrow\)

13

\(\uparrow U_{224}\)

\(\gets U_{223}\)

\(\gets U_{222}\)

\(\gets U_{221}\)

\(\gets U_{220}\)

\(\gets U_{219}\)

\(\gets U_{218}\)

\(\gets U_{217}\)

\(\gets U_{216}\)

\(\gets U_{215}\)

\(\gets U_{214}\)

\(\gets U_{213}\)

\(\gets U_{212}\)

\(\gets U_{211}\)

\(\gets U_{210}\)

\(\gets U_{209}\)

12

\(U_{193} \to\)

\(U_{194} \to\)

\(U_{195} \to\)

\(U_{196} \to\)

\(U_{197} \to\)

\(U_{198} \to\)

\(U_{199} \to\)

\(U_{200} \to\)

\(U_{201} \to\)

\(U_{202} \to\)

\(U_{203} \to\)

\(U_{204} \to\)

\(U_{205} \to\)

\(U_{206} \to\)

\(U_{207} \to\)

\(U_{208} \uparrow\)

11

\(\uparrow U_{192}\)

\(\gets U_{191}\)

\(\gets U_{190}\)

\(\gets U_{189}\)

\(\gets U_{188}\)

\(\gets U_{187}\)

\(\gets U_{186}\)

\(\gets U_{185}\)

\(\gets U_{184}\)

\(\gets U_{183}\)

\(\gets U_{182}\)

\(\gets U_{181}\)

\(\gets U_{180}\)

\(\gets U_{179}\)

\(\gets U_{178}\)

\(\gets U_{177}\)

10

\(U_{161} \to\)

\(U_{162} \to\)

\(U_{163} \to\)

\(U_{164} \to\)

\(U_{165} \to\)

\(U_{166} \to\)

\(U_{167} \to\)

\(U_{168} \to\)

\(U_{169} \to\)

\(U_{170} \to\)

\(U_{171} \to\)

\(U_{172} \to\)

\(U_{173} \to\)

\(U_{174} \to\)

\(U_{175} \to\)

\(U_{176} \uparrow\)

9

\(\uparrow U_{160}\)

\(\gets U_{159}\)

\(\gets U_{158}\)

\(\gets U_{157}\)

\(\gets U_{156}\)

\(\gets U_{155}\)

\(\gets U_{154}\)

\(\gets U_{153}\)

\(\gets U_{152}\)

\(\gets U_{151}\)

\(\gets U_{150}\)

\(\gets U_{149}\)

\(\gets U_{148}\)

\(\gets U_{147}\)

\(\gets U_{146}\)

\(\gets U_{145}\)

8

\(U_{129} \to\)

\(U_{130} \to\)

\(U_{131} \to\)

\(U_{132} \to\)

\(U_{133} \to\)

\(U_{134} \to\)

\(U_{135} \to\)

\(U_{136} \to\)

\(U_{137} \to\)

\(U_{138} \to\)

\(U_{139} \to\)

\(U_{140} \to\)

\(U_{141} \to\)

\(U_{142} \to\)

\(U_{143} \to\)

\(U_{144} \uparrow\)

7

\(\uparrow U_{128}\)

\(\gets U_{127}\)

\(\gets U_{126}\)

\(\gets U_{125}\)

\(\gets U_{124}\)

\(\gets U_{123}\)

\(\gets U_{122}\)

\(\gets U_{121}\)

\(\gets U_{120}\)

\(\gets U_{119}\)

\(\gets U_{118}\)

\(\gets U_{117}\)

\(\gets U_{116}\)

\(\gets U_{115}\)

\(\gets U_{114}\)

\(\gets U_{113}\)

6

\(U_{97} \to\)

\(U_{98} \to\)

\(U_{99} \to\)

\(U_{100} \to\)

\(U_{101} \to\)

\(U_{102} \to\)

\(U_{103} \to\)

\(U_{104} \to\)

\(U_{105} \to\)

\(U_{106} \to\)

\(U_{107} \to\)

\(U_{108} \to\)

\(U_{109} \to\)

\(U_{110} \to\)

\(U_{111} \to\)

\(U_{112} \uparrow\)

5

\(\uparrow U_{96}\)

\(\gets U_{95}\)

\(\gets U_{94}\)

\(\gets U_{93}\)

\(\gets U_{92}\)

\(\gets U_{91}\)

\(\gets U_{90}\)

\(\gets U_{89}\)

\(\gets U_{88}\)

\(\gets U_{87}\)

\(\gets U_{86}\)

\(\gets U_{85}\)

\(\gets U_{84}\)

\(\gets U_{83}\)

\(\gets U_{82}\)

\(\gets U_{81}\)

4

\(U_{65} \to\)

\(U_{66} \to\)

\(U_{67} \to\)

\(U_{68} \to\)

\(U_{69} \to\)

\(U_{70} \to\)

\(U_{71} \to\)

\(U_{72} \to\)

\(U_{73} \to\)

\(U_{74} \to\)

\(U_{75} \to\)

\(U_{76} \to\)

\(U_{77} \to\)

\(U_{78} \to\)

\(U_{79} \to\)

\(U_{80} \uparrow\)

3

\(\uparrow U_{64}\)

\(\gets U_{63}\)

\(\gets U_{62}\)

\(\gets U_{61}\)

\(\gets U_{60}\)

\(\gets U_{59}\)

\(\gets U_{58}\)

\(\gets U_{57}\)

\(\gets U_{56}\)

\(\gets U_{55}\)

\(\gets U_{54}\)

\(\gets U_{53}\)

\(\gets U_{52}\)

\(\gets U_{51}\)

\(\gets U_{50}\)

\(\gets U_{49}\)

2

\(U_{33} \to\)

\(U_{34} \to\)

\(U_{35} \to\)

\(U_{36} \to\)

\(U_{37} \to\)

\(U_{38} \to\)

\(U_{39} \to\)

\(U_{40} \to\)

\(U_{41} \to\)

\(U_{42} \to\)

\(U_{43} \to\)

\(U_{44} \to\)

\(U_{45} \to\)

\(U_{46} \to\)

\(U_{47} \to\)

\(U_{48} \uparrow\)

1

\(\uparrow U_{32}\)

\(\gets U_{31}\)

\(\gets U_{30}\)

\(\gets U_{29}\)

\(\gets U_{28}\)

\(\gets U_{27}\)

\(\gets U_{26}\)

\(\gets U_{25}\)

\(\gets U_{24}\)

\(\gets U_{23}\)

\(\gets U_{22}\)

\(\gets U_{21}\)

\(\gets U_{20}\)

\(\gets U_{19}\)

\(\gets U_{18}\)

\(\gets U_{17}\)

0 DIN

\(U_1 \to\)

\(U_2 \to\)

\(U_3 \to\)

\(U_4 \to\)

\(U_5 \to\)

\(U_6 \to\)

\(U_7 \to\)

\(U_8 \to\)

\(U_9 \to\)

\(U_{10} \to\)

\(U_{11} \to\)

\(U_{12} \to\)

\(U_{13} \to\)

\(U_{14} \to\)

\(U_{15} \to\)

\(U_{16} \uparrow\)

PIO program

Pro takto jednoduchý protokol se nabízí použít PIO (programovatelný vstup výstup). Můžeme nastavit přesné časování pulsů a posílání dat na displej a nebude to nijak zatěžovat hlavní procesor.

;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program ws2812                         ; (1)
.side_set 1                             ; (2)

.define public T1 2                     ; (3)
.define public T2 5
.define public T3 3

.wrap_target                            ; (4)
bitloop:
    out x, 1       side 0 [T3 - 1] ; Side-set se uplatní i když se instrukce out zastaví.
    jmp !x do_zero side 1 [T1 - 1] ; Rozvětvení na základě bitu, vysunutého z OUT shift registru. Začínáme kladným pulsem.
do_one:
    jmp  bitloop   side 1 [T2 - 1] ; Pokračujeme vysokou hodnotou pro jedničkový bit.
do_zero:
    nop            side 0 [T2 - 1] ; Nebo jdeme na nulu, pro nulový bit.
.wrap
1 Definice jména programu.
2 Nastavujeme side set pro jeden pin.
3 Definice konstant T1, T2 a T3 (tyto konstanty můžeme číst v hlavním programu).
4 Vnější smyčka

Každá PIO instrukce trvá 1 tik hodin. Frekvence hodin je 800 KHz. Hodnota poslaná na výstupní pin trvá dokud není změněna. Čísla v hranatých závorkách [] jsou dodatečné hodinové tiky.

out x, 1 side 0 [T3 - 1] načte bit z vstupní fronty do registru x (pokud je fronta prázdná, tak se zde program pozastaví)

C program

/**
 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <stdio.h>
#include <stdlib.h>

#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "hardware/clocks.h"
#include "ws2812.pio.h"

/**
 * NOTE:
 *  Take into consideration if your WS2812 is a RGB or RGBW variant.
 *
 *  If it is RGBW, you need to set IS_RGBW to true and provide 4 bytes per
 *  pixel (Red, Green, Blue, White) and use urgbw_u32().
 *
 *  If it is RGB, set IS_RGBW to false and provide 3 bytes per pixel (Red,
 *  Green, Blue) and use urgb_u32().
 *
 *  When RGBW is used with urgb_u32(), the White channel will be ignored (off).
 *
 */
#define IS_RGBW false
// počet LED v řetězu
#define NUM_PIXELS 8
// kde máme připojen DIN
#define PICO_DEFAULT_WS2812_PIN 2

#ifdef PICO_DEFAULT_WS2812_PIN
#define WS2812_PIN PICO_DEFAULT_WS2812_PIN
#else
// default to pin 2 if the board doesn't have a default WS2812 pin defined
#define WS2812_PIN 2
#endif

// Check the pin is compatible with the platform
#if WS2812_PIN >= NUM_BANK0_GPIOS
#error Attempting to use a pin>=32 on a platform that does not support it
#endif

// pošle barvy na jednu RGB LED
static inline void put_pixel(PIO pio, uint sm, uint32_t pixel_grb)
{
    pio_sm_put_blocking(pio, sm, pixel_grb << 8u);
}

// převod barev R,G,B  0-255 na uint32_t
static inline uint32_t urgb_u32(uint8_t r, uint8_t g, uint8_t b)
{
    return
            ((uint32_t) (r) << 8) |
            ((uint32_t) (g) << 16) |
            (uint32_t) (b);
}

int main()
{
    stdio_init_all();

    PIO pio;
    uint sm;
    uint offset;

    // Tato funkce najde volný pio a stavový stroj pro náš program a nahraje ho do paměti pio
    // Použijeme funkci pio_claim_free_sm_and_add_program_for_gpio_range,
    // takže dostaneme PIO instanci vhodnou pro adresování GPIO pinů >= 32
    // jestliže to potřebujeme a je to podporováno hardware (např. RP2035)
    bool success = pio_claim_free_sm_and_add_program_for_gpio_range(&ws2812_program, &pio, &sm, &offset, WS2812_PIN, 1, true);
    hard_assert(success);

    ws2812_program_init(pio, sm, offset, WS2812_PIN, 800000, IS_RGBW);

    int t = 0;
    uint32_t color = rand();
    while (1) {
        // pošleme červenou barvu na všechny LED
        for( int i=0; i< NUM_PIXELS; i++ ) {
            put_pixel(pio, sm, urgb_u32(0xff, 0x00, 0x08));
        }
        sleep_ms(2000);
        // pošleme náhodnou barvu na každou LED
        for( int i=0; i< NUM_PIXELS; i++) {
            put_pixel(pio, sm, color);
            color = rand();
        }
        sleep_ms(2000);

    }

    // Toto uvolní zdroje a vyhodí náš PIO program z paměti
    pio_remove_program_and_unclaim_sm(&ws2812_program, pio, sm, offset);
}
cmake_minimum_required(VERSION 3.13)

set(PICO_BOARD pico)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)

project(ws2812 C CXX ASM)

pico_sdk_init()

add_executable (${PROJECT_NAME} ${PROJECT_NAME}.c)

# generování PIO programu a hlavičkového souboru pomocí pioasembleru
pico_generate_pio_header(ws2812 ${CMAKE_CURRENT_LIST_DIR}/ws2812.pio)



target_link_libraries(
  ${PROJECT_NAME}
  pico_stdlib
  hardware_pio
  )

pico_enable_stdio_usb(${PROJECT_NAME} 1)
pico_enable_stdio_uart(${PROJECT_NAME} 0)

pico_add_extra_outputs(${PROJECT_NAME})