esp32_ledc
LEDC: the ESP32’s PWM generator.
Eight high-speed channels sharing four timers. A timer sets the period; a channel picks a timer and a duty. Despite the name it is a general PWM peripheral — servos, motor drivers and buzzers all use it.
This drives the high-speed channels only. The low-speed ones exist
(LEDC_LSCH0_CONF0_REG at offset 0xA0) and differ in that they can run
from the slow clock while the CPU sleeps. Nothing here sleeps yet, and a
second set of channels with subtly different registers is a second set of
things to get wrong.
The one encoding that is not obvious
Section titled “The one encoding that is not obvious”LEDC_HSCHn_DUTY_REG does not hold the duty. It holds duty << 4:
bits [24:4] are the integer part and the low four bits are a fraction the
hardware uses to dither between two lpoint values. Writing a duty
directly gives one sixteenth of the intended output — a signal that looks
plausible on a scope and is wrong everywhere.
Quoted from ledc_reg.h:
reg_lpoint_hsch0 = (reg_hpoint_hsch0[19:0] + reg_duty_hsch0[24:4])… The least four bits in this register represent the decimal part
[Channel::set_duty] takes a plain duty and does the shift.
Register facts
Section titled “Register facts”From soc/ledc_reg.h, soc/soc.h, soc/dport_reg.h and
soc/gpio_sig_map.h, read rather than recalled:
DR_REG_LEDC_BASE |
0x3ff59000 |
| HS channel stride | 0x14 — five registers each |
| HS timer stride | 0x08 |
LEDC_TICK_SEL_HSTIMERn |
bit 25, 1 = APB, 0 = REF_TICK |
LEDC_HS_SIG_OUT0_IDX |
71 |
DPORT_LEDC_CLK_EN |
BIT(11) |
Structs
Section titled “Structs”Channel
Section titled “Channel”struct Channel { /* private fields */ }One of the eight high-speed channels.
struct Timer { /* private fields */ }One of the four high-speed timers.
Functions
Section titled “Functions”divider_for
Section titled “divider_for”const fn divider_for(freq_hz: u32, res_bits: u8) -> Option<u32>Q10.8 timer divider for freq_hz at res_bits resolution, or None if
the combination is out of range.
freq = APB / (div/256 * 2^res), so div = APB * 256 / (freq * 2^res).
Returning None rather than clamping is deliberate: a clamped divider
silently produces a different frequency, and a servo driven at the wrong
frequency moves to the wrong place rather than failing.
duty_for_percent
Section titled “duty_for_percent”const fn duty_for_percent(pct: u8, res_bits: u8) -> u32Duty steps for a percentage, rounded to nearest and clamped at 100.
freq_for
Section titled “freq_for”const fn freq_for(div: u32, res_bits: u8) -> u32The frequency a divider actually produces, for reporting what was asked against what was got.
max_duty
Section titled “max_duty”const fn max_duty(res_bits: u8) -> u32Duty steps at res_bits resolution: full scale, i.e. always-on.
Constants
Section titled “Constants”APB_HZ
Section titled “APB_HZ”const APB_HZ: u32;APB clock, the only source this driver uses.
CHANNELS
Section titled “CHANNELS”const CHANNELS: u8;High-speed channels.
MAX_RES_BITS
Section titled “MAX_RES_BITS”const MAX_RES_BITS: u8;Widest duty resolution the timer’s DUTY_RES field allows for a period
that still fits HPOINT.
TIMERS
Section titled “TIMERS”const TIMERS: u8;High-speed timers.