mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-08-16 04:43:25 +00:00
ws2812: make assembly generator architecture independent
This commit is contained in:
committed by
Ron Evans
parent
49c8810432
commit
050cf4dbbc
@@ -5,9 +5,11 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -34,10 +36,47 @@ import (
|
|||||||
// The timings deviate a little bit from the code here, but so far the timings
|
// The timings deviate a little bit from the code here, but so far the timings
|
||||||
// from wp.josh.com seem to be fine for the ws2812.
|
// from wp.josh.com seem to be fine for the ws2812.
|
||||||
|
|
||||||
// Clock frequencies to support, in MHz.
|
// Architecture implementation. Describes the template and the timings of the
|
||||||
var clockFrequencies = []int{16, 48, 64, 120, 168}
|
// blocks of instructions so that most code can remain architecture-independent.
|
||||||
|
type architectureImpl struct {
|
||||||
|
buildTag string
|
||||||
|
minBaseCyclesT0H int
|
||||||
|
maxBaseCyclesT0H int
|
||||||
|
minBaseCyclesT1H int
|
||||||
|
maxBaseCyclesT1H int
|
||||||
|
minBaseCyclesTLD int
|
||||||
|
template string
|
||||||
|
}
|
||||||
|
|
||||||
func writeImplementation(f *os.File, megahertz int) error {
|
var architectures = map[string]architectureImpl{
|
||||||
|
"cortexm": {
|
||||||
|
// Assume that a branch is 1 to 3 cycles, no matter whether it's taken
|
||||||
|
// or not. This is a rather conservative estimate, for Cortex-M+ for
|
||||||
|
// example the instruction cycles are precisely known.
|
||||||
|
buildTag: "cortexm",
|
||||||
|
minBaseCyclesT0H: 1 + 1 + 2, // shift + branch (not taken) + store
|
||||||
|
maxBaseCyclesT0H: 1 + 3 + 2, // shift + branch (not taken) + store
|
||||||
|
minBaseCyclesT1H: 1 + 1 + 2, // shift + branch (taken) + store
|
||||||
|
maxBaseCyclesT1H: 1 + 3 + 2, // shift + branch (taken) + store
|
||||||
|
minBaseCyclesTLD: 1 + 1 + 2, // subtraction + branch + store (in next cycle)
|
||||||
|
template: `
|
||||||
|
1: @ send_bit
|
||||||
|
str {maskSet}, {portSet} @ [2] T0H and T0L start here
|
||||||
|
@DELAY1
|
||||||
|
lsls {value}, #1 @ [1]
|
||||||
|
bcs.n 2f @ [1/3] skip_store
|
||||||
|
str {maskClear}, {portClear} @ [2] T0H -> T0L transition
|
||||||
|
2: @ skip_store
|
||||||
|
@DELAY2
|
||||||
|
str {maskClear}, {portClear} @ [2] T1H -> T1L transition
|
||||||
|
@DELAY3
|
||||||
|
subs {i}, #1 @ [1]
|
||||||
|
bne.n 1b @ [1/3] send_bit
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeImplementation(f *os.File, arch string, megahertz int) error {
|
||||||
cycleTimeNS := 1 / float64(megahertz)
|
cycleTimeNS := 1 / float64(megahertz)
|
||||||
// These timings are taken from the table "Updated simplified timing
|
// These timings are taken from the table "Updated simplified timing
|
||||||
// constraints for NeoPixel strings" at:
|
// constraints for NeoPixel strings" at:
|
||||||
@@ -72,7 +111,7 @@ func writeImplementation(f *os.File, megahertz int) error {
|
|||||||
maxCyclesT1H := int(math.Floor(5.500 / cycleTimeNS))
|
maxCyclesT1H := int(math.Floor(5.500 / cycleTimeNS))
|
||||||
minCyclesTLD := int(math.Ceil(1.150 / cycleTimeNS))
|
minCyclesTLD := int(math.Ceil(1.150 / cycleTimeNS))
|
||||||
|
|
||||||
// Assembly template:
|
// The assembly template looks something like this:
|
||||||
// 1: @ send_bit
|
// 1: @ send_bit
|
||||||
// str {maskSet}, {portSet} @ [2] T0H and T0L start here
|
// str {maskSet}, {portSet} @ [2] T0H and T0L start here
|
||||||
// ...delay 1
|
// ...delay 1
|
||||||
@@ -87,29 +126,31 @@ func writeImplementation(f *os.File, megahertz int) error {
|
|||||||
// bne.n 1b @ [1/3] send_bit
|
// bne.n 1b @ [1/3] send_bit
|
||||||
//
|
//
|
||||||
// We need to calculate the number of nop instructions in the three delays.
|
// We need to calculate the number of nop instructions in the three delays.
|
||||||
|
archImpl, ok := architectures[arch]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unknown architecture: %s", arch)
|
||||||
|
}
|
||||||
|
|
||||||
// Determine number of nops for delay1. This is primarily based on the T0H
|
// Determine number of nops for delay1. This is primarily based on the T0H
|
||||||
// delay, which is relatively short (<500ns).
|
// delay, which is relatively short (<500ns).
|
||||||
minBaseCyclesT0H := 1 + 1 + 2 // shift + branch + store
|
delay1 := minCyclesT0H - archImpl.minBaseCyclesT0H
|
||||||
maxBaseCyclesT0H := 1 + 3 + 2 // shift + branch + store
|
|
||||||
delay1 := minCyclesT0H - minBaseCyclesT0H
|
|
||||||
if delay1 < 0 {
|
if delay1 < 0 {
|
||||||
// The minCyclesT0H constraint could not be satisfied. Don't insert
|
// The minCyclesT0H constraint could not be satisfied. Don't insert
|
||||||
// nops, in the hope that it isn't too long.
|
// nops, in the hope that it isn't too long.
|
||||||
delay1 = 0
|
delay1 = 0
|
||||||
}
|
}
|
||||||
if delay1+maxBaseCyclesT0H > maxCyclesT0H {
|
if delay1+archImpl.maxBaseCyclesT0H > maxCyclesT0H {
|
||||||
return fmt.Errorf("MCU appears to be too slow to satisfy minimum requirements for the T0H signal")
|
return fmt.Errorf("MCU appears to be too slow to satisfy minimum requirements for the T0H signal")
|
||||||
}
|
}
|
||||||
actualMinCyclesT0H := minBaseCyclesT0H + delay1
|
actualMinCyclesT0H := archImpl.minBaseCyclesT0H + delay1
|
||||||
actualMaxCyclesT0H := maxBaseCyclesT0H + delay1
|
actualMaxCyclesT0H := archImpl.maxBaseCyclesT0H + delay1
|
||||||
actualMinNanosecondsT0H := float64(actualMinCyclesT0H) / float64(megahertz) * 1000
|
actualMinNanosecondsT0H := float64(actualMinCyclesT0H) / float64(megahertz) * 1000
|
||||||
actualMaxNanosecondsT0H := float64(actualMaxCyclesT0H) / float64(megahertz) * 1000
|
actualMaxNanosecondsT0H := float64(actualMaxCyclesT0H) / float64(megahertz) * 1000
|
||||||
|
|
||||||
// Determine number of nops for delay2. This is delay1 plus some extra time
|
// Determine number of nops for delay2. This is delay1 plus some extra time
|
||||||
// so that the pulse is long enough for T1H.
|
// so that the pulse is long enough for T1H.
|
||||||
minBaseCyclesT1H := delay1 + 1 + 1 + 2 // delay1 + shift + branch + store
|
minBaseCyclesT1H := delay1 + archImpl.minBaseCyclesT1H // delay1 + asssembly cycles
|
||||||
maxBaseCyclesT1H := delay1 + 1 + 3 + 2 // delay1 + shift + branch + store
|
maxBaseCyclesT1H := delay1 + archImpl.maxBaseCyclesT1H // delay1 + asssembly cycles
|
||||||
delay2 := minCyclesT1H - minBaseCyclesT1H
|
delay2 := minCyclesT1H - minBaseCyclesT1H
|
||||||
if delay2 < 0 {
|
if delay2 < 0 {
|
||||||
delay2 = 0
|
delay2 = 0
|
||||||
@@ -125,12 +166,11 @@ func writeImplementation(f *os.File, megahertz int) error {
|
|||||||
|
|
||||||
// Determine number of nops for delay3. This is based on the TLD delay, the
|
// Determine number of nops for delay3. This is based on the TLD delay, the
|
||||||
// time between two high pulses.
|
// time between two high pulses.
|
||||||
minBaseCyclesTLD := 1 + 1 + 2 // subtraction + branch + store (in next cycle)
|
delay3 := minCyclesTLD - archImpl.minBaseCyclesTLD
|
||||||
delay3 := minCyclesTLD - minBaseCyclesTLD
|
|
||||||
if delay3 < 0 {
|
if delay3 < 0 {
|
||||||
delay3 = 0
|
delay3 = 0
|
||||||
}
|
}
|
||||||
actualMinCyclesTLD := minBaseCyclesTLD + delay3
|
actualMinCyclesTLD := archImpl.minBaseCyclesTLD + delay3
|
||||||
actualMinNanosecondsTLD := float64(actualMinCyclesTLD) / float64(megahertz) * 1000
|
actualMinNanosecondsTLD := float64(actualMinCyclesTLD) / float64(megahertz) * 1000
|
||||||
|
|
||||||
// Create the Go function in a buffer. Using a buffer here to be able to
|
// Create the Go function in a buffer. Using a buffer here to be able to
|
||||||
@@ -147,20 +187,12 @@ func writeImplementation(f *os.File, megahertz int) error {
|
|||||||
fmt.Fprintf(buf, " // TLD: %2d - cycles or %.1fns -\n", actualMinCyclesTLD, actualMinNanosecondsTLD)
|
fmt.Fprintf(buf, " // TLD: %2d - cycles or %.1fns -\n", actualMinCyclesTLD, actualMinNanosecondsTLD)
|
||||||
fmt.Fprintf(buf, " mask := interrupt.Disable()\n")
|
fmt.Fprintf(buf, " mask := interrupt.Disable()\n")
|
||||||
fmt.Fprintf(buf, " value := uint32(c) << 24\n")
|
fmt.Fprintf(buf, " value := uint32(c) << 24\n")
|
||||||
fmt.Fprintf(buf, " device.AsmFull(`\n")
|
asm := archImpl.template
|
||||||
fmt.Fprintf(buf, " 1: @ send_bit\n")
|
asm = strings.ReplaceAll(asm, " @DELAY1\n", strings.Repeat(" nop\n", delay1))
|
||||||
fmt.Fprintf(buf, " str {maskSet}, {portSet} @ [2] T0H and T0L start here\n")
|
asm = strings.ReplaceAll(asm, " @DELAY2\n", strings.Repeat(" nop\n", delay2))
|
||||||
buf.WriteString(strings.Repeat(" nop\n", delay1))
|
asm = strings.ReplaceAll(asm, " @DELAY3\n", strings.Repeat(" nop\n", delay3))
|
||||||
fmt.Fprintf(buf, " lsls {value}, #1 @ [1]\n")
|
asm = strings.ReplaceAll(asm, "\n", "\n\t")
|
||||||
fmt.Fprintf(buf, " bcs.n 2f @ [1/3] skip_store\n")
|
fmt.Fprintf(buf, " device.AsmFull(`%s`, map[string]interface{}{", asm)
|
||||||
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(`
|
buf.WriteString(`
|
||||||
"value": value,
|
"value": value,
|
||||||
"i": 8,
|
"i": 8,
|
||||||
@@ -179,19 +211,33 @@ func writeImplementation(f *os.File, megahertz int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
f, err := os.Create("ws2812-asm_cortexm.go")
|
arch := flag.String("arch", "cortexm", "architecture to output to")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
// Remaining parameters are all clock frequencies.
|
||||||
|
var clockFrequencies []int
|
||||||
|
for _, s := range flag.Args() {
|
||||||
|
freq, err := strconv.Atoi(s)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "cannot parse frequency:", s)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
clockFrequencies = append(clockFrequencies, freq)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Create("ws2812-asm_" + *arch + ".go")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "could not generate WS2812 assembly code:", err)
|
fmt.Fprintln(os.Stderr, "could not generate WS2812 assembly code:", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
f.WriteString(`//go:build cortexm
|
fmt.Fprintln(f, "//go:build", architectures[*arch].buildTag)
|
||||||
// +build cortexm
|
fmt.Fprintln(f, "// +build", architectures[*arch].buildTag)
|
||||||
|
f.WriteString(`
|
||||||
package ws2812
|
package ws2812
|
||||||
|
|
||||||
// Warning: autogenerated file. Instead of modifying this file, change
|
// Warning: autogenerated file. Instead of modifying this file, change
|
||||||
// gen-ws2812-arm.go and run "go generate".
|
// gen-ws2812.go and run "go generate".
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"device"
|
"device"
|
||||||
@@ -199,9 +245,9 @@ import (
|
|||||||
)
|
)
|
||||||
`)
|
`)
|
||||||
for _, megahertz := range clockFrequencies {
|
for _, megahertz := range clockFrequencies {
|
||||||
err := writeImplementation(f, megahertz)
|
err := writeImplementation(f, *arch, megahertz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "could not generate WS2812 assembly code for %dMHz: %s\n", megahertz, err)
|
fmt.Fprintf(os.Stderr, "could not generate WS2812 assembly code for %s and %dMHz: %s\n", *arch, megahertz, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
package ws2812
|
package ws2812
|
||||||
|
|
||||||
// Warning: autogenerated file. Instead of modifying this file, change
|
// Warning: autogenerated file. Instead of modifying this file, change
|
||||||
// gen-ws2812-arm.go and run "go generate".
|
// gen-ws2812.go and run "go generate".
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"device"
|
"device"
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
// 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
|
//go:generate go run gen-ws2812.go -arch=cortexm 16 48 64 120 168
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|||||||
Reference in New Issue
Block a user