Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/ws2812/others.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build !digispark && !arduino
//go:build !digispark && !arduino && !xiao_esp32c3

package main

Expand Down
11 changes: 11 additions & 0 deletions examples/ws2812/xiao-esp32c3.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//go:build xiao_esp32c3

package main

import "machine"

func init() {
// Replace neo in the code below to match the pin
// that you are using if different.
neo = machine.D6
}
1 change: 1 addition & 0 deletions smoketest.sh
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ tinygo build -size short -o ./build/test.bin -target=m5stamp-c3 ./examp
tinygo build -size short -o ./build/test.hex -target=feather-nrf52840 ./examples/is31fl3731/main.go
tinygo build -size short -o ./build/test.hex -target=arduino ./examples/ws2812
tinygo build -size short -o ./build/test.hex -target=digispark ./examples/ws2812
tinygo build -size short -o ./build/test.hex -target=xiao_esp32c3 ./examples/ws2812
tinygo build -size short -o ./build/test.hex -target=trinket-m0 ./examples/bme280/main.go
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/microphone/main.go
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/buzzer/main.go
Expand Down
54 changes: 49 additions & 5 deletions ws2812/gen-ws2812.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,24 @@ import (
// 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:
// Right now this is specific to specific chips:
// On Cortex-M chips it 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.
// On RISC-V chips it assumes 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 1 clock cycle.
// - Branch instructions can take up 1 or 3 clock cycles, depending on branch
// prediction. This is based on the SiFive FE310 CPU, but hopefully it
// generalizes to other RISC-V chips as well.

// It is certainly possible to extend this to other architectures, such as AVR as needed.
//
// Here are two important resources. For the timings:
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
Expand All @@ -45,6 +53,7 @@ type architectureImpl struct {
maxBaseCyclesT1H int
minBaseCyclesTLD int
valueTemplate string // template for how to pass the 'c' byte to assembly
funcAttr string // C function attribute (default: always_inline)
template string // assembly template
}

Expand Down Expand Up @@ -83,13 +92,44 @@ var architectures = map[string]architectureImpl{
// - branches are 1 or 3 cycles, depending on branch prediction
// - ALU operations are 1 cycle (as on most CPUs)
// Hopefully this generalizes to other chips.
buildTag: "tinygo.riscv32",
buildTag: "tinygo.riscv32 && !esp32c3",
minBaseCyclesT0H: 1 + 1 + 1, // shift + branch (not taken) + store
maxBaseCyclesT0H: 1 + 3 + 1, // shift + branch (not taken) + store
minBaseCyclesT1H: 1 + 1 + 1, // shift + branch (taken) + store
maxBaseCyclesT1H: 1 + 3 + 1, // shift + branch (taken) + store
minBaseCyclesTLD: 1 + 1 + 1, // subtraction + branch + store (in next cycle)
valueTemplate: "(uint32_t)c << 23",
template: `
1: // send_bit
sw %[maskSet], %[portSet] // [1] T0H and T0L start here
@DELAY1
slli %[value], %[value], 1 // [1] shift value left by 1
bltz %[value], 2f // [1/3] skip_store
sw %[maskClear], %[portClear] // [1] T0H -> T0L transition
2: // skip_store
@DELAY2
sw %[maskClear], %[portClear] // [1] T1H -> T1L transition
@DELAY3
addi %[i], %[i], -1 // [1]
bnez %[i], 1b // [1/3] send_bit
`,
},
"esp32c3": {
// ESP32-C3 RISC-V core:
// - stores are 1 cycle
// - branches are 1 or 3 cycles
// - ALU operations are 1 cycle
// Uses the same instruction timing as the SiFive FE310, but the
// function is placed in IRAM instead of flash to avoid instruction
// cache miss stalls that would destroy WS2812 timing.
buildTag: "esp32c3",
minBaseCyclesT0H: 1 + 1 + 1, // shift + branch (not taken) + store
maxBaseCyclesT0H: 1 + 3 + 1, // shift + branch (not taken) + store
minBaseCyclesT1H: 1 + 1 + 1, // shift + branch (taken) + store
maxBaseCyclesT1H: 1 + 3 + 1, // shift + branch (taken) + store
minBaseCyclesTLD: 1 + 1 + 1, // subtraction + branch + store (in next cycle)
valueTemplate: "(uint32_t)c << 23",
funcAttr: `__attribute__((section(".iram1"), noinline))`,
template: `
1: // send_bit
sw %[maskSet], %[portSet] // [1] T0H and T0L start here
Expand Down Expand Up @@ -208,7 +248,11 @@ func writeCAssembly(f *os.File, arch string, megahertz int) error {
// ignore I/O errors.
buf := &bytes.Buffer{}
fmt.Fprintf(buf, "\n")
fmt.Fprintf(buf, "__attribute__((always_inline))\nvoid ws2812_writeByte%d(char c, uint32_t *portSet, uint32_t *portClear, uint32_t maskSet, uint32_t maskClear) {\n", megahertz)
funcAttr := archImpl.funcAttr
if funcAttr == "" {
funcAttr = "__attribute__((always_inline))"
}
fmt.Fprintf(buf, "%s\nvoid ws2812_writeByte%d(char c, uint32_t *portSet, uint32_t *portClear, uint32_t maskSet, uint32_t maskClear) {\n", funcAttr, megahertz)
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)
Expand Down
Loading
Loading