Neopixel LED panely nebo pásky, či samostatné LED jsou soustavy mikročipů WS2812B 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.

![]()
Předběžné práce
Instalace kompilátoru Zig je popsána v článku o UARTu a Zigu.
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.

Jednotlivé bity časově vypadají takto:

| 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:
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:
| 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 nebude 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 still takes place when instruction stalls
jmp !x do_zero side 1 [T1 - 1] ; Branch on the bit we shifted out. Positive pulse
do_one:
jmp bitloop side 1 [T2 - 1] ; Continue driving high, for a long pulse
do_zero:
nop side 0 [T2 - 1] ; Or drive low, for a short pulse
.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. 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í)








Na obrázcích a videu je vidět, že mám v programu chybu, nechce se na matici zobrazit nic (nebo nějaká náhoda) od řádku 13 a dále. Budu to muset nějak vyřešit.
$ ffmpeg -i VID_20240816_202407.mp4 -vf "transpose=2" -acodec copy zig_neopixel.mp4
$ ffmpeg -i zig_neopixel.mp4 -vf "transpose=2" -acodec copy prace_zig_neopixel.mp4
Řešení chyby
Chyba nebyla v programování, ale v elektrickém zapojení. Podle doporučení by se měl na takto velkých maticích používat převodník úrovní, který jsem použil. Osciloskopem jsem zjistil, že signál za převodníkem úrovně má tendeci klesat, přemýšlel jsem, zda to není dlouhými vodiči, nebo nějakou parazitní kapacitou. Evidentně do posledních LED přicházel zkažený signál.
Zapojil jsem jiný panel bez převodníku úrovně a hle, všechno funguje, jak má. Nakonec jsem vyjasnil, že panel je vadný. Zapojil jsem ho jako druhý panel bez oddělovače signálu a situace je stejná. Potom jsem místo něj zapojil jiný panel (bez oddělovače signálů) a všechno funguje, jak má. Prostě jsem měl smůlu a panel půjde do reklamace.

const std = @import("std");
const microzig = @import("microzig");
const rp2040 = microzig.hal;
const gpio = rp2040.gpio;
const rand = rp2040.rand;
const Pio = rp2040.pio.Pio;
const StateMachine = rp2040.pio.StateMachine;
const radku = 16;
const sloupcu = 16;
const pocet_ledek = radku*sloupcu; // pocet zřetězených LED
const obracene = true;
var barvy: [pocet_ledek]u32 = undefined;
const ws2812_program = blk: {
@setEvalBranchQuota(5000);
break :blk rp2040.pio.assemble(
\\;
\\; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
\\;
\\; SPDX-License-Identifier: BSD-3-Clause
\\;
\\.program ws2812
\\.side_set 1
\\
\\.define public T1 2
\\.define public T2 5
\\.define public T3 3
\\
\\.wrap_target
\\bitloop:
\\ out x, 1 side 0 [T3 - 1] ; Side-set still takes place when instruction stalls
\\ jmp !x do_zero side 1 [T1 - 1] ; Branch on the bit we shifted out. Positive pulse
\\do_one:
\\ jmp bitloop side 1 [T2 - 1] ; Continue driving high, for a long pulse
\\do_zero:
\\ nop side 0 [T2 - 1] ; Or drive low, for a short pulse
\\.wrap
, .{}).get_program_by_name("ws2812");
};
const pio: Pio = .pio0;
const sm: StateMachine = .sm0;
const led_pin = gpio.num(15); // GP15 (pin 20 na Raspberry Pi Pico)
pub fn main() void {
pio.gpio_init(led_pin);
sm_set_consecutive_pindirs(pio, sm, @intFromEnum(led_pin), 1, true);
const cycles_per_bit: comptime_int = ws2812_program.defines[0].value + //T1
ws2812_program.defines[1].value + //T2
ws2812_program.defines[2].value; //T3
const div = @as(f32, @floatFromInt(rp2040.clock_config.sys.?.output_freq)) /
(800_000 * cycles_per_bit);
pio.sm_load_and_start_program(sm, ws2812_program, .{
.clkdiv = rp2040.pio.ClkDivOptions.from_float(div),
.pin_mappings = .{
.side_set = .{
.base = @intFromEnum(led_pin),
.count = 1,
},
},
.shift = .{
.out_shiftdir = .left,
.autopull = true,
.pull_threshold = 24,
.join_tx = true,
},
}) catch unreachable;
pio.sm_set_enabled(sm, true);
// random number init
var ascon = rand.Ascon.init();
var rng = ascon.random();
var buffer: [14*3]u8 = undefined;
clear();
while (true) {
// barvy na řádcích 12 a 13 se budou náhodně měnit
rng.bytes(buffer[0..]);
for(0..14) |i| {
bod(i+1, 13, buffer[3*i+0],buffer[3*i+1],buffer[3*i+2]);
}
rng.bytes(buffer[0..]);
for(0..14) |i| {
bod(i+1, 12, buffer[3*i+0],buffer[3*i+1],buffer[3*i+2]);
}
for(0..16) |i| {
bod(i, 0, 0x22, 0x00, 0x00);
bod(i, 15, 0x00, 0x22, 0x00);
bod(0, i, 0x00, 0x00, 0x22);
bod(15, i, 0x22, 0x22, 0x00);
}
bod(0, 0, 0x22, 0x22, 0x22);
bod(0, 15, 0x22, 0x22, 0x22);
bod(15, 0, 0x22, 0x22, 0x22);
bod(15, 15, 0x22, 0x22, 0x22);
write_display(pio,sm);
rp2040.time.sleep_ms(2500);
// nakreslím písmeno Z
bod(2,8,0x00,0x00,0x44);
bod(3,8,0x00,0x00,0x44);
bod(4,8,0x00,0x00,0x44);
bod(5,8,0x00,0x00,0x44);
bod(6,8,0x00,0x00,0x44);
bod(6,7,0x00,0x00,0x44);
bod(5,6,0x00,0x00,0x44);
bod(4,5,0x00,0x00,0x44);
bod(3,4,0x00,0x00,0x44);
bod(2,3,0x00,0x00,0x44);
bod(2,2,0x00,0x00,0x44);
bod(3,2,0x00,0x00,0x44);
bod(4,2,0x00,0x00,0x44);
bod(5,2,0x00,0x00,0x44);
bod(6,2,0x00,0x00,0x44);
// nakreslím písmeno I
bod(8,2,0x00,0x00,0x44);
bod(8,3,0x00,0x00,0x44);
bod(8,4,0x00,0x00,0x44);
bod(8,5,0x00,0x00,0x44);
bod(8,6,0x00,0x00,0x44);
bod(8,7,0x00,0x00,0x44);
bod(8,8,0x00,0x00,0x44);
// nakreslím písmeno G
bod(0xd,8,0x00,0x00,0x44);
bod(0xc,8,0x00,0x00,0x44);
bod(0xb,8,0x00,0x00,0x44);
bod(0xa,7,0x00,0x00,0x44);
bod(0xa,6,0x00,0x00,0x44);
bod(0xa,5,0x00,0x00,0x44);
bod(0xa,4,0x00,0x00,0x44);
bod(0xa,3,0x00,0x00,0x44);
bod(0xb,2,0x00,0x00,0x44);
bod(0xc,2,0x00,0x00,0x44);
bod(0xd,2,0x00,0x00,0x44);
bod(0xd,3,0x00,0x00,0x44);
bod(0xd,4,0x00,0x00,0x44);
bod(0xd,5,0x00,0x00,0x44);
bod(0xc,5,0x00,0x00,0x44);
write_display( pio, sm);
rp2040.time.sleep_ms(2500);
clear();
}
}
fn sm_set_consecutive_pindirs(_pio: Pio, _sm: StateMachine, pin: u5, count: u3, is_out: bool) void {
const sm_regs = _pio.get_sm_regs(_sm);
const pinctrl_saved = sm_regs.pinctrl.raw;
sm_regs.pinctrl.modify(.{
.SET_BASE = pin,
.SET_COUNT = count,
});
_pio.sm_exec(_sm, rp2040.pio.Instruction{
.tag = .set,
.delay_side_set = 0,
.payload = .{
.set = .{
.data = @intFromBool(is_out),
.destination = .pindirs,
},
},
});
sm_regs.pinctrl.raw = pinctrl_saved;
}
/// rozsvití celý řádek LED v dané barvě (red, green, blue)
/// pio.sm_blocking_write() je potřeba volat bez časových pauz
fn plny_radek(_pio: Pio, _sm: StateMachine, red: u32, green: u32, blue: u32) void {
const color: u32 = ((red << 8) + (green << 16) + blue) << 8;
for(0..pocet_ledek) |_| {
_pio.sm_blocking_write(_sm, color);
}
}
/// zapíše pole barvy na displej
fn write_display(_pio: Pio, _sm: StateMachine) void {
for(0..pocet_ledek) |i| {
_pio.sm_blocking_write(_sm, barvy[i] << 8);
}
}
/// vynuluje pole barvy
fn clear() void {
for(0..pocet_ledek) |i| {
barvy[i]=0x000000;
}
}
/// obarví bod o souřadnicích x,y barvou (R,G,B)
fn bod(x: u32, y: u32, red: u32, green: u32, blue: u32) void {
if( x >= 0 and x < radku and y >= 0 and y < sloupcu ) {
const color: u32 = (red << 8) + (green << 16) + blue;
var linbod: u32 = 0;
if(y % 2 == 0) { // sude radky
linbod = y*radku + x;
} else {
linbod = y*radku + (sloupcu - x - 1);
}
if(linbod >= 0 and linbod < pocet_ledek) {
barvy[linbod] = color;
}
}
}
Hodiny
Zapojil jsem dva panely vedle sebe a naprogramoval jednoduché hodiny (zatím neukazují přený čas, ale čas od počátku spuštění programu). Panely jsou oproti předchozímu zapojení otočeny o 90°. Zapojení bude potřeba doplnit o obvod DS3231 AT24C32 (hodiny reálného času na I2c sběrnici).
// neopixel_cas.zig
// (c) Jirka Chráska <jirka@lixis.cz>
// SPDX-License-Identifier: BSD-3-Clause
const std = @import("std");
const microzig = @import("microzig");
const rp2040 = microzig.hal;
const gpio = rp2040.gpio;
const rand = rp2040.rand;
const Pio = rp2040.pio.Pio;
const StateMachine = rp2040.pio.StateMachine;
const radku = 16;
const sloupcu = 32;
const sloupcu_matice = 16;
const pocet_ledek = radku * sloupcu; // pocet zřetězených LED
const obracene = true;
var barvy: [pocet_ledek]u32 = undefined;
// debug uart
const led = gpio.num(25);
const uart = rp2040.uart.num(0);
const baud_rate = 115200;
const uart_tx_pin = gpio.num(0);
const uart_rx_pin = gpio.num(1);
pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn {
std.log.err("panic: {s}", .{message});
@breakpoint();
while (true) {}
}
pub const microzig_options = .{
.log_level = .debug,
.logFn = rp2040.uart.log,
};
const ws2812_program = blk: {
@setEvalBranchQuota(5000);
break :blk rp2040.pio.assemble(
\\;
\\; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
\\;
\\; SPDX-License-Identifier: BSD-3-Clause
\\;
\\.program ws2812
\\.side_set 1
\\
\\.define public T1 2
\\.define public T2 5
\\.define public T3 3
\\
\\.wrap_target
\\bitloop:
\\ out x, 1 side 0 [T3 - 1] ; Side-set still takes place when instruction stalls
\\ jmp !x do_zero side 1 [T1 - 1] ; Branch on the bit we shifted out. Positive pulse
\\do_one:
\\ jmp bitloop side 1 [T2 - 1] ; Continue driving high, for a long pulse
\\do_zero:
\\ nop side 0 [T2 - 1] ; Or drive low, for a short pulse
\\.wrap
, .{}).get_program_by_name("ws2812");
};
const pio: Pio = .pio0;
const sm: StateMachine = .sm0;
const led_pin = gpio.num(15); // GP15 (pin 20 na Raspberry Pi Pico) sem je připojen displej WS2812B
pub fn main() void {
// uart logování
led.set_function(.sio);
led.set_direction(.out);
led.put(1);
uart.apply(.{
.baud_rate = baud_rate,
.tx_pin = uart_tx_pin,
.rx_pin = uart_rx_pin,
.clock_config = rp2040.clock_config,
});
rp2040.uart.init_logger(uart);
// inicializace pinu pro WS2812B displej
pio.gpio_init(led_pin);
// nastavení pinu pro PIO program a side set pinů (jeden)
sm_set_consecutive_pindirs(pio, sm, @intFromEnum(led_pin), 1, true);
// cycles_per_bit určuje, kolik cyklů (tiků) hodin bude trvat přenos jednoho bitu
// T1=2 + T2=5 + T3=3, což je 10 cyklů
const cycles_per_bit: comptime_int = ws2812_program.defines[0].value + //T1
ws2812_program.defines[1].value + //T2
ws2812_program.defines[2].value; //T3
const div = @as(f32, @floatFromInt(rp2040.clock_config.sys.?.output_freq)) / (800_000 * cycles_per_bit);
// načtení a spuštění PIO programu
pio.sm_load_and_start_program(sm, ws2812_program, .{
.clkdiv = rp2040.pio.ClkDivOptions.from_float(div),
.pin_mappings = .{
.side_set = .{
.base = @intFromEnum(led_pin),
.count = 1,
},
},
.shift = .{
.out_shiftdir = .left,
.autopull = true,
.pull_threshold = 24,
.join_tx = true,
},
}) catch unreachable;
pio.sm_set_enabled(sm, true);
// random number init (to je jenom pomůcka pro testování)
var ascon = rand.Ascon.init();
var rng = ascon.random();
var cbuffer: [3]u8 = undefined;
clear();
const sec = [_]u8{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
const dsec = [_]u8{ '0', '1', '2', '3', '4', '5' };
const min = [_]u8{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
const dmin = [_]u8{ '0', '1', '2', '3', '4', '5' };
const hod = [_]u8{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
const dhod = [_]u8{ '0', '1', '2' };
std.log.info("start", .{});
while (true) {
led.put(1);
for (dhod) |dh| {
std.log.info("Dhod {c}", .{dh});
for (hod) |h| {
std.log.info("hod {c}", .{h});
if (h == '4' and dh == '2') {
break;
}
for (dmin) |dm| {
std.log.info("dmin {c}", .{dm});
for (min) |m| {
std.log.info("min {c}", .{m});
for (dsec) |ds| {
std.log.info("ds {c}", .{ds});
rng.bytes(cbuffer[0..]);
const r = cbuffer[0];
const g = cbuffer[1];
const b = cbuffer[2];
for (sec) |s| {
std.log.info("s {c}", .{s});
znak5(0, 8, dh, r, g, b);
znak5(5, 8, h, r, g, b);
bod(10, 10, 0xaa, 0x00, 0x00);
bod(10, 13, 0xaa, 0x00, 0x00);
znak5(11, 8, dm, r, g, b);
znak5(16, 8, m, r, g, b);
bod(21, 10, 0x00, 0xaa, 0x00);
bod(21, 13, 0x00, 0xaa, 0x00);
znak5(22, 8, ds, r, g, b);
znak5(27, 8, s, r, g, b);
write_display(pio, sm);
rp2040.time.sleep_ms(1000);
led.put(0);
clear();
}
}
}
}
}
}
}
}
fn sm_set_consecutive_pindirs(_pio: Pio, _sm: StateMachine, pin: u5, count: u3, is_out: bool) void {
const sm_regs = _pio.get_sm_regs(_sm);
const pinctrl_saved = sm_regs.pinctrl.raw;
sm_regs.pinctrl.modify(.{
.SET_BASE = pin,
.SET_COUNT = count,
});
_pio.sm_exec(_sm, rp2040.pio.Instruction{
.tag = .set,
.delay_side_set = 0,
.payload = .{
.set = .{
.data = @intFromBool(is_out),
.destination = .pindirs,
},
},
});
sm_regs.pinctrl.raw = pinctrl_saved;
}
// zapíše pole barvy na diplej
fn write_display(_pio: Pio, _sm: StateMachine) void {
for (0..pocet_ledek) |i| {
_pio.sm_blocking_write(_sm, barvy[i] << 8);
}
}
/// vynuluje pole barvy
fn clear() void {
for (0..pocet_ledek) |i| {
barvy[i] = 0x000000;
}
}
/// obarví bod o souřadnicích x,y barvou (R,G,B)
fn bod(x: u32, y: u32, red: u32, green: u32, blue: u32) void {
if (x >= 0 and x < sloupcu and y >= 0 and y < radku) {
const color: u32 = (red << 8) + (green << 16) + blue;
var linbod: u32 = 0;
if (x % 2 == 0) { // sudé sloupce jdou shora dolů
if (x < sloupcu_matice) {
linbod = x * radku + (radku - 1 - y);
} else {
linbod = sloupcu_matice * radku + (x - sloupcu_matice) * radku + (radku - 1 - y);
}
} else { // liché sloupce jdou zdola nahoru
if (x < sloupcu_matice) {
linbod = x * radku + y;
} else {
linbod = sloupcu_matice * radku + (x - sloupcu_matice) * radku + y;
}
}
// std.log.info("x {}, y {}, linbod {}", .{ x, y, linbod });
if (linbod >= 0 and linbod < pocet_ledek) {
barvy[linbod] = color;
}
}
}
/// Vykreslí znak 5x8 bodů do pole barvy
fn znak5(x: u8, y: u8, znak: u16, red: u8, green: u8, blue: u8) void {
const z: [5]u8 = switch (znak) {
'.' => [5]u8{ 0x60, 0x60, 0x00, 0x00, 0x00 },
',' => [5]u8{ 0x80, 0x60, 0x00, 0x00, 0x00 },
':' => [5]u8{ 0x6c, 0x6c, 0x00, 0x00, 0x00 },
';' => [5]u8{ 0x8c, 0x6c, 0x00, 0x00, 0x00 },
'!' => [5]u8{ 0x00, 0x6f, 0x6f, 0x00, 0x00 },
'?' => [5]u8{ 0x02, 0x59, 0x59, 0x06, 0x00 },
'+' => [5]u8{ 0x10, 0x10, 0x7c, 0x10, 0x10 },
'-' => [5]u8{ 0x10, 0x10, 0x10, 0x10, 0x00 },
'/' => [5]u8{ 0x60, 0x30, 0x18, 0x0c, 0x06 },
'*' => [5]u8{ 0x54, 0x38, 0x7c, 0x38, 0x54 },
'[' => [5]u8{ 0x7f, 0x41, 0x41, 0x00, 0x00 },
']' => [5]u8{ 0x00, 0x41, 0x41, 0x7f, 0x00 },
'(' => [5]u8{ 0x3c, 0x42, 0x81, 0x00, 0x00 },
')' => [5]u8{ 0x00, 0x00, 0x81, 0x42, 0x3c },
'%' => [5]u8{ 0x63, 0x13, 0x08, 0x64, 0x63 },
'#' => [5]u8{ 0x24, 0x7f, 0x24, 0x7f, 0x24 },
'0' => [5]u8{ 0x3e, 0x41, 0x41, 0x3e, 0x00 },
'1' => [5]u8{ 0x04, 0x42, 0x7f, 0x40, 0x00 },
'2' => [5]u8{ 0x66, 0x51, 0x49, 0x46, 0x00 },
'3' => [5]u8{ 0x22, 0x49, 0x49, 0x36, 0x00 },
'4' => [5]u8{ 0x1c, 0x12, 0x79, 0x10, 0x00 },
'5' => [5]u8{ 0x4f, 0x49, 0x49, 0x31, 0x00 },
'6' => [5]u8{ 0x3e, 0x49, 0x49, 0x32, 0x00 },
'7' => [5]u8{ 0x61, 0x11, 0x09, 0x07, 0x00 },
'8' => [5]u8{ 0x36, 0x49, 0x49, 0x36, 0x00 },
'9' => [5]u8{ 0x46, 0x49, 0x49, 0x3e, 0x00 },
'a' => [5]u8{ 0x20, 0x54, 0x54, 0x54, 0x78 },
'á' => [5]u8{ 0x20, 0x54, 0x56, 0x55, 0x78 },
'A' => [5]u8{ 0x78, 0x14, 0x12, 0x14, 0x78 },
'Á' => [5]u8{ 0x78, 0x14, 0x12, 0x15, 0x78 },
'b' => [5]u8{ 0x7e, 0x48, 0x48, 0x30, 0x00 },
'B' => [5]u8{ 0x7e, 0x4a, 0x4a, 0x34, 0x00 },
'c' => [5]u8{ 0x30, 0x48, 0x48, 0x48, 0x00 },
'C' => [5]u8{ 0x3c, 0x42, 0x42, 0x42, 0x00 },
'Č' => [5]u8{ 0x3c, 0x43, 0x42, 0x43, 0x00 },
'č' => [5]u8{ 0x30, 0x4a, 0x4c, 0x4a, 0x00 },
'd' => [5]u8{ 0x30, 0x48, 0x48, 0x7e, 0x00 },
'ď' => [5]u8{ 0x30, 0x49, 0x4a, 0x7d, 0x00 },
'D' => [5]u8{ 0x7e, 0x42, 0x42, 0x3c, 0x00 },
'Ď' => [5]u8{ 0x7e, 0x43, 0x42, 0x3d, 0x00 },
'e' => [5]u8{ 0x38, 0x54, 0x54, 0x58, 0x00 },
'é' => [5]u8{ 0x38, 0x54, 0x56, 0x59, 0x00 },
'ě' => [5]u8{ 0x38, 0x55, 0x56, 0x59, 0x00 },
'E' => [5]u8{ 0x7c, 0x54, 0x54, 0x54, 0x00 },
'É' => [5]u8{ 0x7c, 0x54, 0x56, 0x55, 0x00 },
'Ě' => [5]u8{ 0x7c, 0x55, 0x56, 0x55, 0x00 },
'f' => [5]u8{ 0x40, 0x78, 0x54, 0x04, 0x00 },
'F' => [5]u8{ 0x7e, 0x12, 0x12, 0x02, 0x00 },
'g' => [5]u8{ 0x98, 0xa4, 0xa4, 0x78, 0x00 },
'G' => [5]u8{ 0x3c, 0x42, 0x4a, 0x7a, 0x00 },
'h' => [5]u8{ 0x7e, 0x08, 0x08, 0x70, 0x00 },
'H' => [5]u8{ 0x7e, 0x08, 0x08, 0x7e, 0x00 },
'i' => [5]u8{ 0x40, 0x7a, 0x40, 0x00, 0x00 },
'í' => [5]u8{ 0x40, 0x78, 0x42, 0x01, 0x00 },
'I' => [5]u8{ 0x42, 0x7e, 0x42, 0x00, 0x00 },
'Í' => [5]u8{ 0x00, 0x44, 0x7e, 0x45, 0x00 },
'j' => [5]u8{ 0x80, 0x80, 0x7a, 0x00, 0x00 },
'J' => [5]u8{ 0x42, 0x42, 0x42, 0x3e, 0x00 },
'k' => [5]u8{ 0x7e, 0x10, 0x28, 0x44, 0x00 },
'K' => [5]u8{ 0x7e, 0x18, 0x24, 0x42, 0x00 },
'l' => [5]u8{ 0x00, 0x00, 0x7e, 0x40, 0x00 },
'L' => [5]u8{ 0x7e, 0x40, 0x40, 0x40, 0x00 },
'm' => [5]u8{ 0x78, 0x08, 0x70, 0x08, 0x78 },
'M' => [5]u8{ 0x7e, 0x04, 0x08, 0x04, 0x7e },
'n' => [5]u8{ 0x78, 0x10, 0x08, 0x70, 0x00 },
'ň' => [5]u8{ 0x78, 0x11, 0x0a, 0x71, 0x00 },
'N' => [5]u8{ 0x7c, 0x08, 0x10, 0x20, 0x7c },
'Ň' => [5]u8{ 0x7c, 0x09, 0x12, 0x21, 0x7c },
'o' => [5]u8{ 0x30, 0x48, 0x48, 0x30, 0x00 },
'ó' => [5]u8{ 0x30, 0x48, 0x4a, 0x31, 0x00 },
'O' => [5]u8{ 0x3c, 0x42, 0x42, 0x42, 0x3c },
'Ó' => [5]u8{ 0x3c, 0x42, 0x43, 0x42, 0x3c },
'p' => [5]u8{ 0xf8, 0x28, 0x28, 0x10, 0x00 },
'P' => [5]u8{ 0x7e, 0x12, 0x12, 0x0c, 0x00 },
'q' => [5]u8{ 0x30, 0x48, 0x48, 0xb0, 0x00 },
'Q' => [5]u8{ 0x3c, 0x42, 0x42, 0x42, 0xbc },
'r' => [5]u8{ 0x78, 0x10, 0x08, 0x10, 0x00 },
'ř' => [5]u8{ 0x78, 0x11, 0x0a, 0x11, 0x00 },
'R' => [5]u8{ 0x7e, 0x12, 0x32, 0x4c, 0x00 },
'Ř' => [5]u8{ 0x7e, 0x13, 0x32, 0x4d, 0x00 },
's' => [5]u8{ 0x48, 0x54, 0x54, 0x24, 0x00 },
'š' => [5]u8{ 0x48, 0x55, 0x56, 0x25, 0x00 },
'S' => [5]u8{ 0x4c, 0x52, 0x52, 0x52, 0x22 },
'Š' => [5]u8{ 0x4c, 0x53, 0x52, 0x53, 0x22 },
't' => [5]u8{ 0x00, 0x10, 0x7c, 0x50, 0x00 },
'ť' => [5]u8{ 0x00, 0x11, 0x7a, 0x51, 0x00 },
'T' => [5]u8{ 0x02, 0x02, 0x7e, 0x02, 0x02 },
'Ť' => [5]u8{ 0x02, 0x03, 0x7e, 0x03, 0x02 },
'u' => [5]u8{ 0x38, 0x40, 0x40, 0x78, 0x00 },
'ú' => [5]u8{ 0x38, 0x40, 0x42, 0x79, 0x00 },
'ů' => [5]u8{ 0x38, 0x40, 0x42, 0x79, 0x00 },
'U' => [5]u8{ 0x3e, 0x40, 0x40, 0x40, 0x3e },
'Ů' => [5]u8{ 0x3c, 0x42, 0x45, 0x42, 0x3c },
'Ú' => [5]u8{ 0x3e, 0x40, 0x42, 0x41, 0x3e },
'v' => [5]u8{ 0x18, 0x20, 0x40, 0x20, 0x18 },
'V' => [5]u8{ 0x1e, 0x20, 0x60, 0x20, 0x1e },
'w' => [5]u8{ 0x78, 0x20, 0x10, 0x20, 0x78 },
'W' => [5]u8{ 0x7e, 0x20, 0x10, 0x20, 0x7e },
'x' => [5]u8{ 0x48, 0x30, 0x30, 0x48, 0x00 },
'X' => [5]u8{ 0x62, 0x14, 0x08, 0x14, 0x62 },
'y' => [5]u8{ 0x98, 0xa0, 0x60, 0x38, 0x00 },
'ý' => [5]u8{ 0x98, 0xa0, 0x62, 0x39, 0x00 },
'Y' => [5]u8{ 0x06, 0x08, 0x70, 0x08, 0x06 },
'Ý' => [5]u8{ 0x06, 0x08, 0x72, 0x09, 0x06 },
'z' => [5]u8{ 0x48, 0x68, 0x58, 0x48, 0x00 },
'ž' => [5]u8{ 0x48, 0x69, 0x5a, 0x49, 0x00 },
'Z' => [5]u8{ 0x42, 0x62, 0x52, 0x4a, 0x46 },
'Ž' => [5]u8{ 0x42, 0x63, 0x52, 0x4b, 0x46 },
else => [5]u8{ 0x00, 0x00, 0x00, 0x00, 0x00 },
};
for (0..5) |i| {
if (z[i] & 0b0000_0001 == 1) {
bod(x + i, y + 7, red, green, blue);
}
if (z[i] & 0b0000_0010 == 2) {
bod(x + i, y + 6, red, green, blue);
}
if (z[i] & 0b0000_0100 == 4) {
bod(x + i, y + 5, red, green, blue);
}
if (z[i] & 0b0000_1000 == 8) {
bod(x + i, y + 4, red, green, blue);
}
if (z[i] & 0b0001_0000 == 16) {
bod(x + i, y + 3, red, green, blue);
}
if (z[i] & 0b0010_0000 == 32) {
bod(x + i, y + 2, red, green, blue);
}
if (z[i] & 0b0100_0000 == 64) {
bod(x + i, y + 1, red, green, blue);
}
if (z[i] & 0b1000_0000 == 128) {
bod(x + i, y + 0, red, green, blue);
}
}
}
Na generování znaků jsem použil Custom Character Generator, který jsem si udělal pro jiné dipleje.