Zephyr: Pulse Width Modulation
BME554L -Fall 2025 - Palmeri
Overview
- Most microcontrollers and SoCs do not have a native DAC.
- Much easier to output digital
HIGH
orLOW
signals. - Timing and duty cycle (pulse width) of
HIGH
andLOW
signals can be used to approximate an analog voltage. - LPF to smooth out the PWM signal.
PWM Signal Generation
Block Diagram
Common Uses
- Adjust LED brightness
- Control motor speed
- Control servo motor direction
- Analog output
- Data encoding
Project Setup
prj.conf
CONFIG_PWM=y
Devicetree
- There are multiple PWM timers (controllers) on the nRF52833.
- Each PWM timer has multiple channels.
- Zephyr has a “compatibility” label called
pwm-leds
.
= {
aliases = &pwmch1;
pwm1 = &pwmch2;
pwm2 };
{
pwm = "pwm-leds";
compatible : pwm_1 {
pwmch1= < &pwm0 0 PWM_MSEC(1) PWM_POLARITY_NORMAL>; // 0 - channel
pwms = "PWM_CH1";
label };
: pwm_2 {
pwmch2= < &pwm0 1 PWM_MSEC(1) PWM_POLARITY_NORMAL>; // 1 - channel
pwms = "PWM_CH2";
label };
};
Configure the controller(s)
&pwm0 {
= "nordic,nrf-pwm";
compatible = <0x4001c000 0x1000>;
reg = <28 NRF_DEFAULT_IRQ_PRIORITY>;
interrupts = "okay";
status #pwm-cells = <3>;
-0 = <&pwm0_default>;
pinctrl-1 = <&pwm0_sleep>;
pinctrl-names = "default", "sleep";
pinctrl};
Use pinctrl
to configure IO behavior for non-GPIO pins.
&pinctrl {
= "nordic,nrf-pinctrl";
compatible = "okay";
status : pwm0_default {
pwm0_default{
group1 = <NRF_PSEL(PWM_OUT0, 0, 28)>, // P0.28, channel 0
psels <NRF_PSEL(PWM_OUT1, 0, 30)>; // P0.30, channel 1
,invert;
nordic};
};
: pwm0_sleep {
pwm0_sleep{
group1 = <NRF_PSEL(PWM_OUT0, 0, 28)>, // P0.28, channel 0
psels <NRF_PSEL(PWM_OUT1, 0, 30)>; // P0.30, channel 1
-power-enable;
low};
};
};
Main Code
// load in the Zephyr library
#include <zephyr/drivers/pwm.h>
// define structs based on DT aliases
static const struct pwm_dt_spec pwm1 = PWM_DT_SPEC_GET(DT_ALIAS(pwm1));
static const struct pwm_dt_spec pwm2 = PWM_DT_SPEC_GET(DT_ALIAS(pwm2));
// check that the PWM controller is ready
if (!device_is_ready(pwm1.dev)) {
("PWM device %s is not ready.", pwm1.dev->name);
LOG_ERRreturn -1;
}
// set the PWM duty cycle (pulse length)
= pwm_set_pulse_dt(pwm1, pwm1->period/2); // 50% duty cycle
err if (err) {
("Could not set pwm1 driver.");
LOG_ERR}
Changing PWM Duty Cycle
- The pulse length (duty cycle) can be changed “on the fly”, but only changes at the next period.
- Can be done with a timer or event-driven.
How to Sinusoidally Module LED Brightness Using a PWM
- Use a timer to change the duty cycle of the PWM signal. How often?
- Set PWM counter period to ???
- LPF
Resources
- Zephry: PWM
- Pulse Width Modulation
- Zephyr: Blinky Sample
- Sine Wave Generation
- Nordic Semiconductor: PinCtrl
- Nordic DevAcademy: PWM