From 4ff437e5d47177ffaa5c9839d35e733cb105f3ce Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Wed, 13 Feb 2019 15:16:50 +0100 Subject: [PATCH] ws2812: add nrf52 support, update timings for m0 and avr This commit adds support for the nrf52, which is a Cortex-M4 running at 64MHz. While implementing this, I discovered that the timings for AVR/16MHz and Cortex-M0 at 16MHz weren't quite right. They do work, but were slightly out of spec. So I fixed this as well. --- ws2812/ws2812_avr_16m.go | 13 +++-- ws2812/ws2812_m0_16m.go | 24 +++++---- ws2812/ws2812_m4_64m.go | 107 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 12 deletions(-) create mode 100644 ws2812/ws2812_m4_64m.go diff --git a/ws2812/ws2812_avr_16m.go b/ws2812/ws2812_avr_16m.go index cb1a850..69a1f0e 100644 --- a/ws2812/ws2812_avr_16m.go +++ b/ws2812/ws2812_avr_16m.go @@ -14,6 +14,12 @@ func (d Device) WriteByte(c byte) error { portSet, maskSet := d.Pin.PortMaskSet() portClear, maskClear := d.Pin.PortMaskClear() + // See: + // https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/ + // T0H: 4 cycles or 250ns + // T0L: 14 cycles or 875ns -> together 18 cycles or 1125ns + // T1H: 9 cycles or 562ns + // T1L: 8 cycles or 500ns -> together 17 cycles or 1062ns avr.AsmFull(` send_bit: st {portSet}, {maskSet} ; [2] set output high @@ -21,13 +27,14 @@ func (d Device) WriteByte(c byte) error { brcs skip_store ; [1/2] branch if this bit is high (long pulse) st {portClear}, {maskClear} ; [2] set output low (short pulse) skip_store: - nop ; [6] wait before changing the output again - nop - nop + nop ; [4] wait before changing the output again nop nop nop st {portClear}, {maskClear} ; [2] set output low (end of pulse) + nop ; [3] + nop + nop subi {i}, 1 ; [1] subtract one (for the loop) brne send_bit ; [1/2] send the next bit, if not at the end of the loop `, map[string]interface{}{ diff --git a/ws2812/ws2812_m0_16m.go b/ws2812/ws2812_m0_16m.go index 11df526..64db6b3 100644 --- a/ws2812/ws2812_m0_16m.go +++ b/ws2812/ws2812_m0_16m.go @@ -15,23 +15,29 @@ func (d Device) WriteByte(c byte) error { portSet, maskSet := d.Pin.PortMaskSet() portClear, maskClear := d.Pin.PortMaskClear() + // See: + // https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/ + // T0H: 4 cycles or 250ns + // T0L: 14 cycles or 875ns -> together 18 cycles or 1125ns + // T1H: 10 cycles or 625ns + // T1H: 8 cycles or 500ns -> together 18 cycles or 1125ns value := uint32(c) << 24 arm.AsmFull(` send_bit: - str {maskSet}, {portSet} - lsls {value}, #1 - bcs.n skip_store - str {maskClear}, {portClear} + str {maskSet}, {portSet} @ [2] T0H and T0L start here + lsls {value}, #1 @ [1] + bcs.n skip_store @ [1/3] + str {maskClear}, {portClear} @ [2] T0H -> T0L transition skip_store: + nop @ [4] nop nop nop + str {maskClear}, {portClear} @ [2] T1H -> T1L transition + nop @ [2] nop - nop - nop - str {maskClear}, {portClear} - subs {i}, #1 - bne.n send_bit + subs {i}, #1 @ [1] + bne.n send_bit @ [1/3] `, map[string]interface{}{ "value": value, "i": 8, diff --git a/ws2812/ws2812_m4_64m.go b/ws2812/ws2812_m4_64m.go new file mode 100644 index 0000000..4090b80 --- /dev/null +++ b/ws2812/ws2812_m4_64m.go @@ -0,0 +1,107 @@ +// +build nrf52 + +package ws2812 + +// This file implements the WS2812 protocol for 64MHz Cortex-M4 +// microcontrollers. + +import ( + "device/arm" +) + +// Send a single byte using the WS2812 protocol. +func (d Device) WriteByte(c byte) error { + // For the Cortex-M4 at 64MHz + portSet, maskSet := d.Pin.PortMaskSet() + portClear, maskClear := d.Pin.PortMaskClear() + + // See: + // https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/ + // T0H: 14-16 cycles or 218.75ns - 250.00ns + // T0L: 54-56 cycles or 843.75ns - 875.00ns + // +: 68-72 cycles or 1062.50ns - 1125.00ns + // T1H: 36-38 cycles or 562.50ns - 593.75ns + // T1L: 30-32 cycles or 468.75ns - 500.0ns + // +: 66-70 cycles or 1031.25ns - 1093.75ns + // A branch is treated here as 1-3 cycles, because apparently it might get + // speculated. This is more of a guess than hard fact, because the only docs + // by ARM that state this are now considered superseded (by what?). + value := uint32(c) << 24 + arm.AsmFull(` + send_bit: + str {maskSet}, {portSet} @ [2] T0H and T0L start here + lsls {value}, #1 @ [1] + nop @ [10] + nop + nop + nop + nop + nop + nop + nop + nop + nop + bcs.n skip_store @ [1-3] + str {maskClear}, {portClear} @ [2] T0H -> T0L transition + skip_store: + nop @ [22] + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + str {maskClear}, {portClear} @ [2] T1H -> T1L transition + nop @ [26] + 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 send_bit @ [1-3] + `, map[string]interface{}{ + "value": value, + "i": 8, + "maskSet": maskSet, + "portSet": portSet, + "maskClear": maskClear, + "portClear": portClear, + }) + return nil +}