zephyr-pwm-control-potencia

Controlling Power with PWM in Zephyr

  • 3 min

The PWM (Pulse Width Modulation) is a technique for simulating an analog output using a digital signal.

The idea is to turn an output on and off very quickly, changing the proportion of time it is active. With this, we can adjust the brightness of an LED, move a servomotor, or control the speed of a fan (always with the appropriate power stage, not by plugging motors in recklessly).

In Zephyr, PWM is also configured through Devicetree and used in C with a common API.

Period and Duty Cycle

There are two concepts in PWM that are good to understand:

  1. Period: how long one complete cycle of the signal lasts.
  2. Pulse: how much time during that cycle the signal is active.

If the period is 20 ms and the pulse is 10 ms, the signal is active half the time. That is a 50% duty cycle.

In Zephyr, the API works with time values, not percentages. That is, we don’t say “set it to 50%”, but rather “use this period and this pulse width”.

The Hardware: Overlay (.overlay)

We need to define a PWM node. Many boards already have pwm_leds nodes for built-in LEDs, but you can also create your own for an external pin.

For example, to control an LED or a servo connected to channel 0 of the pwm0 controller:

#include <zephyr/dt-bindings/pwm/pwm.h>

/ {
    pwm-demo {
        compatible = "pwm-leds";

        my_pwm_device: pwm_led_0 {
            /* PWM controller, channel, period, and flags */
            pwms = <&pwm0 0 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
        };
    };
};

&pwm0 {
    status = "okay";
};
Copied!

Notice PWM_MSEC(20). Zephyr allows us to use time macros, which avoids having to write 20000000 nanoseconds by hand (which is far less user-friendly).

The C Code: pwm_set_dt

The PWM API is quite straightforward. We obtain the specification from Devicetree, check that the device is ready, and call pwm_set_dt.

#include <zephyr/kernel.h>
#include <zephyr/drivers/pwm.h>

/* Obtain the node from Devicetree */
static const struct pwm_dt_spec pwm_led =
    PWM_DT_SPEC_GET(DT_NODELABEL(my_pwm_device));

int main(void)
{
    if (!pwm_is_ready_dt(&pwm_led)) {
        printk("Error: PWM not ready\n");
        return 0;
    }

    uint32_t pulse_width = 0;
    uint32_t step = PWM_USEC(100);
    bool increasing = true;

    while (1) {
        int ret = pwm_set_dt(&pwm_led, pwm_led.period, pulse_width);
        if (ret < 0) {
            printk("Error setting PWM\n");
            return 0;
        }

        if (increasing) {
            pulse_width += step;
            if (pulse_width >= pwm_led.period) {
                pulse_width = pwm_led.period;
                increasing = false;
            }
        } else {
            if (pulse_width >= step) {
                pulse_width -= step;
            } else {
                pulse_width = 0;
                increasing = true;
            }
        }

        k_msleep(10);
    }

    return 0;
}
Copied!

pwm_set_dt(&pwm_led, pwm_led.period, pulse_width) applies the PWM signal using the configuration from Devicetree. The second parameter is the total period, and the third is the active time within that period.

Changing Frequency

If you want a specific frequency, you need to convert it to a period.

For example, for 1 kHz, the period is 1 ms:

pwm_set_dt(&pwm_led, PWM_MSEC(1), PWM_USEC(500));
Copied!

This generates a 1 kHz signal with a 50% duty cycle.

PWM does not create power by magic. For motors, LED strips, or large loads, use an appropriate MOSFET, driver, or power stage. The microcontroller pin only delivers a control signal.