← Writing

PWM (Pulse Width Modulation)

2025-07-13 embedded · basic · pwm


🟠 How to handle pulse width modulation


PWM (pulse width modulation) is used to control the power supplied to the load.

🔸 Classic method - reducing the voltage through a rheostat or potentiometer.
🔻 Disadvantage: inefficient, since part of the energy is lost as heat.

🔸 PWM method - supplying voltage in short pulses with a fixed amplitude.
Instead of changing the voltage, the duty cycle changes:

This control method is efficient and causes almost no losses.

Brief

It has 2 main parameters


Duty cycle

Average voltage level

PWM - often used to vary power by changing the average voltage level across a load.


Switching Frequency


Examples of PWM Systems

SystemDescription
1. Visual Devices● Screen brightness control (high frequency to avoid flickering)
● RGB LED color control
2. Audio● Digital to analog conversion via filtering
● Use efficient Class D amplifiers
3. Switching Power Supplies (SMPS)● The basis of switching converters
● Use MOSFETs to switch pulses
4. DC Motors● Fan or pump speed control
● Lower duty cycle → lower speed
● Inertia smooths out operation
5. Servos● PWM sets position, not speed
● Pulse width corresponds to deviation from neutral
● Power is supplied via a separate channel
6. Inverters (DC→AC)● Generating an alternating signal from PWM pulses
7. Chargers● Charging batteries (including from solar panels)
● PWM regulates average voltage to prevent overcharging and reverse current

🧱 Embedded Rust approach:

  1. HAL library (e.g. stm32f1xx-hal, rp2040-hal, esp32-hal) provides API to peripherals.
  2. PWM is usually implemented via timers (TIM).
  3. Each PWM channel is assigned a GPIO pin and a timer.

🔨 General flow of creating PWM in Rust

  1. Access PWM slices (Slices::new)
  2. Configure PWM7 (it controls GPIO15 pin)
  3. Configure channel B of this PWM (channel connected to GPIO15)
  4. Connect GPIO15 to PWM as output
  5. In the loop, change the duty cycle value to get the breathing effect

💡 Implementation: RP2040

use rp2040_hal::{self as hal, pwm::Slices};

    // PWM Slices Init
    let pwm_slices = Slices::new(pac.PWM, &mut pac.RESETS);
    // Get 7th slice (controls GPIO 14 (A) and GPIO 15 (B))
    let mut pwm_slice = pwm_slices.pwm7;

    pwm_slice.set_ph_correct(); // Phase correct mode = smoother signal
    pwm_slice.set_top(255); // 8-bit duty cycle precision
    pwm_slice.enable(); // enable slice

    // Get channel_b from 7th slice
    let mut channel_b = pwm_slice.channel_b;
    // Make channel_b output to 15th pin
    let _pin = channel_b.output_to(pins.gpio15);

    let mut duty: i16 = 0;
    let mut step: i16 = 1;

    loop {
        let _ = channel_b.set_duty_cycle(duty as u16);
        timer.delay_us(1000);

        duty += step;
        if duty >= 255 || duty <= 0 {
            step = -step;
        }
    }

🧰 Tips for working with PWM