Skip to content

Commit 1f6492a

Browse files
author
Raffael Rostagno
committed
tests: pwm_api: Add timing check
Use GPIO to check PWM output timing. Signed-off-by: Raffael Rostagno <[email protected]>
1 parent cdb9166 commit 1f6492a

File tree

4 files changed

+282
-23
lines changed

4 files changed

+282
-23
lines changed

tests/drivers/pwm/pwm_api/Kconfig

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 api test"
5+
6+
source "Kconfig.zephyr"
7+
8+
config ENABLE_TIMING_CHECK
9+
bool "Enable PWM timing check with GPIO and systimer"
10+
help
11+
Enables timing checks on PWM signals by using a GPIO pin and interrupts
12+
along with the system timer to automatically measure period and duty cycle.
13+
Assumes the system timer is accurate and uses it as reference tick.
14+
15+
config ALLOWED_DEVIATION
16+
int "Allowed deviation (%) for PWM timing checks"
17+
default 5
18+
range 0 100
19+
help
20+
Maximum allowed deviation (%) from the programmed values for the test to be
21+
considered a PASS. For example, if set to 5, the measured period or duty cycle
22+
can deviate by up to 5% from the programmed values for the test to pass.
23+
24+
config TEST_DELAY
25+
int "Test delay duration (ms)"
26+
default 1000
27+
help
28+
Sets delay duration in milliseconds for testing purposes.
29+
It configures the delay period in between test cases.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#
2+
# Copyright (c) 2024 Espressif Systems (Shanghai) Co., Ltd.
3+
#
4+
# SPDX-License-Identifier: Apache-2.0
5+
#
6+
7+
description: |
8+
This binding provides resources required to build and run the test
9+
tests/drivers/pwm/pwm_api when CONFIG_ENABLE_TIMING_CHECK is enabled.
10+
11+
compatible: "test-pwm-api"
12+
13+
properties:
14+
in-gpios:
15+
type: phandle-array
16+
required: true
17+
description: |
18+
GPIO to be used as input to check and validate PWM timing.

tests/drivers/pwm/pwm_api/src/test_pwm.c

Lines changed: 225 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,23 @@
2121
* Always on -> Period : Pulse (1 : 1) -> 3.3V
2222
* Half on -> Period : Pulse (2 : 1) -> 1.65V
2323
* Always off -> Period : Pulse (1 : 0) -> 0V
24+
*
25+
* Note: It is possible to enable timing checks through the symbol
26+
* CONFIG_ENABLE_TIMING_CHECK. This setting will use GPIO channel
27+
* pin interrupts and the systimer - assuming it is validated - to
28+
* do automated measurements.
2429
*/
2530

2631
#include <zephyr/device.h>
2732
#include <inttypes.h>
2833
#include <zephyr/drivers/pwm.h>
2934
#include <zephyr/kernel.h>
3035
#include <zephyr/ztest.h>
36+
#if CONFIG_ENABLE_TIMING_CHECK
37+
#include <zephyr/drivers/gpio.h>
38+
#include <stdlib.h>
39+
#include <math.h>
40+
#endif
3141

3242
#if DT_NODE_HAS_STATUS_OKAY(DT_ALIAS(pwm_0))
3343
#define PWM_DEV_NODE DT_ALIAS(pwm_0)
@@ -60,23 +70,58 @@
6070
#error "Define a PWM device"
6171
#endif
6272

73+
#if CONFIG_ENABLE_TIMING_CHECK
74+
75+
#define DT_RESOURCES DT_INST(0, test_pwm_api)
76+
77+
#if DT_NODE_HAS_STATUS_OKAY(DT_RESOURCES)
78+
#define GPIO_HDL DT_GPIO_CTLR(DT_RESOURCES, in_gpios)
79+
#define GPIO_DEV DEVICE_DT_GET(GPIO_HDL)
80+
#define GPIO_PIN DT_GPIO_PIN(DT_RESOURCES, in_gpios)
81+
#define GPIO_FLG DT_GPIO_FLAGS(DT_RESOURCES, in_gpios)
82+
#else
83+
#error Invalid device tree config for GPIO input pin
84+
#endif
85+
86+
/* Variables for timing measurement */
87+
static struct test_context {
88+
uint32_t last_edge_time;
89+
uint32_t high_time;
90+
uint32_t low_time;
91+
bool gpio_cfg_done;
92+
bool sampling_done;
93+
uint8_t skip_cnt;
94+
} ctx;
95+
96+
/* Skipping a couple of edges greatly improves measurement precision
97+
* due to interrupt latency present on the first edge (ref ZEP-868)
98+
*/
99+
#define SKIP_EDGE_NUM 2
100+
101+
#endif /* CONFIG_ENABLE_TIMING_CHECK */
102+
63103
#if defined(CONFIG_BOARD_COLIBRI_IMX7D_MCIMX7D_M4) || defined(CONFIG_SOC_MK64F12) || \
64-
defined(CONFIG_SOC_MKW41Z4) || defined(CONFIG_SOC_SERIES_ESP32S2) || \
65-
defined(CONFIG_SOC_SERIES_ESP32S3) || defined(CONFIG_SOC_SERIES_ESP32C3)
104+
defined(CONFIG_SOC_MKW41Z4)
66105
#define DEFAULT_PERIOD_CYCLE 1024
67-
#define DEFAULT_PULSE_CYCLE 512
68-
#define DEFAULT_PERIOD_NSEC 2000000
69-
#define DEFAULT_PULSE_NSEC 500000
106+
#define DEFAULT_PULSE_CYCLE 512
107+
#define DEFAULT_PERIOD_NSEC 2000000
108+
#define DEFAULT_PULSE_NSEC 500000
109+
#elif defined(CONFIG_SOC_SERIES_ESP32S2) || defined(CONFIG_SOC_SERIES_ESP32S3) || \
110+
defined(CONFIG_SOC_SERIES_ESP32C3)
111+
#define DEFAULT_PERIOD_CYCLE 16200
112+
#define DEFAULT_PULSE_CYCLE 8100
113+
#define DEFAULT_PERIOD_NSEC 160000
114+
#define DEFAULT_PULSE_NSEC 40000
70115
#elif DT_HAS_COMPAT_STATUS_OKAY(intel_blinky_pwm)
71116
#define DEFAULT_PERIOD_CYCLE 32768
72-
#define DEFAULT_PULSE_CYCLE 16384
73-
#define DEFAULT_PERIOD_NSEC 2000000
74-
#define DEFAULT_PULSE_NSEC 500000
117+
#define DEFAULT_PULSE_CYCLE 16384
118+
#define DEFAULT_PERIOD_NSEC 2000000
119+
#define DEFAULT_PULSE_NSEC 500000
75120
#else
76121
#define DEFAULT_PERIOD_CYCLE 64000
77-
#define DEFAULT_PULSE_CYCLE 32000
78-
#define DEFAULT_PERIOD_NSEC 2000000
79-
#define DEFAULT_PULSE_NSEC 1000000
122+
#define DEFAULT_PULSE_CYCLE 32000
123+
#define DEFAULT_PERIOD_NSEC 2000000
124+
#define DEFAULT_PULSE_NSEC 1000000
80125
#endif
81126

82127
#if defined CONFIG_BOARD_SAM_E70_XPLAINED
@@ -106,18 +151,172 @@
106151
#define DEFAULT_PWM_PORT 0
107152
#endif
108153

109-
#define UNIT_CYCLES 0
110-
#define UNIT_NSECS 1
154+
#define UNIT_CYCLES 0
155+
#define UNIT_NSECS 1
111156

112157
const struct device *get_pwm_device(void)
113158
{
114159
return DEVICE_DT_GET(PWM_DEV_NODE);
115160
}
116161

162+
#if CONFIG_ENABLE_TIMING_CHECK
163+
164+
/* Interrupt handler for edge detection */
165+
void edge_detect_handler(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
166+
{
167+
uint32_t current_time = k_cycle_get_32();
168+
169+
if (ctx.sampling_done || ++ctx.skip_cnt < SKIP_EDGE_NUM) {
170+
return;
171+
}
172+
173+
if (!ctx.last_edge_time) {
174+
/* init last_edge_time for first delta*/
175+
ctx.last_edge_time = current_time;
176+
return;
177+
}
178+
179+
uint32_t elapsed_time = current_time - ctx.last_edge_time;
180+
181+
int pin_state = gpio_pin_get_raw(GPIO_DEV, GPIO_PIN);
182+
183+
if (pin_state) {
184+
ctx.low_time = elapsed_time;
185+
} else {
186+
ctx.high_time = elapsed_time;
187+
}
188+
189+
/* sampling is done when both high and low times were stored */
190+
if (ctx.high_time && ctx.low_time) {
191+
ctx.sampling_done = true;
192+
}
193+
194+
ctx.last_edge_time = current_time;
195+
}
196+
197+
void setup_edge_detection(const struct device *gpio_dev, uint32_t pin)
198+
{
199+
static struct gpio_callback gpio_cb;
200+
201+
/* Configure GPIO pin for edge detection */
202+
if (!ctx.gpio_cfg_done) {
203+
gpio_pin_configure(gpio_dev, pin, (GPIO_INPUT | GPIO_FLG));
204+
gpio_init_callback(&gpio_cb, edge_detect_handler, BIT(pin));
205+
gpio_add_callback(gpio_dev, &gpio_cb);
206+
gpio_pin_interrupt_configure(gpio_dev, pin, GPIO_INT_EDGE_BOTH);
207+
208+
ctx.gpio_cfg_done = true;
209+
}
210+
211+
ctx.last_edge_time = 0;
212+
ctx.high_time = 0;
213+
ctx.low_time = 0;
214+
ctx.sampling_done = false;
215+
ctx.skip_cnt = 0;
216+
}
217+
218+
bool check_range(float refval, float measval)
219+
{
220+
float delta = fabsf(refval - measval);
221+
float allowed_deviation = (refval * (float)CONFIG_ALLOWED_DEVIATION) / 100;
222+
223+
return delta <= allowed_deviation;
224+
}
225+
226+
static int timing_check(const struct device *pwm_dev, uint32_t port, uint32_t period,
227+
uint32_t pulse, uint8_t unit)
228+
{
229+
uint64_t cycles_s_sys, cycles_s_pwm;
230+
uint32_t expected_period_ns;
231+
uint32_t expected_pulse_ns;
232+
uint32_t expected_duty_cycle;
233+
int pin_state;
234+
235+
/* wait for stable signal */
236+
k_sleep(K_MSEC(100));
237+
238+
/* set up GPIO input pin for edge detection */
239+
setup_edge_detection(GPIO_DEV, GPIO_PIN);
240+
241+
/* wait for sampling */
242+
k_sleep(K_MSEC(100));
243+
244+
/* store pin state for duty == 100% or 0% checks */
245+
pin_state = gpio_pin_get_raw(GPIO_DEV, GPIO_PIN);
246+
247+
cycles_s_sys = (uint64_t)sys_clock_hw_cycles_per_sec();
248+
pwm_get_cycles_per_sec(pwm_dev, port, &cycles_s_pwm);
249+
250+
if (unit == UNIT_CYCLES) {
251+
/* convert cycles to ns using PWM clock period */
252+
expected_period_ns = (period * 1e9) / cycles_s_pwm;
253+
expected_pulse_ns = (pulse * 1e9) / cycles_s_pwm;
254+
} else if (unit == UNIT_NSECS) {
255+
/* already in nanoseconds */
256+
expected_period_ns = period;
257+
expected_pulse_ns = pulse;
258+
} else {
259+
TC_PRINT("Unexpected unit");
260+
return TC_FAIL;
261+
}
262+
263+
/* sampling_done should be false for 0 and 100% duty (no switching) */
264+
TC_PRINT("Sampling done: %s\n", ctx.sampling_done ? "true" : "false");
265+
266+
expected_duty_cycle = (expected_pulse_ns * 100) / expected_period_ns;
267+
268+
if (expected_duty_cycle == 100) {
269+
if ((pin_state == 1) && !ctx.sampling_done) {
270+
return TC_PASS;
271+
} else {
272+
return TC_FAIL;
273+
}
274+
} else if (expected_duty_cycle == 0) {
275+
if ((pin_state == 0) && !ctx.sampling_done) {
276+
return TC_PASS;
277+
} else {
278+
return TC_FAIL;
279+
}
280+
} else {
281+
uint32_t measured_period = ctx.high_time + ctx.low_time;
282+
uint32_t measured_period_ns = (measured_period * 1e9) / cycles_s_sys;
283+
float measured_duty_cycle = (ctx.high_time * 100.0f) / measured_period;
284+
uint32_t measured_duty_cycle_2p = (uint32_t)(measured_duty_cycle * 100);
285+
uint32_t period_deviation_2p = (uint64_t)10000 *
286+
abs(measured_period_ns - expected_period_ns) /
287+
expected_period_ns;
288+
uint32_t duty_deviation_2p =
289+
(uint32_t)10000 * fabs(measured_duty_cycle - (float)expected_duty_cycle) /
290+
expected_duty_cycle;
291+
292+
TC_PRINT("Expected period: %u ns, pulse: %u ns duty cycle: %u%%\n",
293+
expected_period_ns, expected_pulse_ns, expected_duty_cycle);
294+
TC_PRINT("Measured period: %u cycles, high: %u, low: %u [unit: systimer ticks]\n",
295+
measured_period, ctx.high_time, ctx.low_time);
296+
TC_PRINT("Measured period: %u ns, deviation: %d.%d%%\n", measured_period_ns,
297+
period_deviation_2p / 100, period_deviation_2p % 100);
298+
TC_PRINT("Measured duty: %d.%d%%, deviation: %d.%d%%\n",
299+
measured_duty_cycle_2p / 100, measured_duty_cycle_2p % 100,
300+
duty_deviation_2p / 100, duty_deviation_2p % 100);
301+
302+
/* Compare measured values with expected ones */
303+
if (check_range(measured_period_ns, expected_period_ns) &&
304+
check_range(measured_duty_cycle, expected_duty_cycle)) {
305+
TC_PRINT("PWM output matches the programmed values\n");
306+
return TC_PASS;
307+
}
308+
309+
TC_PRINT("PWM output does NOT match the programmed values\n");
310+
return TC_FAIL;
311+
}
312+
}
313+
314+
#endif
315+
117316
static int test_task(uint32_t port, uint32_t period, uint32_t pulse, uint8_t unit)
118317
{
119-
TC_PRINT("[PWM]: %" PRIu8 ", [period]: %" PRIu32 ", [pulse]: %" PRIu32 "\n",
120-
port, period, pulse);
318+
TC_PRINT("[PWM]: %" PRIu8 ", [period]: %" PRIu32 ", [pulse]: %" PRIu32 "\n", port, period,
319+
pulse);
121320

122321
const struct device *pwm_dev = get_pwm_device();
123322

@@ -140,43 +339,47 @@ static int test_task(uint32_t port, uint32_t period, uint32_t pulse, uint8_t uni
140339
}
141340
}
142341

342+
#if CONFIG_ENABLE_TIMING_CHECK
343+
return timing_check(pwm_dev, port, period, pulse, unit);
344+
#else
143345
return TC_PASS;
346+
#endif
144347
}
145348

146349
ZTEST_USER(pwm_basic, test_pwm_nsec)
147350
{
148351
/* Period : Pulse (2000000 : 1000000), unit (nsec). Voltage : 1.65V */
149352
zassert_true(test_task(DEFAULT_PWM_PORT, DEFAULT_PERIOD_NSEC,
150353
DEFAULT_PULSE_NSEC, UNIT_NSECS) == TC_PASS, NULL);
151-
k_sleep(K_MSEC(1000));
354+
k_sleep(K_MSEC(CONFIG_TEST_DELAY));
152355

153356
/* Period : Pulse (2000000 : 2000000), unit (nsec). Voltage : 3.3V */
154357
zassert_true(test_task(DEFAULT_PWM_PORT, DEFAULT_PERIOD_NSEC,
155358
DEFAULT_PERIOD_NSEC, UNIT_NSECS) == TC_PASS, NULL);
156-
k_sleep(K_MSEC(1000));
359+
k_sleep(K_MSEC(CONFIG_TEST_DELAY));
157360

158361
/* Period : Pulse (2000000 : 0), unit (nsec). Voltage : 0V */
159362
zassert_true(test_task(DEFAULT_PWM_PORT, DEFAULT_PERIOD_NSEC,
160363
0, UNIT_NSECS) == TC_PASS, NULL);
161-
k_sleep(K_MSEC(1000));
364+
k_sleep(K_MSEC(CONFIG_TEST_DELAY));
162365
}
163366

164367
ZTEST_USER(pwm_basic, test_pwm_cycle)
165368
{
166369
/* Period : Pulse (64000 : 32000), unit (cycle). Voltage : 1.65V */
167370
zassert_true(test_task(DEFAULT_PWM_PORT, DEFAULT_PERIOD_CYCLE,
168371
DEFAULT_PULSE_CYCLE, UNIT_CYCLES) == TC_PASS, NULL);
169-
k_sleep(K_MSEC(1000));
372+
k_sleep(K_MSEC(CONFIG_TEST_DELAY));
170373

171374
/* Period : Pulse (64000 : 64000), unit (cycle). Voltage : 3.3V */
172375
zassert_true(test_task(DEFAULT_PWM_PORT, DEFAULT_PERIOD_CYCLE,
173376
DEFAULT_PERIOD_CYCLE, UNIT_CYCLES) == TC_PASS, NULL);
174-
k_sleep(K_MSEC(1000));
377+
k_sleep(K_MSEC(CONFIG_TEST_DELAY));
175378

176379
/* Period : Pulse (64000 : 0), unit (cycle). Voltage : 0V */
177380
zassert_true(test_task(DEFAULT_PWM_PORT, DEFAULT_PERIOD_CYCLE,
178381
0, UNIT_CYCLES) == TC_PASS, NULL);
179-
k_sleep(K_MSEC(1000));
382+
k_sleep(K_MSEC(CONFIG_TEST_DELAY));
180383
}
181384

182385
#if defined INVALID_PWM_PORT
@@ -185,13 +388,12 @@ ZTEST_USER(pwm_basic, test_pwm_invalid_port)
185388
const struct device *pwm_dev = get_pwm_device();
186389

187390
TC_PRINT("[PWM]: %" PRIu8 ", [period]: %" PRIu32 ", [pulse]: %" PRIu32 "\n",
188-
INVALID_PWM_PORT, DEFAULT_PERIOD_CYCLE, DEFAULT_PULSE_CYCLE);
391+
INVALID_PWM_PORT, DEFAULT_PERIOD_CYCLE, DEFAULT_PULSE_CYCLE);
189392

190393
zassert_true(device_is_ready(pwm_dev), "PWM device is not ready");
191394

192395
zassert_equal(pwm_set_cycles(pwm_dev, INVALID_PWM_PORT, DEFAULT_PERIOD_CYCLE,
193396
DEFAULT_PULSE_CYCLE, 0),
194397
-EINVAL, "Invalid PWM port\n");
195-
196398
}
197399
#endif

0 commit comments

Comments
 (0)