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.
We will include the PWM API in the application by adding the following Kconfig in the prj.conf
file
CONFIG_PWM=y
KconfigWe will include the header file for the PWM API in the source code file
#include <zephyr/drivers/pwm.h>
CIf 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:
pwm0_default_custom
, and map it to our desired GPIO pin&pwm0
.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)>;
};
};
DevicetreeNow 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;
};
};
DevicetreeThen 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";
};
DevicetreeThe 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:
PWM_DT_SPEC_GET_BY_NAME
– given a devicetree node identifier and a namePWM_DT_SPEC_GET_BY_IDX
– given a devicetree node identifier and an indexWe 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));
CLastly 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.
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
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.
We will include the PWM API in the application by adding the following Kconfig in the prj.conf
file
CONFIG_PWM=y
KconfigWe will include the header file for the PWM API in the source code file
#include <zephyr/drivers/pwm.h>
CIf 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:
pwm0_default_custom
, and map it to our desired GPIO pin&pwm0
.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)>;
};
};
DevicetreeNow 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;
};
};
DevicetreeThen 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";
};
DevicetreeThe 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:
PWM_DT_SPEC_GET_BY_NAME
– given a devicetree node identifier and a namePWM_DT_SPEC_GET_BY_IDX
– given a devicetree node identifier and an indexWe 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));
CLastly 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.
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