Skip to content

Commit f0a260b

Browse files
aykevldeadprogram
authored andcommitted
ws2812: convert AVR assembly to C inline assembly
See #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.
1 parent 06e298c commit f0a260b

File tree

1 file changed

+40
-32
lines changed

1 file changed

+40
-32
lines changed

ws2812/ws2812_avr.go

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,49 @@ package ws2812
66
// This file implements the WS2812 protocol for AVR microcontrollers.
77

88
import (
9-
"device/avr"
109
"machine"
1110
"runtime/interrupt"
11+
"unsafe"
1212
)
1313

14+
/*
15+
#include <stdint.h>
16+
17+
__attribute__((always_inline))
18+
void ws2812_writeByte16(char c, uint8_t *port, uint8_t maskSet, uint8_t maskClear) {
19+
// See:
20+
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
21+
// T0H: 4 cycles or 250ns
22+
// T0L: 14 cycles or 875ns -> together 18 cycles or 1125ns
23+
// T1H: 9 cycles or 562ns
24+
// T1L: 8 cycles or 500ns -> together 17 cycles or 1062ns
25+
char i = 8;
26+
__asm__ __volatile__(
27+
"1:\n"
28+
"\t st %[port], %[maskSet] ; [2] set output high\n"
29+
"\t lsl %[value] ; [1] shift off the next bit, store it in C\n"
30+
"\t brcs 2f ; [1/2] branch if this bit is high (long pulse)\n"
31+
"\t st %[port], %[maskClear] ; [2] set output low (short pulse)\n"
32+
"\t2:\n"
33+
"\t nop ; [4] wait before changing the output again\n"
34+
"\t nop\n"
35+
"\t nop\n"
36+
"\t nop\n"
37+
"\t st %[port], %[maskClear] ; [2] set output low (end of pulse)\n"
38+
"\t nop ; [3]\n"
39+
"\t nop\n"
40+
"\t nop\n"
41+
"\t subi %[i], 1 ; [1] subtract one (for the loop)\n"
42+
"\t brne 1b ; [1/2] send the next bit, if not at the end of the loop\n"
43+
: [value]"+r"(c),
44+
[i]"+r"(i)
45+
: [maskSet]"r"(maskSet),
46+
[maskClear]"r"(maskClear),
47+
[port]"m"(*port));
48+
}
49+
*/
50+
import "C"
51+
1452
// Send a single byte using the WS2812 protocol.
1553
func (d Device) WriteByte(c byte) error {
1654
// 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 {
2361

2462
switch machine.CPUFrequency() {
2563
case 16e6: // 16MHz
26-
// See:
27-
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
28-
// T0H: 4 cycles or 250ns
29-
// T0L: 14 cycles or 875ns -> together 18 cycles or 1125ns
30-
// T1H: 9 cycles or 562ns
31-
// T1L: 8 cycles or 500ns -> together 17 cycles or 1062ns
32-
avr.AsmFull(`
33-
send_bit:
34-
st {portSet}, {maskSet} ; [2] set output high
35-
lsl {value} ; [1] shift off the next bit, store it in C
36-
brcs skip_store ; [1/2] branch if this bit is high (long pulse)
37-
st {portClear}, {maskClear} ; [2] set output low (short pulse)
38-
skip_store:
39-
nop ; [4] wait before changing the output again
40-
nop
41-
nop
42-
nop
43-
st {portClear}, {maskClear} ; [2] set output low (end of pulse)
44-
nop ; [3]
45-
nop
46-
nop
47-
subi {i}, 1 ; [1] subtract one (for the loop)
48-
brne send_bit ; [1/2] send the next bit, if not at the end of the loop
49-
`, map[string]interface{}{
50-
"value": c,
51-
"i": byte(8),
52-
"maskSet": maskSet,
53-
"portSet": port,
54-
"maskClear": maskClear,
55-
"portClear": port,
56-
})
64+
C.ws2812_writeByte16(C.char(c), (*uint8)(unsafe.Pointer(port)), maskSet, maskClear)
5765
interrupt.Restore(mask)
5866
return nil
5967
default:

0 commit comments

Comments
 (0)