zephyr-pwm-control-potencia

Controlar potencia con PWM en Zephyr

  • 3 min

El PWM (Pulse Width Modulation) es una técnica para simular una salida analógica usando una señal digital.

La idea es encender y apagar una salida muy rápido, cambiando la proporción de tiempo que está activa. Con eso podemos regular el brillo de un LED, mover un servomotor o controlar la velocidad de un ventilador (siempre con la etapa de potencia adecuada, no enchufando motores a lo loco).

En Zephyr, el PWM también se configura mediante Devicetree y se utiliza desde C con una API común.

Periodo y duty cycle

En PWM hay dos conceptos que conviene tener claros:

  1. Periodo: cuánto dura un ciclo completo de la señal.
  2. Pulso: cuánto tiempo de ese ciclo la señal está activa.

Si el periodo es de 20 ms y el pulso es de 10 ms, la señal está activa la mitad del tiempo. Eso es un duty cycle del 50%.

En Zephyr la API trabaja con tiempos, no con porcentajes. Es decir, no decimos “ponme un 50%”, sino “usa este periodo y este ancho de pulso”.

El hardware: overlay (.overlay)

Necesitamos definir un nodo PWM. Muchas placas ya tienen nodos pwm_leds para LEDs integrados, pero también puedes crear uno propio para un pin externo.

Por ejemplo, para controlar un LED o un servo conectado al canal 0 del controlador pwm0:

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

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

        my_pwm_device: pwm_led_0 {
            /* Controlador PWM, canal, periodo y flags */
            pwms = <&pwm0 0 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
        };
    };
};

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

Fíjate en PWM_MSEC(20). Zephyr nos permite usar macros de tiempo, lo que evita tener que escribir 20000000 nanosegundos a mano (que es bastante menos amigable).

El código C: pwm_set_dt

La API de PWM es bastante directa. Obtenemos la especificación desde Devicetree, comprobamos que el dispositivo está listo y llamamos a pwm_set_dt.

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

/* Obtenemos el nodo del 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 no listo\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 configurando 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) aplica la señal PWM usando la configuración que viene del Devicetree. El segundo parámetro es el periodo total, y el tercero es el tiempo activo dentro de ese periodo.

Cambiar frecuencia

Si quieres una frecuencia concreta, tienes que convertirla a periodo.

Por ejemplo, para 1 kHz, el periodo es 1 ms:

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

Eso genera una señal de 1 kHz con duty cycle del 50%.

El PWM no crea potencia por arte de ciencia ficción. Para motores, tiras LED o cargas grandes, usa un MOSFET, driver o etapa de potencia adecuada. El pin del microcontrolador solo entrega una señal de control.