/* * SPDX-FileCopyrightText: Copyright (c) 2025 Jilay Sandeep Pandya * SPDX-License-Identifier: Apache-2.0 */ #include #include #include LOG_MODULE_REGISTER(stepper_api, CONFIG_STEPPER_LOG_LEVEL); struct stepper_fixture { const struct device *dev; stepper_event_callback_t callback; }; struct k_poll_signal stepper_signal; struct k_poll_event stepper_event; void *user_data_received; #define POLL_AND_CHECK_SIGNAL(signal, event, expected_event, timeout) \ ({ \ do { \ (void)k_poll(&(event), 1, timeout); \ unsigned int signaled; \ int result; \ k_poll_signal_check(&(signal), &signaled, &result); \ zassert_equal(signaled, 1, "Signal not set"); \ zassert_equal(result, (expected_event), "Signal not set"); \ } while (0); \ }) static void stepper_print_event_callback(const struct device *dev, enum stepper_event event, void *user_data) { const struct device *dev_callback = user_data; user_data_received = user_data; switch (event) { case STEPPER_EVENT_STEPS_COMPLETED: k_poll_signal_raise(&stepper_signal, STEPPER_EVENT_STEPS_COMPLETED); break; case STEPPER_EVENT_LEFT_END_STOP_DETECTED: k_poll_signal_raise(&stepper_signal, STEPPER_EVENT_LEFT_END_STOP_DETECTED); break; case STEPPER_EVENT_RIGHT_END_STOP_DETECTED: k_poll_signal_raise(&stepper_signal, STEPPER_EVENT_RIGHT_END_STOP_DETECTED); break; case STEPPER_EVENT_STOPPED: k_poll_signal_raise(&stepper_signal, STEPPER_EVENT_STOPPED); break; default: break; } LOG_DBG("Event %d, %s called for %s, expected for %s\n", event, __func__, dev_callback->name, dev->name); } static void *stepper_setup(void) { static struct stepper_fixture fixture = { .dev = DEVICE_DT_GET(DT_ALIAS(stepper)), .callback = stepper_print_event_callback, }; k_poll_signal_init(&stepper_signal); k_poll_event_init(&stepper_event, K_POLL_TYPE_SIGNAL, K_POLL_MODE_NOTIFY_ONLY, &stepper_signal); zassert_not_null(fixture.dev); zassert_equal( stepper_set_event_callback(fixture.dev, fixture.callback, (void *)fixture.dev), 0, "Failed to set event callback"); return &fixture; } static void stepper_before(void *f) { struct stepper_fixture *fixture = f; (void)stepper_set_reference_position(fixture->dev, 0); k_poll_signal_reset(&stepper_signal); user_data_received = NULL; } ZTEST_SUITE(stepper, NULL, stepper_setup, stepper_before, NULL, NULL); ZTEST_F(stepper, test_set_micro_step_interval_invalid_zero) { int err = stepper_set_microstep_interval(fixture->dev, 0); if (err == -ENOSYS) { ztest_test_skip(); } zassert_equal(err, -EINVAL, "ustep interval cannot be zero"); } ZTEST_F(stepper, test_actual_position) { int32_t pos = 100u; int ret; ret = stepper_set_reference_position(fixture->dev, pos); zassert_equal(ret, 0, "Failed to set reference position"); ret = stepper_get_actual_position(fixture->dev, &pos); zassert_equal(ret, 0, "Failed to get actual position"); zassert_equal(pos, 100u, "Actual position not set correctly"); } ZTEST_F(stepper, test_move_to_positive_step_count) { int32_t pos = 10u; int32_t actual_steps; bool moving = false; int ret; ret = stepper_set_microstep_interval(fixture->dev, 100 * USEC_PER_SEC); if (ret == -ENOSYS) { ztest_test_skip(); } zassert_ok(stepper_move_to(fixture->dev, pos)); zassert_ok(stepper_is_moving(fixture->dev, &moving)); zassert_true(moving, "%s reported not moving after move_to", fixture->dev->name); POLL_AND_CHECK_SIGNAL( stepper_signal, stepper_event, STEPPER_EVENT_STEPS_COMPLETED, K_MSEC(pos * (100 + CONFIG_STEPPER_TEST_TIMING_TIMEOUT_TOLERANCE_PCT))); zassert_ok(stepper_get_actual_position(fixture->dev, &actual_steps)); zassert_equal(pos, actual_steps, "Position should be %d but is %d", pos, actual_steps); zassert_equal(user_data_received, fixture->dev, "User data not received"); zassert_ok(stepper_is_moving(fixture->dev, &moving)); zassert_false(moving, "%s reported moving even after completion of steps", fixture->dev->name); } ZTEST_F(stepper, test_move_to_negative_step_count) { int32_t pos = -10; int32_t actual_steps; bool moving = false; int ret; ret = stepper_set_microstep_interval(fixture->dev, 100 * USEC_PER_SEC); if (ret == -ENOSYS) { ztest_test_skip(); } zassert_ok(stepper_move_to(fixture->dev, pos)); zassert_ok(stepper_is_moving(fixture->dev, &moving)); zassert_true(moving, "%s reported not moving after move_to", fixture->dev->name); POLL_AND_CHECK_SIGNAL( stepper_signal, stepper_event, STEPPER_EVENT_STEPS_COMPLETED, K_MSEC(-pos * (100 + CONFIG_STEPPER_TEST_TIMING_TIMEOUT_TOLERANCE_PCT))); zassert_ok(stepper_get_actual_position(fixture->dev, &actual_steps)); zassert_equal(pos, actual_steps, "Position should be %d but is %d", pos, actual_steps); zassert_ok(stepper_is_moving(fixture->dev, &moving)); zassert_false(moving, "%s reported moving even after completion of steps", fixture->dev->name); } ZTEST_F(stepper, test_move_by_positive_step_count) { int32_t steps = 20; int32_t actual_steps; bool moving = false; int ret; ret = stepper_set_microstep_interval(fixture->dev, 100 * USEC_PER_SEC); if (ret == -ENOSYS) { ztest_test_skip(); } zassert_ok(stepper_move_by(fixture->dev, steps)); zassert_ok(stepper_is_moving(fixture->dev, &moving)); zassert_true(moving, "%s reported not moving after move_by", fixture->dev->name); POLL_AND_CHECK_SIGNAL( stepper_signal, stepper_event, STEPPER_EVENT_STEPS_COMPLETED, K_MSEC(steps * (100 + CONFIG_STEPPER_TEST_TIMING_TIMEOUT_TOLERANCE_PCT))); zassert_ok(stepper_get_actual_position(fixture->dev, &actual_steps)); zassert_equal(steps, actual_steps, "Position should be %d but is %d", steps, actual_steps); zassert_ok(stepper_is_moving(fixture->dev, &moving)); zassert_false(moving, "%s reported moving even after completion of steps", fixture->dev->name); } ZTEST_F(stepper, test_move_by_negative_step_count) { int32_t steps = -20; int32_t actual_steps; bool moving = false; int ret; ret = stepper_set_microstep_interval(fixture->dev, 100 * USEC_PER_SEC); if (ret == -ENOSYS) { ztest_test_skip(); } zassert_ok(stepper_move_by(fixture->dev, steps)); zassert_ok(stepper_is_moving(fixture->dev, &moving)); zassert_true(moving, "%s reported not moving after move_by", fixture->dev->name); POLL_AND_CHECK_SIGNAL( stepper_signal, stepper_event, STEPPER_EVENT_STEPS_COMPLETED, K_MSEC(-steps * (100 + CONFIG_STEPPER_TEST_TIMING_TIMEOUT_TOLERANCE_PCT))); zassert_ok(stepper_get_actual_position(fixture->dev, &actual_steps)); zassert_equal(steps, actual_steps, "Position should be %d but is %d", steps, actual_steps); zassert_ok(stepper_is_moving(fixture->dev, &moving)); zassert_false(moving, "%s reported moving even after completion of steps", fixture->dev->name); } ZTEST_F(stepper, test_run_positive_direction) { uint64_t step_interval = 20000000; int32_t actual_steps = 0; int ret; ret = stepper_set_microstep_interval(fixture->dev, step_interval); if (ret == -ENOSYS) { ztest_test_skip(); } zassert_ok(stepper_run(fixture->dev, STEPPER_DIRECTION_POSITIVE)); k_usleep(110000); zassert_ok(stepper_get_actual_position(fixture->dev, &actual_steps)); zassert_true(IN_RANGE(actual_steps, 1, 6), "Current position should be between 1 and 6 but is %d", actual_steps); } ZTEST_F(stepper, test_run_negative_direction) { uint64_t step_interval = 20000000; int32_t actual_steps = 0; int ret; ret = stepper_set_microstep_interval(fixture->dev, step_interval); if (ret == -ENOSYS) { ztest_test_skip(); } zassert_ok(stepper_run(fixture->dev, STEPPER_DIRECTION_NEGATIVE)); k_usleep(110000); zassert_ok(stepper_get_actual_position(fixture->dev, &actual_steps)); zassert_true(IN_RANGE(actual_steps, -6, -1), "Current position should be between -6 and -1 but is %d", actual_steps); } ZTEST_F(stepper, test_stop) { /* Run the stepper in positive direction */ (void)stepper_run(fixture->dev, STEPPER_DIRECTION_POSITIVE); /* Stop the stepper */ int ret = stepper_stop(fixture->dev); bool is_moving; if (ret == 0) { POLL_AND_CHECK_SIGNAL(stepper_signal, stepper_event, STEPPER_EVENT_STOPPED, K_NO_WAIT); zassert_equal(user_data_received, fixture->dev, "User data not received"); /* Check if the stepper is stopped */ stepper_is_moving(fixture->dev, &is_moving); zassert_equal(is_moving, false, "Stepper is still moving"); } else if (ret == -ENOSYS) { stepper_is_moving(fixture->dev, &is_moving); zassert_equal(is_moving, true, "Stepper should be moving since stop is not implemented"); } else { zassert_unreachable("Stepper stop failed"); } } ZTEST_F(stepper, test_move_zero_steps) { bool moving; int32_t pos = 0; int32_t actual_steps; int err; err = stepper_set_microstep_interval(fixture->dev, 100 * USEC_PER_SEC); if (err == -ENOSYS) { ztest_test_skip(); } zassert_ok(err, "Failed to set microstep interval"); zassert_ok(stepper_move_by(fixture->dev, pos)); POLL_AND_CHECK_SIGNAL(stepper_signal, stepper_event, STEPPER_EVENT_STEPS_COMPLETED, K_NO_WAIT); zassert_ok(stepper_is_moving(fixture->dev, &moving)); zassert_false(moving, "%s reported moving even after completion of steps", fixture->dev->name); zassert_ok(stepper_move_to(fixture->dev, pos)); POLL_AND_CHECK_SIGNAL(stepper_signal, stepper_event, STEPPER_EVENT_STEPS_COMPLETED, K_NO_WAIT); zassert_ok(stepper_get_actual_position(fixture->dev, &actual_steps)); zassert_equal(pos, actual_steps, "Position should not have changed from %d", pos); }