diff --git a/make/smoketest.mk b/make/smoketest.mk index ac42496be4..0316dd6f67 100644 --- a/make/smoketest.mk +++ b/make/smoketest.mk @@ -477,6 +477,8 @@ ifneq ($(XTENSA), 0) @$(MD5SUM) $(SMOKE_OUT).bin $(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32-coreboard-v2 examples/adc @$(MD5SUM) $(SMOKE_OUT).bin + $(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32-coreboard-v2 examples/pwm + @$(MD5SUM) $(SMOKE_OUT).bin $(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32c3-generic examples/machinetest @$(MD5SUM) $(SMOKE_OUT).bin $(TINYGO) build -size short -o $(SMOKE_OUT).bin -target=esp32s3-generic examples/machinetest diff --git a/src/examples/pwm/esp32-coreboard-v2.go b/src/examples/pwm/esp32-coreboard-v2.go new file mode 100644 index 0000000000..7980431bde --- /dev/null +++ b/src/examples/pwm/esp32-coreboard-v2.go @@ -0,0 +1,11 @@ +//go:build esp32_coreboard_v2 + +package main + +import "machine" + +var ( + pwm = machine.PWM0 + pinA = machine.GPIO18 + pinB = machine.GPIO19 +) diff --git a/src/machine/machine_esp32_pwm.go b/src/machine/machine_esp32_pwm.go new file mode 100644 index 0000000000..d1332ad92f --- /dev/null +++ b/src/machine/machine_esp32_pwm.go @@ -0,0 +1,300 @@ +//go:build esp32 + +// PWM on the classic ESP32 uses the LEDC peripheral. +// +// LEDC here has two halves: a high-speed one and a low-speed one. This file uses +// the high-speed half. It gives 4 timers (PWM0-PWM3) and 8 channels. +// +// The code that is the same on every ESP32 chip is in machine_esp32xx_pwm.go. +// This file adds only the parts that are different here. +// +// Three things are different from the ESP32-C3 and the ESP32-S3: +// +// - The clock is switched on in DPORT. The newer chips renamed that block to +// SYSTEM. This chip also has no LEDC.CONF_CLK_EN bit. +// - Register names start with HS, for example HSTIMER0_CONF and HSCH0_CONF0. +// - There is no PARA_UP bit. On the C3 and S3 you set PARA_UP to say "apply my +// changes now". Here the channel applies them by itself, at the end of the +// period it is in. That is what makes it "high speed". +// See the ESP32 TRM, chapter "LED PWM Controller". + +package machine + +import "device/esp" + +// The GPIO matrix can send an internal signal to almost any pin. Each signal has +// a number. High-speed LEDC channel 0 is number 71, and the rest follow on from +// there, so channels 0 to 7 are 71 to 78. +// (From Espressif's soc/gpio_sig_map.h: LEDC_HS_SIG_OUT0_IDX.) +const LEDC_HS_SIG_OUT0_IDX = 71 + +const ledcChannelsESP32 = 8 + +var ( + PWM0 = &LEDCPWM{SigOutBase: LEDC_HS_SIG_OUT0_IDX, NumChannels: ledcChannelsESP32, timerNum: 0} + PWM1 = &LEDCPWM{SigOutBase: LEDC_HS_SIG_OUT0_IDX, NumChannels: ledcChannelsESP32, timerNum: 1} + PWM2 = &LEDCPWM{SigOutBase: LEDC_HS_SIG_OUT0_IDX, NumChannels: ledcChannelsESP32, timerNum: 2} + PWM3 = &LEDCPWM{SigOutBase: LEDC_HS_SIG_OUT0_IDX, NumChannels: ledcChannelsESP32, timerNum: 3} +) + +// enableClock turns the LEDC hardware on and picks its clock. +func (pwm *LEDCPWM) enableClock() { + // Every peripheral starts switched off, to save power. Turn the LEDC clock + // on, then take it out of reset. These registers are in DPORT on this chip. + // The C3 and S3 call the same block SYSTEM. + esp.DPORT.SetPERIP_RST_EN_LEDC_RST(1) + esp.DPORT.SetPERIP_CLK_EN_LEDC_CLK_EN(1) + esp.DPORT.SetPERIP_RST_EN_LEDC_RST(0) + + // Use APB_CLK, which runs at 80MHz. This chip has no CONF_CLK_EN bit, so + // there is nothing more to switch on. + esp.LEDC.SetCONF_APB_CLK_SEL(1) +} + +// setTimerConf writes the resolution and the divider that Configure worked out +// into one timer. +// +// Watch TICK_SEL. It is 1 here, not 0. On this chip: +// +// 1 = APB_CLK, 80MHz <- what we want +// 0 = REF_TICK, 1MHz +// +// The low-speed timers on the C3 and S3 use the opposite meaning, so their code +// writes 0. Writing 0 here would make every frequency 80 times too slow. +// +// These timers have no PARA_UP bit. The short reset pulse at the end is what +// makes the new values take effect. +func (pwm *LEDCPWM) setTimerConf(dutyRes uint8, divReg uint32) { + switch pwm.timerNum { + case 0: + esp.LEDC.SetHSTIMER0_CONF_DUTY_RES(uint32(dutyRes)) + esp.LEDC.SetHSTIMER0_CONF_DIV_NUM(divReg) + esp.LEDC.SetHSTIMER0_CONF_TICK_SEL(1) + esp.LEDC.SetHSTIMER0_CONF_PAUSE(0) + esp.LEDC.SetHSTIMER0_CONF_RST(1) + esp.LEDC.SetHSTIMER0_CONF_RST(0) + case 1: + esp.LEDC.SetHSTIMER1_CONF_DUTY_RES(uint32(dutyRes)) + esp.LEDC.SetHSTIMER1_CONF_DIV_NUM(divReg) + esp.LEDC.SetHSTIMER1_CONF_TICK_SEL(1) + esp.LEDC.SetHSTIMER1_CONF_PAUSE(0) + esp.LEDC.SetHSTIMER1_CONF_RST(1) + esp.LEDC.SetHSTIMER1_CONF_RST(0) + case 2: + esp.LEDC.SetHSTIMER2_CONF_DUTY_RES(uint32(dutyRes)) + esp.LEDC.SetHSTIMER2_CONF_DIV_NUM(divReg) + esp.LEDC.SetHSTIMER2_CONF_TICK_SEL(1) + esp.LEDC.SetHSTIMER2_CONF_PAUSE(0) + esp.LEDC.SetHSTIMER2_CONF_RST(1) + esp.LEDC.SetHSTIMER2_CONF_RST(0) + case 3: + esp.LEDC.SetHSTIMER3_CONF_DUTY_RES(uint32(dutyRes)) + esp.LEDC.SetHSTIMER3_CONF_DIV_NUM(divReg) + esp.LEDC.SetHSTIMER3_CONF_TICK_SEL(1) + esp.LEDC.SetHSTIMER3_CONF_PAUSE(0) + esp.LEDC.SetHSTIMER3_CONF_RST(1) + esp.LEDC.SetHSTIMER3_CONF_RST(0) + } +} + +// chanOp does the work for one channel, numbered 0 to 7. It either sets the +// channel up, changes its duty, or flips its idle level. +// +// Each channel has its own registers, and each register has its own generated +// setter function. There is no array to index, so this has to be a long switch. +// The C3 and S3 files have the same shape. +// +// DUTY_SCALE is set to 0. LEDC can fade slowly from one duty to the next; 0 +// turns that off, so the duty changes in a single step. +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.SetHSCH0_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetHSCH0_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetHSCH0_CONF0_IDLE_LV(0) + esp.LEDC.SetHSCH0_HPOINT_HPOINT(0) + esp.LEDC.SetHSCH0_DUTY_DUTY(0) + esp.LEDC.SetHSCH0_CONF1_DUTY_SCALE(0) + esp.LEDC.SetHSCH0_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetHSCH0_CONF1_DUTY_NUM(1) + esp.LEDC.SetHSCH0_CONF1_DUTY_INC(1) + esp.LEDC.SetHSCH0_CONF1_DUTY_START(1) + case ledcChanOpSetDuty: + esp.LEDC.SetHSCH0_DUTY_DUTY(duty) + esp.LEDC.SetHSCH0_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetHSCH0_CONF1_DUTY_NUM(1) + esp.LEDC.SetHSCH0_CONF1_DUTY_INC(1) + esp.LEDC.SetHSCH0_CONF1_DUTY_START(1) + esp.LEDC.SetHSCH0_CONF0_SIG_OUT_EN(1) + case ledcChanOpSetInvert: + esp.LEDC.SetHSCH0_CONF0_IDLE_LV(invVal) + } + case 1: + switch op { + case ledcChanOpInit: + esp.LEDC.SetHSCH1_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetHSCH1_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetHSCH1_CONF0_IDLE_LV(0) + esp.LEDC.SetHSCH1_HPOINT_HPOINT(0) + esp.LEDC.SetHSCH1_DUTY_DUTY(0) + esp.LEDC.SetHSCH1_CONF1_DUTY_SCALE(0) + esp.LEDC.SetHSCH1_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetHSCH1_CONF1_DUTY_NUM(1) + esp.LEDC.SetHSCH1_CONF1_DUTY_INC(1) + esp.LEDC.SetHSCH1_CONF1_DUTY_START(1) + case ledcChanOpSetDuty: + esp.LEDC.SetHSCH1_DUTY_DUTY(duty) + esp.LEDC.SetHSCH1_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetHSCH1_CONF1_DUTY_NUM(1) + esp.LEDC.SetHSCH1_CONF1_DUTY_INC(1) + esp.LEDC.SetHSCH1_CONF1_DUTY_START(1) + esp.LEDC.SetHSCH1_CONF0_SIG_OUT_EN(1) + case ledcChanOpSetInvert: + esp.LEDC.SetHSCH1_CONF0_IDLE_LV(invVal) + } + case 2: + switch op { + case ledcChanOpInit: + esp.LEDC.SetHSCH2_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetHSCH2_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetHSCH2_CONF0_IDLE_LV(0) + esp.LEDC.SetHSCH2_HPOINT_HPOINT(0) + esp.LEDC.SetHSCH2_DUTY_DUTY(0) + esp.LEDC.SetHSCH2_CONF1_DUTY_SCALE(0) + esp.LEDC.SetHSCH2_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetHSCH2_CONF1_DUTY_NUM(1) + esp.LEDC.SetHSCH2_CONF1_DUTY_INC(1) + esp.LEDC.SetHSCH2_CONF1_DUTY_START(1) + case ledcChanOpSetDuty: + esp.LEDC.SetHSCH2_DUTY_DUTY(duty) + esp.LEDC.SetHSCH2_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetHSCH2_CONF1_DUTY_NUM(1) + esp.LEDC.SetHSCH2_CONF1_DUTY_INC(1) + esp.LEDC.SetHSCH2_CONF1_DUTY_START(1) + esp.LEDC.SetHSCH2_CONF0_SIG_OUT_EN(1) + case ledcChanOpSetInvert: + esp.LEDC.SetHSCH2_CONF0_IDLE_LV(invVal) + } + case 3: + switch op { + case ledcChanOpInit: + esp.LEDC.SetHSCH3_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetHSCH3_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetHSCH3_CONF0_IDLE_LV(0) + esp.LEDC.SetHSCH3_HPOINT_HPOINT(0) + esp.LEDC.SetHSCH3_DUTY_DUTY(0) + esp.LEDC.SetHSCH3_CONF1_DUTY_SCALE(0) + esp.LEDC.SetHSCH3_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetHSCH3_CONF1_DUTY_NUM(1) + esp.LEDC.SetHSCH3_CONF1_DUTY_INC(1) + esp.LEDC.SetHSCH3_CONF1_DUTY_START(1) + case ledcChanOpSetDuty: + esp.LEDC.SetHSCH3_DUTY_DUTY(duty) + esp.LEDC.SetHSCH3_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetHSCH3_CONF1_DUTY_NUM(1) + esp.LEDC.SetHSCH3_CONF1_DUTY_INC(1) + esp.LEDC.SetHSCH3_CONF1_DUTY_START(1) + esp.LEDC.SetHSCH3_CONF0_SIG_OUT_EN(1) + case ledcChanOpSetInvert: + esp.LEDC.SetHSCH3_CONF0_IDLE_LV(invVal) + } + case 4: + switch op { + case ledcChanOpInit: + esp.LEDC.SetHSCH4_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetHSCH4_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetHSCH4_CONF0_IDLE_LV(0) + esp.LEDC.SetHSCH4_HPOINT_HPOINT(0) + esp.LEDC.SetHSCH4_DUTY_DUTY(0) + esp.LEDC.SetHSCH4_CONF1_DUTY_SCALE(0) + esp.LEDC.SetHSCH4_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetHSCH4_CONF1_DUTY_NUM(1) + esp.LEDC.SetHSCH4_CONF1_DUTY_INC(1) + esp.LEDC.SetHSCH4_CONF1_DUTY_START(1) + case ledcChanOpSetDuty: + esp.LEDC.SetHSCH4_DUTY_DUTY(duty) + esp.LEDC.SetHSCH4_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetHSCH4_CONF1_DUTY_NUM(1) + esp.LEDC.SetHSCH4_CONF1_DUTY_INC(1) + esp.LEDC.SetHSCH4_CONF1_DUTY_START(1) + esp.LEDC.SetHSCH4_CONF0_SIG_OUT_EN(1) + case ledcChanOpSetInvert: + esp.LEDC.SetHSCH4_CONF0_IDLE_LV(invVal) + } + case 5: + switch op { + case ledcChanOpInit: + esp.LEDC.SetHSCH5_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetHSCH5_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetHSCH5_CONF0_IDLE_LV(0) + esp.LEDC.SetHSCH5_HPOINT_HPOINT(0) + esp.LEDC.SetHSCH5_DUTY_DUTY(0) + esp.LEDC.SetHSCH5_CONF1_DUTY_SCALE(0) + esp.LEDC.SetHSCH5_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetHSCH5_CONF1_DUTY_NUM(1) + esp.LEDC.SetHSCH5_CONF1_DUTY_INC(1) + esp.LEDC.SetHSCH5_CONF1_DUTY_START(1) + case ledcChanOpSetDuty: + esp.LEDC.SetHSCH5_DUTY_DUTY(duty) + esp.LEDC.SetHSCH5_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetHSCH5_CONF1_DUTY_NUM(1) + esp.LEDC.SetHSCH5_CONF1_DUTY_INC(1) + esp.LEDC.SetHSCH5_CONF1_DUTY_START(1) + esp.LEDC.SetHSCH5_CONF0_SIG_OUT_EN(1) + case ledcChanOpSetInvert: + esp.LEDC.SetHSCH5_CONF0_IDLE_LV(invVal) + } + case 6: + switch op { + case ledcChanOpInit: + esp.LEDC.SetHSCH6_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetHSCH6_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetHSCH6_CONF0_IDLE_LV(0) + esp.LEDC.SetHSCH6_HPOINT_HPOINT(0) + esp.LEDC.SetHSCH6_DUTY_DUTY(0) + esp.LEDC.SetHSCH6_CONF1_DUTY_SCALE(0) + esp.LEDC.SetHSCH6_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetHSCH6_CONF1_DUTY_NUM(1) + esp.LEDC.SetHSCH6_CONF1_DUTY_INC(1) + esp.LEDC.SetHSCH6_CONF1_DUTY_START(1) + case ledcChanOpSetDuty: + esp.LEDC.SetHSCH6_DUTY_DUTY(duty) + esp.LEDC.SetHSCH6_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetHSCH6_CONF1_DUTY_NUM(1) + esp.LEDC.SetHSCH6_CONF1_DUTY_INC(1) + esp.LEDC.SetHSCH6_CONF1_DUTY_START(1) + esp.LEDC.SetHSCH6_CONF0_SIG_OUT_EN(1) + case ledcChanOpSetInvert: + esp.LEDC.SetHSCH6_CONF0_IDLE_LV(invVal) + } + case 7: + switch op { + case ledcChanOpInit: + esp.LEDC.SetHSCH7_CONF0_TIMER_SEL(uint32(pwm.timerNum)) + esp.LEDC.SetHSCH7_CONF0_SIG_OUT_EN(1) + esp.LEDC.SetHSCH7_CONF0_IDLE_LV(0) + esp.LEDC.SetHSCH7_HPOINT_HPOINT(0) + esp.LEDC.SetHSCH7_DUTY_DUTY(0) + esp.LEDC.SetHSCH7_CONF1_DUTY_SCALE(0) + esp.LEDC.SetHSCH7_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetHSCH7_CONF1_DUTY_NUM(1) + esp.LEDC.SetHSCH7_CONF1_DUTY_INC(1) + esp.LEDC.SetHSCH7_CONF1_DUTY_START(1) + case ledcChanOpSetDuty: + esp.LEDC.SetHSCH7_DUTY_DUTY(duty) + esp.LEDC.SetHSCH7_CONF1_DUTY_CYCLE(1) + esp.LEDC.SetHSCH7_CONF1_DUTY_NUM(1) + esp.LEDC.SetHSCH7_CONF1_DUTY_INC(1) + esp.LEDC.SetHSCH7_CONF1_DUTY_START(1) + esp.LEDC.SetHSCH7_CONF0_SIG_OUT_EN(1) + case ledcChanOpSetInvert: + esp.LEDC.SetHSCH7_CONF0_IDLE_LV(invVal) + } + } +} diff --git a/src/machine/machine_esp32xx_ls_pwm.go b/src/machine/machine_esp32xx_ls_pwm.go new file mode 100644 index 0000000000..a1b7138c47 --- /dev/null +++ b/src/machine/machine_esp32xx_ls_pwm.go @@ -0,0 +1,69 @@ +//go:build esp32c3 || esp32s3 + +// Low-speed LEDC code, shared by the ESP32-C3 and the ESP32-S3. +// +// Both chips have only the low-speed LEDC block. Their clock and timer registers +// use the same names, so one copy of this code serves both. +// +// The classic ESP32 has a high-speed block instead, with different register +// names. Its versions of these two functions are in machine_esp32_pwm.go. +// +// The SVD (lib/cmsis-svd/data/Espressif/esp32s3.svd) describes the two bits that +// make a change take effect on these chips. 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" + +// enableClock turns the LEDC hardware on and picks APB_CLK as its clock. +func (pwm *LEDCPWM) enableClock() { + // 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) +} + +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) + } +} diff --git a/src/machine/machine_esp32xx_pwm.go b/src/machine/machine_esp32xx_pwm.go index 03a63d60be..558e720153 100644 --- a/src/machine/machine_esp32xx_pwm.go +++ b/src/machine/machine_esp32xx_pwm.go @@ -1,23 +1,30 @@ -//go:build esp32c3 || esp32s3 +//go:build esp32 || 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). +// PWM on ESP32 chips uses the LEDC peripheral. LEDC means "LED Control", but it +// works for any PWM job, not only LEDs. // -// 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". +// A timer sets the frequency. Channels hang off a timer. Every channel on the +// same timer shares that frequency, but each channel has its own duty. The +// signal reaches a pin through the GPIO matrix, using signal number +// SigOutBase + channel. +// +// This file holds the part that is the same on every chip. Three functions are +// different per chip and live in other files: +// +// enableClock turn the LEDC hardware on and pick its clock +// setTimerConf program one timer +// chanOp set up a channel, change its duty, or invert it +// +// The classic ESP32 has them in machine_esp32_pwm.go. The C3 and S3 have them in +// machine_esp32xx_ls_pwm.go and machine_esp32{c3,s3}_pwm.go. +// +// The order of setup is the one ESP-IDF uses: configure the timer, then the +// channel, then write the duty and tell the hardware to use it. See +// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/ledc.html package machine -import ( - "device/esp" - "errors" -) +import "errors" const ledcApbClock = 80_000000 @@ -45,14 +52,9 @@ const ( ) 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) + // Turn the LEDC hardware on and pick its clock source. The registers have + // different names on each chip, so this lives in the per-chip file. + pwm.enableClock() period := config.Period if period == 0 { @@ -90,44 +92,6 @@ func (pwm *LEDCPWM) Configure(config PWMConfig) error { 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")