1 /*
2 * Copyright (c) 2025 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 TEST_PWM_COUNT DT_PROP_LEN(DT_PATH(zephyr_user), pwms)
26 #define TEST_PWM_CONFIG_ENTRY(idx, node_id) PWM_DT_SPEC_GET_BY_IDX(node_id, idx)
27 #define TEST_PWM_CONFIG_ARRAY(node_id) \
28 { \
29 LISTIFY(TEST_PWM_COUNT, TEST_PWM_CONFIG_ENTRY, (,), node_id) \
30 }
31
32 #define TEST_GPIO_COUNT DT_PROP_LEN(DT_PATH(zephyr_user), gpios)
33 #define TEST_GPIO_CONFIG_ENTRY(idx, node_id) GPIO_DT_SPEC_GET_BY_IDX(node_id, gpios, idx)
34 #define TEST_GPIO_CONFIG_ARRAY(node_id) \
35 { \
36 LISTIFY(TEST_GPIO_COUNT, TEST_GPIO_CONFIG_ENTRY, (,), node_id) \
37 }
38
39 static const struct pwm_dt_spec pwms_dt[] = TEST_PWM_CONFIG_ARRAY(DT_PATH(zephyr_user));
40 static const struct gpio_dt_spec gpios_dt[] = TEST_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
gpio_edge_isr(const struct device * dev,struct gpio_callback * cb,uint32_t pins)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
setup_edge_detect(void)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
config_gpio(const struct gpio_dt_spec * gpio_dt)98 static void config_gpio(const struct gpio_dt_spec *gpio_dt)
99 {
100 gpio_pin_configure_dt(gpio_dt, GPIO_INPUT);
101 gpio_init_callback(&gpio_cb, gpio_edge_isr, BIT(gpio_dt->pin));
102 gpio_add_callback(gpio_dt->port, &gpio_cb);
103 }
104
enable_int_gpio(const struct gpio_dt_spec * gpio_dt,bool enable)105 static void enable_int_gpio(const struct gpio_dt_spec *gpio_dt, bool enable)
106 {
107 if (enable) {
108 gpio_pin_interrupt_configure(gpio_dt->port, gpio_dt->pin, GPIO_INT_EDGE_BOTH);
109 gpio_cb.pin_mask = BIT(gpio_dt->pin);
110 } else {
111 gpio_pin_interrupt_configure(gpio_dt->port, gpio_dt->pin, GPIO_INT_DISABLE);
112 }
113 }
114
check_range(float refval,float measval)115 static bool check_range(float refval, float measval)
116 {
117 float delta = fabsf(refval - measval);
118 float allowed_deviation = (refval * (float)CONFIG_ALLOWED_DEVIATION) / 100;
119
120 return delta <= allowed_deviation;
121 }
122
check_timing(const struct pwm_dt_spec * pwm_dt,const struct gpio_dt_spec * gpio_dt,uint8_t duty)123 static int check_timing(const struct pwm_dt_spec *pwm_dt, const struct gpio_dt_spec *gpio_dt,
124 uint8_t duty)
125 {
126 uint64_t cycles_s_sys, cycles_s_pwm;
127 int pin_state;
128 bool inverted = (pwm_dt->flags & PWM_POLARITY_INVERTED) ? true : false;
129
130 /* reset parameters for edge detection */
131 setup_edge_detect();
132
133 /* wait for sampling */
134 k_sleep(K_MSEC(CONFIG_SAMPLING_TIME));
135
136 /* store pin state for duty == 100% or 0% checks */
137 pin_state = gpio_pin_get_dt(gpio_dt);
138
139 if (inverted) {
140 pin_state = !pin_state;
141 }
142
143 cycles_s_sys = (uint64_t)sys_clock_hw_cycles_per_sec();
144 pwm_get_cycles_per_sec(pwm_dt->dev, pwm_dt->channel, &cycles_s_pwm);
145
146 /* sampling_done should be false for 0 and 100% duty (no switching) */
147 TC_PRINT("Sampling done: %s\n", ctx.sampling_done ? "true" : "false");
148
149 if (duty == 100) {
150 if ((pin_state == 1) && !ctx.sampling_done) {
151 return TC_PASS;
152 } else {
153 return TC_FAIL;
154 }
155 } else if (duty == 0) {
156 if ((pin_state == 0) && !ctx.sampling_done) {
157 return TC_PASS;
158 } else {
159 return TC_FAIL;
160 }
161 } else {
162 uint32_t measured_period = ctx.high_time + ctx.low_time;
163 uint32_t measured_period_ns = (measured_period * 1e9) / cycles_s_sys;
164 uint32_t pulse_time = inverted ? ctx.low_time : ctx.high_time;
165 float measured_duty = (pulse_time * 100.0f) / measured_period;
166 uint32_t measured_duty_2p = (uint32_t)(measured_duty * 100);
167 uint32_t period_deviation_2p =
168 (uint64_t)10000 * abs(measured_period_ns - pwm_dt->period) / pwm_dt->period;
169 uint32_t duty_deviation_2p =
170 (uint32_t)10000 * fabs(measured_duty - (float)duty) / duty;
171
172 TC_PRINT("Measured period: %u cycles, high: %u, low: %u [unit: systimer ticks]\n",
173 measured_period, ctx.high_time, ctx.low_time);
174 TC_PRINT("Measured period: %u ns, deviation: %d.%d%%\n", measured_period_ns,
175 period_deviation_2p / 100, period_deviation_2p % 100);
176 TC_PRINT("Measured duty: %d.%d%%, deviation: %d.%d%%\n", measured_duty_2p / 100,
177 measured_duty_2p % 100, duty_deviation_2p / 100, duty_deviation_2p % 100);
178
179 /* Compare measured values with expected ones */
180 if (check_range(measured_period_ns, pwm_dt->period) &&
181 check_range(measured_duty, duty)) {
182 TC_PRINT("PWM output matches the programmed values\n");
183 return TC_PASS;
184 }
185
186 TC_PRINT("PWM output does NOT match the programmed values\n");
187 return TC_FAIL;
188 }
189 }
190
test_run(const struct pwm_dt_spec * pwm_dt,const struct gpio_dt_spec * gpio_dt,uint8_t duty,bool set_channel)191 static void test_run(const struct pwm_dt_spec *pwm_dt, const struct gpio_dt_spec *gpio_dt,
192 uint8_t duty, bool set_channel)
193 {
194 int result;
195 uint32_t pulse = (uint32_t)((pwm_dt->period * duty) / 100);
196 bool inverted = (pwm_dt->flags & PWM_POLARITY_INVERTED) ? true : false;
197
198 TC_PRINT("Test case: [Channel: %" PRIu32 "] [Period: %" PRIu32 "] [Pulse: %" PRIu32
199 "] [Inverted: %s]\n",
200 pwm_dt->channel, pwm_dt->period, pulse, inverted ? "Yes" : "No");
201
202 if (set_channel) {
203 result = pwm_set_dt(pwm_dt, pwm_dt->period, pulse);
204 zassert_false(result, "Failed on pwm_set() call");
205 }
206
207 enable_int_gpio(gpio_dt, true);
208
209 result = check_timing(pwm_dt, gpio_dt, duty);
210
211 enable_int_gpio(gpio_dt, false);
212
213 zassert_equal(result, TC_PASS, "Test case failed");
214 }
215
ZTEST(pwm_gpio_loopback,test_pwm)216 ZTEST(pwm_gpio_loopback, test_pwm)
217 {
218 for (int i = 0; i < TEST_PWM_COUNT; i++) {
219 zassert_true(device_is_ready(pwms_dt[i].dev), "PWM device is not ready");
220
221 /* Test case: [Duty: 25%] */
222 test_run(&pwms_dt[i], &gpios_dt[i], 25, true);
223
224 /* Test case: [Duty: 100%] */
225 test_run(&pwms_dt[i], &gpios_dt[i], 100, true);
226
227 /* Test case: [Duty: 0%] */
228 test_run(&pwms_dt[i], &gpios_dt[i], 0, true);
229
230 /* Test case: [Duty: 80%] */
231 test_run(&pwms_dt[i], &gpios_dt[i], 80, true);
232 }
233 }
234
ZTEST(pwm_gpio_loopback,test_pwm_cross)235 ZTEST(pwm_gpio_loopback, test_pwm_cross)
236 {
237 for (int i = 0; i < TEST_PWM_COUNT; i++) {
238 /* Test case: [Duty: 40%] */
239 test_run(&pwms_dt[i], &gpios_dt[i], 40, true);
240 }
241
242 /* Set all channels and check if they retain the original
243 * configuration without calling pwm_set again
244 */
245 for (int i = 0; i < TEST_PWM_COUNT; i++) {
246 test_run(&pwms_dt[i], &gpios_dt[i], 40, false);
247 }
248 }
249
pwm_gpio_loopback_setup(void)250 static void *pwm_gpio_loopback_setup(void)
251 {
252 for (int i = 0; i < TEST_GPIO_COUNT; i++) {
253 if (device_is_ready(gpios_dt[i].port)) {
254 /* Configure GPIO pin for edge detection */
255 config_gpio(&gpios_dt[i]);
256 } else {
257 TC_PRINT("GPIO device %s is not ready", gpios_dt[i].port->name);
258 }
259 }
260
261 return NULL;
262 }
263
264 ZTEST_SUITE(pwm_gpio_loopback, NULL, pwm_gpio_loopback_setup, NULL, NULL, NULL);
265