Skip to content

Commit 7b9d4fa

Browse files
committed
ws2812: add support for the ESP32-C3 processor
This adds support to the WS2812 for the ESP32-C3 processor which is a RISC-V processor from Espressif. Signed-off-by: deadprogram <ron@hybridgroup.com>
1 parent 8f37293 commit 7b9d4fa

9 files changed

Lines changed: 478 additions & 394 deletions

File tree

examples/ws2812/others.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build !digispark && !arduino && !arduino_uno
1+
//go:build !digispark && !arduino && !arduino_uno && !xiao_esp32c3
22

33
package main
44

examples/ws2812/xiao-esp32c3.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//go:build xiao_esp32c3
2+
3+
package main
4+
5+
import "machine"
6+
7+
func init() {
8+
// Replace neo in the code below to match the pin
9+
// that you are using if different.
10+
neo = machine.D6
11+
}

smoketest.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ tinygo build -size short -o ./build/test.bin -target=m5stamp-c3 ./examp
9393
tinygo build -size short -o ./build/test.hex -target=feather-nrf52840 ./examples/is31fl3731/main.go
9494
tinygo build -size short -o ./build/test.hex -target=arduino ./examples/ws2812
9595
tinygo build -size short -o ./build/test.hex -target=digispark ./examples/ws2812
96+
tinygo build -size short -o ./build/test.bin -target=xiao-esp32c3 ./examples/ws2812
9697
tinygo build -size short -o ./build/test.hex -target=trinket-m0 ./examples/bme280/main.go
9798
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/microphone/main.go
9899
tinygo build -size short -o ./build/test.hex -target=circuitplay-express ./examples/buzzer/main.go

ws2812/gen-ws2812.go

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,24 @@ import (
1717
// the new assembly implementation - no fiddly timings to calculate and no nops
1818
// to count!
1919
//
20-
// Right now this is specific to Cortex-M chips and assume the following things:
20+
// Right now this is specific to specific chips:
21+
// On Cortex-M chips it assume the following things:
2122
// - Arithmetic operations (shift, add, sub) take up 1 clock cycle.
2223
// - The nop instruction also takes up 1 clock cycle.
2324
// - Store instructions (to the GPIO pins) take up 2 clock cycles.
2425
// - Branch instructions can take up 1 to 3 clock cycles. On the Cortex-M0, this
2526
// depends on whether the branch is taken or not. On the M4, the documentation
2627
// is less clear but it appears the instruction is still 1 to 3 cycles
2728
// (possibly including some branch prediction).
28-
// It is certainly possible to extend this to other architectures, such as AVR
29-
// and RISC-V if needed.
29+
// On RISC-V chips it assumes the following things:
30+
// - Arithmetic operations (shift, add, sub) take up 1 clock cycle.
31+
// - The nop instruction also takes up 1 clock cycle.
32+
// - Store instructions (to the GPIO pins) take up 1 clock cycle.
33+
// - Branch instructions can take up 1 or 3 clock cycles, depending on branch
34+
// prediction. This is based on the SiFive FE310 CPU, but hopefully it
35+
// generalizes to other RISC-V chips as well.
36+
37+
// It is certainly possible to extend this to other architectures, such as AVR as needed.
3038
//
3139
// Here are two important resources. For the timings:
3240
// https://wp.josh.com/2014/05/13/ws2812-neopixels-are-not-so-finicky-once-you-get-to-know-them/
@@ -45,6 +53,7 @@ type architectureImpl struct {
4553
maxBaseCyclesT1H int
4654
minBaseCyclesTLD int
4755
valueTemplate string // template for how to pass the 'c' byte to assembly
56+
funcAttr string // C function attribute (default: always_inline)
4857
template string // assembly template
4958
}
5059

@@ -83,13 +92,44 @@ var architectures = map[string]architectureImpl{
8392
// - branches are 1 or 3 cycles, depending on branch prediction
8493
// - ALU operations are 1 cycle (as on most CPUs)
8594
// Hopefully this generalizes to other chips.
86-
buildTag: "tinygo.riscv32",
95+
buildTag: "tinygo.riscv32 && !esp32c3",
96+
minBaseCyclesT0H: 1 + 1 + 1, // shift + branch (not taken) + store
97+
maxBaseCyclesT0H: 1 + 3 + 1, // shift + branch (not taken) + store
98+
minBaseCyclesT1H: 1 + 1 + 1, // shift + branch (taken) + store
99+
maxBaseCyclesT1H: 1 + 3 + 1, // shift + branch (taken) + store
100+
minBaseCyclesTLD: 1 + 1 + 1, // subtraction + branch + store (in next cycle)
101+
valueTemplate: "(uint32_t)c << 23",
102+
template: `
103+
1: // send_bit
104+
sw %[maskSet], %[portSet] // [1] T0H and T0L start here
105+
@DELAY1
106+
slli %[value], %[value], 1 // [1] shift value left by 1
107+
bltz %[value], 2f // [1/3] skip_store
108+
sw %[maskClear], %[portClear] // [1] T0H -> T0L transition
109+
2: // skip_store
110+
@DELAY2
111+
sw %[maskClear], %[portClear] // [1] T1H -> T1L transition
112+
@DELAY3
113+
addi %[i], %[i], -1 // [1]
114+
bnez %[i], 1b // [1/3] send_bit
115+
`,
116+
},
117+
"esp32c3": {
118+
// ESP32-C3 RISC-V core:
119+
// - stores are 1 cycle
120+
// - branches are 1 or 3 cycles
121+
// - ALU operations are 1 cycle
122+
// Uses the same instruction timing as the SiFive FE310, but the
123+
// function is placed in IRAM instead of flash to avoid instruction
124+
// cache miss stalls that would destroy WS2812 timing.
125+
buildTag: "esp32c3",
87126
minBaseCyclesT0H: 1 + 1 + 1, // shift + branch (not taken) + store
88127
maxBaseCyclesT0H: 1 + 3 + 1, // shift + branch (not taken) + store
89128
minBaseCyclesT1H: 1 + 1 + 1, // shift + branch (taken) + store
90129
maxBaseCyclesT1H: 1 + 3 + 1, // shift + branch (taken) + store
91130
minBaseCyclesTLD: 1 + 1 + 1, // subtraction + branch + store (in next cycle)
92131
valueTemplate: "(uint32_t)c << 23",
132+
funcAttr: `__attribute__((section(".iram1"), noinline))`,
93133
template: `
94134
1: // send_bit
95135
sw %[maskSet], %[portSet] // [1] T0H and T0L start here
@@ -208,7 +248,11 @@ func writeCAssembly(f *os.File, arch string, megahertz int) error {
208248
// ignore I/O errors.
209249
buf := &bytes.Buffer{}
210250
fmt.Fprintf(buf, "\n")
211-
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)
251+
funcAttr := archImpl.funcAttr
252+
if funcAttr == "" {
253+
funcAttr = "__attribute__((always_inline))"
254+
}
255+
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)
212256
fmt.Fprintf(buf, " // Timings:\n")
213257
fmt.Fprintf(buf, " // T0H: %2d - %2d cycles or %.1fns - %.1fns\n", actualMinCyclesT0H, actualMaxCyclesT0H, actualMinNanosecondsT0H, actualMaxNanosecondsT0H)
214258
fmt.Fprintf(buf, " // T1H: %2d - %2d cycles or %.1fns - %.1fns\n", actualMinCyclesT1H, actualMaxCyclesT1H, actualMinNanosecondsT1H, actualMaxNanosecondsT1H)

0 commit comments

Comments
 (0)