Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 198 additions & 0 deletions doc/hardware/peripherals/pwm.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,206 @@ Pulse Width Modulation (PWM)
Overview
********

The PWM interface provides access to `pulse width modulation`_ controllers.

A PWM signal is a periodic digital signal. Each period contains an active pulse and an inactive
interval. Applications control the signal by setting the period, pulse width, and polarity.

PWM is commonly used to dim LEDs, drive servos, generate simple clocks, or control other devices
that encode a value in the duty cycle of a digital waveform. From a Zephyr point of view, a PWM
device controls one or more channels. The application selects a channel and sets the timing values
used to generate the output waveform.

Key concepts
************

To generate a PWM output, an application specifies:

#. Which PWM controller and channel are used,
#. How long each PWM period is, and
#. How much of each period is active.

These pieces are usually described in Devicetree and combined in :c:struct:`pwm_dt_spec`.

**PWM controller and channel** (:c:member:`pwm_dt_spec.dev`, :c:member:`pwm_dt_spec.channel`)
A PWM controller may expose one or more channels. The channel value is interpreted by the driver
for that controller.

**Period and pulse width** (:c:member:`pwm_dt_spec.period`)
The period is the total length of one PWM cycle. The pulse width is the active part of that
period. The duty cycle is the ratio between the two values:

.. code-block:: none

duty cycle = pulse width / period

For example, a 20 ms period with a 10 ms pulse width produces a 50% duty cycle. Passing a pulse
width of 0 drives the output inactive. Passing a pulse width equal to the period drives the output
active.

**Polarity** (:c:member:`pwm_dt_spec.flags`)
Polarity controls which electrical level is considered active. :c:macro:`PWM_POLARITY_NORMAL`
represents an active-high pulse, and :c:macro:`PWM_POLARITY_INVERTED` represents an active-low
pulse.

**Time units**
:c:func:`pwm_set`, :c:func:`pwm_set_dt`, and :c:func:`pwm_set_pulse_dt` use nanoseconds for
period and pulse width values. The :c:macro:`PWM_NSEC`, :c:macro:`PWM_USEC`,
:c:macro:`PWM_MSEC`, :c:macro:`PWM_SEC`, :c:macro:`PWM_HZ`, and :c:macro:`PWM_KHZ` helper macros
can be used to express these values in more convenient units.

Typical application flow
========================

Typical use of the PWM API to generate an output signal is:

#. Get a :c:struct:`pwm_dt_spec` from Devicetree, usually with :c:macro:`PWM_DT_SPEC_GET`.
#. Check that the PWM device is ready using :c:func:`pwm_is_ready_dt`.
#. Set the period and pulse width using :c:func:`pwm_set_dt`.
#. If the period is fixed in Devicetree, update only the pulse width with
:c:func:`pwm_set_pulse_dt`.

Devicetree
==========

PWM consumers describe their PWM signal with a ``pwms`` property. The exact channel values are
controller-specific, but the period cell is always expressed in nanoseconds and can use the PWM
period helper macros.

.. code-block:: devicetree
:caption: PWM consumer node with a 50 Hz period

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

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

pwm_led0: pwm_led_0 {
pwms = <&pwm0 0 PWM_HZ(50) PWM_POLARITY_NORMAL>;
};
};

aliases {
pwm-led0 = &pwm_led0;
};
};

In this example, the consumer uses channel 0 of ``pwm0`` with a 50 Hz (20 ms) period and normal
polarity.

.. note::
For SoC-integrated PWM controllers, the controller node usually also needs board-specific
:ref:`pin control <pinctrl-guide>` configuration so the selected channel is routed to the
intended pin. This is separate from the consumer's ``pwms`` property.

Setting a PWM Output
====================

A common pattern is to initialize a :c:struct:`pwm_dt_spec` statically and then use
:c:func:`pwm_set_dt` to configure the output:

.. code-block:: c
:caption: Setting a PWM output to a 50% duty cycle

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

static const struct pwm_dt_spec pwm = PWM_DT_SPEC_GET(DT_ALIAS(pwm_led0));

int main(void)
{
int ret;

if (!pwm_is_ready_dt(&pwm)) {
return 0;
}

ret = pwm_set_dt(&pwm, PWM_MSEC(20), PWM_MSEC(10));
if (ret < 0) {
return ret;
}

return 0;
}

The call to :c:func:`pwm_set_dt` uses the controller, channel, and polarity from Devicetree. The
period argument overrides the period stored in :c:struct:`pwm_dt_spec`. If the application wants to
keep the Devicetree period, it can call :c:func:`pwm_set_pulse_dt` instead.

Capture
=======

Some PWM controllers can measure an incoming PWM signal. Capture support is enabled with
:kconfig:option:`CONFIG_PWM_CAPTURE`.

The blocking helper :c:func:`pwm_capture_cycles` captures the selected parts of a signal and returns
the measured values in controller clock cycles:

.. code-block:: c
:caption: Capturing period and pulse width once

uint32_t period;
uint32_t pulse;
int ret;

ret = pwm_capture_cycles(pwm.dev, pwm.channel,
PWM_CAPTURE_TYPE_BOTH | PWM_CAPTURE_MODE_SINGLE,
&period, &pulse, K_MSEC(500));
if (ret < 0) {
return ret;
}

The :c:macro:`PWM_CAPTURE_TYPE_PERIOD`, :c:macro:`PWM_CAPTURE_TYPE_PULSE`, and
:c:macro:`PWM_CAPTURE_TYPE_BOTH` flags select which values are captured. The
:c:macro:`PWM_CAPTURE_MODE_SINGLE` and :c:macro:`PWM_CAPTURE_MODE_CONTINUOUS` flags select whether
the capture runs once or continuously.

Events
======

Some PWM controllers can notify the application about events such as period completion, faults, or
compare and capture matches. Event support is enabled with :kconfig:option:`CONFIG_PWM_EVENT`.

Applications initialize a :c:struct:`pwm_event_callback` with
:c:func:`pwm_init_event_callback` and register it with :c:func:`pwm_add_event_callback`.

.. code-block:: c
:caption: Registering a PWM period event callback

static struct pwm_event_callback callback;

static void pwm_event_handler(const struct device *dev,
struct pwm_event_callback *cb,
uint32_t channel,
pwm_events_t events)
{
/* Handle the event */
}

pwm_init_event_callback(&callback, pwm_event_handler, pwm.channel,
PWM_EVENT_TYPE_PERIOD);
ret = pwm_add_event_callback(pwm.dev, &callback);

Configuration Options
*********************

Related configuration options:

* :kconfig:option:`CONFIG_PWM`
* :kconfig:option:`CONFIG_PWM_CAPTURE`
* :kconfig:option:`CONFIG_PWM_EVENT`
* :kconfig:option:`CONFIG_PWM_SHELL`

API Reference
*************

.. doxygengroup:: pwm_interface

References
**********

.. target-notes::

.. _`pulse width modulation`: https://en.wikipedia.org/wiki/Pulse-width_modulation
Loading