The purpose of this article is to demonstrate how to control via Pulse Width Modulation (PWM) a servo connected to the IoT Nordic nRF54L15-DK development kit.
Before proceeding with this demo please follow the Installation of Nordic Zephyr Development Tools in Linux.
The following Nordic nRF54L15-DK development kit table was used to derive which PIN in the Nordic nRF54L15-DK development kit can be used to drive using the PWM signal to control external servo,
| P1 signal | Function | Default connected on P1 |
|---|---|---|
| P1.00 | 32.768 kHz, XL1 | No. Solder bridges must be configured. |
| P1.01 | 32.768 kHz, XL2 | No. Solder bridges must be configured. |
| P1.02 | NFC1 | No. 0R resistors must be configured. |
| P1.03 | NFC2 | No. 0R resistors must be configured. |
| P1.04 | UART1_TXD | Yes. |
| P1.05 | UART1_RXD | Yes. |
| P1.06 | UART1_RST | Yes. |
| P1.07 | UART1_CTS | Yes. |
| P1.08 | Button 2 | Yes. |
| P1.09 | Button 1 | Yes. |
| P1.10 | LED 1 | Yes. |
| P1.11 | Yes. | |
| P1.12 | Yes. | |
| P1.13 | Button 0 | Yes. |
| P1.14 | LED 3 | Yes. |
Since P1.11 is available that will be used in this servo control demo. In a previous article P1.10 was used to drive the PWM signal into LED1 in the IoT Nordic nRF54L15-DK development kit. Now create the following Zephyr configuration file called proj.conf in the project folder of your choice,
CONFIG_LOG=y
CONFIG_LED=y
CONFIG_LED_PWM=y
Per standard Zephyr development procedure, then include the CMakeLists.txt file inside the project folder,
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(spi)
target_sources(app PRIVATE src/main.c)
Also include the following Zephyr overlay file called nrf54l15dk_nrf54l15.overlay,
/{
pwmleds {
compatible = "pwm-leds";
pwm_led0: pwm_led_0 {
pwms = <&pwm20 0 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
};
};
};
&pwm20 {
status = "okay";
pinctrl-0 = <&pwm20_custom>;
pinctrl-1 = <&pwm20_csleep>;
pinctrl-names = "default", "sleep";
};
&pinctrl {
pwm20_custom: pwm20_custom {
group1 {
psels = <NRF_PSEL(PWM_OUT0, 1, 11)>;
nordic,invert;
};
};
pwm20_csleep: pwm20_csleep {
group1 {
psels = <NRF_PSEL(PWM_OUT0, 1, 11)>;
low-power-enable;
};
};
};
Here at DigiKey we developed a customized version to illustrate how to control an external servo as illustrated in the following source code main.c,
/* DigiKey Coffee Cup Servo Control Version **/
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/device.h>
#include <zephyr/drivers/pwm.h>
LOG_MODULE_REGISTER(DigiKey_Coffee_Cup, LOG_LEVEL_INF);
#define PWM_PERIOD PWM_MSEC(20)
#define PWM_SERVO_MIN_PULSE_WIDTH PWM_USEC(100)
#define PWM_SERVO_MAX_PULSE_WIDTH PWM_USEC(1100)
#define PWM_LED0 DT_ALIAS(pwm_led0)
static const struct pwm_dt_spec pwm_led0 = PWM_DT_SPEC_GET(PWM_LED0);
int set_motor_angle(uint32_t pulse_width_ns)
{
int err;
err = pwm_set_dt(&pwm_led0, PWM_PERIOD, pulse_width_ns);
if (err) {
LOG_ERR("pwm_set_dt_returned %d", err);
}
return err;
}
int main(void)
{
int err;
if (!pwm_is_ready_dt(&pwm_led0)) {
LOG_ERR("Error: PWM device %s is not ready", pwm_led0.dev->name);
return 0;
}
err = pwm_set_dt(&pwm_led0, PWM_PERIOD, PWM_SERVO_MIN_PULSE_WIDTH );
if (err) {
LOG_ERR("Error in pwm_set_dt(), err: %d", err);
return 0;
}
while(1)
{
err = set_motor_angle(PWM_SERVO_MIN_PULSE_WIDTH);
if (err) {
LOG_ERR("Error: couldn't set duty cycle, err %d", err);
}
k_msleep(1000);
err = set_motor_angle(PWM_SERVO_MAX_PULSE_WIDTH);
if (err) {
LOG_ERR("Error: couldn't set duty cycle, err %d", err);
}
k_msleep(1000);
}
return 0;
}
The Zephyr project folder should look like this,
|-- CMakeLists.txt
|-- nrf54l15dk_nrf54l15.overlay
|-- prj.conf
`-- src
|-- main.c
Now proceed to build this Zephyr project as follows in the python virtual environment,
digikey_coffee_cup (venv) $ west build -p always -b nrf54l15dk/nrf54l15/cpuapp -- -DEXTRA_DTC_OVERLAY_FILE=nrf54l15dk_nrf54l15.overlay
Finally connect the nRF54L15-DK Nordic Development kit via the USB interface to flash it,
digikey_coffee_cup (venv) $ west flash
Before connecting the servo to the Nordic nRF54L15-DK development kit, the Sparkfun 8-channel USB Logic Analyzer was connected to Port 1 - Pin 11 in the board to see the PWM signal. The important aspects of the PWM signal relevant to this servo control demo previously defined in the source code main.c are,
#define PWM_PERIOD PWM_MSEC(20)
#define PWM_SERVO_MIN_PULSE_WIDTH PWM_USEC(100)
#define PWM_SERVO_MAX_PULSE_WIDTH PWM_USEC(1100)
These parameters (used for demo illustration purposes only) define a 20 ms period for the PWM signal with maximum pulse width of 1.1 ms and a minimum width of 100 us. These two widths for the PWM signals were used to control the servo. The next snapshot from the Sparkfun 8-channel USB Logic Analyzer illustrates that these match the specifications in the source code main.c, the 20 ms period of the PWM signal for both states (1.1 ms and 100 us) shown below,
1.1 ms case with 20 ms period
100 us case with 20 ms period
with a zoom using the Sparkfun 8-channel USB Logic Analyzer into both shown next,
(zoomed) 1.1 ms case with 20 ms period
(zoomed) 100 us case with 20 ms period
The Sparkfun 8-channel USB Logic Analyzer can be used to measure these PWM pulses as shown previously.
These two PWM states were defined in the main.c using the following function calls, one for the minimum duty cycle and another for the maximum duty cycle,
set_motor_angle(PWM_SERVO_MIN_PULSE_WIDTH);
set_motor_angle(PWM_SERVO_MAX_PULSE_WIDTH);
The main.c will alternate both duty cycles to control the servo once per second. Now we can connect the servo to the Nordic nRF54L15-DK development kit. The next video shows this servo control demo,
These PWM parameters were used for illustration purposes only, but they can be optimized and customized as needed, depending on the servo used and the application. We have demonstrated how to control a servo using Zephyr with the IoT Nordic nRF54L15-DK development kit. The Nordic nRF54L15-DK development kit is an excellent IoT development platform,
and is available at DigiKey.
Have a nice day!
This article is also available in spanish here.
Este artículo esta disponible en español aquí.




