From f0a260be663077f86608162a36e928ddeee651f3 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 14 Apr 2022 19:34:15 +0200 Subject: [PATCH] ws2812: convert AVR assembly to C inline assembly See https://github.com/tinygo-org/drivers/pull/401 for details. I haven't converted it to autogenerated assembly because AVR is different from many other architectures (8-bit, among others) and it didn't seem worth the effort as many chips run at 16MHz anyway. I ran the two AVR smoke tests for the ws2812 driver and the resulting binary is exactly the same. --- ws2812/ws2812_avr.go | 72 ++++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/ws2812/ws2812_avr.go b/ws2812/ws2812_avr.go index 481c10b..57857bb 100644 --- a/ws2812/ws2812_avr.go +++ b/ws2812/ws2812_avr.go @@ -6,11 +6,49 @@ package ws2812 // This file implements the WS2812 protocol for AVR microcontrollers. import ( - "device/avr" "machine" "runtime/interrupt" + "unsafe" ) +/* +#include + +__attribute__((always_inline)) +void ws2812_writeByte16(char c, uint8_t *port, uint8_t maskSet, uint8_t maskClear) { + // 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 + char i = 8; + __asm__ __volatile__( + "1:\n" + "\t st %[port], %[maskSet] ; [2] set output high\n" + "\t lsl %[value] ; [1] shift off the next bit, store it in C\n" + "\t brcs 2f ; [1/2] branch if this bit is high (long pulse)\n" + "\t st %[port], %[maskClear] ; [2] set output low (short pulse)\n" + "\t2:\n" + "\t nop ; [4] wait before changing the output again\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t st %[port], %[maskClear] ; [2] set output low (end of pulse)\n" + "\t nop ; [3]\n" + "\t nop\n" + "\t nop\n" + "\t subi %[i], 1 ; [1] subtract one (for the loop)\n" + "\t brne 1b ; [1/2] send the next bit, if not at the end of the loop\n" + : [value]"+r"(c), + [i]"+r"(i) + : [maskSet]"r"(maskSet), + [maskClear]"r"(maskClear), + [port]"m"(*port)); +} +*/ +import "C" + // 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 @@ -23,37 +61,7 @@ func (d Device) WriteByte(c byte) error { 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, - }) + C.ws2812_writeByte16(C.char(c), (*uint8)(unsafe.Pointer(port)), maskSet, maskClear) interrupt.Restore(mask) return nil default: