ledc: add esp32c5 support and model ledc_sclk source#4966
ledc: add esp32c5 support and model ledc_sclk source#4966Veanir wants to merge 10 commits intoesp-rs:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds ESP32-C5 support for the LEDC peripheral by wiring it into the device metadata/clock tree, updating the LEDC HAL to use the correct timer source frequency for divider calculations, and introducing a new HIL loopback test binary to validate duty-cycle and output-frequency behavior across chips.
Changes:
- Enable LEDC for ESP32-C5 in metadata and model the
LEDC_SCLKclock mux (PLL_F80M / RC_FAST_CLK / XTAL_CLK). - Update LEDC timer divider calculations to use
ledc_sclk_frequency()when the clock-tree node exists (APB fallback otherwise). - Add
hil-testLEDC loopback tests and includeesp32c5in the LEDC HIL chip matrix.
Reviewed changes
Copilot reviewed 9 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
hil-test/src/bin/ledc.rs |
Adds LEDC HIL loopback tests for duty-cycle changes, 0% duty behavior, and output period validation. |
hil-test/Cargo.toml |
Registers the new ledc HIL binary. |
esp-metadata/devices/esp32c5.toml |
Enables the LEDC peripheral and adds the LEDC_SCLK clock-tree mux node for ESP32-C5. |
esp-metadata-generated/src/_generated_esp32c5.rs |
Regenerates ESP32-C5 clock tree types/APIs to include LEDC_SCLK and LEDC peripheral controls. |
esp-metadata-generated/src/_build_script_utils.rs |
Emits new cfgs for LEDC presence/support and soc_has_clock_node_ledc_sclk. |
esp-hal/src/soc/esp32c5/clocks.rs |
Implements ESP32-C5 LEDC_SCLK enable/configure hooks and sets default preset to PllF80m. |
esp-hal/src/ledc/timer.rs |
Uses LEDC_SCLK frequency (when available) for LS timer source frequency/divider math. |
esp-hal/src/ledc/mod.rs |
Routes global slow LEDC clock setup through the clock-tree config when available; updates PCR handling for esp32c5. |
esp-hal/src/ledc/channel.rs |
Adjusts channel availability for esp32c5 and adds esp32c5-specific duty/fade start handling. |
esp-hal/README.md |
Updates the capabilities table to mark LEDC supported for esp32c5. |
| #[cfg(esp32c6)] | ||
| pcr.ledc_sclk_conf() | ||
| .write(|w| unsafe { w.ledc_sclk_sel().bits(1) }); |
There was a problem hiding this comment.
On esp32c6 this ledc_sclk_conf().write(|w| w.ledc_sclk_sel().bits(1)) call clears ledc_sclk_en (because write starts from the reset value). Since ledc_sclk_en was set just above, this effectively disables LEDC_SCLK again. Use modify here (or set both ledc_sclk_en and ledc_sclk_sel in the same write) so the enable bit stays asserted.
There was a problem hiding this comment.
Fixed in 50b9fe5e: on esp32c6/esp32h2 we now use modify and set ledc_sclk_sel while keeping ledc_sclk_en asserted, so the clock gate is not accidentally cleared by a later write.
| let _ = (duty_inc, duty_steps, cycles_per_step, duty_per_cycle); | ||
| self.ledc | ||
| .ch(self.number as usize) | ||
| .conf1() | ||
| .write(|w| w.duty_start().set_bit()); |
There was a problem hiding this comment.
The esp32c5 start_duty_fade_inner implementation ignores all fade parameters and only sets duty_start. As a result, start_duty_fade() can return Ok(()) but not perform a fade (and is_duty_fade_running() may not reflect reality). Either implement the actual fade configuration for ESP32-C5 or make start_duty_fade return an error/unsupported on esp32c5.
| let _ = (duty_inc, duty_steps, cycles_per_step, duty_per_cycle); | |
| self.ledc | |
| .ch(self.number as usize) | |
| .conf1() | |
| .write(|w| w.duty_start().set_bit()); | |
| let cnum = self.number as usize; | |
| self.ledc | |
| .ch(cnum) | |
| .conf1() | |
| .write(|w| w.duty_start().set_bit()); | |
| self.ledc.ch_gamma_wr(cnum).write(|w| unsafe { | |
| w.ch_gamma_duty_inc() | |
| .variant(duty_inc) | |
| .ch_gamma_duty_num() // count of incs before stopping | |
| .bits(duty_steps) | |
| .ch_gamma_duty_cycle() // overflows between incs | |
| .bits(cycles_per_step) | |
| .ch_gamma_scale() | |
| .bits(duty_per_cycle) | |
| }); | |
| self.ledc | |
| .ch_gamma_wr_addr(cnum) | |
| .write(|w| unsafe { w.ch_gamma_wr_addr().bits(0) }); | |
| self.ledc | |
| .ch_gamma_conf(cnum) | |
| .write(|w| unsafe { w.ch_gamma_entry_num().bits(0x1) }); |
There was a problem hiding this comment.
Implemented for ESP32-C5 in the latest commit (846d86e5).
What changed:
- Added real fade parameter programming for C5 using the documented gamma RAM window (
LEDC + 0x400, 16 ranges/channel), since the current C5 PAC does not exposech_gamma_wr*registers. - Set up fade-complete signaling on C5 (
int_clr+int_ena) and madeis_duty_fade_running()readint_stfor C5. - Fixed the C5 init path by clearing
PCR.LEDC_PD_CTRL.LEDC_MEM_FORCE_PDinLedc::new(); this bit resets to force power-down and otherwise gamma RAM writes do not take effect. - Added a C5 HIL test (
duty_fade_changes_output_and_completes) that verifies fade starts, completes, and actually changes waveform duty.
Verification run on hardware (esp32c5): 4/4 tests passing, repeated multiple times.
There was a problem hiding this comment.
Follow-up: I opened esp-rs/esp-pacs#397 to expose C5 gamma RAM in PAC as a typed LEDC_GAMMA_RAM peripheral (0x6000_7400, 96 entries = 16 ranges x 6 channels).
Small wording correction to my previous note: on C5 the missing piece is gamma RAM range register exposure (not ch_gamma_wr*, which is the C6/H2 register model).
Clear forced LEDC memory power-down on C5 and program gamma RAM ranges so fade parameters take effect. Add a C5 HIL fade test to verify duty progression and completion on hardware.
|
Quick status update: I tested a local I did not push that dependency switch here, so this PR stays on upstream PAC. Once |
| for _ in 0..SAMPLE_COUNT { | ||
| if input.is_high() { | ||
| high_count += 1; | ||
| } | ||
|
|
||
| delay.delay_micros(SAMPLE_INTERVAL_US); | ||
| } |
There was a problem hiding this comment.
Why don't you count transitions instead? This is a bit too complex with the underlying timer and all, and I'm not sure that your 37us sampling interval is actually 37us. You could just loop a GPIO read instead, and bump the counter when it changes from low to high.
There was a problem hiding this comment.
Updated in 41259374: removed delay-based GPIO sampling. sample_high_count is now a tight GPIO read loop (no assumed microsecond delay), and the frequency test measures period by counting rising-edge transitions directly. Verified on ESP32-C5 HIL (3 runs, 4/4 tests passing).
| return true; | ||
| } | ||
|
|
||
| delay.delay_micros(EDGE_POLL_INTERVAL_US); |
There was a problem hiding this comment.
Why wait? Just loop, and if the transition doesn't happen, the test will time out anyway.
There was a problem hiding this comment.
Done in 41259374: removed the explicit wait helper + manual iteration limits; edge detection now just polls in a tight loop and relies on the embedded-test timeout if the signal does not toggle.
On esp32c6/esp32h2, selecting LEDC_SCLK with a cleared back to reset, effectively disabling the clock gate again. Use and set both the mux selection and enable bit together.
Remove delay-based GPIO sampling and edge-wait helpers; count levels in a tight loop and measure PWM period by counting rising edges. This avoids assuming microsecond delays are precise and relies on the embedded-test timeout if edges are missing.
| # ## Miscellaneous | ||
| # [device.io_mux] | ||
| [device.systimer] | ||
| [device.ledc] |
There was a problem hiding this comment.
Let's add a has_gamma_fade cfg to this, please
| }); | ||
|
|
||
| #[cfg(any(esp32c5, esp32c6, esp32h2))] | ||
| let pcr = unsafe { &*crate::peripherals::PCR::ptr() }; |
There was a problem hiding this comment.
Ok I have to ask if this is AI generated
There was a problem hiding this comment.
oh yeah (Codex 5.3 to be exact) - is it strictly forbidden here? Upon some reflection - I should've marked it somewhere.
I needed RMT and LEDC with C5 in my project - once I got it working and tested it locally I thought I could contribute here.
If I'm not being usefull here (or even mallicious) just let me know :)
There was a problem hiding this comment.
Well we ask people to disclose the use of AI, but my main observation is that this is outdated code (use PCR::regs(), and generally not up to the latest standards we have (which is gating it with metadata-generated symbols, like soc_has_pcr in this case).
There was a problem hiding this comment.
Thanks - I’ll review the current project conventions
25104ff to
d82e5f7
Compare
|
New commits in main has made this PR unmergable. Please resolve the conflicts. |
|
@Veanir will you be continue working on this PR anytime soon? I need RMT and LEDC with C5 in my project too and I am already struggling to get this project to work on the current main. Thanks! |
Summary
LEDC_SCLKand use that for LEDC divider calculationsesp32c5in LEDC HIL matrixMotivation
On ESP32-C5, LEDC output could be ~2x the configured frequency due to a clock-source mismatch:
set_global_slow_clock(LSGlobalClkSource::APBClk)selected the LEDC source asPLL_F80M(80 MHz)apb_clock(40 MHz in C5 defaults)This PR fixes that mismatch by modeling
LEDC_SCLKin the C5 clock tree and using the selected LEDC source frequency for timer calculations.What Changed
LEDC_SCLKmux (PLL_F80M/RC_FAST_CLK/XTAL_CLK)enable_ledc_sclk_implandconfigure_ledc_sclk_implLedcSclkConfig::PllF80mledc_sclk_frequency()for divider calculations whenLEDC_SCLKexists (with APB fallback)hil-test/src/bin/ledc.rs)ledcbinary inhil-test/Cargo.tomlesp32c5in LEDC HIL chip listVerification
cargo xtask run tests esp32c5 --test ledc --toolchain nightly --repeat 5cargo xtask run tests esp32s3 --test ledc --toolchain esp --repeat 5cargo xtask run tests esp32c5 --test rmt --toolchain nightly(regression)cargo +nightly build --manifest-path hil-test/Cargo.toml --config hil-test/.cargo/config.toml --target=riscv32imc-unknown-none-elf --features=unstable,esp32c3 --bin=ledc --releasecargo +nightly build --manifest-path hil-test/Cargo.toml --config hil-test/.cargo/config.toml --target=riscv32imac-unknown-none-elf --features=unstable,esp32c6 --bin=ledc --releasecargo +nightly build --manifest-path hil-test/Cargo.toml --config hil-test/.cargo/config.toml --target=riscv32imac-unknown-none-elf --features=unstable,esp32h2 --bin=ledc --releasecargo xtask build tests esp32c5 --test ledc --toolchain nightlycargo xtask run elfs esp32c5 target/tests/esp32c5 --elfs ledccargo xtask build tests esp32s3 --test ledc --toolchain espcargo xtask run elfs esp32s3 target/tests/esp32s3 --elfs ledcNotes