From 47282be6193b2b4bf2d1d3c16329c47ca43c3410 Mon Sep 17 00:00:00 2001 From: Dima Date: Wed, 25 Feb 2026 10:36:56 +0200 Subject: [PATCH] esp3s3 & esp32c3 PWM implements (#5215) * esp3s3 pwm * esp32s3-pwm: added spi alias for esp32s3 for tests * esp32s3-pwm: fix linters * esp32s3-pwm: fix rename * esp32s3-pwm: replace if to switch * esp32s3-pwm: chip specific registry * esp32s3-pwm: fix linters --- GNUmakefile | 20 ++- src/examples/pwm/esp32c3-supermini.go | 11 ++ src/examples/pwm/esp32s3-wroom1.go | 11 ++ src/examples/pwm/xiao_esp32s3.go | 11 ++ src/machine/machine_esp32c3_pwm.go | 178 ++++++++++++++++++++ src/machine/machine_esp32c3_spi.go | 1 + src/machine/machine_esp32s3_pwm.go | 226 ++++++++++++++++++++++++++ src/machine/machine_esp32xx_pwm.go | 179 ++++++++++++++++++++ 8 files changed, 636 insertions(+), 1 deletion(-) create mode 100644 src/examples/pwm/esp32c3-supermini.go create mode 100644 src/examples/pwm/esp32s3-wroom1.go create mode 100644 src/examples/pwm/xiao_esp32s3.go create mode 100644 src/machine/machine_esp32c3_pwm.go create mode 100644 src/machine/machine_esp32s3_pwm.go create mode 100644 src/machine/machine_esp32xx_pwm.go diff --git a/GNUmakefile b/GNUmakefile index 536a3e6c0..3eed5b941 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -923,17 +923,34 @@ ifneq ($(XTENSA), 0) @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target mch2022 examples/machinetest @$(MD5SUM) test.bin + # xiao-esp32s3 $(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/blinky1 @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/blinkm @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/mcp3008 @$(MD5SUM) test.bin - $(TINYGO) build -size short -o test.bin -target=esp32s3-wroom1 examples/mcp3008 + $(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/pwm @$(MD5SUM) test.bin + + # esp32s3-wroom1 $(TINYGO) build -size short -o test.bin -target=esp32s3-wroom1 examples/blinkm @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=esp32s3-wroom1 examples/mcp3008 + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=esp32s3-wroom1 examples/pwm + @$(MD5SUM) test.bin endif + # esp32c3-supermini + $(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/blinky1 + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/blinkm + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/mcp3008 + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/pwm + @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=esp-c3-32s-kit examples/blinky1 @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target=qtpy-esp32c3 examples/machinetest @@ -946,6 +963,7 @@ endif @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.bin -target=esp32c3-12f examples/blinky1 @$(MD5SUM) test.bin + $(TINYGO) build -size short -o test.bin -target=makerfabs-esp32c3spi35 examples/machinetest @$(MD5SUM) test.bin $(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1 diff --git a/src/examples/pwm/esp32c3-supermini.go b/src/examples/pwm/esp32c3-supermini.go new file mode 100644 index 000000000..edca88b97 --- /dev/null +++ b/src/examples/pwm/esp32c3-supermini.go @@ -0,0 +1,11 @@ +//go:build esp32c3_supermini + +package main + +import "machine" + +var ( + pwm = machine.PWM0 + pinA = machine.GPIO5 + pinB = machine.GPIO6 +) diff --git a/src/examples/pwm/esp32s3-wroom1.go b/src/examples/pwm/esp32s3-wroom1.go new file mode 100644 index 000000000..b3e5dcb58 --- /dev/null +++ b/src/examples/pwm/esp32s3-wroom1.go @@ -0,0 +1,11 @@ +//go:build esp32s3_wroom1 + +package main + +import "machine" + +var ( + pwm = machine.PWM0 + pinA = machine.GPIO17 + pinB = machine.GPIO18 +) diff --git a/src/examples/pwm/xiao_esp32s3.go b/src/examples/pwm/xiao_esp32s3.go new file mode 100644 index 000000000..c614abc86 --- /dev/null +++ b/src/examples/pwm/xiao_esp32s3.go @@ -0,0 +1,11 @@ +//go:build xiao_esp32s3 + +package main + +import "machine" + +var ( + pwm = machine.PWM0 + pinA = machine.D0 + pinB = machine.D1 +) diff --git a/src/machine/machine_esp32c3_pwm.go b/src/machine/machine_esp32c3_pwm.go new file mode 100644 index 000000000..d7e575e8d --- /dev/null +++ b/src/machine/machine_esp32c3_pwm.go @@ -0,0 +1,178 @@ +//go:build esp32c3 + +package machine + +import "device/esp" + +// LEDC PWM for ESP32-C3: 4 timers (PWM0–PWM3), 6 channels per timer; each timer has its own frequency. +// Range: frequency from a few Hz up to ~40 MHz (at 1-bit resolution); duty resolution 1–15 bits +// (higher frequency gives lower resolution). Clock source: APB 80 MHz. Low-speed mode only. +// See ESP-IDF LEDC driver, TRM LED PWM Controller. + +// GPIO matrix output signal indices for LEDC (soc/gpio_sig_map.h) +const ( + LEDC_LS_SIG_OUT0_IDX = 45 +) + +const ledcChannelsC3 = 6 + +var ( + PWM0 = &LEDCPWM{SigOutBase: LEDC_LS_SIG_OUT0_IDX, NumChannels: ledcChannelsC3, timerNum: 0} + PWM1 = &LEDCPWM{SigOutBase: LEDC_LS_SIG_OUT0_IDX, NumChannels: ledcChannelsC3, timerNum: 1} + PWM2 = &LEDCPWM{SigOutBase: LEDC_LS_SIG_OUT0_IDX, NumChannels: ledcChannelsC3, timerNum: 2} + PWM3 = &LEDCPWM{SigOutBase: LEDC_LS_SIG_OUT0_IDX, NumChannels: ledcChannelsC3, timerNum: 3} +) + +// chanOp implements LEDC low-speed channel ops for ESP32-C3 (channels 0–5 only). +func (pwm *LEDCPWM) chanOp(ch uint8, op ledcChanOp, duty uint32, inverting bool) { + invVal := uint32(0) + if inverting { + invVal = 1 + } + switch ch { + case 0: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH0_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH0_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH0_CONF0_IDLE_LV(0) + esp.LEDC.SetCH0_HPOINT_HPOINT(0) + esp.LEDC.SetCH0_DUTY_DUTY(0) + esp.LEDC.SetCH0_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH0_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH0_CONF1_DUTY_INC(1) + esp.LEDC.SetCH0_CONF1_DUTY_START(1) + esp.LEDC.SetCH0_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH0_DUTY_DUTY(duty) + esp.LEDC.SetCH0_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH0_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH0_CONF1_DUTY_INC(1) + esp.LEDC.SetCH0_CONF1_DUTY_START(1) + esp.LEDC.SetCH0_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH0_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH0_CONF0_IDLE_LV(invVal) + } + case 1: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH1_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH1_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH1_CONF0_IDLE_LV(0) + esp.LEDC.SetCH1_HPOINT_HPOINT(0) + esp.LEDC.SetCH1_DUTY_DUTY(0) + esp.LEDC.SetCH1_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH1_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH1_CONF1_DUTY_INC(1) + esp.LEDC.SetCH1_CONF1_DUTY_START(1) + esp.LEDC.SetCH1_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH1_DUTY_DUTY(duty) + esp.LEDC.SetCH1_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH1_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH1_CONF1_DUTY_INC(1) + esp.LEDC.SetCH1_CONF1_DUTY_START(1) + esp.LEDC.SetCH1_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH1_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH1_CONF0_IDLE_LV(invVal) + } + case 2: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH2_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH2_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH2_CONF0_IDLE_LV(0) + esp.LEDC.SetCH2_HPOINT_HPOINT(0) + esp.LEDC.SetCH2_DUTY_DUTY(0) + esp.LEDC.SetCH2_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH2_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH2_CONF1_DUTY_INC(1) + esp.LEDC.SetCH2_CONF1_DUTY_START(1) + esp.LEDC.SetCH2_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH2_DUTY_DUTY(duty) + esp.LEDC.SetCH2_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH2_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH2_CONF1_DUTY_INC(1) + esp.LEDC.SetCH2_CONF1_DUTY_START(1) + esp.LEDC.SetCH2_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH2_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH2_CONF0_IDLE_LV(invVal) + } + case 3: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH3_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH3_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH3_CONF0_IDLE_LV(0) + esp.LEDC.SetCH3_HPOINT_HPOINT(0) + esp.LEDC.SetCH3_DUTY_DUTY(0) + esp.LEDC.SetCH3_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH3_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH3_CONF1_DUTY_INC(1) + esp.LEDC.SetCH3_CONF1_DUTY_START(1) + esp.LEDC.SetCH3_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH3_DUTY_DUTY(duty) + esp.LEDC.SetCH3_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH3_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH3_CONF1_DUTY_INC(1) + esp.LEDC.SetCH3_CONF1_DUTY_START(1) + esp.LEDC.SetCH3_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH3_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH3_CONF0_IDLE_LV(invVal) + } + case 4: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH4_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH4_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH4_CONF0_IDLE_LV(0) + esp.LEDC.SetCH4_HPOINT_HPOINT(0) + esp.LEDC.SetCH4_DUTY_DUTY(0) + esp.LEDC.SetCH4_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH4_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH4_CONF1_DUTY_INC(1) + esp.LEDC.SetCH4_CONF1_DUTY_START(1) + esp.LEDC.SetCH4_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH4_DUTY_DUTY(duty) + esp.LEDC.SetCH4_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH4_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH4_CONF1_DUTY_INC(1) + esp.LEDC.SetCH4_CONF1_DUTY_START(1) + esp.LEDC.SetCH4_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH4_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH4_CONF0_IDLE_LV(invVal) + } + case 5: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH5_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH5_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH5_CONF0_IDLE_LV(0) + esp.LEDC.SetCH5_HPOINT_HPOINT(0) + esp.LEDC.SetCH5_DUTY_DUTY(0) + esp.LEDC.SetCH5_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH5_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH5_CONF1_DUTY_INC(1) + esp.LEDC.SetCH5_CONF1_DUTY_START(1) + esp.LEDC.SetCH5_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH5_DUTY_DUTY(duty) + esp.LEDC.SetCH5_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH5_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH5_CONF1_DUTY_INC(1) + esp.LEDC.SetCH5_CONF1_DUTY_START(1) + esp.LEDC.SetCH5_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH5_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH5_CONF0_IDLE_LV(invVal) + } + } +} diff --git a/src/machine/machine_esp32c3_spi.go b/src/machine/machine_esp32c3_spi.go index aec3ca77a..edb0218ac 100644 --- a/src/machine/machine_esp32c3_spi.go +++ b/src/machine/machine_esp32c3_spi.go @@ -53,6 +53,7 @@ type SPI struct { var ( // SPI0 and SPI1 are reserved for use by the caching system etc. SPI2 = &SPI{esp.SPI2} + SPI0 = SPI2 ) // SPIConfig is used to store config info for SPI. diff --git a/src/machine/machine_esp32s3_pwm.go b/src/machine/machine_esp32s3_pwm.go new file mode 100644 index 000000000..f4015a5f9 --- /dev/null +++ b/src/machine/machine_esp32s3_pwm.go @@ -0,0 +1,226 @@ +//go:build esp32s3 + +package machine + +import "device/esp" + +// LEDC PWM for ESP32-S3: 4 timers (PWM0–PWM3), 8 channels per timer; each timer has its own frequency. +// Range: frequency from a few Hz up to ~40 MHz (at 1-bit resolution); duty resolution 1–15 bits +// (higher frequency gives lower resolution). Clock source: APB 80 MHz. Low-speed mode only. +// Duty must not equal 2^resolution (overflow risk). See ESP-IDF LEDC driver, TRM LED PWM Controller. + +// GPIO matrix output signal indices for LEDC (soc/gpio_sig_map.h) +const ( + LEDC_LS_SIG_OUT0_IDX = 73 +) + +const ledcChannelsS3 = 8 + +var ( + PWM0 = &LEDCPWM{SigOutBase: LEDC_LS_SIG_OUT0_IDX, NumChannels: ledcChannelsS3, timerNum: 0} + PWM1 = &LEDCPWM{SigOutBase: LEDC_LS_SIG_OUT0_IDX, NumChannels: ledcChannelsS3, timerNum: 1} + PWM2 = &LEDCPWM{SigOutBase: LEDC_LS_SIG_OUT0_IDX, NumChannels: ledcChannelsS3, timerNum: 2} + PWM3 = &LEDCPWM{SigOutBase: LEDC_LS_SIG_OUT0_IDX, NumChannels: ledcChannelsS3, timerNum: 3} +) + +// chanOp implements LEDC low-speed channel ops for ESP32-S3 (channels 0–7). +func (pwm *LEDCPWM) chanOp(ch uint8, op ledcChanOp, duty uint32, inverting bool) { + invVal := uint32(0) + if inverting { + invVal = 1 + } + switch ch { + case 0: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH0_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH0_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH0_CONF0_IDLE_LV(0) + esp.LEDC.SetCH0_HPOINT_HPOINT(0) + esp.LEDC.SetCH0_DUTY_DUTY(0) + esp.LEDC.SetCH0_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH0_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH0_CONF1_DUTY_INC(1) + esp.LEDC.SetCH0_CONF1_DUTY_START(1) + esp.LEDC.SetCH0_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH0_DUTY_DUTY(duty) + esp.LEDC.SetCH0_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH0_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH0_CONF1_DUTY_INC(1) + esp.LEDC.SetCH0_CONF1_DUTY_START(1) + esp.LEDC.SetCH0_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH0_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH0_CONF0_IDLE_LV(invVal) + } + case 1: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH1_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH1_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH1_CONF0_IDLE_LV(0) + esp.LEDC.SetCH1_HPOINT_HPOINT(0) + esp.LEDC.SetCH1_DUTY_DUTY(0) + esp.LEDC.SetCH1_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH1_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH1_CONF1_DUTY_INC(1) + esp.LEDC.SetCH1_CONF1_DUTY_START(1) + esp.LEDC.SetCH1_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH1_DUTY_DUTY(duty) + esp.LEDC.SetCH1_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH1_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH1_CONF1_DUTY_INC(1) + esp.LEDC.SetCH1_CONF1_DUTY_START(1) + esp.LEDC.SetCH1_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH1_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH1_CONF0_IDLE_LV(invVal) + } + case 2: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH2_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH2_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH2_CONF0_IDLE_LV(0) + esp.LEDC.SetCH2_HPOINT_HPOINT(0) + esp.LEDC.SetCH2_DUTY_DUTY(0) + esp.LEDC.SetCH2_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH2_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH2_CONF1_DUTY_INC(1) + esp.LEDC.SetCH2_CONF1_DUTY_START(1) + esp.LEDC.SetCH2_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH2_DUTY_DUTY(duty) + esp.LEDC.SetCH2_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH2_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH2_CONF1_DUTY_INC(1) + esp.LEDC.SetCH2_CONF1_DUTY_START(1) + esp.LEDC.SetCH2_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH2_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH2_CONF0_IDLE_LV(invVal) + } + case 3: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH3_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH3_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH3_CONF0_IDLE_LV(0) + esp.LEDC.SetCH3_HPOINT_HPOINT(0) + esp.LEDC.SetCH3_DUTY_DUTY(0) + esp.LEDC.SetCH3_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH3_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH3_CONF1_DUTY_INC(1) + esp.LEDC.SetCH3_CONF1_DUTY_START(1) + esp.LEDC.SetCH3_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH3_DUTY_DUTY(duty) + esp.LEDC.SetCH3_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH3_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH3_CONF1_DUTY_INC(1) + esp.LEDC.SetCH3_CONF1_DUTY_START(1) + esp.LEDC.SetCH3_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH3_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH3_CONF0_IDLE_LV(invVal) + } + case 4: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH4_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH4_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH4_CONF0_IDLE_LV(0) + esp.LEDC.SetCH4_HPOINT_HPOINT(0) + esp.LEDC.SetCH4_DUTY_DUTY(0) + esp.LEDC.SetCH4_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH4_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH4_CONF1_DUTY_INC(1) + esp.LEDC.SetCH4_CONF1_DUTY_START(1) + esp.LEDC.SetCH4_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH4_DUTY_DUTY(duty) + esp.LEDC.SetCH4_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH4_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH4_CONF1_DUTY_INC(1) + esp.LEDC.SetCH4_CONF1_DUTY_START(1) + esp.LEDC.SetCH4_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH4_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH4_CONF0_IDLE_LV(invVal) + } + case 5: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH5_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH5_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH5_CONF0_IDLE_LV(0) + esp.LEDC.SetCH5_HPOINT_HPOINT(0) + esp.LEDC.SetCH5_DUTY_DUTY(0) + esp.LEDC.SetCH5_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH5_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH5_CONF1_DUTY_INC(1) + esp.LEDC.SetCH5_CONF1_DUTY_START(1) + esp.LEDC.SetCH5_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH5_DUTY_DUTY(duty) + esp.LEDC.SetCH5_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH5_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH5_CONF1_DUTY_INC(1) + esp.LEDC.SetCH5_CONF1_DUTY_START(1) + esp.LEDC.SetCH5_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH5_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH5_CONF0_IDLE_LV(invVal) + } + case 6: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH6_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH6_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH6_CONF0_IDLE_LV(0) + esp.LEDC.SetCH6_HPOINT_HPOINT(0) + esp.LEDC.SetCH6_DUTY_DUTY(0) + esp.LEDC.SetCH6_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH6_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH6_CONF1_DUTY_INC(1) + esp.LEDC.SetCH6_CONF1_DUTY_START(1) + esp.LEDC.SetCH6_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH6_DUTY_DUTY(duty) + esp.LEDC.SetCH6_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH6_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH6_CONF1_DUTY_INC(1) + esp.LEDC.SetCH6_CONF1_DUTY_START(1) + esp.LEDC.SetCH6_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH6_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH6_CONF0_IDLE_LV(invVal) + } + case 7: + switch op { + case ledcChanOpInit: + esp.LEDC.SetCH7_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetCH7_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH7_CONF0_IDLE_LV(0) + esp.LEDC.SetCH7_HPOINT_HPOINT(0) + esp.LEDC.SetCH7_DUTY_DUTY(0) + esp.LEDC.SetCH7_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH7_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH7_CONF1_DUTY_INC(1) + esp.LEDC.SetCH7_CONF1_DUTY_START(1) + esp.LEDC.SetCH7_CONF0_PARA_UP(1) + case ledcChanOpSetDuty: + esp.LEDC.SetCH7_DUTY_DUTY(duty) + esp.LEDC.SetCH7_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetCH7_CONF1_DUTY_NUM(1) + esp.LEDC.SetCH7_CONF1_DUTY_INC(1) + esp.LEDC.SetCH7_CONF1_DUTY_START(1) + esp.LEDC.SetCH7_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetCH7_CONF0_PARA_UP(1) + case ledcChanOpSetInvert: + esp.LEDC.SetCH7_CONF0_IDLE_LV(invVal) + } + } +} diff --git a/src/machine/machine_esp32xx_pwm.go b/src/machine/machine_esp32xx_pwm.go new file mode 100644 index 000000000..03a63d60b --- /dev/null +++ b/src/machine/machine_esp32xx_pwm.go @@ -0,0 +1,179 @@ +//go:build esp32c3 || esp32s3 + +// PWM on ESP32-C3/S3 uses the LEDC (LED Control) peripheral, low-speed mode only. +// One timer drives multiple channels; each channel has its own duty, shared frequency. +// Pin routing is via GPIO matrix (SigOutBase + channel index). +// +// Channel config (chanOp) follows the hardware contract from: +// - ESP-IDF: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/ledc.html +// (timer config → channel config → duty + update_duty). +// - SVD (e.g. lib/cmsis-svd/data/Espressif/esp32s3.svd): CONF0.PARA_UP "updates +// HPOINT, DUTY_START, SIG_OUT_EN, TIMER_SEL, DUTY_NUM, DUTY_CYCLE, DUTY_SCALE, +// DUTY_INC for channel and is auto-cleared by hardware"; CONF1.DUTY_START "other +// CONF1 fields take effect when this bit is set to 1". + +package machine + +import ( + "device/esp" + "errors" +) + +const ledcApbClock = 80_000000 + +const ledcDutyFracBits = 4 // DUTY register has 4 fractional bits; write value<<4 + +const ledcDividerFracBits = 8 // Clock divider register = actual_divider * 256 + +var errPWMNoChannel = errors.New("pwm: no free channel") + +type LEDCPWM struct { + SigOutBase uint32 // GPIO matrix signal index for channel 0 (e.g. 73 on S3, 45 on C3) + NumChannels uint8 + timerNum uint8 // 0–3: which LEDC timer (frequency) this PWM uses + dutyRes uint8 + configured bool + channelPin [8]Pin +} + +type ledcChanOp uint8 + +const ( + ledcChanOpInit ledcChanOp = iota // initial per-channel setup (timer, enable, HPOINT/DUTY/CONF1, PARA_UP) + ledcChanOpSetDuty // update duty and latch it (DUTY + CONF1 + PARA_UP) + ledcChanOpSetInvert // change idle level (IDLE_LV) +) + +func (pwm *LEDCPWM) Configure(config PWMConfig) error { + // Enable LEDC clock and release reset (SYSTEM perip_clk_en0 / perip_rst_en0). + esp.SYSTEM.SetPERIP_RST_EN0_LEDC_RST(1) + esp.SYSTEM.SetPERIP_CLK_EN0_LEDC_CLK_EN(1) + esp.SYSTEM.SetPERIP_RST_EN0_LEDC_RST(0) + + // LEDC global: APB clock source, enable internal clock. + esp.LEDC.SetCONF_APB_CLK_SEL(1) + esp.LEDC.SetCONF_CLK_EN(1) + + period := config.Period + if period == 0 { + period = 1_000_000 + } + freq := uint64(1e9) / period + dutyRes := uint8(10) + switch { + case freq < 100: + dutyRes = 14 + case freq < 1000: + dutyRes = 12 + case freq > 100_000: + dutyRes = 8 + } + + // Timer divider: period_ns = (2^dutyRes * divActual/256) / 80MHz * 1e9 => divReg = divActual<<8. + divActual := ledcApbClock / (uint32(freq) * (1 << dutyRes)) + if divActual == 0 { + divActual = 1 + } + divReg := divActual << ledcDividerFracBits + if divReg > 0x3ffff { + return ErrPWMPeriodTooLong + } + + // Selected timer: resolution, divider, no pause, reset then latch config with PARA_UP. + pwm.setTimerConf(dutyRes, divReg) + + pwm.dutyRes = dutyRes + pwm.configured = true + for i := range pwm.channelPin { + pwm.channelPin[i] = NoPin + } + return nil +} + +func (pwm *LEDCPWM) setTimerConf(dutyRes uint8, divReg uint32) { + t := pwm.timerNum + switch t { + case 0: + esp.LEDC.SetTIMER0_CONF_DUTY_RES(uint32(dutyRes)) + esp.LEDC.SetTIMER0_CONF_CLK_DIV(divReg) + esp.LEDC.SetTIMER0_CONF_TICK_SEL(0) + esp.LEDC.SetTIMER0_CONF_PAUSE(0) + esp.LEDC.SetTIMER0_CONF_RST(1) + esp.LEDC.SetTIMER0_CONF_RST(0) + esp.LEDC.SetTIMER0_CONF_PARA_UP(1) + case 1: + esp.LEDC.SetTIMER1_CONF_DUTY_RES(uint32(dutyRes)) + esp.LEDC.SetTIMER1_CONF_CLK_DIV(divReg) + esp.LEDC.SetTIMER1_CONF_TICK_SEL(0) + esp.LEDC.SetTIMER1_CONF_PAUSE(0) + esp.LEDC.SetTIMER1_CONF_RST(1) + esp.LEDC.SetTIMER1_CONF_RST(0) + esp.LEDC.SetTIMER1_CONF_PARA_UP(1) + case 2: + esp.LEDC.SetTIMER2_CONF_DUTY_RES(uint32(dutyRes)) + esp.LEDC.SetTIMER2_CONF_CLK_DIV(divReg) + esp.LEDC.SetTIMER2_CONF_TICK_SEL(0) + esp.LEDC.SetTIMER2_CONF_PAUSE(0) + esp.LEDC.SetTIMER2_CONF_RST(1) + esp.LEDC.SetTIMER2_CONF_RST(0) + esp.LEDC.SetTIMER2_CONF_PARA_UP(1) + case 3: + esp.LEDC.SetTIMER3_CONF_DUTY_RES(uint32(dutyRes)) + esp.LEDC.SetTIMER3_CONF_CLK_DIV(divReg) + esp.LEDC.SetTIMER3_CONF_TICK_SEL(0) + esp.LEDC.SetTIMER3_CONF_PAUSE(0) + esp.LEDC.SetTIMER3_CONF_RST(1) + esp.LEDC.SetTIMER3_CONF_RST(0) + esp.LEDC.SetTIMER3_CONF_PARA_UP(1) + } +} + +func (pwm *LEDCPWM) Channel(pin Pin) (uint8, error) { + if !pwm.configured { + return 0, errors.New("pwm: not configured") + } + if pin == NoPin { + return 0, ErrInvalidOutputPin + } + var ch uint8 + for ch = 0; ch < pwm.NumChannels; ch++ { + if pwm.channelPin[ch] == NoPin { + break + } + } + if ch >= pwm.NumChannels { + return 0, errPWMNoChannel + } + + pwm.channelPin[ch] = pin + signal := pwm.SigOutBase + uint32(ch) + pin.configure(PinConfig{Mode: PinOutput}, signal) // GPIO matrix: pin <- LEDC_LS_SIG_OUTn + pwm.chanOp(ch, ledcChanOpInit, 0, false) + return ch, nil +} + +func (pwm *LEDCPWM) Set(channel uint8, value uint32) { + if channel >= pwm.NumChannels { + return + } + top := uint32(1< top { + value = top + } + dutyVal := value << ledcDutyFracBits + pwm.chanOp(channel, ledcChanOpSetDuty, dutyVal, false) +} + +func (pwm *LEDCPWM) Top() uint32 { + if !pwm.configured { + return 0 + } + return uint32(1<= pwm.NumChannels { + return + } + pwm.chanOp(channel, ledcChanOpSetInvert, 0, inverting) +}