From b42bfb07cc2788e385c8188e1b5086fcf100c92b Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sat, 7 Mar 2026 10:27:10 +0100 Subject: [PATCH] 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 --- examples/ws2812/others.go | 2 +- examples/ws2812/xiao-esp32c3.go | 11 + smoketest.sh | 1 + ws2812/gen-ws2812.go | 54 ++++- ws2812/ws2812-asm_esp32c3.go | 396 +++++++++++++++++++++++++++++++ ws2812/ws2812-asm_tinygoriscv.go | 384 +----------------------------- ws2812/ws2812.go | 3 +- ws2812/ws2812_esp32c3.go | 16 ++ ws2812/ws2812_tinygoriscv.go | 5 +- 9 files changed, 478 insertions(+), 394 deletions(-) create mode 100644 examples/ws2812/xiao-esp32c3.go create mode 100644 ws2812/ws2812-asm_esp32c3.go create mode 100644 ws2812/ws2812_esp32c3.go diff --git a/examples/ws2812/others.go b/examples/ws2812/others.go index 27e116e6a..9bb60a64e 100644 --- a/examples/ws2812/others.go +++ b/examples/ws2812/others.go @@ -1,4 +1,4 @@ -//go:build !digispark && !arduino +//go:build !digispark && !arduino && !xiao_esp32c3 package main diff --git a/examples/ws2812/xiao-esp32c3.go b/examples/ws2812/xiao-esp32c3.go new file mode 100644 index 000000000..ad8f5a694 --- /dev/null +++ b/examples/ws2812/xiao-esp32c3.go @@ -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 +} diff --git a/smoketest.sh b/smoketest.sh index d25133dec..2e7a790ce 100755 --- a/smoketest.sh +++ b/smoketest.sh @@ -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 diff --git a/ws2812/gen-ws2812.go b/ws2812/gen-ws2812.go index 54dc6b8c9..c1f2634a0 100644 --- a/ws2812/gen-ws2812.go +++ b/ws2812/gen-ws2812.go @@ -17,7 +17,8 @@ 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. @@ -25,8 +26,15 @@ import ( // 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/ @@ -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 } @@ -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 @@ -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) diff --git a/ws2812/ws2812-asm_esp32c3.go b/ws2812/ws2812-asm_esp32c3.go new file mode 100644 index 000000000..21e36592a --- /dev/null +++ b/ws2812/ws2812-asm_esp32c3.go @@ -0,0 +1,396 @@ +//go:build esp32c3 + +package ws2812 + +// Warning: autogenerated file. Instead of modifying this file, change +// gen-ws2812.go and run "go generate". + +import "runtime/interrupt" +import "unsafe" + +/* +#include + +__attribute__((section(".iram1"), noinline)) +void ws2812_writeByte160(char c, uint32_t *portSet, uint32_t *portClear, uint32_t maskSet, uint32_t maskClear) { + // Timings: + // T0H: 56 - 58 cycles or 350.0ns - 362.5ns + // T1H: 168 - 170 cycles or 1050.0ns - 1062.5ns + // TLD: 184 - cycles or 1150.0ns - + uint32_t value = (uint32_t)c << 23; + char i = 8; + __asm__ __volatile__( + "1: // send_bit\n" + "\t sw %[maskSet], %[portSet] // [1] T0H and T0L start here\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t slli %[value], %[value], 1 // [1] shift value left by 1\n" + "\t bltz %[value], 2f // [1/3] skip_store\n" + "\t sw %[maskClear], %[portClear] // [1] T0H -> T0L transition\n" + "\t2: // skip_store\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t sw %[maskClear], %[portClear] // [1] T1H -> T1L transition\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t nop\n" + "\t addi %[i], %[i], -1 // [1]\n" + "\t bnez %[i], 1b // [1/3] send_bit\n" + : [value]"+r"(value), + [i]"+r"(i) + : [maskSet]"r"(maskSet), + [portSet]"m"(*portSet), + [maskClear]"r"(maskClear), + [portClear]"m"(*portClear)); +} +*/ +import "C" + +func (d Device) writeByte160(c byte) { + portSet, maskSet := d.Pin.PortMaskSet() + portClear, maskClear := d.Pin.PortMaskClear() + + mask := interrupt.Disable() + C.ws2812_writeByte160(C.char(c), (*C.uint32_t)(unsafe.Pointer(portSet)), (*C.uint32_t)(unsafe.Pointer(portClear)), C.uint32_t(maskSet), C.uint32_t(maskClear)) + + interrupt.Restore(mask) +} diff --git a/ws2812/ws2812-asm_tinygoriscv.go b/ws2812/ws2812-asm_tinygoriscv.go index 05e9653ec..046df7ac1 100644 --- a/ws2812/ws2812-asm_tinygoriscv.go +++ b/ws2812/ws2812-asm_tinygoriscv.go @@ -1,4 +1,4 @@ -//go:build tinygo.riscv32 +//go:build tinygo.riscv32 && !esp32c3 package ws2812 @@ -11,378 +11,6 @@ import "unsafe" /* #include -__attribute__((always_inline)) -void ws2812_writeByte160(char c, uint32_t *portSet, uint32_t *portClear, uint32_t maskSet, uint32_t maskClear) { - // Timings: - // T0H: 56 - 58 cycles or 350.0ns - 362.5ns - // T1H: 168 - 170 cycles or 1050.0ns - 1062.5ns - // TLD: 184 - cycles or 1150.0ns - - uint32_t value = (uint32_t)c << 23; - char i = 8; - __asm__ __volatile__( - "1: // send_bit\n" - "\t sw %[maskSet], %[portSet] // [1] T0H and T0L start here\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t slli %[value], %[value], 1 // [1] shift value left by 1\n" - "\t bltz %[value], 2f // [1/3] skip_store\n" - "\t sw %[maskClear], %[portClear] // [1] T0H -> T0L transition\n" - "\t2: // skip_store\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t sw %[maskClear], %[portClear] // [1] T1H -> T1L transition\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t nop\n" - "\t addi %[i], %[i], -1 // [1]\n" - "\t bnez %[i], 1b // [1/3] send_bit\n" - : [value]"+r"(value), - [i]"+r"(i) - : [maskSet]"r"(maskSet), - [portSet]"m"(*portSet), - [maskClear]"r"(maskClear), - [portClear]"m"(*portClear)); -} - __attribute__((always_inline)) void ws2812_writeByte320(char c, uint32_t *portSet, uint32_t *portClear, uint32_t maskSet, uint32_t maskClear) { // Timings: @@ -1109,16 +737,6 @@ void ws2812_writeByte320(char c, uint32_t *portSet, uint32_t *portClear, uint32_ */ import "C" -func (d Device) writeByte160(c byte) { - portSet, maskSet := d.Pin.PortMaskSet() - portClear, maskClear := d.Pin.PortMaskClear() - - mask := interrupt.Disable() - C.ws2812_writeByte160(C.char(c), (*C.uint32_t)(unsafe.Pointer(portSet)), (*C.uint32_t)(unsafe.Pointer(portClear)), C.uint32_t(maskSet), C.uint32_t(maskClear)) - - interrupt.Restore(mask) -} - func (d Device) writeByte320(c byte) { portSet, maskSet := d.Pin.PortMaskSet() portClear, maskClear := d.Pin.PortMaskClear() diff --git a/ws2812/ws2812.go b/ws2812/ws2812.go index dee3cfbc8..2e7cf346d 100644 --- a/ws2812/ws2812.go +++ b/ws2812/ws2812.go @@ -2,7 +2,8 @@ package ws2812 // import "tinygo.org/x/drivers/ws2812" //go:generate go run gen-ws2812.go -arch=cortexm 16 48 64 120 125 150 168 200 -//go:generate go run gen-ws2812.go -arch=tinygoriscv 160 320 +//go:generate go run gen-ws2812.go -arch=tinygoriscv 320 +//go:generate go run gen-ws2812.go -arch=esp32c3 160 import ( "errors" diff --git a/ws2812/ws2812_esp32c3.go b/ws2812/ws2812_esp32c3.go new file mode 100644 index 000000000..d67c761ae --- /dev/null +++ b/ws2812/ws2812_esp32c3.go @@ -0,0 +1,16 @@ +//go:build esp32c3 + +package ws2812 + +import "machine" + +// Send a single byte using the WS2812 protocol. +func (d Device) WriteByte(c byte) error { + switch machine.CPUFrequency() { + case 160_000_000: // 160MHz + d.writeByte160(c) + return nil + default: + return errUnknownClockSpeed + } +} diff --git a/ws2812/ws2812_tinygoriscv.go b/ws2812/ws2812_tinygoriscv.go index 7212310ac..735cdac65 100644 --- a/ws2812/ws2812_tinygoriscv.go +++ b/ws2812/ws2812_tinygoriscv.go @@ -1,4 +1,4 @@ -//go:build tinygo.riscv32 +//go:build tinygo.riscv32 && !esp32c3 package ws2812 @@ -7,9 +7,6 @@ import "machine" // Send a single byte using the WS2812 protocol. func (d Device) WriteByte(c byte) error { switch machine.CPUFrequency() { - case 160_000_000: // 160MHz, e.g. esp32c3 - d.writeByte160(c) - return nil case 320_000_000: // 320MHz, e.g. fe310 d.writeByte320(c) return nil