Bare-Metal Ada on the ESP32-S3 A step-by-step guide to running Ada on the ESP32-S3 with no ESP-IDF, no FreeRTOS, and no Python.

Step 22 of 56

LEDC and sigma-delta: the simple outputs

Eight PWM channels for dimming and clean square waves, and eight 1-bit density-modulated outputs that become analog with one resistor and one capacitor.

LEDC: PWM without the motor-control machinery

Eight low-speed channels fed by four timers. A channel picks a timer — which sets its frequency and duty resolution — and drives a GPIO with a duty cycle you change at run time. This is the "dim an LED, generate a clean PWM" block; for dead-time, fault inputs and capture, see MCPWM.

type Channel_Index is range 0 .. 7;
subtype Resolution   is Positive range 1 .. 14;      --  duty-cycle bits
subtype Duty_Percent is Float range 0.0 .. 100.0;

procedure Claim  (C : in out Channel; Index : Channel_Index);
procedure Configure (C : ...; Freq : ...; Bits : Resolution; Pin : ...);
procedure Set_Duty  (C : Channel; Percent : Duty_Percent);
procedure Stop      (C : Channel);

Two constraints are worth knowing before you pick numbers.

Resolution and frequency trade against each other: freq_max = 80 MHz / 2**Bits. Fourteen bits of duty resolution caps you under 5 kHz; a 100 kHz carrier leaves about nine bits. Choose Bits for the dimming smoothness you actually need, not the maximum.

A channel uses timer Index mod 4. Channels 0 and 4 share a timer, as do 1 and 5, and so on — so two channels four apart cannot run at different frequencies. Spread channels across 0 .. 3 when you need independent rates.

Set_Duty takes effect at the next period, so it is safe to call while running — no glitch, no partial-update flicker. The handle is limited and controlled as everywhere else here, and finalization stops the output as well as releasing the channel, so a leaked handle cannot keep driving a pad.

Sigma-delta: analog for the price of an RC filter

Eight channels in the GPIO sigma-delta unit. Each emits a high-frequency pulse stream whose average density is set by a signed 8-bit value; pass it through an external RC low-pass and you have a cheap analog output — LED dimming, a bias voltage, simple audio.

type Channel_Index is range 0 .. 7;
subtype Density_Percent is Float range 0.0 .. 100.0;

procedure Configure   (C : ...; Pin : ...; Carrier_Hz : ...);   --  starts at 0 %
procedure Set_Density (C : Channel; Percent : Density_Percent); --  one register write

The distinction from LEDC matters when choosing between them. LEDC varies the width of a pulse at a fixed frequency, so its output has energy at the PWM frequency and its harmonics. Sigma-delta varies the density of fixed-width pulses, pushing quantisation noise up in frequency where a simple filter removes it — which is why it makes a better analog voltage and why the same technique appears inside I2S's PDM mode.

Set_Density is a single register write, so it is cheap enough to call from a sample loop.

LEDC and sigma-delta: the simple outputs · Bare-Metal Ada on the ESP32-S3
Bare-Metal Ada on the ESP32-S3 A step-by-step guide to running Ada on the ESP32-S3 with no ESP-IDF, no FreeRTOS, and no Python.

Step 22 of 56

LEDC and sigma-delta: the simple outputs

Eight PWM channels for dimming and clean square waves, and eight 1-bit density-modulated outputs that become analog with one resistor and one capacitor.

LEDC: PWM without the motor-control machinery

Eight low-speed channels fed by four timers. A channel picks a timer — which sets its frequency and duty resolution — and drives a GPIO with a duty cycle you change at run time. This is the "dim an LED, generate a clean PWM" block; for dead-time, fault inputs and capture, see MCPWM.

type Channel_Index is range 0 .. 7;
subtype Resolution   is Positive range 1 .. 14;      --  duty-cycle bits
subtype Duty_Percent is Float range 0.0 .. 100.0;

procedure Claim  (C : in out Channel; Index : Channel_Index);
procedure Configure (C : ...; Freq : ...; Bits : Resolution; Pin : ...);
procedure Set_Duty  (C : Channel; Percent : Duty_Percent);
procedure Stop      (C : Channel);

Two constraints are worth knowing before you pick numbers.

Resolution and frequency trade against each other: freq_max = 80 MHz / 2**Bits. Fourteen bits of duty resolution caps you under 5 kHz; a 100 kHz carrier leaves about nine bits. Choose Bits for the dimming smoothness you actually need, not the maximum.

A channel uses timer Index mod 4. Channels 0 and 4 share a timer, as do 1 and 5, and so on — so two channels four apart cannot run at different frequencies. Spread channels across 0 .. 3 when you need independent rates.

Set_Duty takes effect at the next period, so it is safe to call while running — no glitch, no partial-update flicker. The handle is limited and controlled as everywhere else here, and finalization stops the output as well as releasing the channel, so a leaked handle cannot keep driving a pad.

Sigma-delta: analog for the price of an RC filter

Eight channels in the GPIO sigma-delta unit. Each emits a high-frequency pulse stream whose average density is set by a signed 8-bit value; pass it through an external RC low-pass and you have a cheap analog output — LED dimming, a bias voltage, simple audio.

type Channel_Index is range 0 .. 7;
subtype Density_Percent is Float range 0.0 .. 100.0;

procedure Configure   (C : ...; Pin : ...; Carrier_Hz : ...);   --  starts at 0 %
procedure Set_Density (C : Channel; Percent : Density_Percent); --  one register write

The distinction from LEDC matters when choosing between them. LEDC varies the width of a pulse at a fixed frequency, so its output has energy at the PWM frequency and its harmonics. Sigma-delta varies the density of fixed-width pulses, pushing quantisation noise up in frequency where a simple filter removes it — which is why it makes a better analog voltage and why the same technique appears inside I2S's PDM mode.

Set_Density is a single register write, so it is cheap enough to call from a sample loop.

LEDC and sigma-delta: the simple outputs · Bare-Metal Ada on the ESP32-S3
Bare-Metal Ada on the ESP32-S3 A step-by-step guide to running Ada on the ESP32-S3 with no ESP-IDF, no FreeRTOS, and no Python.

Step 22 of 56

LEDC and sigma-delta: the simple outputs

Eight PWM channels for dimming and clean square waves, and eight 1-bit density-modulated outputs that become analog with one resistor and one capacitor.

LEDC: PWM without the motor-control machinery

Eight low-speed channels fed by four timers. A channel picks a timer — which sets its frequency and duty resolution — and drives a GPIO with a duty cycle you change at run time. This is the "dim an LED, generate a clean PWM" block; for dead-time, fault inputs and capture, see MCPWM.

type Channel_Index is range 0 .. 7;
subtype Resolution   is Positive range 1 .. 14;      --  duty-cycle bits
subtype Duty_Percent is Float range 0.0 .. 100.0;

procedure Claim  (C : in out Channel; Index : Channel_Index);
procedure Configure (C : ...; Freq : ...; Bits : Resolution; Pin : ...);
procedure Set_Duty  (C : Channel; Percent : Duty_Percent);
procedure Stop      (C : Channel);

Two constraints are worth knowing before you pick numbers.

Resolution and frequency trade against each other: freq_max = 80 MHz / 2**Bits. Fourteen bits of duty resolution caps you under 5 kHz; a 100 kHz carrier leaves about nine bits. Choose Bits for the dimming smoothness you actually need, not the maximum.

A channel uses timer Index mod 4. Channels 0 and 4 share a timer, as do 1 and 5, and so on — so two channels four apart cannot run at different frequencies. Spread channels across 0 .. 3 when you need independent rates.

Set_Duty takes effect at the next period, so it is safe to call while running — no glitch, no partial-update flicker. The handle is limited and controlled as everywhere else here, and finalization stops the output as well as releasing the channel, so a leaked handle cannot keep driving a pad.

Sigma-delta: analog for the price of an RC filter

Eight channels in the GPIO sigma-delta unit. Each emits a high-frequency pulse stream whose average density is set by a signed 8-bit value; pass it through an external RC low-pass and you have a cheap analog output — LED dimming, a bias voltage, simple audio.

type Channel_Index is range 0 .. 7;
subtype Density_Percent is Float range 0.0 .. 100.0;

procedure Configure   (C : ...; Pin : ...; Carrier_Hz : ...);   --  starts at 0 %
procedure Set_Density (C : Channel; Percent : Density_Percent); --  one register write

The distinction from LEDC matters when choosing between them. LEDC varies the width of a pulse at a fixed frequency, so its output has energy at the PWM frequency and its harmonics. Sigma-delta varies the density of fixed-width pulses, pushing quantisation noise up in frequency where a simple filter removes it — which is why it makes a better analog voltage and why the same technique appears inside I2S's PDM mode.

Set_Density is a single register write, so it is cheap enough to call from a sample loop.

LEDC and sigma-delta: the simple outputs · Bare-Metal Ada on the ESP32-S3
Bare-Metal Ada on the ESP32-S3 A step-by-step guide to running Ada on the ESP32-S3 with no ESP-IDF, no FreeRTOS, and no Python.

Step 22 of 56

LEDC and sigma-delta: the simple outputs

Eight PWM channels for dimming and clean square waves, and eight 1-bit density-modulated outputs that become analog with one resistor and one capacitor.

LEDC: PWM without the motor-control machinery

Eight low-speed channels fed by four timers. A channel picks a timer — which sets its frequency and duty resolution — and drives a GPIO with a duty cycle you change at run time. This is the "dim an LED, generate a clean PWM" block; for dead-time, fault inputs and capture, see MCPWM.

type Channel_Index is range 0 .. 7;
subtype Resolution   is Positive range 1 .. 14;      --  duty-cycle bits
subtype Duty_Percent is Float range 0.0 .. 100.0;

procedure Claim  (C : in out Channel; Index : Channel_Index);
procedure Configure (C : ...; Freq : ...; Bits : Resolution; Pin : ...);
procedure Set_Duty  (C : Channel; Percent : Duty_Percent);
procedure Stop      (C : Channel);

Two constraints are worth knowing before you pick numbers.

Resolution and frequency trade against each other: freq_max = 80 MHz / 2**Bits. Fourteen bits of duty resolution caps you under 5 kHz; a 100 kHz carrier leaves about nine bits. Choose Bits for the dimming smoothness you actually need, not the maximum.

A channel uses timer Index mod 4. Channels 0 and 4 share a timer, as do 1 and 5, and so on — so two channels four apart cannot run at different frequencies. Spread channels across 0 .. 3 when you need independent rates.

Set_Duty takes effect at the next period, so it is safe to call while running — no glitch, no partial-update flicker. The handle is limited and controlled as everywhere else here, and finalization stops the output as well as releasing the channel, so a leaked handle cannot keep driving a pad.

Sigma-delta: analog for the price of an RC filter

Eight channels in the GPIO sigma-delta unit. Each emits a high-frequency pulse stream whose average density is set by a signed 8-bit value; pass it through an external RC low-pass and you have a cheap analog output — LED dimming, a bias voltage, simple audio.

type Channel_Index is range 0 .. 7;
subtype Density_Percent is Float range 0.0 .. 100.0;

procedure Configure   (C : ...; Pin : ...; Carrier_Hz : ...);   --  starts at 0 %
procedure Set_Density (C : Channel; Percent : Density_Percent); --  one register write

The distinction from LEDC matters when choosing between them. LEDC varies the width of a pulse at a fixed frequency, so its output has energy at the PWM frequency and its harmonics. Sigma-delta varies the density of fixed-width pulses, pushing quantisation noise up in frequency where a simple filter removes it — which is why it makes a better analog voltage and why the same technique appears inside I2S's PDM mode.

Set_Density is a single register write, so it is cheap enough to call from a sample loop.

LEDC and sigma-delta: the simple outputs · Bare-Metal Ada on the ESP32-S3
Bare-Metal Ada on the ESP32-S3 A step-by-step guide to running Ada on the ESP32-S3 with no ESP-IDF, no FreeRTOS, and no Python.

Step 22 of 56

LEDC and sigma-delta: the simple outputs

Eight PWM channels for dimming and clean square waves, and eight 1-bit density-modulated outputs that become analog with one resistor and one capacitor.

LEDC: PWM without the motor-control machinery

Eight low-speed channels fed by four timers. A channel picks a timer — which sets its frequency and duty resolution — and drives a GPIO with a duty cycle you change at run time. This is the "dim an LED, generate a clean PWM" block; for dead-time, fault inputs and capture, see MCPWM.

type Channel_Index is range 0 .. 7;
subtype Resolution   is Positive range 1 .. 14;      --  duty-cycle bits
subtype Duty_Percent is Float range 0.0 .. 100.0;

procedure Claim  (C : in out Channel; Index : Channel_Index);
procedure Configure (C : ...; Freq : ...; Bits : Resolution; Pin : ...);
procedure Set_Duty  (C : Channel; Percent : Duty_Percent);
procedure Stop      (C : Channel);

Two constraints are worth knowing before you pick numbers.

Resolution and frequency trade against each other: freq_max = 80 MHz / 2**Bits. Fourteen bits of duty resolution caps you under 5 kHz; a 100 kHz carrier leaves about nine bits. Choose Bits for the dimming smoothness you actually need, not the maximum.

A channel uses timer Index mod 4. Channels 0 and 4 share a timer, as do 1 and 5, and so on — so two channels four apart cannot run at different frequencies. Spread channels across 0 .. 3 when you need independent rates.

Set_Duty takes effect at the next period, so it is safe to call while running — no glitch, no partial-update flicker. The handle is limited and controlled as everywhere else here, and finalization stops the output as well as releasing the channel, so a leaked handle cannot keep driving a pad.

Sigma-delta: analog for the price of an RC filter

Eight channels in the GPIO sigma-delta unit. Each emits a high-frequency pulse stream whose average density is set by a signed 8-bit value; pass it through an external RC low-pass and you have a cheap analog output — LED dimming, a bias voltage, simple audio.

type Channel_Index is range 0 .. 7;
subtype Density_Percent is Float range 0.0 .. 100.0;

procedure Configure   (C : ...; Pin : ...; Carrier_Hz : ...);   --  starts at 0 %
procedure Set_Density (C : Channel; Percent : Density_Percent); --  one register write

The distinction from LEDC matters when choosing between them. LEDC varies the width of a pulse at a fixed frequency, so its output has energy at the PWM frequency and its harmonics. Sigma-delta varies the density of fixed-width pulses, pushing quantisation noise up in frequency where a simple filter removes it — which is why it makes a better analog voltage and why the same technique appears inside I2S's PDM mode.

Set_Density is a single register write, so it is cheap enough to call from a sample loop.

LEDC and sigma-delta: the simple outputs · Bare-Metal Ada on the ESP32-S3
Bare-Metal Ada on the ESP32-S3 A step-by-step guide to running Ada on the ESP32-S3 with no ESP-IDF, no FreeRTOS, and no Python.

Step 22 of 56

LEDC and sigma-delta: the simple outputs

Eight PWM channels for dimming and clean square waves, and eight 1-bit density-modulated outputs that become analog with one resistor and one capacitor.

LEDC: PWM without the motor-control machinery

Eight low-speed channels fed by four timers. A channel picks a timer — which sets its frequency and duty resolution — and drives a GPIO with a duty cycle you change at run time. This is the "dim an LED, generate a clean PWM" block; for dead-time, fault inputs and capture, see MCPWM.

type Channel_Index is range 0 .. 7;
subtype Resolution   is Positive range 1 .. 14;      --  duty-cycle bits
subtype Duty_Percent is Float range 0.0 .. 100.0;

procedure Claim  (C : in out Channel; Index : Channel_Index);
procedure Configure (C : ...; Freq : ...; Bits : Resolution; Pin : ...);
procedure Set_Duty  (C : Channel; Percent : Duty_Percent);
procedure Stop      (C : Channel);

Two constraints are worth knowing before you pick numbers.

Resolution and frequency trade against each other: freq_max = 80 MHz / 2**Bits. Fourteen bits of duty resolution caps you under 5 kHz; a 100 kHz carrier leaves about nine bits. Choose Bits for the dimming smoothness you actually need, not the maximum.

A channel uses timer Index mod 4. Channels 0 and 4 share a timer, as do 1 and 5, and so on — so two channels four apart cannot run at different frequencies. Spread channels across 0 .. 3 when you need independent rates.

Set_Duty takes effect at the next period, so it is safe to call while running — no glitch, no partial-update flicker. The handle is limited and controlled as everywhere else here, and finalization stops the output as well as releasing the channel, so a leaked handle cannot keep driving a pad.

Sigma-delta: analog for the price of an RC filter

Eight channels in the GPIO sigma-delta unit. Each emits a high-frequency pulse stream whose average density is set by a signed 8-bit value; pass it through an external RC low-pass and you have a cheap analog output — LED dimming, a bias voltage, simple audio.

type Channel_Index is range 0 .. 7;
subtype Density_Percent is Float range 0.0 .. 100.0;

procedure Configure   (C : ...; Pin : ...; Carrier_Hz : ...);   --  starts at 0 %
procedure Set_Density (C : Channel; Percent : Density_Percent); --  one register write

The distinction from LEDC matters when choosing between them. LEDC varies the width of a pulse at a fixed frequency, so its output has energy at the PWM frequency and its harmonics. Sigma-delta varies the density of fixed-width pulses, pushing quantisation noise up in frequency where a simple filter removes it — which is why it makes a better analog voltage and why the same technique appears inside I2S's PDM mode.

Set_Density is a single register write, so it is cheap enough to call from a sample loop.