1 /*
2 * SPDX-FileCopyrightText: Copyright (c) 2025 Jilay Sandeep Pandya
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6 #include <zephyr/ztest.h>
7 #include <zephyr/drivers/stepper.h>
8
9 #include <zephyr/logging/log.h>
10 LOG_MODULE_REGISTER(stepper_api, CONFIG_STEPPER_LOG_LEVEL);
11
12 struct stepper_fixture {
13 const struct device *dev;
14 stepper_event_callback_t callback;
15 };
16
17 struct k_poll_signal stepper_signal;
18 struct k_poll_event stepper_event;
19 void *user_data_received;
20
21 #define POLL_AND_CHECK_SIGNAL(signal, event, expected_event, timeout) \
22 ({ \
23 do { \
24 (void)k_poll(&(event), 1, timeout); \
25 unsigned int signaled; \
26 int result; \
27 k_poll_signal_check(&(signal), &signaled, &result); \
28 zassert_equal(signaled, 1, "Signal not set"); \
29 zassert_equal(result, (expected_event), "Signal not set"); \
30 } while (0); \
31 })
32
stepper_print_event_callback(const struct device * dev,enum stepper_event event,void * user_data)33 static void stepper_print_event_callback(const struct device *dev, enum stepper_event event,
34 void *user_data)
35 {
36 const struct device *dev_callback = user_data;
37 user_data_received = user_data;
38
39 switch (event) {
40 case STEPPER_EVENT_STEPS_COMPLETED:
41 k_poll_signal_raise(&stepper_signal, STEPPER_EVENT_STEPS_COMPLETED);
42 break;
43 case STEPPER_EVENT_LEFT_END_STOP_DETECTED:
44 k_poll_signal_raise(&stepper_signal, STEPPER_EVENT_LEFT_END_STOP_DETECTED);
45 break;
46 case STEPPER_EVENT_RIGHT_END_STOP_DETECTED:
47 k_poll_signal_raise(&stepper_signal, STEPPER_EVENT_RIGHT_END_STOP_DETECTED);
48 break;
49 case STEPPER_EVENT_STOPPED:
50 k_poll_signal_raise(&stepper_signal, STEPPER_EVENT_STOPPED);
51 break;
52 default:
53 break;
54 }
55
56 LOG_DBG("Event %d, %s called for %s, expected for %s\n", event, __func__,
57 dev_callback->name, dev->name);
58 }
59
stepper_setup(void)60 static void *stepper_setup(void)
61 {
62 static struct stepper_fixture fixture = {
63 .dev = DEVICE_DT_GET(DT_ALIAS(stepper)),
64 .callback = stepper_print_event_callback,
65 };
66
67 k_poll_signal_init(&stepper_signal);
68 k_poll_event_init(&stepper_event, K_POLL_TYPE_SIGNAL, K_POLL_MODE_NOTIFY_ONLY,
69 &stepper_signal);
70
71 zassert_not_null(fixture.dev);
72 zassert_equal(
73 stepper_set_event_callback(fixture.dev, fixture.callback, (void *)fixture.dev), 0,
74 "Failed to set event callback");
75 return &fixture;
76 }
77
stepper_before(void * f)78 static void stepper_before(void *f)
79 {
80 struct stepper_fixture *fixture = f;
81 (void)stepper_set_reference_position(fixture->dev, 0);
82
83 k_poll_signal_reset(&stepper_signal);
84
85 user_data_received = NULL;
86 }
87
88 ZTEST_SUITE(stepper, NULL, stepper_setup, stepper_before, NULL, NULL);
89
ZTEST_F(stepper,test_set_micro_step_interval_invalid_zero)90 ZTEST_F(stepper, test_set_micro_step_interval_invalid_zero)
91 {
92 int err = stepper_set_microstep_interval(fixture->dev, 0);
93 if (err == -ENOSYS) {
94 ztest_test_skip();
95 }
96 zassert_equal(err, -EINVAL, "ustep interval cannot be zero");
97 }
98
ZTEST_F(stepper,test_actual_position)99 ZTEST_F(stepper, test_actual_position)
100 {
101 int32_t pos = 100u;
102 int ret;
103
104 ret = stepper_set_reference_position(fixture->dev, pos);
105 zassert_equal(ret, 0, "Failed to set reference position");
106
107 ret = stepper_get_actual_position(fixture->dev, &pos);
108 zassert_equal(ret, 0, "Failed to get actual position");
109 zassert_equal(pos, 100u, "Actual position not set correctly");
110 }
111
ZTEST_F(stepper,test_move_to_positive_step_count)112 ZTEST_F(stepper, test_move_to_positive_step_count)
113 {
114 int32_t pos = 10u;
115 int32_t actual_steps;
116 bool moving = false;
117 int ret;
118
119 ret = stepper_set_microstep_interval(fixture->dev, 100 * USEC_PER_SEC);
120 if (ret == -ENOSYS) {
121 ztest_test_skip();
122 }
123
124 zassert_ok(stepper_move_to(fixture->dev, pos));
125 zassert_ok(stepper_is_moving(fixture->dev, &moving));
126 zassert_true(moving, "%s reported not moving after move_to", fixture->dev->name);
127
128 POLL_AND_CHECK_SIGNAL(
129 stepper_signal, stepper_event, STEPPER_EVENT_STEPS_COMPLETED,
130 K_MSEC(pos * (100 + CONFIG_STEPPER_TEST_TIMING_TIMEOUT_TOLERANCE_PCT)));
131
132 zassert_ok(stepper_get_actual_position(fixture->dev, &actual_steps));
133 zassert_equal(pos, actual_steps, "Position should be %d but is %d", pos, actual_steps);
134 zassert_equal(user_data_received, fixture->dev, "User data not received");
135 zassert_ok(stepper_is_moving(fixture->dev, &moving));
136 zassert_false(moving, "%s reported moving even after completion of steps",
137 fixture->dev->name);
138 }
139
ZTEST_F(stepper,test_move_to_negative_step_count)140 ZTEST_F(stepper, test_move_to_negative_step_count)
141 {
142 int32_t pos = -10;
143 int32_t actual_steps;
144 bool moving = false;
145 int ret;
146
147 ret = stepper_set_microstep_interval(fixture->dev, 100 * USEC_PER_SEC);
148 if (ret == -ENOSYS) {
149 ztest_test_skip();
150 }
151
152 zassert_ok(stepper_move_to(fixture->dev, pos));
153 zassert_ok(stepper_is_moving(fixture->dev, &moving));
154 zassert_true(moving, "%s reported not moving after move_to", fixture->dev->name);
155
156 POLL_AND_CHECK_SIGNAL(
157 stepper_signal, stepper_event, STEPPER_EVENT_STEPS_COMPLETED,
158 K_MSEC(-pos * (100 + CONFIG_STEPPER_TEST_TIMING_TIMEOUT_TOLERANCE_PCT)));
159
160 zassert_ok(stepper_get_actual_position(fixture->dev, &actual_steps));
161 zassert_equal(pos, actual_steps, "Position should be %d but is %d", pos, actual_steps);
162 zassert_ok(stepper_is_moving(fixture->dev, &moving));
163 zassert_false(moving, "%s reported moving even after completion of steps",
164 fixture->dev->name);
165 }
166
ZTEST_F(stepper,test_move_by_positive_step_count)167 ZTEST_F(stepper, test_move_by_positive_step_count)
168 {
169 int32_t steps = 20;
170 int32_t actual_steps;
171 bool moving = false;
172 int ret;
173
174 ret = stepper_set_microstep_interval(fixture->dev, 100 * USEC_PER_SEC);
175 if (ret == -ENOSYS) {
176 ztest_test_skip();
177 }
178
179 zassert_ok(stepper_move_by(fixture->dev, steps));
180 zassert_ok(stepper_is_moving(fixture->dev, &moving));
181 zassert_true(moving, "%s reported not moving after move_by", fixture->dev->name);
182
183 POLL_AND_CHECK_SIGNAL(
184 stepper_signal, stepper_event, STEPPER_EVENT_STEPS_COMPLETED,
185 K_MSEC(steps * (100 + CONFIG_STEPPER_TEST_TIMING_TIMEOUT_TOLERANCE_PCT)));
186
187 zassert_ok(stepper_get_actual_position(fixture->dev, &actual_steps));
188 zassert_equal(steps, actual_steps, "Position should be %d but is %d", steps, actual_steps);
189 zassert_ok(stepper_is_moving(fixture->dev, &moving));
190 zassert_false(moving, "%s reported moving even after completion of steps",
191 fixture->dev->name);
192 }
193
ZTEST_F(stepper,test_move_by_negative_step_count)194 ZTEST_F(stepper, test_move_by_negative_step_count)
195 {
196 int32_t steps = -20;
197 int32_t actual_steps;
198 bool moving = false;
199 int ret;
200
201 ret = stepper_set_microstep_interval(fixture->dev, 100 * USEC_PER_SEC);
202 if (ret == -ENOSYS) {
203 ztest_test_skip();
204 }
205
206 zassert_ok(stepper_move_by(fixture->dev, steps));
207 zassert_ok(stepper_is_moving(fixture->dev, &moving));
208 zassert_true(moving, "%s reported not moving after move_by", fixture->dev->name);
209
210 POLL_AND_CHECK_SIGNAL(
211 stepper_signal, stepper_event, STEPPER_EVENT_STEPS_COMPLETED,
212 K_MSEC(-steps * (100 + CONFIG_STEPPER_TEST_TIMING_TIMEOUT_TOLERANCE_PCT)));
213
214 zassert_ok(stepper_get_actual_position(fixture->dev, &actual_steps));
215 zassert_equal(steps, actual_steps, "Position should be %d but is %d", steps, actual_steps);
216 zassert_ok(stepper_is_moving(fixture->dev, &moving));
217 zassert_false(moving, "%s reported moving even after completion of steps",
218 fixture->dev->name);
219 }
220
ZTEST_F(stepper,test_run_positive_direction)221 ZTEST_F(stepper, test_run_positive_direction)
222 {
223 uint64_t step_interval = 20000000;
224 int32_t actual_steps = 0;
225 int ret;
226
227 ret = stepper_set_microstep_interval(fixture->dev, step_interval);
228 if (ret == -ENOSYS) {
229 ztest_test_skip();
230 }
231
232 zassert_ok(stepper_run(fixture->dev, STEPPER_DIRECTION_POSITIVE));
233 k_usleep(110000);
234
235 zassert_ok(stepper_get_actual_position(fixture->dev, &actual_steps));
236 zassert_true(IN_RANGE(actual_steps, 1, 6),
237 "Current position should be between 1 and 6 but is %d", actual_steps);
238 }
239
ZTEST_F(stepper,test_run_negative_direction)240 ZTEST_F(stepper, test_run_negative_direction)
241 {
242 uint64_t step_interval = 20000000;
243 int32_t actual_steps = 0;
244 int ret;
245
246 ret = stepper_set_microstep_interval(fixture->dev, step_interval);
247 if (ret == -ENOSYS) {
248 ztest_test_skip();
249 }
250
251 zassert_ok(stepper_run(fixture->dev, STEPPER_DIRECTION_NEGATIVE));
252 k_usleep(110000);
253
254 zassert_ok(stepper_get_actual_position(fixture->dev, &actual_steps));
255 zassert_true(IN_RANGE(actual_steps, -6, -1),
256 "Current position should be between -6 and -1 but is %d", actual_steps);
257 }
258
ZTEST_F(stepper,test_stop)259 ZTEST_F(stepper, test_stop)
260 {
261 /* Run the stepper in positive direction */
262 (void)stepper_run(fixture->dev, STEPPER_DIRECTION_POSITIVE);
263
264 /* Stop the stepper */
265 int ret = stepper_stop(fixture->dev);
266 bool is_moving;
267
268 if (ret == 0) {
269 POLL_AND_CHECK_SIGNAL(stepper_signal, stepper_event, STEPPER_EVENT_STOPPED,
270 K_NO_WAIT);
271 zassert_equal(user_data_received, fixture->dev, "User data not received");
272
273 /* Check if the stepper is stopped */
274 stepper_is_moving(fixture->dev, &is_moving);
275 zassert_equal(is_moving, false, "Stepper is still moving");
276 } else if (ret == -ENOSYS) {
277 stepper_is_moving(fixture->dev, &is_moving);
278 zassert_equal(is_moving, true,
279 "Stepper should be moving since stop is not implemented");
280 } else {
281 zassert_unreachable("Stepper stop failed");
282 }
283 }
284
ZTEST_F(stepper,test_move_zero_steps)285 ZTEST_F(stepper, test_move_zero_steps)
286 {
287 bool moving;
288 int32_t pos = 0;
289 int32_t actual_steps;
290 int err;
291
292 err = stepper_set_microstep_interval(fixture->dev, 100 * USEC_PER_SEC);
293 if (err == -ENOSYS) {
294 ztest_test_skip();
295 }
296 zassert_ok(err, "Failed to set microstep interval");
297
298 zassert_ok(stepper_move_by(fixture->dev, pos));
299 POLL_AND_CHECK_SIGNAL(stepper_signal, stepper_event, STEPPER_EVENT_STEPS_COMPLETED,
300 K_NO_WAIT);
301 zassert_ok(stepper_is_moving(fixture->dev, &moving));
302 zassert_false(moving, "%s reported moving even after completion of steps",
303 fixture->dev->name);
304 zassert_ok(stepper_move_to(fixture->dev, pos));
305 POLL_AND_CHECK_SIGNAL(stepper_signal, stepper_event, STEPPER_EVENT_STEPS_COMPLETED,
306 K_NO_WAIT);
307 zassert_ok(stepper_get_actual_position(fixture->dev, &actual_steps));
308 zassert_equal(pos, actual_steps, "Position should not have changed from %d", pos);
309 }
310