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 23 of 56

MCPWM: PWM that can shut itself down

Complementary outputs with dead-time so a half-bridge is never shorted, a chopper carrier, and a fault input that forces the pins safe in hardware — without waiting for your code.

What makes it different from LEDC

Two units, each with three independent generator channels and three capture channels. A generator channel is one timer plus one operator producing an edge-aligned PWM on output A: high at the start of each period, low when the up-counting timer reaches the duty comparator.

type MCPWM_Unit    is (MCPWM0, MCPWM1);
type Channel_Index is (Ch0, Ch1, Ch2);

procedure Claim (C : in out Channel; Unit : MCPWM_Unit; Index : Channel_Index);
procedure Configure_Channel (...; Freq : ...; Complement_Pin : ... );  --  ~10 Hz .. 10 MHz
procedure Start (C : Channel);
procedure Stop  (C : Channel);
procedure Set_Duty (C : Channel; Percent : Duty_Percent);

Set_Duty is a single atomic register write. Stop halts the timer and the output stays in its current state — which is not necessarily the safe state, so think about which level your hardware wants before stopping a running bridge.

Complementary output and dead-time

Pass Complement_Pin and the channel drives a half-bridge or H-bridge pair: the A output plus an inverted B output from the same PWM, with programmable dead-time inserted between their edges so the two are never high together.

That dead-time is the whole reason this peripheral exists. In a half-bridge, both transistors conducting at once is a direct short across the supply — "shoot-through" — which destroys the bridge in microseconds. Software cannot be trusted to sequence the edges; the hardware inserts the gap.

Carrier modulation

subtype Carrier_Prescale is Natural range 0 .. 15;
subtype Carrier_Duty     is Natural range 1 .. 7;
subtype Carrier_Pulse    is Natural range 0 .. 15;

procedure Set_Carrier (...);

Chops the PWM output with a high-frequency carrier. This is what drives a gate-drive transformer (which cannot pass DC) or an IR emitter that expects a modulated burst.

Fault inputs: the safety feature

type Fault_Input is (Fault0, Fault1, Fault2);
type Fault_Mode  is (One_Shot, Cycle_By_Cycle);
type Trip_Action is (No_Change, Force_Low, Force_High);

procedure Configure_Fault  (Input : ...; Pin : ...; Active_High : ...);
procedure Protect_Channel  (C : ...; Input : Fault_Input; Action : Trip_Action);

A fault pin — an over-current comparator, a driver's fault flag — forces the channel's A and B outputs to a chosen state in hardware. No interrupt latency, no scheduler, no chance that your task was busy elsewhere.

ModeBehaviour
Cycle_By_Cycle The output is forced while the fault is asserted and resumes on its own once it clears. For recoverable conditions such as a current limit.
One_Shot The trip latches. The outputs stay forced until you explicitly clear it, and clearing only re-enables them if the fault has actually gone. For conditions that should require a deliberate decision to restart.

Set Trip_Action to the level that is safe for your hardware — Force_Low is right for a low-side switch, but not universally.

MCPWM: PWM that can shut itself down · 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 23 of 56

MCPWM: PWM that can shut itself down

Complementary outputs with dead-time so a half-bridge is never shorted, a chopper carrier, and a fault input that forces the pins safe in hardware — without waiting for your code.

What makes it different from LEDC

Two units, each with three independent generator channels and three capture channels. A generator channel is one timer plus one operator producing an edge-aligned PWM on output A: high at the start of each period, low when the up-counting timer reaches the duty comparator.

type MCPWM_Unit    is (MCPWM0, MCPWM1);
type Channel_Index is (Ch0, Ch1, Ch2);

procedure Claim (C : in out Channel; Unit : MCPWM_Unit; Index : Channel_Index);
procedure Configure_Channel (...; Freq : ...; Complement_Pin : ... );  --  ~10 Hz .. 10 MHz
procedure Start (C : Channel);
procedure Stop  (C : Channel);
procedure Set_Duty (C : Channel; Percent : Duty_Percent);

Set_Duty is a single atomic register write. Stop halts the timer and the output stays in its current state — which is not necessarily the safe state, so think about which level your hardware wants before stopping a running bridge.

Complementary output and dead-time

Pass Complement_Pin and the channel drives a half-bridge or H-bridge pair: the A output plus an inverted B output from the same PWM, with programmable dead-time inserted between their edges so the two are never high together.

That dead-time is the whole reason this peripheral exists. In a half-bridge, both transistors conducting at once is a direct short across the supply — "shoot-through" — which destroys the bridge in microseconds. Software cannot be trusted to sequence the edges; the hardware inserts the gap.

Carrier modulation

subtype Carrier_Prescale is Natural range 0 .. 15;
subtype Carrier_Duty     is Natural range 1 .. 7;
subtype Carrier_Pulse    is Natural range 0 .. 15;

procedure Set_Carrier (...);

Chops the PWM output with a high-frequency carrier. This is what drives a gate-drive transformer (which cannot pass DC) or an IR emitter that expects a modulated burst.

Fault inputs: the safety feature

type Fault_Input is (Fault0, Fault1, Fault2);
type Fault_Mode  is (One_Shot, Cycle_By_Cycle);
type Trip_Action is (No_Change, Force_Low, Force_High);

procedure Configure_Fault  (Input : ...; Pin : ...; Active_High : ...);
procedure Protect_Channel  (C : ...; Input : Fault_Input; Action : Trip_Action);

A fault pin — an over-current comparator, a driver's fault flag — forces the channel's A and B outputs to a chosen state in hardware. No interrupt latency, no scheduler, no chance that your task was busy elsewhere.

ModeBehaviour
Cycle_By_Cycle The output is forced while the fault is asserted and resumes on its own once it clears. For recoverable conditions such as a current limit.
One_Shot The trip latches. The outputs stay forced until you explicitly clear it, and clearing only re-enables them if the fault has actually gone. For conditions that should require a deliberate decision to restart.

Set Trip_Action to the level that is safe for your hardware — Force_Low is right for a low-side switch, but not universally.

MCPWM: PWM that can shut itself down · 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 23 of 56

MCPWM: PWM that can shut itself down

Complementary outputs with dead-time so a half-bridge is never shorted, a chopper carrier, and a fault input that forces the pins safe in hardware — without waiting for your code.

What makes it different from LEDC

Two units, each with three independent generator channels and three capture channels. A generator channel is one timer plus one operator producing an edge-aligned PWM on output A: high at the start of each period, low when the up-counting timer reaches the duty comparator.

type MCPWM_Unit    is (MCPWM0, MCPWM1);
type Channel_Index is (Ch0, Ch1, Ch2);

procedure Claim (C : in out Channel; Unit : MCPWM_Unit; Index : Channel_Index);
procedure Configure_Channel (...; Freq : ...; Complement_Pin : ... );  --  ~10 Hz .. 10 MHz
procedure Start (C : Channel);
procedure Stop  (C : Channel);
procedure Set_Duty (C : Channel; Percent : Duty_Percent);

Set_Duty is a single atomic register write. Stop halts the timer and the output stays in its current state — which is not necessarily the safe state, so think about which level your hardware wants before stopping a running bridge.

Complementary output and dead-time

Pass Complement_Pin and the channel drives a half-bridge or H-bridge pair: the A output plus an inverted B output from the same PWM, with programmable dead-time inserted between their edges so the two are never high together.

That dead-time is the whole reason this peripheral exists. In a half-bridge, both transistors conducting at once is a direct short across the supply — "shoot-through" — which destroys the bridge in microseconds. Software cannot be trusted to sequence the edges; the hardware inserts the gap.

Carrier modulation

subtype Carrier_Prescale is Natural range 0 .. 15;
subtype Carrier_Duty     is Natural range 1 .. 7;
subtype Carrier_Pulse    is Natural range 0 .. 15;

procedure Set_Carrier (...);

Chops the PWM output with a high-frequency carrier. This is what drives a gate-drive transformer (which cannot pass DC) or an IR emitter that expects a modulated burst.

Fault inputs: the safety feature

type Fault_Input is (Fault0, Fault1, Fault2);
type Fault_Mode  is (One_Shot, Cycle_By_Cycle);
type Trip_Action is (No_Change, Force_Low, Force_High);

procedure Configure_Fault  (Input : ...; Pin : ...; Active_High : ...);
procedure Protect_Channel  (C : ...; Input : Fault_Input; Action : Trip_Action);

A fault pin — an over-current comparator, a driver's fault flag — forces the channel's A and B outputs to a chosen state in hardware. No interrupt latency, no scheduler, no chance that your task was busy elsewhere.

ModeBehaviour
Cycle_By_Cycle The output is forced while the fault is asserted and resumes on its own once it clears. For recoverable conditions such as a current limit.
One_Shot The trip latches. The outputs stay forced until you explicitly clear it, and clearing only re-enables them if the fault has actually gone. For conditions that should require a deliberate decision to restart.

Set Trip_Action to the level that is safe for your hardware — Force_Low is right for a low-side switch, but not universally.

MCPWM: PWM that can shut itself down · 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 23 of 56

MCPWM: PWM that can shut itself down

Complementary outputs with dead-time so a half-bridge is never shorted, a chopper carrier, and a fault input that forces the pins safe in hardware — without waiting for your code.

What makes it different from LEDC

Two units, each with three independent generator channels and three capture channels. A generator channel is one timer plus one operator producing an edge-aligned PWM on output A: high at the start of each period, low when the up-counting timer reaches the duty comparator.

type MCPWM_Unit    is (MCPWM0, MCPWM1);
type Channel_Index is (Ch0, Ch1, Ch2);

procedure Claim (C : in out Channel; Unit : MCPWM_Unit; Index : Channel_Index);
procedure Configure_Channel (...; Freq : ...; Complement_Pin : ... );  --  ~10 Hz .. 10 MHz
procedure Start (C : Channel);
procedure Stop  (C : Channel);
procedure Set_Duty (C : Channel; Percent : Duty_Percent);

Set_Duty is a single atomic register write. Stop halts the timer and the output stays in its current state — which is not necessarily the safe state, so think about which level your hardware wants before stopping a running bridge.

Complementary output and dead-time

Pass Complement_Pin and the channel drives a half-bridge or H-bridge pair: the A output plus an inverted B output from the same PWM, with programmable dead-time inserted between their edges so the two are never high together.

That dead-time is the whole reason this peripheral exists. In a half-bridge, both transistors conducting at once is a direct short across the supply — "shoot-through" — which destroys the bridge in microseconds. Software cannot be trusted to sequence the edges; the hardware inserts the gap.

Carrier modulation

subtype Carrier_Prescale is Natural range 0 .. 15;
subtype Carrier_Duty     is Natural range 1 .. 7;
subtype Carrier_Pulse    is Natural range 0 .. 15;

procedure Set_Carrier (...);

Chops the PWM output with a high-frequency carrier. This is what drives a gate-drive transformer (which cannot pass DC) or an IR emitter that expects a modulated burst.

Fault inputs: the safety feature

type Fault_Input is (Fault0, Fault1, Fault2);
type Fault_Mode  is (One_Shot, Cycle_By_Cycle);
type Trip_Action is (No_Change, Force_Low, Force_High);

procedure Configure_Fault  (Input : ...; Pin : ...; Active_High : ...);
procedure Protect_Channel  (C : ...; Input : Fault_Input; Action : Trip_Action);

A fault pin — an over-current comparator, a driver's fault flag — forces the channel's A and B outputs to a chosen state in hardware. No interrupt latency, no scheduler, no chance that your task was busy elsewhere.

ModeBehaviour
Cycle_By_Cycle The output is forced while the fault is asserted and resumes on its own once it clears. For recoverable conditions such as a current limit.
One_Shot The trip latches. The outputs stay forced until you explicitly clear it, and clearing only re-enables them if the fault has actually gone. For conditions that should require a deliberate decision to restart.

Set Trip_Action to the level that is safe for your hardware — Force_Low is right for a low-side switch, but not universally.

MCPWM: PWM that can shut itself down · 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 23 of 56

MCPWM: PWM that can shut itself down

Complementary outputs with dead-time so a half-bridge is never shorted, a chopper carrier, and a fault input that forces the pins safe in hardware — without waiting for your code.

What makes it different from LEDC

Two units, each with three independent generator channels and three capture channels. A generator channel is one timer plus one operator producing an edge-aligned PWM on output A: high at the start of each period, low when the up-counting timer reaches the duty comparator.

type MCPWM_Unit    is (MCPWM0, MCPWM1);
type Channel_Index is (Ch0, Ch1, Ch2);

procedure Claim (C : in out Channel; Unit : MCPWM_Unit; Index : Channel_Index);
procedure Configure_Channel (...; Freq : ...; Complement_Pin : ... );  --  ~10 Hz .. 10 MHz
procedure Start (C : Channel);
procedure Stop  (C : Channel);
procedure Set_Duty (C : Channel; Percent : Duty_Percent);

Set_Duty is a single atomic register write. Stop halts the timer and the output stays in its current state — which is not necessarily the safe state, so think about which level your hardware wants before stopping a running bridge.

Complementary output and dead-time

Pass Complement_Pin and the channel drives a half-bridge or H-bridge pair: the A output plus an inverted B output from the same PWM, with programmable dead-time inserted between their edges so the two are never high together.

That dead-time is the whole reason this peripheral exists. In a half-bridge, both transistors conducting at once is a direct short across the supply — "shoot-through" — which destroys the bridge in microseconds. Software cannot be trusted to sequence the edges; the hardware inserts the gap.

Carrier modulation

subtype Carrier_Prescale is Natural range 0 .. 15;
subtype Carrier_Duty     is Natural range 1 .. 7;
subtype Carrier_Pulse    is Natural range 0 .. 15;

procedure Set_Carrier (...);

Chops the PWM output with a high-frequency carrier. This is what drives a gate-drive transformer (which cannot pass DC) or an IR emitter that expects a modulated burst.

Fault inputs: the safety feature

type Fault_Input is (Fault0, Fault1, Fault2);
type Fault_Mode  is (One_Shot, Cycle_By_Cycle);
type Trip_Action is (No_Change, Force_Low, Force_High);

procedure Configure_Fault  (Input : ...; Pin : ...; Active_High : ...);
procedure Protect_Channel  (C : ...; Input : Fault_Input; Action : Trip_Action);

A fault pin — an over-current comparator, a driver's fault flag — forces the channel's A and B outputs to a chosen state in hardware. No interrupt latency, no scheduler, no chance that your task was busy elsewhere.

ModeBehaviour
Cycle_By_Cycle The output is forced while the fault is asserted and resumes on its own once it clears. For recoverable conditions such as a current limit.
One_Shot The trip latches. The outputs stay forced until you explicitly clear it, and clearing only re-enables them if the fault has actually gone. For conditions that should require a deliberate decision to restart.

Set Trip_Action to the level that is safe for your hardware — Force_Low is right for a low-side switch, but not universally.