mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-04 15:07:46 +00:00
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.
This commit is contained in:
committed by
Ron Evans
parent
06e298c514
commit
f0a260be66
+40
-32
@@ -6,11 +6,49 @@ package ws2812
|
||||
// This file implements the WS2812 protocol for AVR microcontrollers.
|
||||
|
||||
import (
|
||||
"device/avr"
|
||||
"machine"
|
||||
"runtime/interrupt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#include <stdint.h>
|
||||
|
||||
__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:
|
||||
|
||||
Reference in New Issue
Block a user