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