Skip to content

Commit 3af9db3

Browse files
Raffael Rostagnonordic-pikr
authored andcommitted
[nrf fromtree] tests: pwm: Add test suite with GPIO loopback
Add PWM test suite which uses GPIO as loopback input to check the programmed timings. Signed-off-by: Raffael Rostagno <[email protected]> (cherry picked from commit ef96ed9)
1 parent 1b965ae commit 3af9db3

File tree

5 files changed

+310
-0
lines changed

5 files changed

+310
-0
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
cmake_minimum_required(VERSION 3.20.0)
4+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
5+
project(pwm_gpio)
6+
7+
FILE(GLOB app_sources src/*.c)
8+
target_sources(app PRIVATE ${app_sources})
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Copyright (c) 2024 Espressif Systems (Shanghai) Co., Ltd.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
mainmenu "PWM GPIO loopback test"
5+
6+
source "Kconfig.zephyr"
7+
8+
config SAMPLING_TIME
9+
int "Sampling wait time (ms)"
10+
default 50
11+
help
12+
Time to wait for PWM edge sampling, in milliseconds.
13+
14+
config SKIP_EDGE_NUM
15+
int "Number of edges to skip before sampling PWM"
16+
default 2
17+
help
18+
Number of PWM edges to skip before starting sampling.
19+
This parameter improves measurement precision as there can be significant
20+
latency in the first sampled edge.
21+
22+
config ALLOWED_DEVIATION
23+
int "Allowed deviation (%) for PWM timing checks"
24+
default 5
25+
range 0 100
26+
help
27+
Maximum allowed deviation (%) from the programmed values for the test to be
28+
considered a PASS. For example, if set to 5, the measured period or duty cycle
29+
can deviate by up to 5% from the programmed values for the test to pass.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
CONFIG_PWM=y
2+
CONFIG_ZTEST=y
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
/*
2+
* Copyright (c) 2024 Espressif Systems (Shanghai) Co., Ltd.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/**
8+
* @file
9+
* @brief Generate PWM signals in different configurations and use a GPIO
10+
* input pin to check the programmed timing. This test uses the systimer as
11+
* benchmark, so it assumes the system tick is verified and precise.
12+
*/
13+
14+
#include <zephyr/device.h>
15+
#include <zephyr/devicetree.h>
16+
#include <zephyr/drivers/pwm.h>
17+
#include <zephyr/kernel.h>
18+
#include <zephyr/ztest.h>
19+
#include <zephyr/drivers/gpio.h>
20+
#include <stdlib.h>
21+
#include <math.h>
22+
23+
static struct gpio_callback gpio_cb;
24+
25+
#define PWM_COUNT DT_PROP_LEN(DT_PATH(zephyr_user), pwms)
26+
#define PWM_CONFIG_ENTRY(idx, node_id) PWM_DT_SPEC_GET_BY_IDX(node_id, idx)
27+
#define PWM_CONFIG_ARRAY(node_id) \
28+
{ \
29+
LISTIFY(PWM_COUNT, PWM_CONFIG_ENTRY, (,), node_id) \
30+
}
31+
32+
#define GPIO_COUNT DT_PROP_LEN(DT_PATH(zephyr_user), gpios)
33+
#define GPIO_CONFIG_ENTRY(idx, node_id) GPIO_DT_SPEC_GET_BY_IDX(node_id, gpios, idx)
34+
#define GPIO_CONFIG_ARRAY(node_id) \
35+
{ \
36+
LISTIFY(GPIO_COUNT, GPIO_CONFIG_ENTRY, (,), node_id) \
37+
}
38+
39+
static const struct pwm_dt_spec pwms_dt[] = PWM_CONFIG_ARRAY(DT_PATH(zephyr_user));
40+
static const struct gpio_dt_spec gpios_dt[] = GPIO_CONFIG_ARRAY(DT_PATH(zephyr_user));
41+
42+
static struct test_context {
43+
uint32_t last_edge_time;
44+
uint32_t high_time;
45+
uint32_t low_time;
46+
bool sampling_done;
47+
uint8_t skip_cnt;
48+
} ctx;
49+
50+
static void gpio_edge_isr(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
51+
{
52+
int pin_state;
53+
uint32_t current_time = k_cycle_get_32();
54+
55+
if (ctx.sampling_done || ++ctx.skip_cnt < CONFIG_SKIP_EDGE_NUM) {
56+
return;
57+
}
58+
59+
if (!ctx.last_edge_time) {
60+
/* init last_edge_time for first delta */
61+
ctx.last_edge_time = current_time;
62+
return;
63+
}
64+
65+
uint32_t elapsed_time = current_time - ctx.last_edge_time;
66+
67+
int pin = __builtin_ffs(pins) - 1;
68+
69+
if (pin >= 0) {
70+
pin_state = gpio_pin_get(dev, pin);
71+
} else {
72+
return;
73+
}
74+
75+
if (pin_state) {
76+
ctx.low_time = elapsed_time;
77+
} else {
78+
ctx.high_time = elapsed_time;
79+
}
80+
81+
/* sampling is done when both high and low times were stored */
82+
if (ctx.high_time && ctx.low_time) {
83+
ctx.sampling_done = true;
84+
}
85+
86+
ctx.last_edge_time = current_time;
87+
}
88+
89+
static void setup_edge_detect(void)
90+
{
91+
ctx.last_edge_time = 0;
92+
ctx.high_time = 0;
93+
ctx.low_time = 0;
94+
ctx.sampling_done = false;
95+
ctx.skip_cnt = 0;
96+
}
97+
98+
static void config_gpio(const struct gpio_dt_spec *gpio_dt)
99+
{
100+
/* Configure GPIO pin for edge detection */
101+
gpio_pin_configure_dt(gpio_dt, GPIO_INPUT);
102+
103+
gpio_cb.pin_mask = BIT(gpio_dt->pin);
104+
105+
gpio_init_callback(&gpio_cb, gpio_edge_isr, gpio_cb.pin_mask);
106+
gpio_add_callback(gpio_dt->port, &gpio_cb);
107+
gpio_pin_interrupt_configure(gpio_dt->port, gpio_dt->pin, GPIO_INT_EDGE_BOTH);
108+
}
109+
110+
static void unconfig_gpio(const struct gpio_dt_spec *gpio_dt)
111+
{
112+
/* Disable interrupt for already tested channel */
113+
gpio_pin_interrupt_configure(gpio_dt->port, gpio_dt->pin, GPIO_INT_DISABLE);
114+
115+
gpio_cb.pin_mask &= ~BIT(gpio_dt->pin);
116+
}
117+
118+
static bool check_range(float refval, float measval)
119+
{
120+
float delta = fabsf(refval - measval);
121+
float allowed_deviation = (refval * (float)CONFIG_ALLOWED_DEVIATION) / 100;
122+
123+
return delta <= allowed_deviation;
124+
}
125+
126+
static int check_timing(const struct pwm_dt_spec *pwm_dt, const struct gpio_dt_spec *gpio_dt,
127+
uint8_t duty)
128+
{
129+
uint64_t cycles_s_sys, cycles_s_pwm;
130+
int pin_state;
131+
bool inverted = (pwm_dt->flags & PWM_POLARITY_INVERTED) ? true : false;
132+
133+
/* reset parameters for edge detection */
134+
setup_edge_detect();
135+
136+
/* wait for sampling */
137+
k_sleep(K_MSEC(CONFIG_SAMPLING_TIME));
138+
139+
/* store pin state for duty == 100% or 0% checks */
140+
pin_state = gpio_pin_get_dt(gpio_dt);
141+
142+
if (inverted) {
143+
pin_state = !pin_state;
144+
}
145+
146+
cycles_s_sys = (uint64_t)sys_clock_hw_cycles_per_sec();
147+
pwm_get_cycles_per_sec(pwm_dt->dev, pwm_dt->channel, &cycles_s_pwm);
148+
149+
/* sampling_done should be false for 0 and 100% duty (no switching) */
150+
TC_PRINT("Sampling done: %s\n", ctx.sampling_done ? "true" : "false");
151+
152+
if (duty == 100) {
153+
if ((pin_state == 1) && !ctx.sampling_done) {
154+
return TC_PASS;
155+
} else {
156+
return TC_FAIL;
157+
}
158+
} else if (duty == 0) {
159+
if ((pin_state == 0) && !ctx.sampling_done) {
160+
return TC_PASS;
161+
} else {
162+
return TC_FAIL;
163+
}
164+
} else {
165+
uint32_t measured_period = ctx.high_time + ctx.low_time;
166+
uint32_t measured_period_ns = (measured_period * 1e9) / cycles_s_sys;
167+
uint32_t pulse_time = inverted ? ctx.low_time : ctx.high_time;
168+
float measured_duty = (pulse_time * 100.0f) / measured_period;
169+
uint32_t measured_duty_2p = (uint32_t)(measured_duty * 100);
170+
uint32_t period_deviation_2p =
171+
(uint64_t)10000 * abs(measured_period_ns - pwm_dt->period) / pwm_dt->period;
172+
uint32_t duty_deviation_2p =
173+
(uint32_t)10000 * fabs(measured_duty - (float)duty) / duty;
174+
175+
TC_PRINT("Measured period: %u cycles, high: %u, low: %u [unit: systimer ticks]\n",
176+
measured_period, ctx.high_time, ctx.low_time);
177+
TC_PRINT("Measured period: %u ns, deviation: %d.%d%%\n", measured_period_ns,
178+
period_deviation_2p / 100, period_deviation_2p % 100);
179+
TC_PRINT("Measured duty: %d.%d%%, deviation: %d.%d%%\n", measured_duty_2p / 100,
180+
measured_duty_2p % 100, duty_deviation_2p / 100, duty_deviation_2p % 100);
181+
182+
/* Compare measured values with expected ones */
183+
if (check_range(measured_period_ns, pwm_dt->period) &&
184+
check_range(measured_duty, duty)) {
185+
TC_PRINT("PWM output matches the programmed values\n");
186+
return TC_PASS;
187+
}
188+
189+
TC_PRINT("PWM output does NOT match the programmed values\n");
190+
return TC_FAIL;
191+
}
192+
}
193+
194+
static void test_run(const struct pwm_dt_spec *pwm_dt, const struct gpio_dt_spec *gpio_dt,
195+
uint8_t duty, bool set_channel)
196+
{
197+
int result;
198+
uint32_t pulse = (uint32_t)((pwm_dt->period * duty) / 100);
199+
bool inverted = (pwm_dt->flags & PWM_POLARITY_INVERTED) ? true : false;
200+
201+
TC_PRINT("Test case: [Channel: %" PRIu32 "] [Period: %" PRIu32 "] [Pulse: %" PRIu32
202+
"] [Inverted: %s]\n",
203+
pwm_dt->channel, pwm_dt->period, pulse, inverted ? "Yes" : "No");
204+
205+
if (set_channel) {
206+
result = pwm_set_dt(pwm_dt, pwm_dt->period, pulse);
207+
zassert_false(result, "Failed on pwm_set() call");
208+
}
209+
210+
config_gpio(gpio_dt);
211+
212+
result = check_timing(pwm_dt, gpio_dt, duty);
213+
214+
unconfig_gpio(gpio_dt);
215+
216+
zassert_equal(result, TC_PASS, "Test case failed");
217+
}
218+
219+
ZTEST(pwm_gpio_loopback, test_pwm)
220+
{
221+
for (int i = 0; i < PWM_COUNT; i++) {
222+
zassert_true(device_is_ready(pwms_dt[i].dev), "PWM device is not ready");
223+
zassert_true(device_is_ready(gpios_dt[i].port), "GPIO device is not ready");
224+
225+
/* Test case: [Duty: 25%] */
226+
test_run(&pwms_dt[i], &gpios_dt[i], 25, true);
227+
228+
/* Test case: [Duty: 100%] */
229+
test_run(&pwms_dt[i], &gpios_dt[i], 100, true);
230+
231+
/* Test case: [Duty: 0%] */
232+
test_run(&pwms_dt[i], &gpios_dt[i], 0, true);
233+
234+
/* Test case: [Duty: 80%] */
235+
test_run(&pwms_dt[i], &gpios_dt[i], 80, true);
236+
}
237+
}
238+
239+
ZTEST(pwm_gpio_loopback, test_pwm_cross)
240+
{
241+
for (int i = 0; i < PWM_COUNT; i++) {
242+
/* Test case: [Duty: 40%] */
243+
test_run(&pwms_dt[i], &gpios_dt[i], 40, true);
244+
}
245+
246+
/* Set all channels and check if they retain the original
247+
* configuration without calling pwm_set again
248+
*/
249+
for (int i = 0; i < PWM_COUNT; i++) {
250+
test_run(&pwms_dt[i], &gpios_dt[i], 40, false);
251+
}
252+
}
253+
254+
ZTEST_SUITE(pwm_gpio_loopback, NULL, NULL, NULL, NULL, NULL);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
common:
2+
depends_on: pwm
3+
tags:
4+
- drivers
5+
- pwm
6+
harness: ztest
7+
harness_config:
8+
fixture: gpio_loopback
9+
tests:
10+
drivers.pwm.gpio_loopback.esp:
11+
platform_allow:
12+
- esp32_devkitc_wrover/esp32/procpu
13+
- esp8684_devkitm
14+
- esp32c3_devkitm
15+
- esp32c6_devkitc
16+
- esp32s2_saola
17+
- esp32s3_devkitm/esp32s3/procpu

0 commit comments

Comments
 (0)