ws2812: improve timings to be compatible with the WS2811

The WS2811 has a slightly slower data rate, but it is close enough that
a single library can support both LEDs at the same time. In my testing,
this package was already compatible with WS2811 LEDs before this change
when running at 4-5V, but when the voltage dropped to 3V or so the LEDs
started misbehaving. With these improved timings, the LEDs remained of
the correct color even at very low voltages (although blue was starting
to fade due to lack of voltage - that's not a software/protocol issue).
This commit is contained in:
Ayke van Laethem
2021-09-16 17:41:26 +02:00
committed by Ron Evans
parent cad03a7d4e
commit 7a2b1b8c90
2 changed files with 334 additions and 15 deletions
+24 -3
View File
@@ -26,6 +26,13 @@ import (
// (possibly including some branch prediction).
// It is certainly possible to extend this to other architectures, such as AVR
// and RISC-V if needed.
//
// Here are two important resources. For the timings:
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
// For the assembly (more or less):
// https://cpldcpu.wordpress.com/2014/01/19/light_ws2812-library-v2-0/
// The timings deviate a little bit from the code here, but so far the timings
// from wp.josh.com seem to be fine for the ws2812.
// Clock frequencies to support, in MHz.
var clockFrequencies = []int{16, 48, 64, 120}
@@ -41,15 +48,29 @@ func writeImplementation(f *os.File, megahertz int) error {
// T1H 1 code, high voltage time 550 700 5500 ns
// TLD data, low voltage time 450 600 5000 ns
// TLL latch, low voltage time 6000 ns
// The equivalent table for WS2811 LEDs would be the following:
// Symbol Parameter Min Typical Max Units
// T0H 0 code, high voltage time 350 500 650 ns
// T1H 1 code, high voltage time 1050 1200 5500 ns
// TLD data, low voltage time 1150 1300 5000 ns
// TLL latch, low voltage time 6000 ns
// Combining the two (min and max) leads to the following table:
// Symbol Parameter Min Typical Max Units
// T0H 0 code, high voltage time 350 - 500 ns
// T1H 1 code, high voltage time 1050 - 5500 ns
// TLD data, low voltage time 1150 - 5000 ns
// TLL latch, low voltage time 6000 ns
// These comined timings are used so that the ws2812 package is compatible
// with both WS2812 and with WS2811 chips.
// T0H is the time the pin should be high to send a "0" bit.
// T1H is the time the pin should be high to send a "1" bit.
// TLD is the time the pin should be low between bits.
// TLL is the time the pin should be low to apply (latch) the new colors.
minCyclesT0H := int(math.Ceil(0.200 / cycleTimeNS))
minCyclesT0H := int(math.Ceil(0.350 / cycleTimeNS))
maxCyclesT0H := int(math.Floor(0.500 / cycleTimeNS))
minCyclesT1H := int(math.Ceil(0.550 / cycleTimeNS))
minCyclesT1H := int(math.Ceil(1.050 / cycleTimeNS))
maxCyclesT1H := int(math.Floor(5.500 / cycleTimeNS))
minCyclesTLD := int(math.Ceil(0.450 / cycleTimeNS))
minCyclesTLD := int(math.Ceil(1.150 / cycleTimeNS))
// Assembly template:
// 1: @ send_bit
+310 -12
View File
@@ -16,18 +16,26 @@ func (d Device) writeByte16(c byte) {
portClear, maskClear := d.Pin.PortMaskClear()
// Timings:
// T0H: 4 - 6 cycles or 250.0ns - 375.0ns
// T1H: 9 - 11 cycles or 562.5ns - 687.5ns
// TLD: 8 - cycles or 500.0ns -
// T0H: 6 - 8 cycles or 375.0ns - 500.0ns
// T1H: 17 - 19 cycles or 1062.5ns - 1187.5ns
// TLD: 19 - cycles or 1187.5ns -
mask := interrupt.Disable()
value := uint32(c) << 24
device.AsmFull(`
1: @ send_bit
str {maskSet}, {portSet} @ [2] T0H and T0L start here
nop
nop
lsls {value}, #1 @ [1]
bcs.n 2f @ [1/3] skip_store
str {maskClear}, {portClear} @ [2] T0H -> T0L transition
2: @ skip_store
nop
nop
nop
nop
nop
nop
nop
nop
nop
@@ -38,6 +46,17 @@ func (d Device) writeByte16(c byte) {
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
subs {i}, #1 @ [1]
bne.n 1b @ [1/3] send_bit
`, map[string]interface{}{
@@ -56,9 +75,9 @@ func (d Device) writeByte48(c byte) {
portClear, maskClear := d.Pin.PortMaskClear()
// Timings:
// T0H: 10 - 12 cycles or 208.3ns - 250.0ns
// T1H: 27 - 29 cycles or 562.5ns - 604.2ns
// TLD: 22 - cycles or 458.3ns -
// T0H: 17 - 19 cycles or 354.2ns - 395.8ns
// T1H: 51 - 53 cycles or 1062.5ns - 1104.2ns
// TLD: 56 - cycles or 1166.7ns -
mask := interrupt.Disable()
value := uint32(c) << 24
device.AsmFull(`
@@ -70,10 +89,34 @@ func (d Device) writeByte48(c byte) {
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
lsls {value}, #1 @ [1]
bcs.n 2f @ [1/3] skip_store
str {maskClear}, {portClear} @ [2] T0H -> T0L transition
2: @ skip_store
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
@@ -110,6 +153,40 @@ func (d Device) writeByte48(c byte) {
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
subs {i}, #1 @ [1]
bne.n 1b @ [1/3] send_bit
`, map[string]interface{}{
@@ -128,9 +205,9 @@ func (d Device) writeByte64(c byte) {
portClear, maskClear := d.Pin.PortMaskClear()
// Timings:
// T0H: 13 - 15 cycles or 203.1ns - 234.4ns
// T1H: 36 - 38 cycles or 562.5ns - 593.8ns
// TLD: 29 - cycles or 453.1ns -
// T0H: 23 - 25 cycles or 359.4ns - 390.6ns
// T1H: 68 - 70 cycles or 1062.5ns - 1093.8ns
// TLD: 74 - cycles or 1156.2ns -
mask := interrupt.Disable()
value := uint32(c) << 24
device.AsmFull(`
@@ -145,10 +222,42 @@ func (d Device) writeByte64(c byte) {
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
lsls {value}, #1 @ [1]
bcs.n 2f @ [1/3] skip_store
str {maskClear}, {portClear} @ [2] T0H -> T0L transition
2: @ skip_store
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
@@ -198,6 +307,51 @@ func (d Device) writeByte64(c byte) {
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
subs {i}, #1 @ [1]
bne.n 1b @ [1/3] send_bit
`, map[string]interface{}{
@@ -216,9 +370,9 @@ func (d Device) writeByte120(c byte) {
portClear, maskClear := d.Pin.PortMaskClear()
// Timings:
// T0H: 24 - 26 cycles or 200.0ns - 216.7ns
// T1H: 66 - 68 cycles or 550.0ns - 566.7ns
// TLD: 54 - cycles or 450.0ns -
// T0H: 42 - 44 cycles or 350.0ns - 366.7ns
// T1H: 126 - 128 cycles or 1050.0ns - 1066.7ns
// TLD: 138 - cycles or 1150.0ns -
mask := interrupt.Disable()
value := uint32(c) << 24
device.AsmFull(`
@@ -244,10 +398,70 @@ func (d Device) writeByte120(c byte) {
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
lsls {value}, #1 @ [1]
bcs.n 2f @ [1/3] skip_store
str {maskClear}, {portClear} @ [2] T0H -> T0L transition
2: @ skip_store
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
@@ -341,6 +555,90 @@ func (d Device) writeByte120(c byte) {
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
subs {i}, #1 @ [1]
bne.n 1b @ [1/3] send_bit
`, map[string]interface{}{