mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-17 05:13:23 +00:00
ws2812: generate assembly instead of handwriting it
Previously, a new implementation had to be written for each processor
speed. This is tedious and error-prone, therefore I've rewritten all of
the assembly code using a tool that will calculate exactly how many NOP
instructions are required.
I've also switched from build tags to a switch statement over all
processor speeds.
All in all, this means that:
- Chips with a supported CPU performance will automatically be
supported (no build tags necessary).
- Adding a new chip is as easy as adding the MHz count to the
generator script and to the switch statement.
Right now this is only for the Cortex-M, but it's certainly feasible to
extend this to other architectures such as the AVR.
This commit is contained in:
committed by
Ron Evans
parent
9a3bfd4826
commit
b3af3f594e
@@ -0,0 +1,185 @@
|
|||||||
|
// +build none
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This file generates assembly to precisely time the WS2812 protocol for
|
||||||
|
// various chips. Just add a new frequency below and run `go generate` to add
|
||||||
|
// the new assembly implementation - no fiddly timings to calculate and no nops
|
||||||
|
// to count!
|
||||||
|
//
|
||||||
|
// Right now this is specific to Cortex-M chips and assume the following things:
|
||||||
|
// - Arithmetic operations (shift, add, sub) take up 1 clock cycle.
|
||||||
|
// - The nop instruction also takes up 1 clock cycle.
|
||||||
|
// - Store instructions (to the GPIO pins) take up 2 clock cycles.
|
||||||
|
// - Branch instructions can take up 1 to 3 clock cycles. On the Cortex-M0, this
|
||||||
|
// depends on whether the branch is taken or not. On the M4, the documentation
|
||||||
|
// is less clear but it appears the instruction is still 1 to 3 cycles
|
||||||
|
// (possibly including some branch prediction).
|
||||||
|
// It is certainly possible to extend this to other architectures, such as AVR
|
||||||
|
// and RISC-V if needed.
|
||||||
|
|
||||||
|
// Clock frequencies to support, in MHz.
|
||||||
|
var clockFrequencies = []int{16, 48, 64, 120}
|
||||||
|
|
||||||
|
func writeImplementation(f *os.File, megahertz int) error {
|
||||||
|
cycleTimeNS := 1 / float64(megahertz)
|
||||||
|
// These timings are taken from the table "Updated simplified timing
|
||||||
|
// constraints for NeoPixel strings" at:
|
||||||
|
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
|
||||||
|
// Here is a copy:
|
||||||
|
// Symbol Parameter Min Typical Max Units
|
||||||
|
// T0H 0 code, high voltage time 200 350 500 ns
|
||||||
|
// T1H 1 code, high voltage time 550 700 5500 ns
|
||||||
|
// TLD data, low voltage time 450 600 5000 ns
|
||||||
|
// TLL latch, low voltage time 6000 ns
|
||||||
|
// T0H is the time the pin should be high to send a "0" bit.
|
||||||
|
// T1H is the time the pin should be high to send a "1" bit.
|
||||||
|
// TLD is the time the pin should be low between bits.
|
||||||
|
// TLL is the time the pin should be low to apply (latch) the new colors.
|
||||||
|
minCyclesT0H := int(math.Ceil(0.200 / cycleTimeNS))
|
||||||
|
maxCyclesT0H := int(math.Floor(0.500 / cycleTimeNS))
|
||||||
|
minCyclesT1H := int(math.Ceil(0.550 / cycleTimeNS))
|
||||||
|
maxCyclesT1H := int(math.Floor(5.500 / cycleTimeNS))
|
||||||
|
minCyclesTLD := int(math.Ceil(0.450 / cycleTimeNS))
|
||||||
|
|
||||||
|
// Assembly template:
|
||||||
|
// 1: @ send_bit
|
||||||
|
// str {maskSet}, {portSet} @ [2] T0H and T0L start here
|
||||||
|
// ...delay 1
|
||||||
|
// lsls {value}, #1 @ [1]
|
||||||
|
// bcs.n 2f @ [1/3] skip_store
|
||||||
|
// str {maskClear}, {portClear} @ [2] T0H -> T0L transition
|
||||||
|
// 2: @ skip_store
|
||||||
|
// ...delay 2
|
||||||
|
// str {maskClear}, {portClear} @ [2] T1H -> T1L transition
|
||||||
|
// ...delay 3
|
||||||
|
// subs {i}, #1 @ [1]
|
||||||
|
// bne.n 1b @ [1/3] send_bit
|
||||||
|
//
|
||||||
|
// We need to calculate the number of nop instructions in the three delays.
|
||||||
|
|
||||||
|
// Determine number of nops for delay1. This is primarily based on the T0H
|
||||||
|
// delay, which is relatively short (<500ns).
|
||||||
|
minBaseCyclesT0H := 1 + 1 + 2 // shift + branch + store
|
||||||
|
maxBaseCyclesT0H := 1 + 3 + 2 // shift + branch + store
|
||||||
|
delay1 := minCyclesT0H - minBaseCyclesT0H
|
||||||
|
if delay1 < 0 {
|
||||||
|
// The minCyclesT0H constraint could not be satisfied. Don't insert
|
||||||
|
// nops, in the hope that it isn't too long.
|
||||||
|
delay1 = 0
|
||||||
|
}
|
||||||
|
if delay1+maxBaseCyclesT0H > maxCyclesT0H {
|
||||||
|
return fmt.Errorf("MCU appears to be too slow to satisfy minimum requirements for the T0H signal")
|
||||||
|
}
|
||||||
|
actualMinCyclesT0H := minBaseCyclesT0H + delay1
|
||||||
|
actualMaxCyclesT0H := maxBaseCyclesT0H + delay1
|
||||||
|
actualMinNanosecondsT0H := float64(actualMinCyclesT0H) / float64(megahertz) * 1000
|
||||||
|
actualMaxNanosecondsT0H := float64(actualMaxCyclesT0H) / float64(megahertz) * 1000
|
||||||
|
|
||||||
|
// Determine number of nops for delay2. This is delay1 plus some extra time
|
||||||
|
// so that the pulse is long enough for T1H.
|
||||||
|
minBaseCyclesT1H := delay1 + 1 + 1 + 2 // delay1 + shift + branch + store
|
||||||
|
maxBaseCyclesT1H := delay1 + 1 + 3 + 2 // delay1 + shift + branch + store
|
||||||
|
delay2 := minCyclesT1H - minBaseCyclesT1H
|
||||||
|
if delay2 < 0 {
|
||||||
|
delay2 = 0
|
||||||
|
}
|
||||||
|
if delay2+maxBaseCyclesT1H > maxCyclesT1H {
|
||||||
|
// Unlikely, we have 5500ns for this operation.
|
||||||
|
return fmt.Errorf("MCU appears to be too slow to satisfy minimum requirements for the T1H signal")
|
||||||
|
}
|
||||||
|
actualMinCyclesT1H := minBaseCyclesT1H + delay2
|
||||||
|
actualMaxCyclesT1H := maxBaseCyclesT1H + delay2
|
||||||
|
actualMinNanosecondsT1H := float64(actualMinCyclesT1H) / float64(megahertz) * 1000
|
||||||
|
actualMaxNanosecondsT1H := float64(actualMaxCyclesT1H) / float64(megahertz) * 1000
|
||||||
|
|
||||||
|
// Determine number of nops for delay3. This is based on the TLD delay, the
|
||||||
|
// time between two high pulses.
|
||||||
|
minBaseCyclesTLD := 1 + 1 + 2 // subtraction + branch + store (in next cycle)
|
||||||
|
delay3 := minCyclesTLD - minBaseCyclesTLD
|
||||||
|
if delay3 < 0 {
|
||||||
|
delay3 = 0
|
||||||
|
}
|
||||||
|
actualMinCyclesTLD := minBaseCyclesTLD + delay3
|
||||||
|
actualMinNanosecondsTLD := float64(actualMinCyclesTLD) / float64(megahertz) * 1000
|
||||||
|
|
||||||
|
// Create the Go function in a buffer. Using a buffer here to be able to
|
||||||
|
// ignore I/O errors.
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
fmt.Fprintf(buf, "\n")
|
||||||
|
fmt.Fprintf(buf, "func (d Device) writeByte%d(c byte) {\n", megahertz)
|
||||||
|
fmt.Fprintf(buf, " portSet, maskSet := d.Pin.PortMaskSet()\n")
|
||||||
|
fmt.Fprintf(buf, " portClear, maskClear := d.Pin.PortMaskClear()\n")
|
||||||
|
fmt.Fprintf(buf, "\n")
|
||||||
|
fmt.Fprintf(buf, " // Timings:\n")
|
||||||
|
fmt.Fprintf(buf, " // T0H: %2d - %2d cycles or %.1fns - %.1fns\n", actualMinCyclesT0H, actualMaxCyclesT0H, actualMinNanosecondsT0H, actualMaxNanosecondsT0H)
|
||||||
|
fmt.Fprintf(buf, " // T1H: %2d - %2d cycles or %.1fns - %.1fns\n", actualMinCyclesT1H, actualMaxCyclesT1H, actualMinNanosecondsT1H, actualMaxNanosecondsT1H)
|
||||||
|
fmt.Fprintf(buf, " // TLD: %2d - cycles or %.1fns -\n", actualMinCyclesTLD, actualMinNanosecondsTLD)
|
||||||
|
fmt.Fprintf(buf, " mask := interrupt.Disable()\n")
|
||||||
|
fmt.Fprintf(buf, " value := uint32(c) << 24\n")
|
||||||
|
fmt.Fprintf(buf, " device.AsmFull(`\n")
|
||||||
|
fmt.Fprintf(buf, " 1: @ send_bit\n")
|
||||||
|
fmt.Fprintf(buf, " str {maskSet}, {portSet} @ [2] T0H and T0L start here\n")
|
||||||
|
buf.WriteString(strings.Repeat(" nop\n", delay1))
|
||||||
|
fmt.Fprintf(buf, " lsls {value}, #1 @ [1]\n")
|
||||||
|
fmt.Fprintf(buf, " bcs.n 2f @ [1/3] skip_store\n")
|
||||||
|
fmt.Fprintf(buf, " str {maskClear}, {portClear} @ [2] T0H -> T0L transition\n")
|
||||||
|
fmt.Fprintf(buf, " 2: @ skip_store\n")
|
||||||
|
buf.WriteString(strings.Repeat(" nop\n", delay2))
|
||||||
|
fmt.Fprintf(buf, " str {maskClear}, {portClear} @ [2] T1H -> T1L transition\n")
|
||||||
|
buf.WriteString(strings.Repeat(" nop\n", delay3))
|
||||||
|
fmt.Fprintf(buf, " subs {i}, #1 @ [1]\n")
|
||||||
|
fmt.Fprintf(buf, " bne.n 1b @ [1/3] send_bit\n")
|
||||||
|
fmt.Fprintf(buf, " `, map[string]interface{}{")
|
||||||
|
buf.WriteString(`
|
||||||
|
"value": value,
|
||||||
|
"i": 8,
|
||||||
|
"maskSet": maskSet,
|
||||||
|
"portSet": portSet,
|
||||||
|
"maskClear": maskClear,
|
||||||
|
"portClear": portClear,
|
||||||
|
})
|
||||||
|
interrupt.Restore(mask)
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
// Now write the buffer contents (with the assembly function) to a file.
|
||||||
|
_, err := f.Write(buf.Bytes())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
f, err := os.Create("ws2812-asm_cortexm.go")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "could not generate WS2812 assembly code:", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
f.WriteString(`// +build cortexm
|
||||||
|
|
||||||
|
package ws2812
|
||||||
|
|
||||||
|
// Warning: autogenerated file. Instead of modifying this file, change
|
||||||
|
// gen-ws2812-arm.go and run "go generate".
|
||||||
|
|
||||||
|
import (
|
||||||
|
"device"
|
||||||
|
"runtime/interrupt"
|
||||||
|
)
|
||||||
|
`)
|
||||||
|
for _, megahertz := range clockFrequencies {
|
||||||
|
err := writeImplementation(f, megahertz)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "could not generate WS2812 assembly code for %dMHz: %s\n", megahertz, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,354 @@
|
|||||||
|
// +build cortexm
|
||||||
|
|
||||||
|
package ws2812
|
||||||
|
|
||||||
|
// Warning: autogenerated file. Instead of modifying this file, change
|
||||||
|
// gen-ws2812-arm.go and run "go generate".
|
||||||
|
|
||||||
|
import (
|
||||||
|
"device"
|
||||||
|
"runtime/interrupt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d Device) writeByte16(c byte) {
|
||||||
|
portSet, maskSet := d.Pin.PortMaskSet()
|
||||||
|
portClear, maskClear := d.Pin.PortMaskClear()
|
||||||
|
|
||||||
|
// Timings:
|
||||||
|
// T0H: 4 - 6 cycles or 250.0ns - 375.0ns
|
||||||
|
// T1H: 9 - 11 cycles or 562.5ns - 687.5ns
|
||||||
|
// TLD: 8 - cycles or 500.0ns -
|
||||||
|
mask := interrupt.Disable()
|
||||||
|
value := uint32(c) << 24
|
||||||
|
device.AsmFull(`
|
||||||
|
1: @ send_bit
|
||||||
|
str {maskSet}, {portSet} @ [2] T0H and T0L start here
|
||||||
|
lsls {value}, #1 @ [1]
|
||||||
|
bcs.n 2f @ [1/3] skip_store
|
||||||
|
str {maskClear}, {portClear} @ [2] T0H -> T0L transition
|
||||||
|
2: @ skip_store
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
str {maskClear}, {portClear} @ [2] T1H -> T1L transition
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
subs {i}, #1 @ [1]
|
||||||
|
bne.n 1b @ [1/3] send_bit
|
||||||
|
`, map[string]interface{}{
|
||||||
|
"value": value,
|
||||||
|
"i": 8,
|
||||||
|
"maskSet": maskSet,
|
||||||
|
"portSet": portSet,
|
||||||
|
"maskClear": maskClear,
|
||||||
|
"portClear": portClear,
|
||||||
|
})
|
||||||
|
interrupt.Restore(mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Device) writeByte48(c byte) {
|
||||||
|
portSet, maskSet := d.Pin.PortMaskSet()
|
||||||
|
portClear, maskClear := d.Pin.PortMaskClear()
|
||||||
|
|
||||||
|
// Timings:
|
||||||
|
// T0H: 10 - 12 cycles or 208.3ns - 250.0ns
|
||||||
|
// T1H: 27 - 29 cycles or 562.5ns - 604.2ns
|
||||||
|
// TLD: 22 - cycles or 458.3ns -
|
||||||
|
mask := interrupt.Disable()
|
||||||
|
value := uint32(c) << 24
|
||||||
|
device.AsmFull(`
|
||||||
|
1: @ send_bit
|
||||||
|
str {maskSet}, {portSet} @ [2] T0H and T0L start here
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
lsls {value}, #1 @ [1]
|
||||||
|
bcs.n 2f @ [1/3] skip_store
|
||||||
|
str {maskClear}, {portClear} @ [2] T0H -> T0L transition
|
||||||
|
2: @ skip_store
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
str {maskClear}, {portClear} @ [2] T1H -> T1L transition
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
subs {i}, #1 @ [1]
|
||||||
|
bne.n 1b @ [1/3] send_bit
|
||||||
|
`, map[string]interface{}{
|
||||||
|
"value": value,
|
||||||
|
"i": 8,
|
||||||
|
"maskSet": maskSet,
|
||||||
|
"portSet": portSet,
|
||||||
|
"maskClear": maskClear,
|
||||||
|
"portClear": portClear,
|
||||||
|
})
|
||||||
|
interrupt.Restore(mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Device) writeByte64(c byte) {
|
||||||
|
portSet, maskSet := d.Pin.PortMaskSet()
|
||||||
|
portClear, maskClear := d.Pin.PortMaskClear()
|
||||||
|
|
||||||
|
// Timings:
|
||||||
|
// T0H: 13 - 15 cycles or 203.1ns - 234.4ns
|
||||||
|
// T1H: 36 - 38 cycles or 562.5ns - 593.8ns
|
||||||
|
// TLD: 29 - cycles or 453.1ns -
|
||||||
|
mask := interrupt.Disable()
|
||||||
|
value := uint32(c) << 24
|
||||||
|
device.AsmFull(`
|
||||||
|
1: @ send_bit
|
||||||
|
str {maskSet}, {portSet} @ [2] T0H and T0L start here
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
lsls {value}, #1 @ [1]
|
||||||
|
bcs.n 2f @ [1/3] skip_store
|
||||||
|
str {maskClear}, {portClear} @ [2] T0H -> T0L transition
|
||||||
|
2: @ skip_store
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
str {maskClear}, {portClear} @ [2] T1H -> T1L transition
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
subs {i}, #1 @ [1]
|
||||||
|
bne.n 1b @ [1/3] send_bit
|
||||||
|
`, map[string]interface{}{
|
||||||
|
"value": value,
|
||||||
|
"i": 8,
|
||||||
|
"maskSet": maskSet,
|
||||||
|
"portSet": portSet,
|
||||||
|
"maskClear": maskClear,
|
||||||
|
"portClear": portClear,
|
||||||
|
})
|
||||||
|
interrupt.Restore(mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Device) writeByte120(c byte) {
|
||||||
|
portSet, maskSet := d.Pin.PortMaskSet()
|
||||||
|
portClear, maskClear := d.Pin.PortMaskClear()
|
||||||
|
|
||||||
|
// Timings:
|
||||||
|
// T0H: 24 - 26 cycles or 200.0ns - 216.7ns
|
||||||
|
// T1H: 66 - 68 cycles or 550.0ns - 566.7ns
|
||||||
|
// TLD: 54 - cycles or 450.0ns -
|
||||||
|
mask := interrupt.Disable()
|
||||||
|
value := uint32(c) << 24
|
||||||
|
device.AsmFull(`
|
||||||
|
1: @ send_bit
|
||||||
|
str {maskSet}, {portSet} @ [2] T0H and T0L start here
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
lsls {value}, #1 @ [1]
|
||||||
|
bcs.n 2f @ [1/3] skip_store
|
||||||
|
str {maskClear}, {portClear} @ [2] T0H -> T0L transition
|
||||||
|
2: @ skip_store
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
str {maskClear}, {portClear} @ [2] T1H -> T1L transition
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
nop
|
||||||
|
subs {i}, #1 @ [1]
|
||||||
|
bne.n 1b @ [1/3] send_bit
|
||||||
|
`, map[string]interface{}{
|
||||||
|
"value": value,
|
||||||
|
"i": 8,
|
||||||
|
"maskSet": maskSet,
|
||||||
|
"portSet": portSet,
|
||||||
|
"maskClear": maskClear,
|
||||||
|
"portClear": portClear,
|
||||||
|
})
|
||||||
|
interrupt.Restore(mask)
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
// Package ws2812 implements a driver for WS2812 and SK6812 RGB LED strips.
|
// Package ws2812 implements a driver for WS2812 and SK6812 RGB LED strips.
|
||||||
package ws2812 // import "tinygo.org/x/drivers/ws2812"
|
package ws2812 // import "tinygo.org/x/drivers/ws2812"
|
||||||
|
|
||||||
|
//go:generate go run gen-ws2812-arm.go
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// +build cortexm
|
||||||
|
|
||||||
|
package ws2812
|
||||||
|
|
||||||
|
// This file implements the WS2812 protocol for various Cortex-M
|
||||||
|
// microcontrollers. It is intended to work with various variants: M0, M0+, M3,
|
||||||
|
// and M4. Because machine.CPUFrequency() is usually a constant, the value will
|
||||||
|
// usually be constant-propagated and the switch below will be a direct
|
||||||
|
// (inlinable function) - thus there is usually no code size penalty over build
|
||||||
|
// tags per CPU speed.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"machine"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Send a single byte using the WS2812 protocol.
|
||||||
|
func (d Device) WriteByte(c byte) error {
|
||||||
|
switch machine.CPUFrequency() {
|
||||||
|
case 16_000_000: // 16MHz
|
||||||
|
d.writeByte16(c)
|
||||||
|
return nil
|
||||||
|
case 48_000_000: // 48MHz
|
||||||
|
d.writeByte48(c)
|
||||||
|
return nil
|
||||||
|
case 64_000_000: // 64MHz
|
||||||
|
d.writeByte64(c)
|
||||||
|
return nil
|
||||||
|
case 120_000_000: // 120MHz
|
||||||
|
d.writeByte120(c)
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return errUnknownClockSpeed
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
// +build nrf51
|
|
||||||
|
|
||||||
package ws2812
|
|
||||||
|
|
||||||
// This file implements the WS2812 protocol for 16MHz Cortex-M0
|
|
||||||
// microcontrollers.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"device/arm"
|
|
||||||
"runtime/interrupt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Send a single byte using the WS2812 protocol.
|
|
||||||
func (d Device) WriteByte(c byte) error {
|
|
||||||
// For the Cortex-M0 at 16MHz
|
|
||||||
portSet, maskSet := d.Pin.PortMaskSet()
|
|
||||||
portClear, maskClear := d.Pin.PortMaskClear()
|
|
||||||
mask := interrupt.Disable()
|
|
||||||
|
|
||||||
// See:
|
|
||||||
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
|
|
||||||
// Note: timings have been increased slightly to also support ws2811 LEDs.
|
|
||||||
// T0H: 5 cycles or 312.5ns
|
|
||||||
// T0L: 14 cycles or 875.0ns -> together 19 cycles or 1187.5ns
|
|
||||||
// T1H: 11 cycles or 687.5ns
|
|
||||||
// T1H: 8 cycles or 500.0ns -> together 19 cycles or 1187.5ns
|
|
||||||
value := uint32(c) << 24
|
|
||||||
arm.AsmFull(`
|
|
||||||
1: @ send_bit
|
|
||||||
str {maskSet}, {portSet} @ [2] T0H and T0L start here
|
|
||||||
nop @ [1]
|
|
||||||
lsls {value}, #1 @ [1]
|
|
||||||
bcs.n 2f @ [1/3] skip_store
|
|
||||||
str {maskClear}, {portClear} @ [2] T0H -> T0L transition
|
|
||||||
2: @ skip_store
|
|
||||||
nop @ [4]
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
str {maskClear}, {portClear} @ [2] T1H -> T1L transition
|
|
||||||
nop @ [2]
|
|
||||||
nop
|
|
||||||
subs {i}, #1 @ [1]
|
|
||||||
bne.n 1b @ [1/3] send_bit
|
|
||||||
`, map[string]interface{}{
|
|
||||||
"value": value,
|
|
||||||
"i": 8,
|
|
||||||
"maskSet": maskSet,
|
|
||||||
"portSet": portSet,
|
|
||||||
"maskClear": maskClear,
|
|
||||||
"portClear": portClear,
|
|
||||||
})
|
|
||||||
interrupt.Restore(mask)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
// +build atsamd21
|
|
||||||
|
|
||||||
package ws2812
|
|
||||||
|
|
||||||
// This file implements the WS2812 protocol for 48MHz Cortex-M0
|
|
||||||
// microcontrollers.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"device/arm"
|
|
||||||
"runtime/interrupt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Send a single byte using the WS2812 protocol.
|
|
||||||
func (d Device) WriteByte(c byte) error {
|
|
||||||
// For the Cortex-M0 at 48MHz
|
|
||||||
portSet, maskSet := d.Pin.PortMaskSet()
|
|
||||||
portClear, maskClear := d.Pin.PortMaskClear()
|
|
||||||
mask := interrupt.Disable()
|
|
||||||
|
|
||||||
// See:
|
|
||||||
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
|
|
||||||
// T0H: 10 cycles or 208.3ns
|
|
||||||
// T0L: 39 cycles or 812.5ns -> together 49 cycles or 1020.8ns
|
|
||||||
// T1H: 27 cycles or 562.5ns
|
|
||||||
// T1L: 22 cycles or 458.3ns -> together 49 cycles or 1020.8ns
|
|
||||||
value := uint32(c) << 24
|
|
||||||
arm.AsmFull(`
|
|
||||||
1: @ send_bit
|
|
||||||
str {maskSet}, {portSet} @ [2] T0H and T0L start here
|
|
||||||
lsls {value}, #1 @ [1]
|
|
||||||
nop @ [6]
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
bcs.n 2f @ [1/3] skip_store
|
|
||||||
str {maskClear}, {portClear} @ [2] T0H -> T0L transition
|
|
||||||
2: @ skip_store
|
|
||||||
nop @ [15]
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
str {maskClear}, {portClear} @ [2] T1H -> T1L transition
|
|
||||||
nop @ [16]
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
subs {i}, #1 @ [1]
|
|
||||||
bne.n 1b @ [1/3] send_bit
|
|
||||||
`, map[string]interface{}{
|
|
||||||
"value": value,
|
|
||||||
"i": 8,
|
|
||||||
"maskSet": maskSet,
|
|
||||||
"portSet": portSet,
|
|
||||||
"maskClear": maskClear,
|
|
||||||
"portClear": portClear,
|
|
||||||
})
|
|
||||||
interrupt.Restore(mask)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
// +build atsamd51 atsame5x
|
|
||||||
|
|
||||||
package ws2812
|
|
||||||
|
|
||||||
// This file implements the WS2812 protocol for 120MHz Cortex-M4
|
|
||||||
// microcontrollers.
|
|
||||||
// Note: This implementation does not work with tinygo 0.9.0 or older.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"device/arm"
|
|
||||||
"runtime/interrupt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Send a single byte using the WS2812 protocol.
|
|
||||||
func (d Device) WriteByte(c byte) error {
|
|
||||||
// For the Cortex-M4 at 120MHz
|
|
||||||
portSet, maskSet := d.Pin.PortMaskSet()
|
|
||||||
portClear, maskClear := d.Pin.PortMaskClear()
|
|
||||||
mask := interrupt.Disable()
|
|
||||||
|
|
||||||
// See:
|
|
||||||
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
|
|
||||||
// T0H: 32-34 cycles or 266.67ns - 283.33ns
|
|
||||||
// T0L: 101-103 cycles or 841.67ns - 858.33ns
|
|
||||||
// +: 133-137 cycles or 1108.33ns - 1141.67ns
|
|
||||||
// T1H: 73-75 cycles or 608.33ns - 625.00ns
|
|
||||||
// T1L: 58-60 cycles or 483.33ns - 500.00ns
|
|
||||||
// +: 131-135 cycles or 1091.67ns - 1125.00ns
|
|
||||||
// A branch is treated here as 1-3 cycles, because apparently it might get
|
|
||||||
// speculated. This is more of a guess than hard fact, because the only docs
|
|
||||||
// by ARM that state this are now considered superseded (by what?).
|
|
||||||
value := uint32(c) << 24
|
|
||||||
arm.AsmFull(`
|
|
||||||
1: @ send_bit
|
|
||||||
str {maskSet}, {portSet} @ [2] T0H and T0L start here
|
|
||||||
lsls {value}, #1 @ [1]
|
|
||||||
nop @ [28]
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
bcs.n 2f @ [1-3] skip_store
|
|
||||||
str {maskClear}, {portClear} @ [2] T0H -> T0L transition
|
|
||||||
2: @ skip_store
|
|
||||||
nop @ [41]
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
str {maskClear}, {portClear} @ [2] T1H -> T1L transition
|
|
||||||
nop @ [54]
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
subs {i}, #1 @ [1]
|
|
||||||
bne.n 1b @ [1-3] send_bit
|
|
||||||
`, map[string]interface{}{
|
|
||||||
"value": value,
|
|
||||||
"i": 8,
|
|
||||||
"maskSet": maskSet,
|
|
||||||
"portSet": portSet,
|
|
||||||
"maskClear": maskClear,
|
|
||||||
"portClear": portClear,
|
|
||||||
})
|
|
||||||
interrupt.Restore(mask)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
// +build nrf52 nrf52840 nrf52833
|
|
||||||
|
|
||||||
package ws2812
|
|
||||||
|
|
||||||
// This file implements the WS2812 protocol for 64MHz Cortex-M4
|
|
||||||
// microcontrollers.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"device/arm"
|
|
||||||
"runtime/interrupt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Send a single byte using the WS2812 protocol.
|
|
||||||
func (d Device) WriteByte(c byte) error {
|
|
||||||
// For the Cortex-M4 at 64MHz
|
|
||||||
portSet, maskSet := d.Pin.PortMaskSet()
|
|
||||||
portClear, maskClear := d.Pin.PortMaskClear()
|
|
||||||
mask := interrupt.Disable()
|
|
||||||
|
|
||||||
// See:
|
|
||||||
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
|
|
||||||
// T0H: 17-19 cycles or 265.63ns - 296.88ns
|
|
||||||
// T0L: 54-56 cycles or 843.75ns - 875.00ns
|
|
||||||
// +: 71-75 cycles or 1109.38ns - 1171.88ns
|
|
||||||
// T1H: 39-41 cycles or 609.38ns - 640.63ns
|
|
||||||
// T1L: 30-32 cycles or 468.75ns - 500.0ns
|
|
||||||
// +: 69-73 cycles or 1078.13ns - 1140.63ns
|
|
||||||
// A branch is treated here as 1-3 cycles, because apparently it might get
|
|
||||||
// speculated. This is more of a guess than hard fact, because the only docs
|
|
||||||
// by ARM that state this are now considered superseded (by what?).
|
|
||||||
value := uint32(c) << 24
|
|
||||||
arm.AsmFull(`
|
|
||||||
1: @ send_bit
|
|
||||||
str {maskSet}, {portSet} @ [2] T0H and T0L start here
|
|
||||||
lsls {value}, #1 @ [1]
|
|
||||||
nop @ [13]
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
bcs.n 2f @ [1-3] skip_store
|
|
||||||
str {maskClear}, {portClear} @ [2] T0H -> T0L transition
|
|
||||||
2: @ skip_store
|
|
||||||
nop @ [22]
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
str {maskClear}, {portClear} @ [2] T1H -> T1L transition
|
|
||||||
nop @ [26]
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
nop
|
|
||||||
subs {i}, #1 @ [1]
|
|
||||||
bne.n 1b @ [1-3] send_bit
|
|
||||||
`, map[string]interface{}{
|
|
||||||
"value": value,
|
|
||||||
"i": 8,
|
|
||||||
"maskSet": maskSet,
|
|
||||||
"portSet": portSet,
|
|
||||||
"maskClear": maskClear,
|
|
||||||
"portClear": portClear,
|
|
||||||
})
|
|
||||||
interrupt.Restore(mask)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user