From 7a2b1b8c90d093af0cf75a24b7a6247f95a18742 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 16 Sep 2021 17:41:26 +0200 Subject: [PATCH] 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). --- ws2812/gen-ws2812-arm.go | 27 ++- ws2812/ws2812-asm_cortexm.go | 322 +++++++++++++++++++++++++++++++++-- 2 files changed, 334 insertions(+), 15 deletions(-) diff --git a/ws2812/gen-ws2812-arm.go b/ws2812/gen-ws2812-arm.go index a25738c..8f9b14e 100644 --- a/ws2812/gen-ws2812-arm.go +++ b/ws2812/gen-ws2812-arm.go @@ -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 diff --git a/ws2812/ws2812-asm_cortexm.go b/ws2812/ws2812-asm_cortexm.go index 30f5941..de32ee9 100644 --- a/ws2812/ws2812-asm_cortexm.go +++ b/ws2812/ws2812-asm_cortexm.go @@ -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{}{