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:
- Periodo: cuánto dura un ciclo completo de la señal.
- 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";
};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;
}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));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.