Skip to content

Commit 1eaed39

Browse files
committed
fix ws2812 implementation
1 parent a22ac45 commit 1eaed39

File tree

5 files changed

+73
-14
lines changed

5 files changed

+73
-14
lines changed

go.sum

Whitespace-only changes.

rp2-pio/examples/ws2812/main.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
func main() {
1313
const ws2812Pin = machine.GP16
1414
sm, _ := pio.PIO0.ClaimStateMachine()
15-
ws, err := piolib.NewWS2812(sm, ws2812Pin, 400_000)
15+
ws, err := piolib.NewWS2812(sm, ws2812Pin)
1616
if err != nil {
1717
panic(err.Error())
1818
}
@@ -26,19 +26,24 @@ func main() {
2626
const longWait = 6 * time.Second
2727
const shortWait = 2 * time.Second
2828
// Start Stoplight in red (STOP).
29+
println("red")
2930
ws.SetColor(red)
3031
time.Sleep(4 * time.Second)
3132

3233
// Before green we go through a red+yellow stage (PREP. PULL AWAY)
34+
println("green/amber switching")
3335
for i := 0; i < 2; i++ {
3436
const semiSleep = time.Second / 2
3537
ws.SetColor(amber)
3638
time.Sleep(semiSleep)
3739
ws.SetColor(red)
3840
time.Sleep(semiSleep)
3941
}
42+
println("green")
4043
ws.SetColor(green)
4144
time.Sleep(longWait)
45+
46+
println("amber")
4247
ws.SetColor(amber)
4348
time.Sleep(shortWait)
4449
}

rp2-pio/piolib/ws2812.go

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,51 @@ type WS2812 struct {
1414
offset uint8
1515
}
1616

17-
func NewWS2812(sm pio.StateMachine, pin machine.Pin, baud uint32) (*WS2812, error) {
17+
func NewWS2812(sm pio.StateMachine, pin machine.Pin) (*WS2812, error) {
18+
// These timings are taken from the table "Updated simplified timing
19+
// constraints for NeoPixel strings" at:
20+
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
21+
// Here is a copy:
22+
// Symbol Parameter Min Typical Max Units
23+
// T0H 0 code, high voltage time 200 350 500 ns
24+
// T1H 1 code, high voltage time 550 700 5500 ns
25+
// TLD data, low voltage time 450 600 5000 ns
26+
// TLL latch, low voltage time 6000 ns
27+
// The equivalent table for WS2811 LEDs would be the following:
28+
// Symbol Parameter Min Typical Max Units
29+
// T0H 0 code, high voltage time 350 500 650 ns
30+
// T1H 1 code, high voltage time 1050 1200 5500 ns
31+
// TLD data, low voltage time 1150 1300 5000 ns
32+
// TLL latch, low voltage time 6000 ns
33+
// Combining the two (min and max) leads to the following table:
34+
// Symbol Parameter Min Typical Max Units
35+
// T0H 0 code, high voltage time 350 - 500 ns
36+
// T1H 1 code, high voltage time 1050 - 5500 ns
37+
// TLD data, low voltage time 1150 - 5000 ns
38+
// TLL latch, low voltage time 6000 ns
39+
// These comined timings are used so that the ws2812 package is compatible
40+
// with both WS2812 and with WS2811 chips.
41+
// T0H is the time the pin should be high to send a "0" bit.
42+
// T1H is the time the pin should be high to send a "1" bit.
43+
// TLD is the time the pin should be low between bits.
44+
// TLL is the time the pin should be low to apply (latch) the new colors.
45+
//
46+
// These constants are for the most part not used but serve as
47+
// a notebook of documentation. When in a IDE such as VSCode one
48+
// can mouse over constants to observe their value.
49+
const (
50+
t0h = 352 // augment it slightly to compensate for PIO clock instability.
51+
t0h_cycles = 3 // Taken from PIO program by observing instructions used.
52+
freq = 1_000_000_000 * t0h_cycles / t0h
53+
t1h_t0 = 1050. / t0h // =3
54+
tld_t0 = 1150. / t0h // ~3.8
55+
tll_t0 = 6000. / t0h // ~18
56+
t1h_cycles = t1h_t0 * t0h_cycles // =9
57+
tld_cycles = tld_t0 * t0h_cycles // ~9.8
58+
tll_cycles = tll_t0 * t0h_cycles // ~51.42
59+
)
1860
sm.TryClaim() // SM should be claimed beforehand, we just guarantee it's claimed.
19-
whole, frac, err := pio.ClkDivFromFrequency(baud, machine.CPUFrequency())
61+
whole, frac, err := pio.ClkDivFromFrequency(freq, machine.CPUFrequency())
2062
if err != nil {
2163
return nil, err
2264
}
@@ -40,8 +82,9 @@ func NewWS2812(sm pio.StateMachine, pin machine.Pin, baud uint32) (*WS2812, erro
4082
}
4183

4284
func (ws *WS2812) SetRGB(r, g, b uint8) {
43-
color := uint32(r)<<16 | uint32(g)<<8 | uint32(b)
44-
println("r", r, "g", g, "b", b)
85+
// Has little endian encoding over wire, last bits are most significant.
86+
// So green comes first, followed by red and lastly blue.
87+
color := uint32(g) | uint32(r)<<8 | uint32(b)<<16
4588
ws.sm.TxPut(color)
4689
}
4790

rp2-pio/piolib/ws2812.pio

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,26 @@ public entry_point:
44
pull
55
set x, 23 ; Loop over 24 bits
66

7+
; In bitloop we have 3 unavoidable instructions during high cycle
8+
; This means 3 cycles == t0h, we then use t0h as a multiple
9+
; to reach all other timings.
10+
; t1h is 3*t0h, so 9 PIO cycles.
711
bitloop:
8-
set pins, 1 ; Drive pin high
9-
out y, 1 [5] ; Shift 1 bit out, and write it to y
10-
jmp !y skip ; Skip the extra delay if the bit was 0
11-
nop [5]
12+
set pins, 1 ; Drive pin high
13+
out y, 1 ; Shift 1 bit out, and write it to y
14+
jmp !y skip ; Skip the extra delay if the bit was 0
15+
nop [5] ; Here we finish t1h with 6 cycles reaching 9 total.
1216

17+
; tld is 3.28571*t0h, so approximately 10 PIO cycles.
18+
; tll is 17.14*t0h, so approx 52 cycles.
1319
skip:
14-
set pins, 0 [5]
20+
set pins, 0 [9]
1521
jmp x-- bitloop ; Jump if x nonzero, and decrement x
22+
nop [31] ; +32, need to still add 52-32
23+
nop [19] ; +20, 52 cycles reached so color should be latched.
1624
jmp entry_point
1725

26+
1827
% go {
1928
//go:build rp2040
2029
package piolib

rp2-pio/piolib/ws2812_pio.go

Lines changed: 6 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)