Feedback
Feedback

If you are having issues with the exercises, please create a ticket on DevZone: devzone.nordicsemi.com
Click or drag files to this area to upload. You can upload up to 2 files.

Zephyr PWM API

v2.8.x – v2.7.0

To learn how to set up the PWM module in nRF Connect SDK, we will use the PWM API available in Zephyr.

In nRF Connect SDK, there are two ways to access the PWM module, either through the PWM API in Zephyr or the PWM driver in nrfx (See note in Lesson 6 about the advantages vs disadvantages of using nrfx drivers directly).

In this lesson, we will use the Zephyr approach to learn about the PWM module. If you either need or want to have a look at how the PWM driver in nrfx works, you can investigate the pwm samples located in the nrfx lib in nRF Connect SDK.

Enabling driver

We will include the PWM API in the application by adding the following Kconfig in the prj.conf file

CONFIG_PWM=y
Kconfig

We will include the header file for the PWM API in the source code file

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

Configure the devicetree

If the device you want to drive with PWM is not already defined in the board’s devicetree, you need to manually define it using a devicetree overlay file and configure the pins accordingly.

In the overlay file, we need to do three things:

  1. Define a devicetree instance for the device we want to drive with PWM.
  2. Define a new state configuration node, pwm0_default_custom, and map it to our desired GPIO pin
  3. Assign the new state configuration node to the PWM instance, &pwm0.

Define a devicetree instance

Let’s define the devicetree instance for the device we want to drive with PWM. For instance, say we want to drive a servo motor like we will be doing in Exercise 2 of this lesson.

Then, we will create a node for the motor, with a relevant compatible, the PWM that is driving the servo motor, and a minimum and maximum pulse width. This is the code snippet to add to the overlay file to modify the Devicetree

/ {
    servo: servo {
        compatible = "pwm-servo";
        pwms = <&pwm0 1 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
        min-pulse = <PWM_USEC(700)>;
        max-pulse = <PWM_USEC(2500)>;
    };
};
Devicetree

Define new state configuration node

Now we need to configure which pins the &pwm0 instance should use to drive the servo motor.

The &pwm0 is already defined in the Devicetree of the device, like this

In the overlay file, we want to overwrite this node, by defining a new state configuration node pwm0_default_custom, with our desired GPIO pin, in this case P0.13 (port 0, pin 13).

&pinctrl {
	pwm0_default_custom: pwm0_default_custom {
		group1 {
			psels = <NRF_PSEL(PWM_OUT0, 0, 13)>;
			nordic,invert;
		};
	};
Devicetree

Assign new state configuration node to PWM instance

Then we want to overwrite &pwm0 with the new state configuration node and delete the sleep state since we won’t be using the device power management.

&pwm0 {
	pinctrl-0 = <&pwm0_default_custom>;
	/delete_property/ pin-ctrl-1;
	pinctrl-names = "default";
};
Devicetree

Initializing the device

The PWM API has an API-specific struct pwm_dt_spec which has the following signature

This structure contains the device pointer for the PWM device, channel number and the PWM signal period.

To retrieve this structure, we will use the API-specific function PWM_DT_SPEC_GET(), which takes the devicetree node identifier as a parameter and returns the static initializer for the pwm_dt_spec struct.

The API contains multiple options for initializing the struct:

We can use DT_NODELABEL() to get the node identifier based on the node label we defined in the overlay file, servo.

static const struct pwm_dt_spec pwm_led0 = PWM_DT_SPEC_GET(DT_NODELABEL(servo));
C

Validate device is ready

Lastly we need to validate that the PWM device is ready, using pwm_is_ready_dt(), which takes the pwm_dt_spec structure of the initialized device as an input parameter and returns true or false, depending on if the PWM device is ready for use.

Set period and pulse

In the exercises, we will set the period and pulse width of the PWM device, using the API function pwm_set_dt(), which has the following signature

This can be used with the period specified in the Devicetree node, which needs to be changed at runtime.

If the period does not need to be changed, pwm_set_pulse_dt() can be used, which has the following signature

v2.6.2 -v2.5.2

To learn how to set up the PWM module in nRF Connect SDK, we will use the PWM API available in Zephyr.

In nRF Connect SDK, there are two ways to access the PWM module, either through the PWM API in Zephyr or the PWM driver in nrfx (See note in Lesson 6 about the advantages vs disadvantages of using nrfx drivers directly).

In this lesson, we will use the Zephyr approach to learn about the PWM module. If you either need or want to have a look at how the PWM driver in nrfx works, you can investigate the pwm samples located in the nrfx lib in nRF Connect SDK.

Enabling driver

We will include the PWM API in the application by adding the following Kconfig in the prj.conf file

CONFIG_PWM=y
Kconfig

We will include the header file for the PWM API in the source code file

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

Configure the devicetree

If the device you want to drive with PWM is not already defined in the board’s devicetree, you need to manually define it using a devicetree overlay file and configure the pins accordingly.

In the overlay file, we need to do three things:

  1. Define a devicetree instance for the device we want to drive with PWM.
  2. Define a new state configuration node, pwm0_default_custom, and map it to our desired GPIO pin
  3. Assign the new state configuration node to the PWM instance, &pwm0.

Define a devicetree instance

Let’s define the devicetree instance for the device we want to drive with PWM. For instance, say we want to drive a servo motor like we will be doing in Exercise 2 of this lesson.

Then, we will create a node for the motor, with a relevant compatible, the PWM that is driving the servo motor, and a minimum and maximum pulse width. This is the code snippet to add to the overlay file to modify the Devicetree

/ {
    servo: servo {
        compatible = "pwm-servo";
        pwms = <&pwm0 1 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
        min-pulse = <PWM_USEC(700)>;
        max-pulse = <PWM_USEC(2500)>;
    };
};
Devicetree

Define new state configuration node

Now we need to configure which pins the &pwm0 instance should use to drive the servo motor.

The &pwm0 is already defined in the Devicetree of the device, like this

In the overlay file, we want to overwrite this node, by defining a new state configuration node pwm0_default_custom, with our desired GPIO pin, in this case P0.13 (port 0, pin 13).

&pinctrl {
	pwm0_default_custom: pwm0_default_custom {
		group1 {
			psels = <NRF_PSEL(PWM_OUT0, 0, 13)>;
			nordic,invert;
		};
	};
Devicetree

Assign new state configuration node to PWM instance

Then we want to overwrite &pwm0 with the new state configuration node and delete the sleep state since we won’t be using the device power management.

&pwm0 {
	pinctrl-0 = <&pwm0_default_custom>;
	/delete_property/ pin-ctrl-1;
	pinctrl-names = "default";
};
Devicetree

Initializing the device

The PWM API has an API-specific struct pwm_dt_spec which has the following signature

This structure contains the device pointer for the PWM device, channel number and the PWM signal period.

To retrieve this structure, we will use the API-specific function PWM_DT_SPEC_GET(), which takes the devicetree node identifier as a parameter and returns the static initializer for the pwm_dt_spec struct.

The API contains multiple options for initializing the struct:

We can use DT_NODELABEL() to get the node identifier based on the node label we defined in the overlay file, servo.

static const struct pwm_dt_spec pwm_led0 = PWM_DT_SPEC_GET(DT_NODELABEL(servo));
C

Validate device is ready

Lastly we need to validate that the PWM device is ready, using pwm_is_ready_dt(), which takes the pwm_dt_spec structure of the initialized device as an input parameter and returns true or false, depending on if the PWM device is ready for use.

Set period and pulse

In the exercises, we will set the period and pulse width of the PWM device, using the API function pwm_set_dt(), which has the following signature

This can be used with the period specified in the Devicetree node, which needs to be changed at runtime.

If the period does not need to be changed, pwm_set_pulse_dt() can be used, which has the following signature

Register an account
Already have an account? Log in
(All fields are required unless specified optional)

  • 8 or more characters
  • Upper and lower case letters
  • At least one number or special character

Forgot your password?
Enter the email associated with your account, and we will send you a link to reset your password.