From df64ce8f506a60def91bf9946b80f0bad04a4e09 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 16 Oct 2020 14:00:14 +0200 Subject: [PATCH] ws2812: make AVR support more robust * Merge AVR support for Digispark and non-Digispark * Fix the error "inline assembly requires more registers than available" * Use a single file for all AVR targets, in a similar style as the Xtensa support. --- Makefile | 2 ++ examples/ws2812/arduino.go | 9 ++++++ examples/ws2812/others.go | 2 +- ws2812/ws2812.go | 3 ++ ws2812/ws2812_avr.go | 58 ++++++++++++++++++++++++++++++++++ ws2812/ws2812_avr_16m.go | 49 ---------------------------- ws2812/ws2812_avr_digispark.go | 50 ----------------------------- ws2812/ws2812_xtensa.go | 3 -- 8 files changed, 73 insertions(+), 103 deletions(-) create mode 100644 examples/ws2812/arduino.go create mode 100644 ws2812/ws2812_avr.go delete mode 100644 ws2812/ws2812_avr_16m.go delete mode 100644 ws2812/ws2812_avr_digispark.go diff --git a/Makefile b/Makefile index 4808532..19047d4 100644 --- a/Makefile +++ b/Makefile @@ -132,6 +132,8 @@ smoke-test: tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/ws2812 @md5sum ./build/test.hex ifneq ($(AVR), 0) + tinygo build -size short -o ./build/test.hex -target=arduino ./examples/ws2812 + @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=digispark ./examples/ws2812 @md5sum ./build/test.hex endif diff --git a/examples/ws2812/arduino.go b/examples/ws2812/arduino.go new file mode 100644 index 0000000..bcb26da --- /dev/null +++ b/examples/ws2812/arduino.go @@ -0,0 +1,9 @@ +// +build arduino + +package main + +import "machine" + +// Replace neo in the code below to match the pin +// that you are using if different. +var neo = machine.D2 diff --git a/examples/ws2812/others.go b/examples/ws2812/others.go index d0ea7d2..1db6b4c 100644 --- a/examples/ws2812/others.go +++ b/examples/ws2812/others.go @@ -1,4 +1,4 @@ -// +build !digispark +// +build !digispark,!arduino package main diff --git a/ws2812/ws2812.go b/ws2812/ws2812.go index 8cd4607..d87d12e 100644 --- a/ws2812/ws2812.go +++ b/ws2812/ws2812.go @@ -2,10 +2,13 @@ package ws2812 // import "tinygo.org/x/drivers/ws2812" import ( + "errors" "image/color" "machine" ) +var errUnknownClockSpeed = errors.New("ws2812: unknown CPU clock speed") + // Device wraps a pin object for an easy driver interface. type Device struct { Pin machine.Pin diff --git a/ws2812/ws2812_avr.go b/ws2812/ws2812_avr.go new file mode 100644 index 0000000..f4a4929 --- /dev/null +++ b/ws2812/ws2812_avr.go @@ -0,0 +1,58 @@ +// +build avr + +package ws2812 + +// This file implements the WS2812 protocol for AVR microcontrollers. + +import ( + "device/avr" + "machine" +) + +// Send a single byte using the WS2812 protocol. +func (d Device) WriteByte(c byte) error { + // On AVR, the port is always the same for setting and clearing a register + // so use only one. This avoids the following error: + // error: inline assembly requires more registers than available + // Probably this is about pointer registers, which are very limited on AVR. + port, maskSet := d.Pin.PortMaskSet() + _, maskClear := d.Pin.PortMaskClear() + + switch machine.CPUFrequency() { + case 16e6: // 16MHz + // 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 + lsl {value} ; [1] shift off the next bit, store it in C + 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 ; [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{}{ + "value": c, + "i": byte(8), + "maskSet": maskSet, + "portSet": port, + "maskClear": maskClear, + "portClear": port, + }) + return nil + default: + return errUnknownClockSpeed + } +} diff --git a/ws2812/ws2812_avr_16m.go b/ws2812/ws2812_avr_16m.go deleted file mode 100644 index 3309bf1..0000000 --- a/ws2812/ws2812_avr_16m.go +++ /dev/null @@ -1,49 +0,0 @@ -// +build atmega328p - -package ws2812 - -// This file implements the WS2812 protocol for 16MHz AVR microcontrollers. - -import ( - "device/avr" -) - -// Send a single byte using the WS2812 protocol. -func (d Device) WriteByte(c byte) error { - // For the AVR at 16MHz - 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 - lsl {value} ; [1] shift off the next bit, store it in C - 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 ; [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{}{ - "value": c, - "i": 8, - "maskSet": maskSet, - "portSet": portSet, - "maskClear": maskClear, - "portClear": portClear, - }) - return nil -} diff --git a/ws2812/ws2812_avr_digispark.go b/ws2812/ws2812_avr_digispark.go deleted file mode 100644 index c29c3a5..0000000 --- a/ws2812/ws2812_avr_digispark.go +++ /dev/null @@ -1,50 +0,0 @@ -// +build digispark - -package ws2812 - -// This file implements the WS2812 protocol for 16.5MHz Digispark AVR microcontrollers. -// This is a slightly different implementation than the one for the atmega to work around a compiler bug. - -import ( - "device/avr" -) - -// Send a single byte using the WS2812 protocol. -func (d Device) WriteByte(c byte) error { - // For the AVR at 16MHz - 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 - lsl {value} ; [1] shift off the next bit, store it in C - 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 ; [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{}{ - "value": c, - "i": byte(8), - "maskSet": maskSet, - "portSet": portSet, - "maskClear": maskClear, - "portClear": portClear, - }) - return nil -} diff --git a/ws2812/ws2812_xtensa.go b/ws2812/ws2812_xtensa.go index ab95c1c..72f93a4 100644 --- a/ws2812/ws2812_xtensa.go +++ b/ws2812/ws2812_xtensa.go @@ -4,13 +4,10 @@ package ws2812 import ( "device" - "errors" "machine" "unsafe" ) -var errUnknownClockSpeed = errors.New("ws2812: unknown CPU clock speed") - func (d Device) WriteByte(c byte) error { portSet, maskSet := d.Pin.PortMaskSet() portClear, maskClear := d.Pin.PortMaskClear()