Skip to content

Commit

Permalink
ws2812: convert AVR assembly to C inline assembly
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
aykevl authored and deadprogram committed Apr 21, 2022
1 parent 06e298c commit f0a260b
Showing 1 changed file with 40 additions and 32 deletions.
72 changes: 40 additions & 32 deletions ws2812/ws2812_avr.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down

0 comments on commit f0a260b

Please sign in to comment.