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
This commit is contained in:
Dima
2026-02-25 10:36:56 +02:00
committed by deadprogram
parent 9bdbe943ce
commit 47282be619
8 changed files with 636 additions and 1 deletions
+19 -1
View File
@@ -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
+11
View File
@@ -0,0 +1,11 @@
//go:build esp32c3_supermini
package main
import "machine"
var (
pwm = machine.PWM0
pinA = machine.GPIO5
pinB = machine.GPIO6
)
+11
View File
@@ -0,0 +1,11 @@
//go:build esp32s3_wroom1
package main
import "machine"
var (
pwm = machine.PWM0
pinA = machine.GPIO17
pinB = machine.GPIO18
)
+11
View File
@@ -0,0 +1,11 @@
//go:build xiao_esp32s3
package main
import "machine"
var (
pwm = machine.PWM0
pinA = machine.D0
pinB = machine.D1
)
+178
View File
@@ -0,0 +1,178 @@
//go:build esp32c3
package machine
import "device/esp"
// LEDC PWM for ESP32-C3: 4 timers (PWM0PWM3), 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 115 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 05 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)
}
}
}
+1
View File
@@ -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.
+226
View File
@@ -0,0 +1,226 @@
//go:build esp32s3
package machine
import "device/esp"
// LEDC PWM for ESP32-S3: 4 timers (PWM0PWM3), 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 115 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 07).
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)
}
}
}
+179
View File
@@ -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 // 03: 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<<pwm.dutyRes) - 1
if value > 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.dutyRes) - 1
}
func (pwm *LEDCPWM) SetInverting(channel uint8, inverting bool) {
if channel >= pwm.NumChannels {
return
}
pwm.chanOp(channel, ledcChanOpSetInvert, 0, inverting)
}