From 022f16da58e084703cce07657c551434f51f6442 Mon Sep 17 00:00:00 2001 From: Jilay Pandya Date: Sat, 21 Jun 2025 14:25:59 +0200 Subject: [PATCH 1/6] drivers: stepper: introduce stepper_drv api introducing stepper_drv api which is to be implemented by the stepper drivers Add fault handling in drv84xx using a fault cb mechanism With the introduction of the stepper_drv api, the goal is to achieve better separation of concerns where motion controllers are responsible for generating step pulses whereaas stepper drivers do are responsible for stepping, enabling, setting microstep resolution Signed-off-by: Jilay Pandya --- drivers/stepper/CMakeLists.txt | 5 + drivers/stepper/Kconfig | 5 + .../stepper/Kconfig.stepper_event_template | 18 - .../Kconfig.zephyr_stepper_motion_controller | 24 ++ drivers/stepper/adi_tmc/tmc22xx.c | 17 +- drivers/stepper/allegro/a4979.c | 18 +- drivers/stepper/h_bridge_stepper.c | 249 ++--------- drivers/stepper/step_dir/CMakeLists.txt | 2 - drivers/stepper/step_dir/Kconfig | 16 +- .../step_dir/step_dir_stepper_common.c | 304 +------------ .../step_dir/step_dir_stepper_common.h | 166 +------ drivers/stepper/stepper_shell.c | 3 - .../stepper_timing_sources/CMakeLists.txt | 7 + .../stepper/stepper_timing_sources/Kconfig | 18 + .../stepper_counter_timing.c} | 38 +- .../stepper_timing_source.h} | 51 ++- .../stepper_work_timing.c} | 45 +- drivers/stepper/ti/Kconfig.drv84xx | 1 - drivers/stepper/ti/drv84xx.c | 38 +- .../zephyr_stepper_motion_controller.c | 406 ++++++++++++++++++ dts/bindings/stepper/adi/adi,tmc2209.yaml | 2 +- dts/bindings/stepper/adi/adi,tmc50xx.yaml | 2 +- .../stepper/adi/adi,tmc51xx-base.yaml | 2 +- .../stepper/allegro/allegro,a4979.yaml | 2 +- .../{stepper-controller.yaml => stepper.yaml} | 4 - dts/bindings/stepper/ti/ti,drv84xx.yaml | 2 +- dts/bindings/stepper/zephyr,fake-stepper.yaml | 2 +- .../stepper/zephyr,h-bridge-stepper.yaml | 4 +- .../zephyr,stepper-motion-control.yaml | 26 ++ include/zephyr/drivers/stepper.h | 261 ++++++++++- .../generic/boards/nucleo_g071rb.overlay | 15 + samples/drivers/stepper/generic/prj.conf | 1 + samples/drivers/stepper/generic/src/main.c | 37 +- tests/drivers/build_all/stepper/app.overlay | 13 + tests/drivers/build_all/stepper/gpio.dtsi | 3 - .../drv84xx/api/boards/native_sim.overlay | 9 +- tests/drivers/stepper/drv84xx/api/prj.conf | 1 + tests/drivers/stepper/drv84xx/api/src/main.c | 2 +- .../drv84xx/emul/boards/native_sim.overlay | 1 - tests/drivers/stepper/drv84xx/emul/src/main.c | 11 +- .../stepper/stepper_api/CMakeLists.txt | 2 +- .../boards/native_sim_adi_tmc2209.overlay | 9 +- .../native_sim_adi_tmc2209_work_q.overlay | 9 +- .../boards/native_sim_allegro_a4979.overlay | 7 + .../native_sim_allegro_a4979_work_q.overlay | 7 + .../boards/native_sim_ti_drv84xx.overlay | 7 + .../native_sim_ti_drv84xx_work_q.overlay | 7 + ...native_sim_zephyr_h_bridge_stepper.overlay | 8 + ...emu_x86_64_zephyr_h_bridge_stepper.overlay | 7 + tests/drivers/stepper/stepper_api/src/main.c | 18 +- .../drivers/stepper/stepper_api/testcase.yaml | 12 +- 51 files changed, 1078 insertions(+), 846 deletions(-) delete mode 100644 drivers/stepper/Kconfig.stepper_event_template create mode 100644 drivers/stepper/Kconfig.zephyr_stepper_motion_controller create mode 100644 drivers/stepper/stepper_timing_sources/CMakeLists.txt create mode 100644 drivers/stepper/stepper_timing_sources/Kconfig rename drivers/stepper/{step_dir/step_dir_stepper_counter_timing.c => stepper_timing_sources/stepper_counter_timing.c} (63%) rename drivers/stepper/{step_dir/step_dir_stepper_timing_source.h => stepper_timing_sources/stepper_timing_source.h} (50%) rename drivers/stepper/{step_dir/step_dir_stepper_work_timing.c => stepper_timing_sources/stepper_work_timing.c} (53%) create mode 100644 drivers/stepper/zephyr_stepper_motion_controller.c rename dts/bindings/stepper/{stepper-controller.yaml => stepper.yaml} (90%) create mode 100644 dts/bindings/stepper/zephyr,stepper-motion-control.yaml diff --git a/drivers/stepper/CMakeLists.txt b/drivers/stepper/CMakeLists.txt index bd3677500704b..6ba4a03e89025 100644 --- a/drivers/stepper/CMakeLists.txt +++ b/drivers/stepper/CMakeLists.txt @@ -7,6 +7,10 @@ zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/drivers/stepper.h) add_subdirectory_ifdef(CONFIG_STEPPER_ADI_TMC adi_tmc) add_subdirectory_ifdef(CONFIG_STEPPER_ALLEGRO allegro) add_subdirectory_ifdef(CONFIG_STEPPER_TI ti) +# zephyr-keep-sorted-stop + +# zephyr-keep-sorted-start +add_subdirectory_ifdef(CONFIG_STEPPER_TIMING_SOURCES stepper_timing_sources) add_subdirectory_ifdef(CONFIG_STEP_DIR_STEPPER step_dir) # zephyr-keep-sorted-stop @@ -16,3 +20,4 @@ zephyr_library_property(ALLOW_EMPTY TRUE) zephyr_library_sources_ifdef(CONFIG_FAKE_STEPPER fake_stepper_controller.c) zephyr_library_sources_ifdef(CONFIG_H_BRIDGE_STEPPER h_bridge_stepper.c) zephyr_library_sources_ifdef(CONFIG_STEPPER_SHELL stepper_shell.c) +zephyr_library_sources_ifdef(CONFIG_ZEPHYR_STEPPER_MOTION_CONTROL zephyr_stepper_motion_controller.c) diff --git a/drivers/stepper/Kconfig b/drivers/stepper/Kconfig index f35ec8f8d3662..1bd5ee6ca6d2e 100644 --- a/drivers/stepper/Kconfig +++ b/drivers/stepper/Kconfig @@ -27,12 +27,17 @@ config STEPPER_SHELL comment "Stepper Driver Common" rsource "step_dir/Kconfig" +rsource "stepper_timing_sources/Kconfig" comment "Stepper Drivers" # zephyr-keep-sorted-start rsource "Kconfig.fake" rsource "Kconfig.h_bridge" +rsource "Kconfig.zephyr_stepper_motion_controller" +# zephyr-keep-sorted-stop + +# zephyr-keep-sorted-start rsource "adi_tmc/Kconfig" rsource "allegro/Kconfig" rsource "ti/Kconfig" diff --git a/drivers/stepper/Kconfig.stepper_event_template b/drivers/stepper/Kconfig.stepper_event_template deleted file mode 100644 index fb29a1e91e0e2..0000000000000 --- a/drivers/stepper/Kconfig.stepper_event_template +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2024 Fabian Blatz -# SPDX-License-Identifier: Apache-2.0 - -config STEPPER_$(module)_GENERATE_ISR_SAFE_EVENTS - bool "$(module-str) guarantee non ISR callbacks upon stepper events" - help - Enable the dispatch of stepper generated events via - a message queue to guarantee that the event handler - code is not run inside of an ISR. Can be disabled, but - then registered stepper event callback must be ISR safe. - -config STEPPER_$(module)_EVENT_QUEUE_LEN - int "$(module-str) maximum number of pending stepper events" - default 4 - depends on STEPPER_$(module)_GENERATE_ISR_SAFE_EVENTS - help - The maximum number of stepper events that can be pending before new events - are dropped. diff --git a/drivers/stepper/Kconfig.zephyr_stepper_motion_controller b/drivers/stepper/Kconfig.zephyr_stepper_motion_controller new file mode 100644 index 0000000000000..4f0666f00174a --- /dev/null +++ b/drivers/stepper/Kconfig.zephyr_stepper_motion_controller @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 Jilay Sandeep Pandya +# SPDX-License-Identifier: Apache-2.0 + +config ZEPHYR_STEPPER_MOTION_CONTROL + bool "Zephyr CPU based Stepper motion control" + depends on DT_HAS_ZEPHYR_STEPPER_MOTION_CONTROL_ENABLED + select STEPPER_TIMING_SOURCES + default y + +config ZEPHYR_STEPPER_MOTION_CONTROL_GENERATE_ISR_SAFE_EVENTS + bool "Guarantee non ISR callbacks upon stepper events" + help + Enable the dispatch of stepper generated events via + a message queue to guarantee that the event handler + code is not run inside of an ISR. Can be disabled, but + then registered stepper event callback must be ISR safe. + +config ZEPHYR_STEPPER_MOTION_CONTROL_EVENT_QUEUE_LEN + int "Maximum number of pending stepper events" + default 4 + depends on ZEPHYR_STEPPER_MOTION_CONTROL_GENERATE_ISR_SAFE_EVENTS + help + The maximum number of stepper events that can be pending before new events + are dropped. diff --git a/drivers/stepper/adi_tmc/tmc22xx.c b/drivers/stepper/adi_tmc/tmc22xx.c index fb73dc35fc28d..4283516e7f0ba 100644 --- a/drivers/stepper/adi_tmc/tmc22xx.c +++ b/drivers/stepper/adi_tmc/tmc22xx.c @@ -19,11 +19,10 @@ struct tmc22xx_config { }; struct tmc22xx_data { - struct step_dir_stepper_common_data common; enum stepper_micro_step_resolution resolution; }; -STEP_DIR_STEPPER_STRUCT_CHECK(struct tmc22xx_config, struct tmc22xx_data); +STEP_DIR_STEPPER_STRUCT_CHECK(struct tmc22xx_config); static int tmc22xx_stepper_enable(const struct device *dev) { @@ -147,18 +146,11 @@ static int tmc22xx_stepper_init(const struct device *dev) return 0; } -static DEVICE_API(stepper, tmc22xx_stepper_api) = { +static DEVICE_API(stepper_drv, tmc22xx_stepper_api) = { .enable = tmc22xx_stepper_enable, .disable = tmc22xx_stepper_disable, - .move_by = step_dir_stepper_common_move_by, - .is_moving = step_dir_stepper_common_is_moving, - .set_reference_position = step_dir_stepper_common_set_reference_position, - .get_actual_position = step_dir_stepper_common_get_actual_position, - .move_to = step_dir_stepper_common_move_to, - .set_microstep_interval = step_dir_stepper_common_set_microstep_interval, - .run = step_dir_stepper_common_run, - .stop = step_dir_stepper_common_stop, - .set_event_callback = step_dir_stepper_common_set_event_callback, + .step = step_dir_stepper_common_step, + .set_direction = step_dir_stepper_common_set_direction, .set_micro_step_res = tmc22xx_stepper_set_micro_step_res, .get_micro_step_res = tmc22xx_stepper_get_micro_step_res, }; @@ -183,7 +175,6 @@ static DEVICE_API(stepper, tmc22xx_stepper_api) = { (.msx_pins = tmc22xx_stepper_msx_pins_##inst)) \ }; \ static struct tmc22xx_data tmc22xx_data_##inst = { \ - .common = STEP_DIR_STEPPER_DT_INST_COMMON_DATA_INIT(inst), \ .resolution = DT_INST_PROP(inst, micro_step_res), \ }; \ DEVICE_DT_INST_DEFINE(inst, tmc22xx_stepper_init, NULL, &tmc22xx_data_##inst, \ diff --git a/drivers/stepper/allegro/a4979.c b/drivers/stepper/allegro/a4979.c index 45f062270c1ec..ea710d87f77c7 100644 --- a/drivers/stepper/allegro/a4979.c +++ b/drivers/stepper/allegro/a4979.c @@ -5,7 +5,6 @@ #define DT_DRV_COMPAT allegro_a4979 -#include #include #include #include "../step_dir/step_dir_stepper_common.h" @@ -22,11 +21,10 @@ struct a4979_config { }; struct a4979_data { - const struct step_dir_stepper_common_data common; enum stepper_micro_step_resolution micro_step_res; }; -STEP_DIR_STEPPER_STRUCT_CHECK(struct a4979_config, struct a4979_data); +STEP_DIR_STEPPER_STRUCT_CHECK(struct a4979_config); static int a4979_set_microstep_pin(const struct device *dev, const struct gpio_dt_spec *pin, int value) @@ -222,20 +220,13 @@ static int a4979_init(const struct device *dev) return 0; } -static DEVICE_API(stepper, a4979_stepper_api) = { +static DEVICE_API(stepper_drv, a4979_stepper_api) = { .enable = a4979_stepper_enable, .disable = a4979_stepper_disable, - .move_by = step_dir_stepper_common_move_by, - .move_to = step_dir_stepper_common_move_to, - .is_moving = step_dir_stepper_common_is_moving, - .set_reference_position = step_dir_stepper_common_set_reference_position, - .get_actual_position = step_dir_stepper_common_get_actual_position, - .set_microstep_interval = step_dir_stepper_common_set_microstep_interval, - .run = step_dir_stepper_common_run, - .stop = step_dir_stepper_common_stop, .set_micro_step_res = a4979_stepper_set_micro_step_res, .get_micro_step_res = a4979_stepper_get_micro_step_res, - .set_event_callback = step_dir_stepper_common_set_event_callback, + .step = step_dir_stepper_common_step, + .set_direction = step_dir_stepper_common_set_direction, }; #define A4979_DEVICE(inst) \ @@ -249,7 +240,6 @@ static DEVICE_API(stepper, a4979_stepper_api) = { }; \ \ static struct a4979_data a4979_data_##inst = { \ - .common = STEP_DIR_STEPPER_DT_INST_COMMON_DATA_INIT(inst), \ .micro_step_res = DT_INST_PROP(inst, micro_step_res), \ }; \ \ diff --git a/drivers/stepper/h_bridge_stepper.c b/drivers/stepper/h_bridge_stepper.c index 6cd866317856b..11d6f57173038 100644 --- a/drivers/stepper/h_bridge_stepper.c +++ b/drivers/stepper/h_bridge_stepper.c @@ -8,7 +8,6 @@ #include #include -#include #include #include @@ -29,17 +28,10 @@ struct h_bridge_stepper_config { }; struct h_bridge_stepper_data { - const struct device *dev; struct k_spinlock lock; enum stepper_direction direction; - enum stepper_run_mode run_mode; uint8_t step_gap; uint8_t coil_charge; - struct k_work_delayable stepper_dwork; - int32_t actual_position; - uint64_t delay_in_ns; - int32_t step_count; - stepper_event_callback_t callback; void *event_cb_user_data; }; @@ -90,183 +82,11 @@ static void update_coil_charge(const struct device *dev) if (data->direction == STEPPER_DIRECTION_POSITIVE) { config->invert_direction ? decrement_coil_charge(dev) : increment_coil_charge(dev); - data->actual_position++; } else if (data->direction == STEPPER_DIRECTION_NEGATIVE) { config->invert_direction ? increment_coil_charge(dev) : decrement_coil_charge(dev); - data->actual_position--; } } -static void update_remaining_steps(const struct device *dev) -{ - struct h_bridge_stepper_data *data = dev->data; - - if (data->step_count > 0) { - data->step_count--; - } else if (data->step_count < 0) { - data->step_count++; - } -} - -static void update_direction_from_step_count(const struct device *dev) -{ - struct h_bridge_stepper_data *data = dev->data; - - if (data->step_count > 0) { - data->direction = STEPPER_DIRECTION_POSITIVE; - } else if (data->step_count < 0) { - data->direction = STEPPER_DIRECTION_NEGATIVE; - } else { - LOG_ERR("Step count is zero"); - } -} - -static void position_mode_task(const struct device *dev) -{ - struct h_bridge_stepper_data *data = dev->data; - int ret; - - update_remaining_steps(dev); - ret = stepper_motor_set_coil_charge(dev); - if (ret < 0) { - LOG_ERR("Failed to set coil charge: %d", ret); - return; - } - - update_coil_charge(dev); - if (data->step_count) { - (void)k_work_reschedule(&data->stepper_dwork, K_NSEC(data->delay_in_ns)); - } else { - if (data->callback) { - data->callback(data->dev, STEPPER_EVENT_STEPS_COMPLETED, - data->event_cb_user_data); - } - (void)k_work_cancel_delayable(&data->stepper_dwork); - } -} - -static void velocity_mode_task(const struct device *dev) -{ - struct h_bridge_stepper_data *data = dev->data; - int ret; - - ret = stepper_motor_set_coil_charge(dev); - if (ret < 0) { - LOG_ERR("Failed to set coil charge: %d", ret); - return; - } - - update_coil_charge(dev); - (void)k_work_reschedule(&data->stepper_dwork, K_NSEC(data->delay_in_ns)); -} - -static void stepper_work_step_handler(struct k_work *work) -{ - struct k_work_delayable *dwork = k_work_delayable_from_work(work); - struct h_bridge_stepper_data *data = - CONTAINER_OF(dwork, struct h_bridge_stepper_data, stepper_dwork); - - K_SPINLOCK(&data->lock) { - switch (data->run_mode) { - case STEPPER_RUN_MODE_POSITION: - position_mode_task(data->dev); - break; - case STEPPER_RUN_MODE_VELOCITY: - velocity_mode_task(data->dev); - break; - default: - LOG_WRN("Unsupported run mode %d", data->run_mode); - break; - } - } -} - -static int h_bridge_stepper_move_by(const struct device *dev, int32_t micro_steps) -{ - struct h_bridge_stepper_data *data = dev->data; - - if (data->delay_in_ns == 0) { - LOG_ERR("Step interval not set or invalid step interval set"); - return -EINVAL; - } - K_SPINLOCK(&data->lock) { - data->run_mode = STEPPER_RUN_MODE_POSITION; - data->step_count = micro_steps; - update_direction_from_step_count(dev); - (void)k_work_reschedule(&data->stepper_dwork, K_NO_WAIT); - } - return 0; -} - -static int h_bridge_stepper_set_reference_position(const struct device *dev, int32_t position) -{ - struct h_bridge_stepper_data *data = dev->data; - - K_SPINLOCK(&data->lock) { - data->actual_position = position; - } - return 0; -} - -static int h_bridge_stepper_get_actual_position(const struct device *dev, int32_t *position) -{ - struct h_bridge_stepper_data *data = dev->data; - - K_SPINLOCK(&data->lock) { - *position = data->actual_position; - } - return 0; -} - -static int h_bridge_stepper_move_to(const struct device *dev, int32_t micro_steps) -{ - struct h_bridge_stepper_data *data = dev->data; - int32_t steps_to_move; - - K_SPINLOCK(&data->lock) { - steps_to_move = micro_steps - data->actual_position; - } - return h_bridge_stepper_move_by(dev, steps_to_move); -} - -static int h_bridge_stepper_is_moving(const struct device *dev, bool *is_moving) -{ - struct h_bridge_stepper_data *data = dev->data; - - *is_moving = k_work_delayable_is_pending(&data->stepper_dwork); - LOG_DBG("Motor is %s moving", *is_moving ? "" : "not"); - return 0; -} - -static int h_bridge_stepper_set_microstep_interval(const struct device *dev, - uint64_t microstep_interval_ns) -{ - struct h_bridge_stepper_data *data = dev->data; - - if (microstep_interval_ns == 0) { - LOG_ERR("Step interval is invalid."); - return -EINVAL; - } - - K_SPINLOCK(&data->lock) { - data->delay_in_ns = microstep_interval_ns; - } - LOG_DBG("Setting Motor step interval to %llu", microstep_interval_ns); - return 0; -} - -static int h_bridge_stepper_run(const struct device *dev, const enum stepper_direction direction) -{ - struct h_bridge_stepper_data *data = dev->data; - - K_SPINLOCK(&data->lock) { - data->run_mode = STEPPER_RUN_MODE_VELOCITY; - data->direction = direction; - (void)k_work_reschedule(&data->stepper_dwork, K_NO_WAIT); - } - return 0; -} - static int h_bridge_stepper_set_micro_step_res(const struct device *dev, enum stepper_micro_step_resolution micro_step_res) { @@ -295,18 +115,6 @@ static int h_bridge_stepper_get_micro_step_res(const struct device *dev, return 0; } -static int h_bridge_stepper_set_event_callback(const struct device *dev, - stepper_event_callback_t callback, void *user_data) -{ - struct h_bridge_stepper_data *data = dev->data; - - K_SPINLOCK(&data->lock) { - data->callback = callback; - data->event_cb_user_data = user_data; - } - return 0; -} - static int h_bridge_stepper_enable(const struct device *dev) { const struct h_bridge_stepper_config *config = dev->config; @@ -342,31 +150,46 @@ static int h_bridge_stepper_disable(const struct device *dev) return err; } -static int h_bridge_stepper_stop(const struct device *dev) +static int h_bridge_stepper_step(const struct device *dev) { struct h_bridge_stepper_data *data = dev->data; - int err; K_SPINLOCK(&data->lock) { - err = k_work_cancel_delayable(&data->stepper_dwork); + stepper_motor_set_coil_charge(dev); + update_coil_charge(dev); + } - if (data->callback && !err) { - data->callback(data->dev, STEPPER_EVENT_STOPPED, data->event_cb_user_data); - } + return 0; +} + +static int h_bridge_stepper_set_direction(const struct device *dev, + const enum stepper_direction direction) +{ + struct h_bridge_stepper_data *data = dev->data; + + K_SPINLOCK(&data->lock) { + data->direction = direction; } - return err; + + return 0; } static int h_bridge_stepper_init(const struct device *dev) { - struct h_bridge_stepper_data *data = dev->data; const struct h_bridge_stepper_config *config = dev->config; int err; - data->dev = dev; - LOG_DBG("Initializing %s h_bridge_stepper with %d pin", dev->name, NUM_CONTROL_PINS); + LOG_DBG("Initializing %s h_bridge with %d pin", dev->name, NUM_CONTROL_PINS); for (uint8_t n_pin = 0; n_pin < NUM_CONTROL_PINS; n_pin++) { - (void)gpio_pin_configure_dt(&config->control_pins[n_pin], GPIO_OUTPUT_INACTIVE); + if (!gpio_is_ready_dt(&config->control_pins[n_pin])) { + LOG_ERR("Control pin %d is not ready", n_pin); + return -ENODEV; + } + err = gpio_pin_configure_dt(&config->control_pins[n_pin], GPIO_OUTPUT_INACTIVE); + if (err != 0) { + LOG_ERR("%s: Failed to configure pin %d, error:%d", dev->name, n_pin, err); + return -ENODEV; + } } if (config->en_pin.port != NULL) { @@ -382,36 +205,28 @@ static int h_bridge_stepper_init(const struct device *dev) } } - k_work_init_delayable(&data->stepper_dwork, stepper_work_step_handler); return 0; } -static DEVICE_API(stepper, h_bridge_stepper_api) = { +static DEVICE_API(stepper_drv, h_bridge_stepper_api) = { .enable = h_bridge_stepper_enable, .disable = h_bridge_stepper_disable, .set_micro_step_res = h_bridge_stepper_set_micro_step_res, .get_micro_step_res = h_bridge_stepper_get_micro_step_res, - .set_reference_position = h_bridge_stepper_set_reference_position, - .get_actual_position = h_bridge_stepper_get_actual_position, - .set_event_callback = h_bridge_stepper_set_event_callback, - .set_microstep_interval = h_bridge_stepper_set_microstep_interval, - .move_by = h_bridge_stepper_move_by, - .move_to = h_bridge_stepper_move_to, - .run = h_bridge_stepper_run, - .stop = h_bridge_stepper_stop, - .is_moving = h_bridge_stepper_is_moving, + .step = h_bridge_stepper_step, + .set_direction = h_bridge_stepper_set_direction, }; #define H_BRIDGE_STEPPER_DEFINE(inst) \ - static const struct gpio_dt_spec h_bridge_stepper_motor_control_pins_##inst[] = { \ + static const struct gpio_dt_spec h_bridge_stepper_control_pins_##inst[] = { \ DT_INST_FOREACH_PROP_ELEM_SEP(inst, gpios, GPIO_DT_SPEC_GET_BY_IDX, (,)), \ }; \ - BUILD_ASSERT(ARRAY_SIZE(h_bridge_stepper_motor_control_pins_##inst) == 4, \ + BUILD_ASSERT(ARRAY_SIZE(h_bridge_stepper_control_pins_##inst) == 4, \ "h_bridge stepper driver currently supports only 4 wire configuration"); \ static const struct h_bridge_stepper_config h_bridge_stepper_config_##inst = { \ .en_pin = GPIO_DT_SPEC_INST_GET_OR(inst, en_gpios, {0}), \ .invert_direction = DT_INST_PROP(inst, invert_direction), \ - .control_pins = h_bridge_stepper_motor_control_pins_##inst}; \ + .control_pins = h_bridge_stepper_control_pins_##inst}; \ static struct h_bridge_stepper_data h_bridge_stepper_data_##inst = { \ .step_gap = MAX_MICRO_STEP_RES >> (DT_INST_PROP(inst, micro_step_res) - 1), \ }; \ diff --git a/drivers/stepper/step_dir/CMakeLists.txt b/drivers/stepper/step_dir/CMakeLists.txt index 7daa4cc04d5b2..6bb451e000676 100644 --- a/drivers/stepper/step_dir/CMakeLists.txt +++ b/drivers/stepper/step_dir/CMakeLists.txt @@ -4,5 +4,3 @@ zephyr_library() zephyr_library_sources(step_dir_stepper_common.c) -zephyr_library_sources(step_dir_stepper_work_timing.c) -zephyr_library_sources_ifdef(CONFIG_STEP_DIR_STEPPER_COUNTER_TIMING step_dir_stepper_counter_timing.c) diff --git a/drivers/stepper/step_dir/Kconfig b/drivers/stepper/step_dir/Kconfig index d8629c649ffd2..62d23cf516ff5 100644 --- a/drivers/stepper/step_dir/Kconfig +++ b/drivers/stepper/step_dir/Kconfig @@ -3,20 +3,6 @@ config STEP_DIR_STEPPER bool + select GPIO help Enable library used for step direction stepper drivers. - -if STEP_DIR_STEPPER - -config STEP_DIR_STEPPER_COUNTER_TIMING - bool "Counter use for stepping" - select COUNTER - default y - help - Enable usage of a counter device for accurate stepping. - -module = STEP_DIR -module-str = step_dir -rsource "../Kconfig.stepper_event_template" - -endif # STEP_DIR_STEPPER diff --git a/drivers/stepper/step_dir/step_dir_stepper_common.c b/drivers/stepper/step_dir/step_dir_stepper_common.c index b6b7566f07fbd..272361fa783c3 100644 --- a/drivers/stepper/step_dir/step_dir_stepper_common.c +++ b/drivers/stepper/step_dir/step_dir_stepper_common.c @@ -8,29 +8,10 @@ #include LOG_MODULE_REGISTER(step_dir_stepper, CONFIG_STEPPER_LOG_LEVEL); -static inline int step_dir_stepper_perform_step(const struct device *dev) +int step_dir_stepper_common_step(const struct device *dev) { const struct step_dir_stepper_common_config *config = dev->config; - struct step_dir_stepper_common_data *data = dev->data; - int ret; - - switch (data->direction) { - case STEPPER_DIRECTION_POSITIVE: - ret = gpio_pin_set_dt(&config->dir_pin, 1 ^ config->invert_direction); - break; - case STEPPER_DIRECTION_NEGATIVE: - ret = gpio_pin_set_dt(&config->dir_pin, 0 ^ config->invert_direction); - break; - default: - LOG_ERR("Unsupported direction: %d", data->direction); - return -ENOTSUP; - } - if (ret < 0) { - LOG_ERR("Failed to set direction: %d", ret); - return ret; - } - - ret = gpio_pin_toggle_dt(&config->step_pin); + int ret = gpio_pin_toggle_dt(&config->step_pin); if (ret < 0) { LOG_ERR("Failed to toggle step pin: %d", ret); return ret; @@ -44,142 +25,28 @@ static inline int step_dir_stepper_perform_step(const struct device *dev) } } - if (data->direction == STEPPER_DIRECTION_POSITIVE) { - data->actual_position++; - } else { - data->actual_position--; - } - return 0; } -void stepper_trigger_callback(const struct device *dev, enum stepper_event event) -{ - struct step_dir_stepper_common_data *data = dev->data; - - if (!data->callback) { - LOG_WRN_ONCE("No callback set"); - return; - } - - if (!k_is_in_isr()) { - data->callback(dev, event, data->event_cb_user_data); - return; - } - -#ifdef CONFIG_STEPPER_STEP_DIR_GENERATE_ISR_SAFE_EVENTS - /* Dispatch to msgq instead of raising directly */ - int ret = k_msgq_put(&data->event_msgq, &event, K_NO_WAIT); - - if (ret != 0) { - LOG_WRN("Failed to put event in msgq: %d", ret); - } - - ret = k_work_submit(&data->event_callback_work); - if (ret < 0) { - LOG_ERR("Failed to submit work item: %d", ret); - } -#else - LOG_WRN_ONCE("Event callback called from ISR context without ISR safe events enabled"); -#endif /* CONFIG_STEPPER_STEP_DIR_GENERATE_ISR_SAFE_EVENTS */ -} - -#ifdef CONFIG_STEPPER_STEP_DIR_GENERATE_ISR_SAFE_EVENTS -static void stepper_work_event_handler(struct k_work *work) +int step_dir_stepper_common_set_direction(const struct device *dev, + const enum stepper_direction dir) { - struct step_dir_stepper_common_data *data = - CONTAINER_OF(work, struct step_dir_stepper_common_data, event_callback_work); - enum stepper_event event; - int ret; - - ret = k_msgq_get(&data->event_msgq, &event, K_NO_WAIT); - if (ret != 0) { - return; - } - - /* Run the callback */ - if (data->callback != NULL) { - data->callback(data->dev, event, data->event_cb_user_data); - } - - /* If there are more pending events, resubmit this work item to handle them */ - if (k_msgq_num_used_get(&data->event_msgq) > 0) { - k_work_submit(work); - } -} -#endif /* CONFIG_STEPPER_STEP_DIR_GENERATE_ISR_SAFE_EVENTS */ - -static void update_remaining_steps(struct step_dir_stepper_common_data *data) -{ - const struct step_dir_stepper_common_config *config = data->dev->config; - - if (data->step_count > 0) { - data->step_count--; - } else if (data->step_count < 0) { - data->step_count++; - } else { - stepper_trigger_callback(data->dev, STEPPER_EVENT_STEPS_COMPLETED); - config->timing_source->stop(data->dev); - } -} - -static void update_direction_from_step_count(const struct device *dev) -{ - struct step_dir_stepper_common_data *data = dev->data; - - if (data->step_count > 0) { - data->direction = STEPPER_DIRECTION_POSITIVE; - } else if (data->step_count < 0) { - data->direction = STEPPER_DIRECTION_NEGATIVE; - } else { - LOG_ERR("Step count is zero"); - } -} - -static void position_mode_task(const struct device *dev) -{ - struct step_dir_stepper_common_data *data = dev->data; const struct step_dir_stepper_common_config *config = dev->config; + int ret; - if (data->step_count) { - (void)step_dir_stepper_perform_step(dev); - } - - if (config->timing_source->needs_reschedule(dev) && data->step_count != 0) { - (void)config->timing_source->start(dev); - } - - update_remaining_steps(dev->data); -} - -static void velocity_mode_task(const struct device *dev) -{ - const struct step_dir_stepper_common_config *config = dev->config; - - (void)step_dir_stepper_perform_step(dev); - - if (config->timing_source->needs_reschedule(dev)) { - (void)config->timing_source->start(dev); + switch (dir) { + case STEPPER_DIRECTION_POSITIVE: + ret = gpio_pin_set_dt(&config->dir_pin, 1 ^ config->invert_direction); + break; + case STEPPER_DIRECTION_NEGATIVE: + ret = gpio_pin_set_dt(&config->dir_pin, 0 ^ config->invert_direction); + break; + default: + LOG_ERR("Unsupported direction: %d", dir); + return -ENOTSUP; } -} -void stepper_handle_timing_signal(const struct device *dev) -{ - struct step_dir_stepper_common_data *data = dev->data; - - K_SPINLOCK(&data->lock) { - switch (data->run_mode) { - case STEPPER_RUN_MODE_POSITION: - position_mode_task(dev); - break; - case STEPPER_RUN_MODE_VELOCITY: - velocity_mode_task(dev); - break; - default: - LOG_WRN("Unsupported run mode: %d", data->run_mode); - break; - } - } + return ret; } int step_dir_stepper_common_init(const struct device *dev) @@ -204,144 +71,5 @@ int step_dir_stepper_common_init(const struct device *dev) return ret; } - if (config->timing_source->init) { - ret = config->timing_source->init(dev); - if (ret < 0) { - LOG_ERR("Failed to initialize timing source: %d", ret); - return ret; - } - } - -#ifdef CONFIG_STEPPER_STEP_DIR_GENERATE_ISR_SAFE_EVENTS - struct step_dir_stepper_common_data *data = dev->data; - - k_msgq_init(&data->event_msgq, data->event_msgq_buffer, sizeof(enum stepper_event), - CONFIG_STEPPER_STEP_DIR_EVENT_QUEUE_LEN); - k_work_init(&data->event_callback_work, stepper_work_event_handler); -#endif /* CONFIG_STEPPER_STEP_DIR_GENERATE_ISR_SAFE_EVENTS */ - - return 0; -} - -int step_dir_stepper_common_move_by(const struct device *dev, const int32_t micro_steps) -{ - struct step_dir_stepper_common_data *data = dev->data; - const struct step_dir_stepper_common_config *config = dev->config; - - if (data->microstep_interval_ns == 0) { - LOG_ERR("Step interval not set or invalid step interval set"); - return -EINVAL; - } - - K_SPINLOCK(&data->lock) { - data->run_mode = STEPPER_RUN_MODE_POSITION; - data->step_count = micro_steps; - config->timing_source->update(dev, data->microstep_interval_ns); - update_direction_from_step_count(dev); - config->timing_source->start(dev); - } - - return 0; -} - -int step_dir_stepper_common_set_microstep_interval(const struct device *dev, - const uint64_t microstep_interval_ns) -{ - struct step_dir_stepper_common_data *data = dev->data; - const struct step_dir_stepper_common_config *config = dev->config; - - if (microstep_interval_ns == 0) { - LOG_ERR("Step interval cannot be zero"); - return -EINVAL; - } - - K_SPINLOCK(&data->lock) { - data->microstep_interval_ns = microstep_interval_ns; - config->timing_source->update(dev, microstep_interval_ns); - } - - return 0; -} - -int step_dir_stepper_common_set_reference_position(const struct device *dev, const int32_t value) -{ - struct step_dir_stepper_common_data *data = dev->data; - - K_SPINLOCK(&data->lock) { - data->actual_position = value; - } - - return 0; -} - -int step_dir_stepper_common_get_actual_position(const struct device *dev, int32_t *value) -{ - struct step_dir_stepper_common_data *data = dev->data; - - K_SPINLOCK(&data->lock) { - *value = data->actual_position; - } - - return 0; -} - -int step_dir_stepper_common_move_to(const struct device *dev, const int32_t value) -{ - struct step_dir_stepper_common_data *data = dev->data; - int32_t steps_to_move; - - /* Calculate the relative movement required */ - K_SPINLOCK(&data->lock) { - steps_to_move = value - data->actual_position; - } - - return step_dir_stepper_common_move_by(dev, steps_to_move); -} - -int step_dir_stepper_common_is_moving(const struct device *dev, bool *is_moving) -{ - const struct step_dir_stepper_common_config *config = dev->config; - - *is_moving = config->timing_source->is_running(dev); - return 0; -} - -int step_dir_stepper_common_run(const struct device *dev, const enum stepper_direction direction) -{ - struct step_dir_stepper_common_data *data = dev->data; - const struct step_dir_stepper_common_config *config = dev->config; - - K_SPINLOCK(&data->lock) { - data->run_mode = STEPPER_RUN_MODE_VELOCITY; - data->direction = direction; - config->timing_source->update(dev, data->microstep_interval_ns); - config->timing_source->start(dev); - } - - return 0; -} - -int step_dir_stepper_common_stop(const struct device *dev) -{ - const struct step_dir_stepper_common_config *config = dev->config; - int ret; - - ret = config->timing_source->stop(dev); - if (ret != 0) { - LOG_ERR("Failed to stop timing source: %d", ret); - return ret; - } - - stepper_trigger_callback(dev, STEPPER_EVENT_STOPPED); - return 0; -} - -int step_dir_stepper_common_set_event_callback(const struct device *dev, - stepper_event_callback_t callback, void *user_data) -{ - struct step_dir_stepper_common_data *data = dev->data; - - data->callback = callback; - data->event_cb_user_data = user_data; return 0; } diff --git a/drivers/stepper/step_dir/step_dir_stepper_common.h b/drivers/stepper/step_dir/step_dir_stepper_common.h index b345cf5d68a5d..a6a1327cc4e3c 100644 --- a/drivers/stepper/step_dir/step_dir_stepper_common.h +++ b/drivers/stepper/step_dir/step_dir_stepper_common.h @@ -14,12 +14,8 @@ * @{ */ -#include #include #include -#include - -#include "step_dir_stepper_timing_source.h" /** * @brief Common step direction stepper config. @@ -30,8 +26,6 @@ struct step_dir_stepper_common_config { const struct gpio_dt_spec step_pin; const struct gpio_dt_spec dir_pin; bool dual_edge; - const struct stepper_timing_source_api *timing_source; - const struct device *counter; bool invert_direction; }; @@ -47,11 +41,7 @@ struct step_dir_stepper_common_config { .step_pin = GPIO_DT_SPEC_GET(node_id, step_gpios), \ .dir_pin = GPIO_DT_SPEC_GET(node_id, dir_gpios), \ .dual_edge = DT_PROP_OR(node_id, dual_edge_step, false), \ - .counter = DEVICE_DT_GET_OR_NULL(DT_PHANDLE(node_id, counter)), \ .invert_direction = DT_PROP(node_id, invert_direction), \ - .timing_source = COND_CODE_1(DT_NODE_HAS_PROP(node_id, counter), \ - (&step_counter_timing_source_api), \ - (&step_work_timing_source_api)), \ } /** @@ -61,65 +51,14 @@ struct step_dir_stepper_common_config { #define STEP_DIR_STEPPER_DT_INST_COMMON_CONFIG_INIT(inst) \ STEP_DIR_STEPPER_DT_COMMON_CONFIG_INIT(DT_DRV_INST(inst)) -/** - * @brief Common step direction stepper data. - * - * This structure **must** be placed first in the driver's data structure. - */ -struct step_dir_stepper_common_data { - const struct device *dev; - struct k_spinlock lock; - enum stepper_direction direction; - enum stepper_run_mode run_mode; - int32_t actual_position; - uint64_t microstep_interval_ns; - int32_t step_count; - stepper_event_callback_t callback; - void *event_cb_user_data; - - struct k_work_delayable stepper_dwork; - -#ifdef CONFIG_STEP_DIR_STEPPER_COUNTER_TIMING - struct counter_top_cfg counter_top_cfg; - bool counter_running; -#endif /* CONFIG_STEP_DIR_STEPPER_COUNTER_TIMING */ - -#ifdef CONFIG_STEPPER_STEP_DIR_GENERATE_ISR_SAFE_EVENTS - struct k_work event_callback_work; - struct k_msgq event_msgq; - uint8_t event_msgq_buffer[CONFIG_STEPPER_STEP_DIR_EVENT_QUEUE_LEN * - sizeof(enum stepper_event)]; -#endif /* CONFIG_STEPPER_STEP_DIR_GENERATE_ISR_SAFE_EVENTS */ -}; - -/** - * @brief Initialize common step direction stepper data from devicetree instance. - * - * @param node_id The devicetree node identifier. - */ -#define STEP_DIR_STEPPER_DT_COMMON_DATA_INIT(node_id) \ - { \ - .dev = DEVICE_DT_GET(node_id), \ - } - -/** - * @brief Initialize common step direction stepper data from devicetree instance. - * @param inst Instance. - */ -#define STEP_DIR_STEPPER_DT_INST_COMMON_DATA_INIT(inst) \ - STEP_DIR_STEPPER_DT_COMMON_DATA_INIT(DT_DRV_INST(inst)) - /** * @brief Validate the offset of the common data structures. * * @param config Name of the config structure. - * @param data Name of the data structure. */ -#define STEP_DIR_STEPPER_STRUCT_CHECK(config, data) \ +#define STEP_DIR_STEPPER_STRUCT_CHECK(config) \ BUILD_ASSERT(offsetof(config, common) == 0, \ - "struct step_dir_stepper_common_config must be placed first"); \ - BUILD_ASSERT(offsetof(data, common) == 0, \ - "struct step_dir_stepper_common_data must be placed first"); + "struct step_dir_stepper_common_config must be placed first"); /** * @brief Common function to initialize a step direction stepper device at init time. @@ -134,103 +73,26 @@ struct step_dir_stepper_common_data { int step_dir_stepper_common_init(const struct device *dev); /** - * @brief Move the stepper motor by a given number of micro_steps. - * - * @param dev Pointer to the device structure. - * @param micro_steps Number of micro_steps to move. Can be positive or negative. - * @return 0 on success, or a negative error code on failure. - */ -int step_dir_stepper_common_move_by(const struct device *dev, const int32_t micro_steps); - -/** - * @brief Set the step interval of the stepper motor. + * @brief Common function to perform a step on a step direction stepper device. * - * @param dev Pointer to the device structure. - * @param microstep_interval_ns The step interval in nanoseconds. - * @return 0 on success, or a negative error code on failure. - */ -int step_dir_stepper_common_set_microstep_interval(const struct device *dev, - const uint64_t microstep_interval_ns); - -/** - * @brief Set the reference position of the stepper motor. - * - * @param dev Pointer to the device structure. - * @param value The reference position value to set. - * @return 0 on success, or a negative error code on failure. - */ -int step_dir_stepper_common_set_reference_position(const struct device *dev, const int32_t value); - -/** - * @brief Get the actual (reference) position of the stepper motor. - * - * @param dev Pointer to the device structure. - * @param value Pointer to a variable where the position value will be stored. - * @return 0 on success, or a negative error code on failure. - */ -int step_dir_stepper_common_get_actual_position(const struct device *dev, int32_t *value); - -/** - * @brief Set the absolute target position of the stepper motor. - * - * @param dev Pointer to the device structure. - * @param value The target position to set. - * @return 0 on success, or a negative error code on failure. - */ -int step_dir_stepper_common_move_to(const struct device *dev, const int32_t value); - -/** - * @brief Check if the stepper motor is still moving. - * - * @param dev Pointer to the device structure. - * @param is_moving Pointer to a boolean where the movement status will be stored. - * @return 0 on success, or a negative error code on failure. - */ -int step_dir_stepper_common_is_moving(const struct device *dev, bool *is_moving); - -/** - * @brief Run the stepper with a given direction and step interval. - * - * @param dev Pointer to the device structure. - * @param direction The direction of movement (positive or negative). - * @return 0 on success, or a negative error code on failure. - */ -int step_dir_stepper_common_run(const struct device *dev, const enum stepper_direction direction); - -/** - * @brief Stop the stepper motor. + * @param dev Step direction stepper device instance. * - * @param dev Pointer to the device structure. - * @return 0 on success, or a negative error code on failure. + * @retval 0 If step performed successfully. + * @retval -errno Negative errno in case of failure. */ -int step_dir_stepper_common_stop(const struct device *dev); +int step_dir_stepper_common_step(const struct device *dev); /** - * @brief Set a callback function for stepper motor events. + * @brief Common function to set the direction of a step direction stepper device. * - * This function sets a user-defined callback that will be invoked when a stepper motor event - * occurs. + * @param dev Step direction stepper device instance. + * @param dir Direction to set. * - * @param dev Pointer to the device structure. - * @param callback The callback function to set. - * @param user_data Pointer to user-defined data that will be passed to the callback. - * @return 0 on success, or a negative error code on failure. - */ -int step_dir_stepper_common_set_event_callback(const struct device *dev, - stepper_event_callback_t callback, void *user_data); - -/** - * @brief Handle a timing signal and update the stepper position. - * @param dev Pointer to the device structure. - */ -void stepper_handle_timing_signal(const struct device *dev); - -/** - * @brief Trigger callback function for stepper motor events. - * @param dev Pointer to the device structure. - * @param event The stepper_event to rigger the callback for. + * @retval 0 If direction set successfully. + * @retval -errno Negative errno in case of failure. */ -void stepper_trigger_callback(const struct device *dev, enum stepper_event event); +int step_dir_stepper_common_set_direction(const struct device *dev, + const enum stepper_direction dir); /** @} */ diff --git a/drivers/stepper/stepper_shell.c b/drivers/stepper/stepper_shell.c index 1e96fb20c5f11..e18da9acb553c 100644 --- a/drivers/stepper/stepper_shell.c +++ b/drivers/stepper/stepper_shell.c @@ -64,9 +64,6 @@ static void print_callback(const struct device *dev, const enum stepper_event ev case STEPPER_EVENT_STOPPED: shell_info(sh, "%s: Stepper stopped.", dev->name); break; - case STEPPER_EVENT_FAULT_DETECTED: - shell_info(sh, "%s: Fault detected.", dev->name); - break; default: shell_info(sh, "%s: Unknown signal received.", dev->name); break; diff --git a/drivers/stepper/stepper_timing_sources/CMakeLists.txt b/drivers/stepper/stepper_timing_sources/CMakeLists.txt new file mode 100644 index 0000000000000..2b91d8e29d159 --- /dev/null +++ b/drivers/stepper/stepper_timing_sources/CMakeLists.txt @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 Jilay Sandeep Pandya +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources(stepper_work_timing.c) +zephyr_library_sources_ifdef(CONFIG_STEPPER_TIMING_SOURCES_COUNTER_TIMING stepper_counter_timing.c) diff --git a/drivers/stepper/stepper_timing_sources/Kconfig b/drivers/stepper/stepper_timing_sources/Kconfig new file mode 100644 index 0000000000000..dd3c9c4f78e52 --- /dev/null +++ b/drivers/stepper/stepper_timing_sources/Kconfig @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 Jilay Sandeep Pandya +# SPDX-License-Identifier: Apache-2.0 + +config STEPPER_TIMING_SOURCES + bool "Stepper Timing Sources" + help + Enable timing sources for stepper + +if STEPPER_TIMING_SOURCES + +config STEPPER_TIMING_SOURCES_COUNTER_TIMING + bool "Counter use for stepping" + select COUNTER + default y + help + Enable usage of a counter device for accurate stepping. + +endif diff --git a/drivers/stepper/step_dir/step_dir_stepper_counter_timing.c b/drivers/stepper/stepper_timing_sources/stepper_counter_timing.c similarity index 63% rename from drivers/stepper/step_dir/step_dir_stepper_counter_timing.c rename to drivers/stepper/stepper_timing_sources/stepper_counter_timing.c index bad216961e575..691c60840af44 100644 --- a/drivers/stepper/step_dir/step_dir_stepper_counter_timing.c +++ b/drivers/stepper/stepper_timing_sources/stepper_counter_timing.c @@ -3,25 +3,23 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include -#include "step_dir_stepper_common.h" +#include "stepper_timing_source.h" #include -LOG_MODULE_DECLARE(step_dir_stepper); +LOG_MODULE_REGISTER(stepper_counter_timing, CONFIG_STEPPER_LOG_LEVEL); static void step_counter_top_interrupt(const struct device *dev, void *user_data) { ARG_UNUSED(dev); - struct step_dir_stepper_common_data *data = user_data; + struct timing_source_data *data = user_data; - stepper_handle_timing_signal(data->dev); + data->stepper_handle_timing_signal_cb(data->motion_control_dev); } -int step_counter_timing_source_update(const struct device *dev, +int step_counter_timing_source_update(const struct timing_source_config *config, + struct timing_source_data *data, const uint64_t microstep_interval_ns) { - const struct step_dir_stepper_common_config *config = dev->config; - struct step_dir_stepper_common_data *data = dev->data; int ret; if (microstep_interval_ns == 0) { @@ -39,17 +37,17 @@ int step_counter_timing_source_update(const struct device *dev, irq_unlock(key); if (ret != 0) { - LOG_ERR("%s: Failed to set counter top value (error: %d)", dev->name, ret); + LOG_ERR("%s: Failed to set counter top value (error: %d)", + data->motion_control_dev->name, ret); return ret; } return 0; } -int step_counter_timing_source_start(const struct device *dev) +int step_counter_timing_source_start(const struct timing_source_config *config, + struct timing_source_data *data) { - const struct step_dir_stepper_common_config *config = dev->config; - struct step_dir_stepper_common_data *data = dev->data; int ret; ret = counter_start(config->counter); @@ -63,10 +61,9 @@ int step_counter_timing_source_start(const struct device *dev) return 0; } -int step_counter_timing_source_stop(const struct device *dev) +int step_counter_timing_source_stop(const struct timing_source_config *config, + struct timing_source_data *data) { - const struct step_dir_stepper_common_config *config = dev->config; - struct step_dir_stepper_common_data *data = dev->data; int ret; ret = counter_stop(config->counter); @@ -86,18 +83,15 @@ bool step_counter_timing_source_needs_reschedule(const struct device *dev) return false; } -bool step_counter_timing_source_is_running(const struct device *dev) +bool step_counter_timing_source_is_running(const struct timing_source_config *config, + struct timing_source_data *data) { - struct step_dir_stepper_common_data *data = dev->data; - return data->counter_running; } -int step_counter_timing_source_init(const struct device *dev) +int step_counter_timing_source_init(const struct timing_source_config *config, + struct timing_source_data *data) { - const struct step_dir_stepper_common_config *config = dev->config; - struct step_dir_stepper_common_data *data = dev->data; - if (!device_is_ready(config->counter)) { LOG_ERR("Counter device is not ready"); return -ENODEV; diff --git a/drivers/stepper/step_dir/step_dir_stepper_timing_source.h b/drivers/stepper/stepper_timing_sources/stepper_timing_source.h similarity index 50% rename from drivers/stepper/step_dir/step_dir_stepper_timing_source.h rename to drivers/stepper/stepper_timing_sources/stepper_timing_source.h index 4c77993614afc..fc76303536b2a 100644 --- a/drivers/stepper/step_dir/step_dir_stepper_timing_source.h +++ b/drivers/stepper/stepper_timing_sources/stepper_timing_source.h @@ -7,32 +7,56 @@ #define ZEPHYR_DRIVER_STEPPER_STEP_DIR_STEPPER_TIMING_SOURCE_H_ #include +#include +#include + +struct timing_source_config { + const struct device *counter; +}; + +struct timing_source_data { + const struct device *motion_control_dev; + uint64_t microstep_interval_ns; + struct k_work_delayable stepper_dwork; + void (*stepper_handle_timing_signal_cb)(const struct device *dev); + +#ifdef CONFIG_STEPPER_TIMING_SOURCES_COUNTER_TIMING + struct counter_top_cfg counter_top_cfg; + bool counter_running; +#endif /* CONFIG_STEPPER_TIMING_SOURCES_COUNTER_TIMING */ +}; /** * @brief Initialize the stepper timing source. * - * @param dev Pointer to the device structure. + * @param config Pointer to the timing source configuration structure. + * @param data Pointer to the timing source data structure. * @return 0 on success, or a negative error code on failure. */ -typedef int (*stepper_timing_source_init)(const struct device *dev); +typedef int (*stepper_timing_source_init)(const struct timing_source_config *config, + struct timing_source_data *data); /** * @brief Update the stepper timing source. * - * @param dev Pointer to the device structure. + * @param config Pointer to the timing source configuration structure. + * @param data Pointer to the timing source data structure. * @param microstep_interval_ns Step interval in nanoseconds. * @return 0 on success, or a negative error code on failure. */ -typedef int (*stepper_timing_source_update)(const struct device *dev, +typedef int (*stepper_timing_source_update)(const struct timing_source_config *config, + struct timing_source_data *data, uint64_t microstep_interval_ns); /** * @brief Start the stepper timing source. * - * @param dev Pointer to the device structure. + * @param config Pointer to the timing source configuration structure. + * @param data Pointer to the timing source data structure. * @return 0 on success, or a negative error code on failure. */ -typedef int (*stepper_timing_source_start)(const struct device *dev); +typedef int (*stepper_timing_source_start)(const struct timing_source_config *config, + struct timing_source_data *data); /** * @brief Whether the stepper timing source requires rescheduling (keeps running @@ -46,18 +70,21 @@ typedef bool (*stepper_timing_sources_requires_reschedule)(const struct device * /** * @brief Stop the stepper timing source. * - * @param dev Pointer to the device structure. + * @param config Pointer to the timing source configuration structure. + * @param data Pointer to the timing source data structure. * @return 0 on success, or a negative error code on failure. */ -typedef int (*stepper_timing_source_stop)(const struct device *dev); +typedef int (*stepper_timing_source_stop)(const struct timing_source_config *config, + struct timing_source_data *data); /** * @brief Check if the stepper timing source is running. * - * @param dev Pointer to the device structure. + * @param data Pointer to the timing source data structure. * @return true if the timing source is running, false otherwise. */ -typedef bool (*stepper_timing_source_is_running)(const struct device *dev); +typedef bool (*stepper_timing_source_is_running)(const struct timing_source_config *config, + struct timing_source_data *data); /** * @brief Stepper timing source API. @@ -72,8 +99,8 @@ struct stepper_timing_source_api { }; extern const struct stepper_timing_source_api step_work_timing_source_api; -#ifdef CONFIG_STEP_DIR_STEPPER_COUNTER_TIMING +#ifdef CONFIG_STEPPER_TIMING_SOURCES_COUNTER_TIMING extern const struct stepper_timing_source_api step_counter_timing_source_api; -#endif /* CONFIG_STEP_DIR_STEPPER_COUNTER_TIMING */ +#endif /* CONFIG_STEPPER_TIMING_SOURCES_COUNTER_TIMING */ #endif /* ZEPHYR_DRIVER_STEPPER_STEP_DIR_STEPPER_TIMING_SOURCE_H_ */ diff --git a/drivers/stepper/step_dir/step_dir_stepper_work_timing.c b/drivers/stepper/stepper_timing_sources/stepper_work_timing.c similarity index 53% rename from drivers/stepper/step_dir/step_dir_stepper_work_timing.c rename to drivers/stepper/stepper_timing_sources/stepper_work_timing.c index ac605f1f49727..917c8aeeacdf7 100644 --- a/drivers/stepper/step_dir/step_dir_stepper_work_timing.c +++ b/drivers/stepper/stepper_timing_sources/stepper_work_timing.c @@ -3,13 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include "step_dir_stepper_timing_source.h" -#include "step_dir_stepper_common.h" +#include -static k_timeout_t stepper_movement_delay(const struct device *dev) -{ - const struct step_dir_stepper_common_data *data = dev->data; +#include "stepper_timing_source.h" +static k_timeout_t stepper_movement_delay(const struct timing_source_data *data) +{ if (data->microstep_interval_ns == 0) { return K_FOREVER; } @@ -20,39 +19,40 @@ static k_timeout_t stepper_movement_delay(const struct device *dev) static void stepper_work_step_handler(struct k_work *work) { struct k_work_delayable *dwork = k_work_delayable_from_work(work); - struct step_dir_stepper_common_data *data = - CONTAINER_OF(dwork, struct step_dir_stepper_common_data, stepper_dwork); + struct timing_source_data *data = + CONTAINER_OF(dwork, struct timing_source_data, stepper_dwork); - stepper_handle_timing_signal(data->dev); + data->stepper_handle_timing_signal_cb(data->motion_control_dev); } -int step_work_timing_source_init(const struct device *dev) +int step_work_timing_source_init(const struct timing_source_config *config, + struct timing_source_data *data) { - struct step_dir_stepper_common_data *data = dev->data; - k_work_init_delayable(&data->stepper_dwork, stepper_work_step_handler); return 0; } -int step_work_timing_source_update(const struct device *dev, const uint64_t microstep_interval_ns) +int step_work_timing_source_update(const struct timing_source_config *config, + struct timing_source_data *data, + const uint64_t microstep_interval_ns) { - ARG_UNUSED(dev); + ARG_UNUSED(config); + ARG_UNUSED(data); ARG_UNUSED(microstep_interval_ns); return 0; } -int step_work_timing_source_start(const struct device *dev) +int step_work_timing_source_start(const struct timing_source_config *config, + struct timing_source_data *data) { - struct step_dir_stepper_common_data *data = dev->data; - - return k_work_reschedule(&data->stepper_dwork, stepper_movement_delay(dev)); + ARG_UNUSED(config); + return k_work_reschedule(&data->stepper_dwork, stepper_movement_delay(data)); } -int step_work_timing_source_stop(const struct device *dev) +int step_work_timing_source_stop(const struct timing_source_config *config, + struct timing_source_data *data) { - struct step_dir_stepper_common_data *data = dev->data; - return k_work_cancel_delayable(&data->stepper_dwork); } @@ -62,10 +62,9 @@ bool step_work_timing_source_needs_reschedule(const struct device *dev) return true; } -bool step_work_timing_source_is_running(const struct device *dev) +bool step_work_timing_source_is_running(const struct timing_source_config *config, + struct timing_source_data *data) { - struct step_dir_stepper_common_data *data = dev->data; - return k_work_delayable_is_pending(&data->stepper_dwork); } diff --git a/drivers/stepper/ti/Kconfig.drv84xx b/drivers/stepper/ti/Kconfig.drv84xx index e5466890c66da..10cdd06433ee4 100644 --- a/drivers/stepper/ti/Kconfig.drv84xx +++ b/drivers/stepper/ti/Kconfig.drv84xx @@ -7,6 +7,5 @@ config DRV84XX depends on DT_HAS_TI_DRV84XX_ENABLED select STEPPER_TI select STEP_DIR_STEPPER - select STEPPER_STEP_DIR_GENERATE_ISR_SAFE_EVENTS help Enable driver for TI DRV84XX stepper motor driver. diff --git a/drivers/stepper/ti/drv84xx.c b/drivers/stepper/ti/drv84xx.c index 5e99964abea67..7f24affefa987 100644 --- a/drivers/stepper/ti/drv84xx.c +++ b/drivers/stepper/ti/drv84xx.c @@ -5,7 +5,6 @@ #define DT_DRV_COMPAT ti_drv84xx -#include #include #include #include @@ -47,14 +46,15 @@ struct drv84xx_pin_states { * @brief DRV84XX stepper driver data. */ struct drv84xx_data { - const struct step_dir_stepper_common_data common; const struct device *dev; struct drv84xx_pin_states pin_states; enum stepper_micro_step_resolution ustep_res; struct gpio_callback fault_cb_data; + stepper_drv_fault_cb_t fault_cb; + void *fault_cb_user_data; }; -STEP_DIR_STEPPER_STRUCT_CHECK(struct drv84xx_config, struct drv84xx_data); +STEP_DIR_STEPPER_STRUCT_CHECK(struct drv84xx_config); static int drv84xx_set_microstep_pin(const struct device *dev, const struct gpio_dt_spec *pin, int value) @@ -255,6 +255,17 @@ static int drv84xx_disable(const struct device *dev) return ret; } +static int drv84xx_set_fault_cb(const struct device *dev, stepper_drv_fault_cb_t fault_cb, + void *user_data) +{ + struct drv84xx_data *data = dev->data; + + data->fault_cb = fault_cb; + data->fault_cb_user_data = user_data; + + return 0; +} + static int drv84xx_set_micro_step_res(const struct device *dev, enum stepper_micro_step_resolution micro_step_res) { @@ -348,7 +359,11 @@ void fault_event(const struct device *dev, struct gpio_callback *cb, uint32_t pi { struct drv84xx_data *data = CONTAINER_OF(cb, struct drv84xx_data, fault_cb_data); - stepper_trigger_callback(data->dev, STEPPER_EVENT_FAULT_DETECTED); + if (data->fault_cb != NULL) { + data->fault_cb(data->dev, data->fault_cb_user_data); + } else { + LOG_WRN_ONCE("%s: Fault pin triggered but no callback is set", dev->name); + } } static int drv84xx_init(const struct device *dev) @@ -432,20 +447,14 @@ static int drv84xx_init(const struct device *dev) return 0; } -static DEVICE_API(stepper, drv84xx_stepper_api) = { +static DEVICE_API(stepper_drv, drv84xx_stepper_api) = { .enable = drv84xx_enable, .disable = drv84xx_disable, - .move_by = step_dir_stepper_common_move_by, - .move_to = step_dir_stepper_common_move_to, - .is_moving = step_dir_stepper_common_is_moving, - .set_reference_position = step_dir_stepper_common_set_reference_position, - .get_actual_position = step_dir_stepper_common_get_actual_position, - .set_microstep_interval = step_dir_stepper_common_set_microstep_interval, - .run = step_dir_stepper_common_run, - .stop = step_dir_stepper_common_stop, + .set_fault_cb = drv84xx_set_fault_cb, .set_micro_step_res = drv84xx_set_micro_step_res, .get_micro_step_res = drv84xx_get_micro_step_res, - .set_event_callback = step_dir_stepper_common_set_event_callback, + .step = step_dir_stepper_common_step, + .set_direction = step_dir_stepper_common_set_direction, }; #define DRV84XX_DEVICE(inst) \ @@ -460,7 +469,6 @@ static DEVICE_API(stepper, drv84xx_stepper_api) = { }; \ \ static struct drv84xx_data drv84xx_data_##inst = { \ - .common = STEP_DIR_STEPPER_DT_INST_COMMON_DATA_INIT(inst), \ .ustep_res = DT_INST_PROP(inst, micro_step_res), \ .dev = DEVICE_DT_INST_GET(inst), \ }; \ diff --git a/drivers/stepper/zephyr_stepper_motion_controller.c b/drivers/stepper/zephyr_stepper_motion_controller.c new file mode 100644 index 0000000000000..a6b65124c4efe --- /dev/null +++ b/drivers/stepper/zephyr_stepper_motion_controller.c @@ -0,0 +1,406 @@ +/** + * SPDX-FileCopyrightText: Copyright (c) 2025 Jilay Sandeep Pandya + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT zephyr_stepper_motion_control + +#include +#include +#include + +#include "stepper_timing_sources/stepper_timing_source.h" + +#include +LOG_MODULE_REGISTER(stepper_motion_control, CONFIG_STEPPER_LOG_LEVEL); + +struct stepper_motion_control_config { + const struct device *stepper; + const struct timing_source_config timing_config; + const struct stepper_timing_source_api *timing_source; +}; + +struct stepper_motion_control_data { + const struct device *dev; + struct k_spinlock lock; + int32_t step_count; + enum stepper_direction direction; + enum stepper_run_mode run_mode; + int32_t actual_position; + stepper_event_callback_t callback; + void *event_cb_user_data; + struct timing_source_data timing_data; +#ifdef CONFIG_ZEPHYR_STEPPER_MOTION_CONTROL_GENERATE_ISR_SAFE_EVENTS + struct k_work event_callback_work; + struct k_msgq event_msgq; + uint8_t event_msgq_buffer[CONFIG_ZEPHYR_STEPPER_MOTION_CONTROL_EVENT_QUEUE_LEN * + sizeof(enum stepper_event)]; +#endif /* CONFIG_ZEPHYR_STEPPER_MOTION_CONTROL_GENERATE_ISR_SAFE_EVENTS */ +}; + +void stepper_trigger_callback(const struct device *dev, enum stepper_event event) +{ + struct stepper_motion_control_data *data = dev->data; + + if (!data->callback) { + LOG_WRN_ONCE("No callback set"); + return; + } + + if (!k_is_in_isr()) { + data->callback(dev, event, data->event_cb_user_data); + return; + } + +#ifdef CONFIG_ZEPHYR_STEPPER_MOTION_CONTROL_GENERATE_ISR_SAFE_EVENTS + /* Dispatch to msgq instead of raising directly */ + int ret = k_msgq_put(&data->event_msgq, &event, K_NO_WAIT); + + if (ret != 0) { + LOG_WRN("Failed to put event in msgq: %d", ret); + } + + ret = k_work_submit(&data->event_callback_work); + if (ret < 0) { + LOG_ERR("Failed to submit work item: %d", ret); + } +#else + LOG_WRN_ONCE("Event callback called from ISR context without ISR safe events enabled"); +#endif /* CONFIG_ZEPHYR_STEPPER_MOTION_CONTROL_GENERATE_ISR_SAFE_EVENTS */ +} + +#ifdef CONFIG_ZEPHYR_STEPPER_MOTION_CONTROL_GENERATE_ISR_SAFE_EVENTS +static void stepper_work_event_handler(struct k_work *work) +{ + struct stepper_motion_control_data *data = + CONTAINER_OF(work, struct stepper_motion_control_data, event_callback_work); + enum stepper_event event; + int ret; + + ret = k_msgq_get(&data->event_msgq, &event, K_NO_WAIT); + if (ret != 0) { + return; + } + + /* Run the callback */ + if (data->callback != NULL) { + data->callback(data->dev, event, data->event_cb_user_data); + } + + /* If there are more pending events, resubmit this work item to handle them */ + if (k_msgq_num_used_get(&data->event_msgq) > 0) { + k_work_submit(work); + } +} +#endif /* CONFIG_ZEPHYR_STEPPER_MOTION_CONTROL_GENERATE_ISR_SAFE_EVENTS */ + +static void update_direction_from_step_count(const struct device *dev) +{ + struct stepper_motion_control_data *data = dev->data; + + if (data->step_count > 0) { + data->direction = STEPPER_DIRECTION_POSITIVE; + } else if (data->step_count < 0) { + data->direction = STEPPER_DIRECTION_NEGATIVE; + } else { + LOG_ERR("Step count is zero"); + } +} + +static int z_stepper_motion_control_move_by(const struct device *dev, int32_t micro_steps) +{ + const struct stepper_motion_control_config *config = dev->config; + struct stepper_motion_control_data *data = dev->data; + + if (data->timing_data.microstep_interval_ns == 0) { + LOG_ERR("Step interval not set or invalid step interval set"); + return -EINVAL; + } + + if (micro_steps == 0) { + stepper_trigger_callback(data->dev, STEPPER_EVENT_STEPS_COMPLETED); + config->timing_source->stop(&config->timing_config, &data->timing_data); + return 0; + } + + K_SPINLOCK(&data->lock) { + data->run_mode = STEPPER_RUN_MODE_POSITION; + data->step_count = micro_steps; + config->timing_source->update(&config->timing_config, &data->timing_data, + data->timing_data.microstep_interval_ns); + update_direction_from_step_count(dev); + stepper_drv_set_direction(config->stepper, data->direction); + config->timing_source->start(&config->timing_config, &data->timing_data); + } + return 0; +} + +static int z_stepper_motion_control_move_to(const struct device *dev, int32_t micro_steps) +{ + struct stepper_motion_control_data *data = dev->data; + int32_t steps_to_move; + + K_SPINLOCK(&data->lock) { + steps_to_move = micro_steps - data->actual_position; + } + return z_stepper_motion_control_move_by(dev, steps_to_move); +} + +static int z_stepper_motion_control_run(const struct device *dev, + const enum stepper_direction direction) +{ + const struct stepper_motion_control_config *config = dev->config; + struct stepper_motion_control_data *data = dev->data; + + K_SPINLOCK(&data->lock) { + data->run_mode = STEPPER_RUN_MODE_VELOCITY; + data->direction = direction; + config->timing_source->update(&config->timing_config, &data->timing_data, + data->timing_data.microstep_interval_ns); + stepper_drv_set_direction(config->stepper, direction); + config->timing_source->start(&config->timing_config, &data->timing_data); + } + return 0; +} + +static int z_stepper_motion_control_stop(const struct device *dev) +{ + const struct stepper_motion_control_config *config = dev->config; + struct stepper_motion_control_data *data = dev->data; + int ret; + + ret = config->timing_source->stop(&config->timing_config, &data->timing_data); + if (ret != 0) { + LOG_ERR("Failed to stop timing source: %d", ret); + return ret; + } + stepper_trigger_callback(dev, STEPPER_EVENT_STOPPED); + + return 0; +} + +static int z_stepper_motion_control_set_reference_position(const struct device *dev, + int32_t position) +{ + struct stepper_motion_control_data *data = dev->data; + + K_SPINLOCK(&data->lock) { + data->actual_position = position; + } + return 0; +} + +static int z_stepper_motion_control_get_actual_position(const struct device *dev, int32_t *position) +{ + struct stepper_motion_control_data *data = dev->data; + + K_SPINLOCK(&data->lock) { + *position = data->actual_position; + } + return 0; +} + +static int z_stepper_motion_control_set_step_interval(const struct device *dev, + uint64_t microstep_interval_ns) +{ + const struct stepper_motion_control_config *config = dev->config; + struct stepper_motion_control_data *data = dev->data; + + if (microstep_interval_ns == 0) { + LOG_ERR("Step interval is invalid."); + return -EINVAL; + } + + K_SPINLOCK(&data->lock) { + data->timing_data.microstep_interval_ns = microstep_interval_ns; + config->timing_source->update(&config->timing_config, &data->timing_data, + microstep_interval_ns); + } + LOG_DBG("Setting Motor step interval to %llu", microstep_interval_ns); + return 0; +} + +static int z_stepper_motion_control_is_moving(const struct device *dev, bool *is_moving) +{ + const struct stepper_motion_control_config *config = dev->config; + struct stepper_motion_control_data *data = dev->data; + + *is_moving = config->timing_source->is_running(&config->timing_config, &data->timing_data); + LOG_DBG("Motor is %s moving", *is_moving ? "" : "not"); + return 0; +} + +static void update_remaining_steps(const struct device *dev) +{ + struct stepper_motion_control_data *data = dev->data; + + if (data->step_count > 0) { + data->step_count--; + } else if (data->step_count < 0) { + data->step_count++; + } +} + +static void update_actual_position(const struct device *dev) +{ + struct stepper_motion_control_data *data = dev->data; + + if (data->direction == STEPPER_DIRECTION_POSITIVE) { + data->actual_position++; + } else { + data->actual_position--; + } +} + +static void position_mode_task(const struct device *dev) +{ + const struct stepper_motion_control_config *config = dev->config; + struct stepper_motion_control_data *data = dev->data; + + stepper_drv_step(config->stepper); + update_remaining_steps(dev); + update_actual_position(dev); + + if (config->timing_source->needs_reschedule(dev) && data->step_count != 0) { + config->timing_source->start(&config->timing_config, &data->timing_data); + } else if (data->step_count == 0) { + config->timing_source->stop(&config->timing_config, &data->timing_data); + stepper_trigger_callback(data->dev, STEPPER_EVENT_STEPS_COMPLETED); + } +} + +static void velocity_mode_task(const struct device *dev) +{ + const struct stepper_motion_control_config *config = dev->config; + struct stepper_motion_control_data *data = dev->data; + + stepper_drv_step(config->stepper); + update_actual_position(dev); + + if (config->timing_source->needs_reschedule(dev)) { + (void)config->timing_source->start(&config->timing_config, &data->timing_data); + } +} + +static int z_stepper_motion_control_set_event_callback(const struct device *dev, + stepper_event_callback_t cb, void *user_data) +{ + struct stepper_motion_control_data *data = dev->data; + + K_SPINLOCK(&data->lock) { + data->callback = cb; + data->event_cb_user_data = user_data; + } + return 0; +} + +void stepper_handle_timing_signal(const struct device *dev) +{ + struct stepper_motion_control_data *data = dev->data; + + K_SPINLOCK(&data->lock) { + switch (data->run_mode) { + case STEPPER_RUN_MODE_POSITION: + position_mode_task(dev); + break; + case STEPPER_RUN_MODE_VELOCITY: + velocity_mode_task(dev); + break; + default: + LOG_WRN("Unsupported run mode: %d", data->run_mode); + break; + } + } +} + +static int z_stepper_motion_control_enable(const struct device *dev) +{ + const struct stepper_motion_control_config *config = dev->config; + + return stepper_drv_enable(config->stepper); +} + +static int z_stepper_motion_control_disable(const struct device *dev) +{ + const struct stepper_motion_control_config *config = dev->config; + + return stepper_drv_disable(config->stepper); +} + +static int z_stepper_motion_control_set_micro_step_res(const struct device *dev, + const enum stepper_micro_step_resolution res) +{ + const struct stepper_motion_control_config *config = dev->config; + + return stepper_drv_set_micro_stepper_res(config->stepper, res); +} + +static int z_stepper_motion_control_get_micro_step_res(const struct device *dev, + enum stepper_micro_step_resolution *res) +{ + const struct stepper_motion_control_config *config = dev->config; + + return stepper_drv_get_micro_step_res(config->stepper, res); +} + +static int stepper_motion_control_init(const struct device *dev) +{ + const struct stepper_motion_control_config *config = dev->config; + struct stepper_motion_control_data *data = dev->data; + int ret; + + data->dev = dev; + + __ASSERT_NO_MSG(config->timing_source->init != NULL); + + ret = config->timing_source->init(&config->timing_config, &data->timing_data); + if (ret < 0) { + LOG_ERR("Failed to initialize timing source: %d", ret); + return ret; + } + +#ifdef CONFIG_ZEPHYR_STEPPER_MOTION_CONTROL_GENERATE_ISR_SAFE_EVENTS + + k_msgq_init(&data->event_msgq, data->event_msgq_buffer, sizeof(enum stepper_event), + CONFIG_ZEPHYR_STEPPER_MOTION_CONTROL_EVENT_QUEUE_LEN); + k_work_init(&data->event_callback_work, stepper_work_event_handler); +#endif /* CONFIG_ZEPHYR_STEPPER_MOTION_CONTROL_GENERATE_ISR_SAFE_EVENTS */ + + LOG_DBG("Stepper Control initialized for stepper driver %s", config->stepper->name); + return 0; +} + +static DEVICE_API(stepper, zephyr_stepper_motion_control_api) = { + .enable = z_stepper_motion_control_enable, + .disable = z_stepper_motion_control_disable, + .set_micro_step_res = z_stepper_motion_control_set_micro_step_res, + .get_micro_step_res = z_stepper_motion_control_get_micro_step_res, + .move_to = z_stepper_motion_control_move_to, + .move_by = z_stepper_motion_control_move_by, + .run = z_stepper_motion_control_run, + .set_microstep_interval = z_stepper_motion_control_set_step_interval, + .get_actual_position = z_stepper_motion_control_get_actual_position, + .set_reference_position = z_stepper_motion_control_set_reference_position, + .is_moving = z_stepper_motion_control_is_moving, + .stop = z_stepper_motion_control_stop, + .set_event_callback = z_stepper_motion_control_set_event_callback, +}; + +#define STEPPER_MOTION_CONTROL_DEFINE(inst) \ + static const struct stepper_motion_control_config stepper_motion_control_config_##inst = { \ + .stepper = DEVICE_DT_GET(DT_PHANDLE(DT_DRV_INST(inst), stepper)), \ + .timing_config.counter = \ + DEVICE_DT_GET_OR_NULL(DT_PHANDLE(DT_DRV_INST(inst), counter)), \ + .timing_source = COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, counter), \ + (&step_counter_timing_source_api), (&step_work_timing_source_api)), \ + }; \ + struct stepper_motion_control_data stepper_motion_control_data_##inst = { \ + .timing_data.motion_control_dev = DEVICE_DT_GET(DT_DRV_INST(inst)), \ + .timing_data.stepper_handle_timing_signal_cb = stepper_handle_timing_signal, \ + }; \ + DEVICE_DT_INST_DEFINE(inst, stepper_motion_control_init, NULL, \ + &stepper_motion_control_data_##inst, \ + &stepper_motion_control_config_##inst, POST_KERNEL, \ + CONFIG_STEPPER_INIT_PRIORITY, &zephyr_stepper_motion_control_api); + +DT_INST_FOREACH_STATUS_OKAY(STEPPER_MOTION_CONTROL_DEFINE) diff --git a/dts/bindings/stepper/adi/adi,tmc2209.yaml b/dts/bindings/stepper/adi/adi,tmc2209.yaml index 17e44b273082f..f9e74c82cbe54 100644 --- a/dts/bindings/stepper/adi/adi,tmc2209.yaml +++ b/dts/bindings/stepper/adi/adi,tmc2209.yaml @@ -18,7 +18,7 @@ description: | compatible: "adi,tmc2209" include: - - name: stepper-controller.yaml + - name: stepper.yaml properties: msx-gpios: diff --git a/dts/bindings/stepper/adi/adi,tmc50xx.yaml b/dts/bindings/stepper/adi/adi,tmc50xx.yaml index eba9cd3f664c2..c629158635935 100644 --- a/dts/bindings/stepper/adi/adi,tmc50xx.yaml +++ b/dts/bindings/stepper/adi/adi,tmc50xx.yaml @@ -85,7 +85,7 @@ properties: child-binding: include: - - name: stepper-controller.yaml + - name: stepper.yaml - name: base.yaml property-allowlist: - reg diff --git a/dts/bindings/stepper/adi/adi,tmc51xx-base.yaml b/dts/bindings/stepper/adi/adi,tmc51xx-base.yaml index dbdd40065c4a8..62def383cbaf0 100644 --- a/dts/bindings/stepper/adi/adi,tmc51xx-base.yaml +++ b/dts/bindings/stepper/adi/adi,tmc51xx-base.yaml @@ -9,7 +9,7 @@ include: property-allowlist: - en-pwm-mode - test-mode - - name: stepper-controller.yaml + - name: stepper.yaml - name: adi,trinamic-ramp-generator.yaml property-allowlist: - vstart diff --git a/dts/bindings/stepper/allegro/allegro,a4979.yaml b/dts/bindings/stepper/allegro/allegro,a4979.yaml index c9e53b7a066c4..5ce264b50094b 100644 --- a/dts/bindings/stepper/allegro/allegro,a4979.yaml +++ b/dts/bindings/stepper/allegro/allegro,a4979.yaml @@ -24,7 +24,7 @@ description: | compatible: "allegro,a4979" include: - - name: stepper-controller.yaml + - name: stepper.yaml properties: m0-gpios: diff --git a/dts/bindings/stepper/stepper-controller.yaml b/dts/bindings/stepper/stepper.yaml similarity index 90% rename from dts/bindings/stepper/stepper-controller.yaml rename to dts/bindings/stepper/stepper.yaml index a73037bb6b975..eae57a18a25c9 100644 --- a/dts/bindings/stepper/stepper-controller.yaml +++ b/dts/bindings/stepper/stepper.yaml @@ -40,7 +40,3 @@ properties: description: | The GPIO pins used to send direction signals to the stepper motor. Pin will be driven high for forward direction and low for reverse direction. - - counter: - type: phandle - description: Counter used for generating step-accurate pulse signals. diff --git a/dts/bindings/stepper/ti/ti,drv84xx.yaml b/dts/bindings/stepper/ti/ti,drv84xx.yaml index 3b891d1d17c36..668c3d8861ef5 100644 --- a/dts/bindings/stepper/ti/ti,drv84xx.yaml +++ b/dts/bindings/stepper/ti/ti,drv84xx.yaml @@ -30,7 +30,7 @@ description: | compatible: "ti,drv84xx" include: - - name: stepper-controller.yaml + - name: stepper.yaml properties: fault-gpios: diff --git a/dts/bindings/stepper/zephyr,fake-stepper.yaml b/dts/bindings/stepper/zephyr,fake-stepper.yaml index d286dfbfeb4c5..3c6d01ff08593 100644 --- a/dts/bindings/stepper/zephyr,fake-stepper.yaml +++ b/dts/bindings/stepper/zephyr,fake-stepper.yaml @@ -7,4 +7,4 @@ description: | compatible: "zephyr,fake-stepper" -include: stepper-controller.yaml +include: stepper.yaml diff --git a/dts/bindings/stepper/zephyr,h-bridge-stepper.yaml b/dts/bindings/stepper/zephyr,h-bridge-stepper.yaml index b243a8d929385..6b40f83b8c1e0 100644 --- a/dts/bindings/stepper/zephyr,h-bridge-stepper.yaml +++ b/dts/bindings/stepper/zephyr,h-bridge-stepper.yaml @@ -3,7 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 description: | - GPIO Stepper Controller for darlington transistor arrays or dual H-bridge + H-Bridge Stepper Driver Example: /* Lead A is connected Lead C and Lead B is connected to Lead D*/ @@ -18,7 +18,7 @@ description: | compatible: "zephyr,h-bridge-stepper" -include: stepper-controller.yaml +include: stepper.yaml properties: gpios: diff --git a/dts/bindings/stepper/zephyr,stepper-motion-control.yaml b/dts/bindings/stepper/zephyr,stepper-motion-control.yaml new file mode 100644 index 0000000000000..85146f4e2eb00 --- /dev/null +++ b/dts/bindings/stepper/zephyr,stepper-motion-control.yaml @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: Copyright (c) 2025 Jilay Sandeep Pandya +# SPDX-License-Identifier: Apache-2.0 + +description: | + CPU based Stepper Motion Controller for stepper motor drivers + This binding is used to configure the stepper controller in Zephyr. + It is used to control the stepper motor driver. + Example: + stepper_motion_control: stepper_motion_control { + compatible = "zephyr,stepper-motion-control"; + stepper = <&gpio_stepper>; + }; + +compatible: "zephyr,stepper-motion-control" + +properties: + counter: + type: phandle + description: Counter used for generating step-accurate pulse signals. + + stepper: + type: phandle + required: true + description: | + The stepper motor driver to be controlled by the stepper motion controller. + This should be a reference to a stepper motor driver node in the device tree. diff --git a/include/zephyr/drivers/stepper.h b/include/zephyr/drivers/stepper.h index 3440cc2e084ab..34b3dfc5244e6 100644 --- a/include/zephyr/drivers/stepper.h +++ b/include/zephyr/drivers/stepper.h @@ -102,8 +102,6 @@ enum stepper_event { STEPPER_EVENT_RIGHT_END_STOP_DETECTED = 3, /** Stepper has stopped */ STEPPER_EVENT_STOPPED = 4, - /** Fault with the stepper controller detected */ - STEPPER_EVENT_FAULT_DETECTED = 5, }; /** @@ -142,6 +140,7 @@ typedef int (*stepper_set_micro_step_res_t)(const struct device *dev, */ typedef int (*stepper_get_micro_step_res_t)(const struct device *dev, enum stepper_micro_step_resolution *resolution); + /** * @brief Set the reference position of the stepper * @@ -169,6 +168,7 @@ typedef void (*stepper_event_callback_t)(const struct device *dev, const enum st */ typedef int (*stepper_set_event_callback_t)(const struct device *dev, stepper_event_callback_t callback, void *user_data); + /** * @brief Set the time interval between steps in nanoseconds. * @@ -545,6 +545,263 @@ static inline int z_impl_stepper_is_moving(const struct device *dev, bool *is_mo return api->is_moving(dev, is_moving); } +/** + * @} + */ + +/** + * @brief Stepper-Drv Driver Interface + * @defgroup stepper_drv_interface Stepper Drv Driver Interface + * @since 4.3 + * @version 0.1.0 + * @ingroup io_interfaces + * @{ + */ + +/** + * @cond INTERNAL_HIDDEN + * + * Stepper Drv driver API definition and system call entry points. + * + */ + +/** + * @brief Enable the stepper driver + * + * @see stepper_drv_enable() for details. + */ +typedef int (*stepper_drv_enable_t)(const struct device *dev); + +/** + * @brief Disable the stepper driver + * + * @see stepper_drv_disable() for details. + */ +typedef int (*stepper_drv_disable_t)(const struct device *dev); + +/** + * @brief Set the stepper direction + * + * @see stepper_drv_set_direction() for details. + */ +typedef int (*stepper_drv_direction_t)(const struct device *dev, + const enum stepper_direction direction); + +/** + * @brief Do a step + * + * @see stepper_drv_step() for details. + */ +typedef int (*stepper_drv_step_t)(const struct device *dev); + +/** + * @brief Set the stepper micro-step resolution + * + * @see stepper_drv_set_micro_step_res() for details. + */ +typedef int (*stepper_drv_set_micro_step_res_t)( + const struct device *dev, const enum stepper_micro_step_resolution resolution); + +/** + * @brief Get the stepper micro-step resolution + * + * @see stepper_drv_get_micro_step_res() for details. + */ +typedef int (*stepper_drv_get_micro_step_res_t)(const struct device *dev, + enum stepper_micro_step_resolution *resolution); + +/** + * @brief Callback function for stepper fault events + */ +typedef int (*stepper_drv_fault_cb_t)(const struct device *dev, void *user_data); + +/** + * @brief Set the callback function to be called when a stepper driver fault occurs + * + * @see stepper_drv_set_fault_callback() for details. + */ +typedef int (*stepper_drv_set_fault_callback_t)(const struct device *dev, + stepper_drv_fault_cb_t callback, void *user_data); + +/** + * @brief Stepper DRV Driver API + */ +__subsystem struct stepper_drv_driver_api { + stepper_drv_enable_t enable; + stepper_drv_disable_t disable; + stepper_drv_direction_t set_direction; + stepper_drv_step_t step; + stepper_drv_set_micro_step_res_t set_micro_step_res; + stepper_drv_get_micro_step_res_t get_micro_step_res; + stepper_drv_set_fault_callback_t set_fault_cb; +}; + +/** + * @endcond + */ + +/** + * @brief Enable stepper driver + * + * @details Enabling the driver shall switch on the power stage and energize the coils. + * + * @param dev pointer to the stepper_drv driver instance + * + * @retval -EIO Error during Enabling + * @retval 0 Success + */ +__syscall int stepper_drv_enable(const struct device *dev); + +static inline int z_impl_stepper_drv_enable(const struct device *dev) +{ + const struct stepper_drv_driver_api *api = (const struct stepper_drv_driver_api *)dev->api; + + return api->enable(dev); +} + +/** + * @brief Disable stepper driver + * + * @details Disabling the driver shall switch off the power stage and de-energize the coils. + * + * @param dev pointer to the stepper_drv driver instance + * + * @retval -ENOTSUP Disabling of driver is not supported. + * @retval -EIO Error during Disabling + * @retval 0 Success + */ +__syscall int stepper_drv_disable(const struct device *dev); + +static inline int z_impl_stepper_drv_disable(const struct device *dev) +{ + const struct stepper_drv_driver_api *api = (const struct stepper_drv_driver_api *)dev->api; + + return api->disable(dev); +} + +/** + * @brief Do a step. + * @details This function will cause the stepper to do one micro-step. + * This function is typically used in stepper_drv stepper drivers where + * an external stepper motion controller is used to control the stepper. + * This function will not increment/decrement any position related data. Doing so + * is responsibility of the caller (e.g. stepper motion controller). + * + * @param dev pointer to the stepper_drv driver instance + * + * @retval -EIO General input / output error + * @retval 0 Success + */ +__syscall int stepper_drv_step(const struct device *dev); + +static inline int z_impl_stepper_drv_step(const struct device *dev) +{ + const struct stepper_drv_driver_api *api = (const struct stepper_drv_driver_api *)dev->api; + + return api->step(dev); +} + +/** + * @brief Set the stepper direction + * @details This function sets the direction of the stepper motor. + * + * @param dev pointer to the stepper_drv driver instance + * @param direction The direction to set + * + * @retval -EINVAL If the requested direction is invalid + * @retval -EIO General input / output error + * @retval 0 Success + */ +__syscall int stepper_drv_set_direction(const struct device *dev, + const enum stepper_direction direction); + +static inline int z_impl_stepper_drv_set_direction(const struct device *dev, + const enum stepper_direction direction) +{ + const struct stepper_drv_driver_api *api = (const struct stepper_drv_driver_api *)dev->api; + + if ((direction != STEPPER_DIRECTION_POSITIVE) && + (direction != STEPPER_DIRECTION_NEGATIVE)) { + return -EINVAL; + } + + return api->set_direction(dev, direction); +} +/** + * @brief Set the micro-step resolution in stepper driver + * + * @param dev pointer to the step dir driver instance + * @param resolution micro-step resolution + * + * @retval -EIO General input / output error + * @retval -ENOSYS If not implemented by device driver + * @retval -EINVAL If the requested resolution is invalid + * @retval -ENOTSUP If the requested resolution is not supported + * @retval 0 Success + */ +__syscall int stepper_drv_set_micro_stepper_res(const struct device *dev, + enum stepper_micro_step_resolution resolution); + +static inline int +z_impl_stepper_drv_set_micro_stepper_res(const struct device *dev, + enum stepper_micro_step_resolution resolution) +{ + const struct stepper_drv_driver_api *api = (const struct stepper_drv_driver_api *)dev->api; + + if (!VALID_MICRO_STEP_RES(resolution)) { + return -EINVAL; + } + return api->set_micro_step_res(dev, resolution); +} + +/** + * @brief Get the micro-step resolution in stepper driver + * + * @param dev pointer to the stepper_drv driver instance + * @param resolution micro-step resolution + * + * @retval -EIO General input / output error + * @retval -ENOSYS If not implemented by device driver + * @retval 0 Success + */ +__syscall int stepper_drv_get_micro_step_res(const struct device *dev, + enum stepper_micro_step_resolution *resolution); + +static inline int +z_impl_stepper_drv_get_micro_step_res(const struct device *dev, + enum stepper_micro_step_resolution *resolution) +{ + const struct stepper_drv_driver_api *api = (const struct stepper_drv_driver_api *)dev->api; + + return api->get_micro_step_res(dev, resolution); +} + +/** + * @brief Set the callback function to be called when a stepper fault occurs + * + * @param dev pointer to the stepper_drv driver instance + * @param callback Callback function to be called when a stepper fault occurs + * passing NULL will disable the callback + * @param user_data User data to be passed to the callback function + * + * @retval -ENOSYS If not implemented by device driver + * @retval 0 Success + */ +__syscall int stepper_drv_set_fault_cb(const struct device *dev, stepper_drv_fault_cb_t callback, + void *user_data); + +static inline int z_impl_stepper_drv_set_fault_cb(const struct device *dev, + stepper_drv_fault_cb_t callback, void *user_data) +{ + const struct stepper_drv_driver_api *api = (const struct stepper_drv_driver_api *)dev->api; + + if (api->set_fault_cb == NULL) { + return -ENOSYS; + } + + return api->set_fault_cb(dev, callback, user_data); +} + /** * @} */ diff --git a/samples/drivers/stepper/generic/boards/nucleo_g071rb.overlay b/samples/drivers/stepper/generic/boards/nucleo_g071rb.overlay index 9533bbb982e26..880cf1eae00ad 100644 --- a/samples/drivers/stepper/generic/boards/nucleo_g071rb.overlay +++ b/samples/drivers/stepper/generic/boards/nucleo_g071rb.overlay @@ -1,6 +1,14 @@ / { aliases { stepper = &h_bridge_stepper; + stepper-motion-control = &stepper_motion_control; + }; +}; + +&timers7 { + st,prescaler = <79>; + counter: counter { + status = "okay"; }; }; @@ -15,4 +23,11 @@ <&gpiob 0 GPIO_ACTIVE_HIGH>, /* D10 */ <&gpioa 7 GPIO_ACTIVE_HIGH>; /* D11 */ }; + + stepper_motion_control: stepper_motion_control { + compatible = "zephyr,stepper-motion-control"; + status = "okay"; + counter = <&counter>; + stepper = <&h_bridge_stepper>; + }; }; diff --git a/samples/drivers/stepper/generic/prj.conf b/samples/drivers/stepper/generic/prj.conf index 7b579deb99fba..b7d159296509e 100644 --- a/samples/drivers/stepper/generic/prj.conf +++ b/samples/drivers/stepper/generic/prj.conf @@ -1,3 +1,4 @@ +CONFIG_ZEPHYR_STEPPER_MOTION_CONTROL_GENERATE_ISR_SAFE_EVENTS=y CONFIG_STEPPER=y CONFIG_LOG=y CONFIG_INPUT=y diff --git a/samples/drivers/stepper/generic/src/main.c b/samples/drivers/stepper/generic/src/main.c index f1323e50850a2..c81798fa8224d 100644 --- a/samples/drivers/stepper/generic/src/main.c +++ b/samples/drivers/stepper/generic/src/main.c @@ -12,7 +12,8 @@ #include LOG_MODULE_REGISTER(stepper, CONFIG_STEPPER_LOG_LEVEL); -static const struct device *stepper = DEVICE_DT_GET(DT_ALIAS(stepper)); +static const struct device *stepper_motion_controller = + DEVICE_DT_GET(DT_ALIAS(stepper_motion_control)); enum stepper_mode { STEPPER_MODE_ENABLE, @@ -65,47 +66,53 @@ INPUT_CALLBACK_DEFINE(NULL, button_pressed, NULL); int main(void) { LOG_INF("Starting generic stepper sample\n"); - if (!device_is_ready(stepper)) { - LOG_ERR("Device %s is not ready\n", stepper->name); + if (!device_is_ready(stepper_motion_controller)) { + LOG_ERR("Device %s is not ready\n", stepper_motion_controller->name); return -ENODEV; } - LOG_DBG("stepper is %p, name is %s\n", stepper, stepper->name); + LOG_DBG("stepper is %p, name is %s\n", stepper_motion_controller, + stepper_motion_controller->name); - stepper_set_event_callback(stepper, stepper_callback, NULL); - stepper_set_reference_position(stepper, 0); - stepper_set_microstep_interval(stepper, CONFIG_STEP_INTERVAL_NS); + if (!device_is_ready(stepper_motion_controller)) { + LOG_ERR("Device %s is not ready\n", stepper_motion_controller->name); + return -ENODEV; + } + + stepper_set_event_callback(stepper_motion_controller, stepper_callback, NULL); + stepper_set_reference_position(stepper_motion_controller, 0); + stepper_set_microstep_interval(stepper_motion_controller, CONFIG_STEP_INTERVAL_NS); for (;;) { k_sem_take(&stepper_generic_sem, K_FOREVER); switch (atomic_get(&stepper_mode)) { case STEPPER_MODE_ENABLE: - stepper_enable(stepper); + stepper_enable(stepper_motion_controller); LOG_INF("mode: enable\n"); break; case STEPPER_MODE_STOP: - stepper_stop(stepper); + stepper_stop(stepper_motion_controller); LOG_INF("mode: stop\n"); break; case STEPPER_MODE_ROTATE_CW: - stepper_run(stepper, STEPPER_DIRECTION_POSITIVE); + stepper_run(stepper_motion_controller, STEPPER_DIRECTION_POSITIVE); LOG_INF("mode: rotate cw\n"); break; case STEPPER_MODE_ROTATE_CCW: - stepper_run(stepper, STEPPER_DIRECTION_NEGATIVE); + stepper_run(stepper_motion_controller, STEPPER_DIRECTION_NEGATIVE); LOG_INF("mode: rotate ccw\n"); break; case STEPPER_MODE_PING_PONG_RELATIVE: ping_pong_target_position *= -1; - stepper_move_by(stepper, ping_pong_target_position); + stepper_move_by(stepper_motion_controller, ping_pong_target_position); LOG_INF("mode: ping pong relative\n"); break; case STEPPER_MODE_PING_PONG_ABSOLUTE: ping_pong_target_position *= -1; - stepper_move_to(stepper, ping_pong_target_position); + stepper_move_to(stepper_motion_controller, ping_pong_target_position); LOG_INF("mode: ping pong absolute\n"); break; case STEPPER_MODE_DISABLE: - stepper_disable(stepper); + stepper_disable(stepper_motion_controller); LOG_INF("mode: disable\n"); break; } @@ -118,7 +125,7 @@ static void monitor_thread(void) for (;;) { int32_t actual_position; - stepper_get_actual_position(stepper, &actual_position); + stepper_get_actual_position(stepper_motion_controller, &actual_position); LOG_DBG("Actual position: %d\n", actual_position); k_sleep(K_MSEC(CONFIG_MONITOR_THREAD_TIMEOUT_MS)); } diff --git a/tests/drivers/build_all/stepper/app.overlay b/tests/drivers/build_all/stepper/app.overlay index 177d15926bcde..33dfe04d09bc5 100644 --- a/tests/drivers/build_all/stepper/app.overlay +++ b/tests/drivers/build_all/stepper/app.overlay @@ -39,5 +39,18 @@ #include "uart.dtsi" }; + + zephyr_stepper_motion_control_counter: zephyr_stepper_motion_control_counter { + compatible = "zephyr,stepper-motion-control"; + status = "okay"; + counter = <&counter0>; + stepper = <&zephyr_h_bridge_stepper>; + }; + + zephyr_stepper_motion_control_workq: zephyr_stepper_motion_control_workq { + compatible = "zephyr,stepper-motion-control"; + status = "okay"; + stepper = <&zephyr_h_bridge_stepper>; + }; }; }; diff --git a/tests/drivers/build_all/stepper/gpio.dtsi b/tests/drivers/build_all/stepper/gpio.dtsi index 6fe38b0ca30cd..9434a20ded97a 100644 --- a/tests/drivers/build_all/stepper/gpio.dtsi +++ b/tests/drivers/build_all/stepper/gpio.dtsi @@ -16,7 +16,6 @@ adi_tmc2209: adi_tmc2209 { en-gpios = <&test_gpio 0 0>; step-gpios = <&test_gpio 0 0>; dir-gpios = <&test_gpio 0 0>; - counter = <&counter0>; }; allegro_a4979: allegro_a4979 { @@ -29,7 +28,6 @@ allegro_a4979: allegro_a4979 { en-gpios = <&test_gpio 0 0>; m0-gpios = <&test_gpio 0 0>; m1-gpios = <&test_gpio 0 0>; - counter = <&counter0>; }; ti_drv84xx: ti_drv84xx { @@ -42,7 +40,6 @@ ti_drv84xx: ti_drv84xx { en-gpios = <&test_gpio 0 0>; m0-gpios = <&test_gpio 0 0>; m1-gpios = <&test_gpio 0 0>; - counter = <&counter0>; }; zephyr_h_bridge_stepper: zephyr_h_bridge_stepper { diff --git a/tests/drivers/stepper/drv84xx/api/boards/native_sim.overlay b/tests/drivers/stepper/drv84xx/api/boards/native_sim.overlay index ed869356721fa..41aea39a2358b 100644 --- a/tests/drivers/stepper/drv84xx/api/boards/native_sim.overlay +++ b/tests/drivers/stepper/drv84xx/api/boards/native_sim.overlay @@ -8,6 +8,7 @@ / { aliases { stepper = &drv8424; + stepper-motion-control = &stepper_motion_control; }; }; @@ -22,13 +23,19 @@ en-gpios = <&gpio2 1 0>; /* 5 */ m0-gpios = <&gpio3 0 0>; m1-gpios = <&gpio3 1 0>; - counter = <&counter0>; #address-cells = <1>; #size-cells = <0>; #stepper-motor-cells = <0>; }; + stepper_motion_control: stepper_motion_control { + compatible = "zephyr,stepper-motion-control"; + status = "okay"; + counter = <&counter0>; + stepper = <&drv8424>; + }; + gpio1: gpio1 { compatible = "zephyr,gpio-emul"; #gpio-cells = <0x2>; diff --git a/tests/drivers/stepper/drv84xx/api/prj.conf b/tests/drivers/stepper/drv84xx/api/prj.conf index e05e8cb5a8809..b44dd95fdfd8a 100644 --- a/tests/drivers/stepper/drv84xx/api/prj.conf +++ b/tests/drivers/stepper/drv84xx/api/prj.conf @@ -1,6 +1,7 @@ # Copyright (c) 2024 Navimatix GmbH # SPDX-License-Identifier: Apache-2.0 +CONFIG_ZEPHYR_STEPPER_MOTION_CONTROL_GENERATE_ISR_SAFE_EVENTS=y CONFIG_ZTEST=y CONFIG_TEST=y CONFIG_TEST_USERSPACE=y diff --git a/tests/drivers/stepper/drv84xx/api/src/main.c b/tests/drivers/stepper/drv84xx/api/src/main.c index 04fe097730dbb..d8f0aa69028e9 100644 --- a/tests/drivers/stepper/drv84xx/api/src/main.c +++ b/tests/drivers/stepper/drv84xx/api/src/main.c @@ -44,7 +44,7 @@ static void drv84xx_api_print_event_callback(const struct device *dev, enum step static void *drv84xx_api_setup(void) { static struct drv84xx_api_fixture fixture = { - .dev = DEVICE_DT_GET(DT_ALIAS(stepper)), + .dev = DEVICE_DT_GET(DT_ALIAS(stepper_motion_control)), .callback = drv84xx_api_print_event_callback, }; diff --git a/tests/drivers/stepper/drv84xx/emul/boards/native_sim.overlay b/tests/drivers/stepper/drv84xx/emul/boards/native_sim.overlay index 0ad7728928969..2a91ca474a3d4 100644 --- a/tests/drivers/stepper/drv84xx/emul/boards/native_sim.overlay +++ b/tests/drivers/stepper/drv84xx/emul/boards/native_sim.overlay @@ -17,7 +17,6 @@ en-gpios = <&gpio2 1 0>; /* 5 */ m0-gpios = <&gpio3 0 0>; m1-gpios = <&gpio3 1 0>; - counter = <&counter0>; #address-cells = <1>; #size-cells = <0>; diff --git a/tests/drivers/stepper/drv84xx/emul/src/main.c b/tests/drivers/stepper/drv84xx/emul/src/main.c index c730bc457ce96..3fbb23b36746e 100644 --- a/tests/drivers/stepper/drv84xx/emul/src/main.c +++ b/tests/drivers/stepper/drv84xx/emul/src/main.c @@ -33,20 +33,19 @@ static void *drv84xx_emul_setup(void) static void drv84xx_emul_before(void *f) { struct drv84xx_emul_fixture *fixture = f; - (void)stepper_set_reference_position(fixture->dev, 0); - (void)stepper_set_micro_step_res(fixture->dev, 1); + (void)stepper_drv_set_micro_stepper_res(fixture->dev, 1); } static void drv84xx_emul_after(void *f) { struct drv84xx_emul_fixture *fixture = f; - (void)stepper_disable(fixture->dev); + (void)stepper_drv_disable(fixture->dev); } ZTEST_F(drv84xx_emul, test_enable_on_gpio_pins) { int value = 0; - (void)stepper_enable(fixture->dev); + (void)stepper_drv_enable(fixture->dev); /* As sleep and enable pins are optional, check if they exist*/ if (en_pin.port != NULL) { value = gpio_emul_output_get(en_pin.port, en_pin.pin); @@ -64,8 +63,8 @@ ZTEST_F(drv84xx_emul, test_enable_off_gpio_pins) /* Enable first to ensure that disable works correctly and the check is not against values * from initialisation or from previous tests */ - (void)stepper_enable(fixture->dev); - (void)stepper_disable(fixture->dev); + (void)stepper_drv_enable(fixture->dev); + (void)stepper_drv_disable(fixture->dev); /* As sleep and enable pins are optional, check if they exist*/ if (en_pin.port != NULL) { value = gpio_emul_output_get(en_pin.port, en_pin.pin); diff --git a/tests/drivers/stepper/stepper_api/CMakeLists.txt b/tests/drivers/stepper/stepper_api/CMakeLists.txt index a1ebdab059bd3..e1a62a23c19b4 100644 --- a/tests/drivers/stepper/stepper_api/CMakeLists.txt +++ b/tests/drivers/stepper/stepper_api/CMakeLists.txt @@ -3,6 +3,6 @@ cmake_minimum_required(VERSION 3.20.0) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) -project(stepper_api) +project(zephyr_stepper_motion_control) target_sources(app PRIVATE src/main.c) diff --git a/tests/drivers/stepper/stepper_api/boards/native_sim_adi_tmc2209.overlay b/tests/drivers/stepper/stepper_api/boards/native_sim_adi_tmc2209.overlay index caf4f3c5b3a78..b1aad6a6d4079 100644 --- a/tests/drivers/stepper/stepper_api/boards/native_sim_adi_tmc2209.overlay +++ b/tests/drivers/stepper/stepper_api/boards/native_sim_adi_tmc2209.overlay @@ -7,7 +7,8 @@ / { aliases { - stepper = &adi_tmc2209; + stepper = &adi_tmc2209; + stepper-motion-control = &zephyr_stepper_motion_control; }; }; @@ -20,6 +21,12 @@ step-gpios = <&gpio1 1 0>; en-gpios = <&gpio2 1 0>; msx-gpios = <&gpio3 0 0>, <&gpio4 1 0>; + }; + + zephyr_stepper_motion_control: zephyr_stepper_motion_control { + compatible = "zephyr,stepper-motion-control"; + status = "okay"; counter = <&counter0>; + stepper = <&adi_tmc2209>; }; }; diff --git a/tests/drivers/stepper/stepper_api/boards/native_sim_adi_tmc2209_work_q.overlay b/tests/drivers/stepper/stepper_api/boards/native_sim_adi_tmc2209_work_q.overlay index a7756000d955d..5b0b71ae6762c 100644 --- a/tests/drivers/stepper/stepper_api/boards/native_sim_adi_tmc2209_work_q.overlay +++ b/tests/drivers/stepper/stepper_api/boards/native_sim_adi_tmc2209_work_q.overlay @@ -7,7 +7,8 @@ / { aliases { - stepper = &adi_tmc2209; + stepper = &adi_tmc2209; + stepper-motion-control = &zephyr_stepper_motion_control; }; }; @@ -21,4 +22,10 @@ en-gpios = <&gpio2 1 0>; msx-gpios = <&gpio3 0 0>, <&gpio4 1 0>; }; + + zephyr_stepper_motion_control: zephyr_stepper_motion_control { + compatible = "zephyr,stepper-motion-control"; + status = "okay"; + stepper = <&adi_tmc2209>; + }; }; diff --git a/tests/drivers/stepper/stepper_api/boards/native_sim_allegro_a4979.overlay b/tests/drivers/stepper/stepper_api/boards/native_sim_allegro_a4979.overlay index 3d1c69339dfa5..2652c194f213f 100644 --- a/tests/drivers/stepper/stepper_api/boards/native_sim_allegro_a4979.overlay +++ b/tests/drivers/stepper/stepper_api/boards/native_sim_allegro_a4979.overlay @@ -8,6 +8,7 @@ / { aliases { stepper = &allegro_a4979; + stepper-motion-control = &zephyr_stepper_motion_control; }; }; @@ -22,6 +23,12 @@ en-gpios = <&gpio2 1 0>; m0-gpios = <&gpio3 0 0>; m1-gpios = <&gpio3 1 0>; + }; + + zephyr_stepper_motion_control: zephyr_stepper_motion_control { + compatible = "zephyr,stepper-motion-control"; + status = "okay"; counter = <&counter0>; + stepper = <&allegro_a4979>; }; }; diff --git a/tests/drivers/stepper/stepper_api/boards/native_sim_allegro_a4979_work_q.overlay b/tests/drivers/stepper/stepper_api/boards/native_sim_allegro_a4979_work_q.overlay index fe4e02eb1f799..b2e477fbed3be 100644 --- a/tests/drivers/stepper/stepper_api/boards/native_sim_allegro_a4979_work_q.overlay +++ b/tests/drivers/stepper/stepper_api/boards/native_sim_allegro_a4979_work_q.overlay @@ -8,6 +8,7 @@ / { aliases { stepper = &allegro_a4979; + stepper-motion-control = &zephyr_stepper_motion_control; }; }; @@ -23,4 +24,10 @@ m0-gpios = <&gpio3 0 0>; m1-gpios = <&gpio3 1 0>; }; + + zephyr_stepper_motion_control: zephyr_stepper_motion_control { + compatible = "zephyr,stepper-motion-control"; + status = "okay"; + stepper = <&allegro_a4979>; + }; }; diff --git a/tests/drivers/stepper/stepper_api/boards/native_sim_ti_drv84xx.overlay b/tests/drivers/stepper/stepper_api/boards/native_sim_ti_drv84xx.overlay index 0763a19e393b7..90b9fc584ece6 100644 --- a/tests/drivers/stepper/stepper_api/boards/native_sim_ti_drv84xx.overlay +++ b/tests/drivers/stepper/stepper_api/boards/native_sim_ti_drv84xx.overlay @@ -8,6 +8,7 @@ / { aliases { stepper = &ti_drv84xx; + stepper-motion-control = &zephyr_stepper_motion_control; }; }; @@ -22,6 +23,12 @@ en-gpios = <&gpio2 1 0>; m0-gpios = <&gpio3 0 0>; m1-gpios = <&gpio3 1 0>; + }; + + zephyr_stepper_motion_control: zephyr_stepper_motion_control { + compatible = "zephyr,stepper-motion-control"; + status = "okay"; counter = <&counter0>; + stepper = <&ti_drv84xx>; }; }; diff --git a/tests/drivers/stepper/stepper_api/boards/native_sim_ti_drv84xx_work_q.overlay b/tests/drivers/stepper/stepper_api/boards/native_sim_ti_drv84xx_work_q.overlay index 2ca7a34d38888..55740f8576bda 100644 --- a/tests/drivers/stepper/stepper_api/boards/native_sim_ti_drv84xx_work_q.overlay +++ b/tests/drivers/stepper/stepper_api/boards/native_sim_ti_drv84xx_work_q.overlay @@ -8,6 +8,7 @@ / { aliases { stepper = &ti_drv84xx; + stepper-motion-control = &zephyr_stepper_motion_control; }; }; @@ -23,4 +24,10 @@ m0-gpios = <&gpio3 0 0>; m1-gpios = <&gpio3 1 0>; }; + + zephyr_stepper_motion_control: zephyr_stepper_motion_control { + compatible = "zephyr,stepper-motion-control"; + status = "okay"; + stepper = <&ti_drv84xx>; + }; }; diff --git a/tests/drivers/stepper/stepper_api/boards/native_sim_zephyr_h_bridge_stepper.overlay b/tests/drivers/stepper/stepper_api/boards/native_sim_zephyr_h_bridge_stepper.overlay index 8782f68e9611c..d65918e1080d3 100644 --- a/tests/drivers/stepper/stepper_api/boards/native_sim_zephyr_h_bridge_stepper.overlay +++ b/tests/drivers/stepper/stepper_api/boards/native_sim_zephyr_h_bridge_stepper.overlay @@ -8,6 +8,7 @@ / { aliases { stepper = &zephyr_h_bridge_stepper; + stepper-motion-control = &zephyr_stepper_motion_control; }; }; @@ -22,4 +23,11 @@ <&gpio3 0 0>, <&gpio4 0 0>; }; + + zephyr_stepper_motion_control: zephyr_stepper_motion_control { + compatible = "zephyr,stepper-motion-control"; + status = "okay"; + counter = <&counter0>; + stepper = <&zephyr_h_bridge_stepper>; + }; }; diff --git a/tests/drivers/stepper/stepper_api/boards/qemu_x86_64_zephyr_h_bridge_stepper.overlay b/tests/drivers/stepper/stepper_api/boards/qemu_x86_64_zephyr_h_bridge_stepper.overlay index a5ca16772ba7a..5de7f9cf70485 100644 --- a/tests/drivers/stepper/stepper_api/boards/qemu_x86_64_zephyr_h_bridge_stepper.overlay +++ b/tests/drivers/stepper/stepper_api/boards/qemu_x86_64_zephyr_h_bridge_stepper.overlay @@ -8,6 +8,7 @@ / { aliases { stepper = &zephyr_h_bridge_stepper; + stepper-motion-control = &zephyr_stepper_motion_control; }; }; @@ -51,4 +52,10 @@ <&gpio3 0 0>, <&gpio4 0 0>; }; + + zephyr_stepper_motion_control: zephyr_stepper_motion_control { + compatible = "zephyr,stepper-motion-control"; + status = "okay"; + stepper = <&zephyr_h_bridge_stepper>; + }; }; diff --git a/tests/drivers/stepper/stepper_api/src/main.c b/tests/drivers/stepper/stepper_api/src/main.c index 9a2a1179b50c2..e16155e162093 100644 --- a/tests/drivers/stepper/stepper_api/src/main.c +++ b/tests/drivers/stepper/stepper_api/src/main.c @@ -63,7 +63,7 @@ static void stepper_print_event_callback(const struct device *dev, enum stepper_ static void *stepper_setup(void) { static struct stepper_fixture fixture = { - .dev = DEVICE_DT_GET(DT_ALIAS(stepper)), + .dev = DEVICE_DT_GET(DT_ALIAS(stepper_motion_control)), .callback = stepper_print_event_callback, }; @@ -179,6 +179,22 @@ ZTEST_F(stepper, test_move_by_negative_step_count) zassert_equal(steps, -20u, "Target position should be %d but is %d", -20u, steps); } +/* move_by(dev, 0) shall stop the stepper and raise STEPPER_EVENT_STEPS_COMPLETED */ +ZTEST_F(stepper, test_move_by_zero_steps) +{ + bool is_moving; + + (void)stepper_set_microstep_interval(fixture->dev, 100 * USEC_PER_SEC); + (void)stepper_set_event_callback(fixture->dev, fixture->callback, (void *)fixture->dev); + (void)stepper_move_by(fixture->dev, 0); + + POLL_AND_CHECK_SIGNAL(stepper_signal, stepper_event, STEPPER_EVENT_STEPS_COMPLETED, + K_MSEC(100)); + + stepper_is_moving(fixture->dev, &is_moving); + zassert_equal(is_moving, false, "Stepper is still moving"); +} + ZTEST_F(stepper, test_stop) { (void)stepper_set_event_callback(fixture->dev, fixture->callback, (void *)fixture->dev); diff --git a/tests/drivers/stepper/stepper_api/testcase.yaml b/tests/drivers/stepper/stepper_api/testcase.yaml index 3298af729fd3d..34594cdce0463 100644 --- a/tests/drivers/stepper/stepper_api/testcase.yaml +++ b/tests/drivers/stepper/stepper_api/testcase.yaml @@ -13,7 +13,7 @@ tests: extra_configs: - CONFIG_GPIO=y - CONFIG_COUNTER=y - - CONFIG_STEPPER_STEP_DIR_GENERATE_ISR_SAFE_EVENTS=y + - CONFIG_ZEPHYR_STEPPER_MOTION_CONTROL_GENERATE_ISR_SAFE_EVENTS=y platform_allow: - native_sim/native/64 drivers.stepper.stepper_api.adi_tmc2209_work_q: @@ -21,7 +21,7 @@ tests: - platform:native_sim/native/64:DTC_OVERLAY_FILE="boards/native_sim_adi_tmc2209_work_q.overlay" extra_configs: - CONFIG_GPIO=y - - CONFIG_STEPPER_STEP_DIR_GENERATE_ISR_SAFE_EVENTS=y + - CONFIG_ZEPHYR_STEPPER_MOTION_CONTROL_GENERATE_ISR_SAFE_EVENTS=y - CONFIG_STEPPER_TEST_TIMING_TIMEOUT_TOLERANCE_PCT=30 platform_allow: - native_sim/native/64 @@ -31,7 +31,7 @@ tests: extra_configs: - CONFIG_GPIO=y - CONFIG_COUNTER=y - - CONFIG_STEPPER_STEP_DIR_GENERATE_ISR_SAFE_EVENTS=y + - CONFIG_ZEPHYR_STEPPER_MOTION_CONTROL_GENERATE_ISR_SAFE_EVENTS=y platform_allow: - native_sim/native/64 drivers.stepper.stepper_api.allegro_a4979_work_q: @@ -39,7 +39,7 @@ tests: - platform:native_sim/native/64:DTC_OVERLAY_FILE="boards/native_sim_allegro_a4979_work_q.overlay" extra_configs: - CONFIG_GPIO=y - - CONFIG_STEPPER_STEP_DIR_GENERATE_ISR_SAFE_EVENTS=y + - CONFIG_ZEPHYR_STEPPER_MOTION_CONTROL_GENERATE_ISR_SAFE_EVENTS=y - CONFIG_STEPPER_TEST_TIMING_TIMEOUT_TOLERANCE_PCT=30 platform_allow: - native_sim/native/64 @@ -49,7 +49,7 @@ tests: extra_configs: - CONFIG_GPIO=y - CONFIG_COUNTER=y - - CONFIG_STEPPER_STEP_DIR_GENERATE_ISR_SAFE_EVENTS=y + - CONFIG_ZEPHYR_STEPPER_MOTION_CONTROL_GENERATE_ISR_SAFE_EVENTS=y platform_allow: - native_sim/native/64 drivers.stepper.stepper_api.ti_drv84xx_work_q: @@ -57,7 +57,7 @@ tests: - platform:native_sim/native/64:DTC_OVERLAY_FILE="boards/native_sim_ti_drv84xx_work_q.overlay" extra_configs: - CONFIG_GPIO=y - - CONFIG_STEPPER_STEP_DIR_GENERATE_ISR_SAFE_EVENTS=y + - CONFIG_ZEPHYR_STEPPER_MOTION_CONTROL_GENERATE_ISR_SAFE_EVENTS=y - CONFIG_STEPPER_TEST_TIMING_TIMEOUT_TOLERANCE_PCT=30 platform_allow: - native_sim/native/64 From e5a04e9d640fbac10b82de3d90d6224c185fcd52 Mon Sep 17 00:00:00 2001 From: Jilay Pandya Date: Tue, 24 Jun 2025 19:50:36 +0200 Subject: [PATCH 2/6] drivers: stepper: deprecate stepper_drv functions from stepper_api - introduce stepper_index - Drop stepper_drv functions from stepper_api - Refactor Stepper Motion Controller - Refactor Shell Signed-off-by: Jilay Pandya --- drivers/stepper/CMakeLists.txt | 3 +- drivers/stepper/Kconfig.fake | 7 + .../adi_tmc/Kconfig.tmc_rampgen_template | 1 - drivers/stepper/adi_tmc/tmc50xx.c | 263 +++++++++------ drivers/stepper/adi_tmc/tmc51xx/tmc51xx.c | 278 ++++++++++------ drivers/stepper/adi_tmc/tmc51xx/tmc51xx.h | 31 +- drivers/stepper/fake_stepper_controller.c | 104 ++---- drivers/stepper/fake_stepper_drv.c | 92 ++++++ drivers/stepper/stepper_shell.c | 96 ++++-- .../zephyr_stepper_motion_controller.c | 83 +++-- .../stepper/adi/adi,tmc51xx-base.yaml | 56 ++-- .../zephyr,fake-stepper-controller.yaml | 7 + dts/bindings/stepper/zephyr,fake-stepper.yaml | 3 +- .../zephyr,stepper-motion-control.yaml | 7 +- include/zephyr/drivers/stepper.h | 304 +++++++----------- include/zephyr/drivers/stepper/stepper_fake.h | 26 +- .../zephyr/drivers/stepper/stepper_trinamic.h | 17 +- samples/drivers/stepper/generic/src/main.c | 46 ++- .../tmc50xx/boards/nucleo_g071rb.overlay | 1 + samples/drivers/stepper/tmc50xx/src/main.c | 39 ++- tests/drivers/build_all/stepper/spi.dtsi | 113 ++++--- tests/drivers/build_all/stepper/uart.dtsi | 54 ++-- tests/drivers/stepper/drv84xx/api/Kconfig | 6 + tests/drivers/stepper/drv84xx/api/src/main.c | 136 +++----- tests/drivers/stepper/drv84xx/emul/src/main.c | 2 +- tests/drivers/stepper/shell/CMakeLists.txt | 2 +- tests/drivers/stepper/shell/app.overlay | 5 + tests/drivers/stepper/shell/src/main.c | 84 +++-- tests/drivers/stepper/stepper_api/Kconfig | 6 + .../boards/native_sim_adi_tmc2209.overlay | 1 - .../native_sim_adi_tmc2209_work_q.overlay | 1 - .../boards/native_sim_allegro_a4979.overlay | 1 - .../native_sim_allegro_a4979_work_q.overlay | 1 - .../boards/native_sim_ti_drv84xx.overlay | 1 - .../native_sim_ti_drv84xx_work_q.overlay | 1 - tests/drivers/stepper/stepper_api/src/main.c | 75 ++--- 36 files changed, 1089 insertions(+), 864 deletions(-) create mode 100644 drivers/stepper/fake_stepper_drv.c create mode 100644 dts/bindings/stepper/zephyr,fake-stepper-controller.yaml diff --git a/drivers/stepper/CMakeLists.txt b/drivers/stepper/CMakeLists.txt index 6ba4a03e89025..e4a48bb8bf8f4 100644 --- a/drivers/stepper/CMakeLists.txt +++ b/drivers/stepper/CMakeLists.txt @@ -17,7 +17,8 @@ add_subdirectory_ifdef(CONFIG_STEP_DIR_STEPPER step_dir) zephyr_library() zephyr_library_property(ALLOW_EMPTY TRUE) -zephyr_library_sources_ifdef(CONFIG_FAKE_STEPPER fake_stepper_controller.c) +zephyr_library_sources_ifdef(CONFIG_FAKE_STEPPER fake_stepper_drv.c) +zephyr_library_sources_ifdef(CONFIG_FAKE_STEPPER_CONTROLLER fake_stepper_controller.c) zephyr_library_sources_ifdef(CONFIG_H_BRIDGE_STEPPER h_bridge_stepper.c) zephyr_library_sources_ifdef(CONFIG_STEPPER_SHELL stepper_shell.c) zephyr_library_sources_ifdef(CONFIG_ZEPHYR_STEPPER_MOTION_CONTROL zephyr_stepper_motion_controller.c) diff --git a/drivers/stepper/Kconfig.fake b/drivers/stepper/Kconfig.fake index 942a556f1be2f..9922c29a4cefc 100644 --- a/drivers/stepper/Kconfig.fake +++ b/drivers/stepper/Kconfig.fake @@ -9,3 +9,10 @@ config FAKE_STEPPER depends on DT_HAS_ZEPHYR_FAKE_STEPPER_ENABLED help Enable support for the FFF-based fake stepper driver. + +config FAKE_STEPPER_CONTROLLER + bool "Fake stepper controller driver" + default y + depends on DT_HAS_ZEPHYR_FAKE_STEPPER_CONTROLLER_ENABLED + help + Enable support for the FFF-based fake stepper controller driver. diff --git a/drivers/stepper/adi_tmc/Kconfig.tmc_rampgen_template b/drivers/stepper/adi_tmc/Kconfig.tmc_rampgen_template index a357f8917d87b..a3a79bd490674 100644 --- a/drivers/stepper/adi_tmc/Kconfig.tmc_rampgen_template +++ b/drivers/stepper/adi_tmc/Kconfig.tmc_rampgen_template @@ -3,7 +3,6 @@ config STEPPER_ADI_$(module)_RAMPSTAT_POLL_INTERVAL_IN_MSEC int "$(module-str) poll ramp status interval in ms" - depends on !$(dt_compat_any_has_prop,$(DT_COMPAT_ADI_$(module)),diag0-gpios) default 100 help When DIAG0 pin is not available, the driver automatically falls back to diff --git a/drivers/stepper/adi_tmc/tmc50xx.c b/drivers/stepper/adi_tmc/tmc50xx.c index 87e886251b773..8a999cc08139b 100644 --- a/drivers/stepper/adi_tmc/tmc50xx.c +++ b/drivers/stepper/adi_tmc/tmc50xx.c @@ -25,6 +25,8 @@ struct tmc50xx_config { const uint32_t gconf; struct spi_dt_spec spi; const uint32_t clock_frequency; + const struct device **steppers; + const uint8_t num_steppers; }; struct tmc50xx_stepper_data { @@ -51,7 +53,8 @@ struct tmc50xx_stepper_config { #endif }; -static int read_actual_position(const struct tmc50xx_stepper_config *config, int32_t *position); +static int read_actual_position(const struct device *controller, const uint8_t stepper_index, + int32_t *position); static int tmc50xx_write(const struct device *dev, const uint8_t reg_addr, const uint32_t reg_val) { @@ -93,22 +96,33 @@ static int tmc50xx_read(const struct device *dev, const uint8_t reg_addr, uint32 return 0; } -static int tmc50xx_stepper_set_event_callback(const struct device *dev, - stepper_event_callback_t callback, void *user_data) +static void check_stepper_idx(const struct device *controller, const uint8_t stepper_index) { - struct tmc50xx_stepper_data *data = dev->data; + __ASSERT_NO_MSG(controller != NULL); + __maybe_unused const struct tmc50xx_config *tmc50xx_config = controller->config; + + CHECK_STEPPER_IDX(controller, stepper_index, tmc50xx_config->num_steppers); +} + +static int tmc50xx_set_event_callback(const struct device *controller, const uint8_t stepper_index, + stepper_event_callback_t callback, void *user_data) +{ + const struct tmc50xx_config *tmc50xx_config = controller->config; + check_stepper_idx(controller, stepper_index); + struct tmc50xx_stepper_data *data = tmc50xx_config->steppers[stepper_index]->data; data->callback = callback; data->event_cb_user_data = user_data; return 0; } -static int read_vactual(const struct tmc50xx_stepper_config *config, int32_t *actual_velocity) +static int read_vactual(const struct device *controller, const uint8_t stepper_index, + int32_t *actual_velocity) { __ASSERT(actual_velocity != NULL, "actual_velocity pointer must not be NULL"); int err; - err = tmc50xx_read(config->controller, TMC50XX_VACTUAL(config->index), actual_velocity); + err = tmc50xx_read(controller, TMC50XX_VACTUAL(stepper_index), actual_velocity); if (err) { LOG_ERR("Failed to read VACTUAL register"); return err; @@ -121,13 +135,16 @@ static int read_vactual(const struct tmc50xx_stepper_config *config, int32_t *ac return 0; } -static int stallguard_enable(const struct device *dev, const bool enable) +static int stallguard_enable(const struct device *controller, const uint8_t stepper_index, + const bool enable) { - const struct tmc50xx_stepper_config *config = dev->config; + const struct tmc50xx_config *tmc50xx_config = controller->config; + const struct tmc50xx_stepper_config *config = + tmc50xx_config->steppers[stepper_index]->config; uint32_t reg_value; int err; - err = tmc50xx_read(config->controller, TMC50XX_SWMODE(config->index), ®_value); + err = tmc50xx_read(controller, TMC50XX_SWMODE(stepper_index), ®_value); if (err) { LOG_ERR("Failed to read SWMODE register"); return -EIO; @@ -138,7 +155,7 @@ static int stallguard_enable(const struct device *dev, const bool enable) int32_t actual_velocity; - err = read_vactual(config, &actual_velocity); + err = read_vactual(controller, stepper_index, &actual_velocity); if (err) { return -EIO; } @@ -148,7 +165,7 @@ static int stallguard_enable(const struct device *dev, const bool enable) } else { reg_value &= ~TMC5XXX_SW_MODE_SG_STOP_ENABLE; } - err = tmc50xx_write(config->controller, TMC50XX_SWMODE(config->index), reg_value); + err = tmc50xx_write(controller, TMC50XX_SWMODE(stepper_index), reg_value); if (err) { LOG_ERR("Failed to write SWMODE register"); return -EIO; @@ -166,7 +183,7 @@ static void stallguard_work_handler(struct k_work *work) int err; const struct tmc50xx_stepper_config *stepper_config = stepper_data->stepper->config; - err = stallguard_enable(stepper_data->stepper, true); + err = stallguard_enable(stepper_config->controller, stepper_config->index, true); if (err == -EAGAIN) { k_work_reschedule(dwork, K_MSEC(stepper_config->sg_velocity_check_interval_ms)); } @@ -176,15 +193,17 @@ static void stallguard_work_handler(struct k_work *work) } } -static void execute_callback(const struct device *dev, const enum stepper_event event) +static void execute_callback(const struct device *controller, const uint8_t stepper_index, + const enum stepper_event event) { - struct tmc50xx_stepper_data *data = dev->data; + const struct tmc50xx_config *tmc50xx_config = controller->config; + struct tmc50xx_stepper_data *data = tmc50xx_config->steppers[stepper_index]->data; if (!data->callback) { LOG_WRN_ONCE("No callback registered"); return; } - data->callback(dev, event, data->event_cb_user_data); + data->callback(controller, stepper_index, event, data->event_cb_user_data); } #ifdef CONFIG_STEPPER_ADI_TMC50XX_RAMPSTAT_POLL_STALLGUARD_LOG @@ -204,8 +223,8 @@ static void log_stallguard(struct tmc50xx_stepper_data *stepper_data, const uint const uint8_t sg_result = FIELD_GET(TMC5XXX_DRV_STATUS_SG_RESULT_MASK, drv_status); const bool sg_status = FIELD_GET(TMC5XXX_DRV_STATUS_SG_STATUS_MASK, drv_status); - LOG_DBG("%s position: %d | sg result: %3d status: %d", - stepper_data->stepper->name, position, sg_result, sg_status); + LOG_DBG("%s position: %d | sg result: %3d status: %d", stepper_data->stepper->name, + position, sg_result, sg_status); } #endif @@ -213,7 +232,7 @@ static void log_stallguard(struct tmc50xx_stepper_data *stepper_data, const uint static void rampstat_work_reschedule(struct k_work_delayable *rampstat_callback_dwork) { k_work_reschedule(rampstat_callback_dwork, - K_MSEC(CONFIG_STEPPER_ADI_TMC50XX_RAMPSTAT_POLL_INTERVAL_IN_MSEC)); + K_MSEC(CONFIG_STEPPER_ADI_TMC50XX_RAMPSTAT_POLL_INTERVAL_IN_MSEC)); } static void rampstat_work_handler(struct k_work *work) @@ -265,13 +284,13 @@ static void rampstat_work_handler(struct k_work *work) case TMC5XXX_STOP_LEFT_EVENT: LOG_DBG("RAMPSTAT %s:Left end-stop detected", stepper_data->stepper->name); - execute_callback(stepper_data->stepper, + execute_callback(stepper_config->controller, stepper_config->index, STEPPER_EVENT_LEFT_END_STOP_DETECTED); break; case TMC5XXX_STOP_RIGHT_EVENT: LOG_DBG("RAMPSTAT %s:Right end-stop detected", stepper_data->stepper->name); - execute_callback(stepper_data->stepper, + execute_callback(stepper_config->controller, stepper_config->index, STEPPER_EVENT_RIGHT_END_STOP_DETECTED); break; @@ -279,13 +298,15 @@ static void rampstat_work_handler(struct k_work *work) case TMC5XXX_POS_REACHED: case TMC5XXX_POS_REACHED_AND_EVENT: LOG_DBG("RAMPSTAT %s:Position reached", stepper_data->stepper->name); - execute_callback(stepper_data->stepper, STEPPER_EVENT_STEPS_COMPLETED); + execute_callback(stepper_config->controller, stepper_config->index, + STEPPER_EVENT_STEPS_COMPLETED); break; case TMC5XXX_STOP_SG_EVENT: LOG_DBG("RAMPSTAT %s:Stall detected", stepper_data->stepper->name); - stallguard_enable(stepper_data->stepper, false); - execute_callback(stepper_data->stepper, STEPPER_EVENT_STALL_DETECTED); + stallguard_enable(stepper_config->controller, stepper_config->index, false); + execute_callback(stepper_config->controller, stepper_config->index, + STEPPER_EVENT_STALL_DETECTED); break; default: LOG_ERR("Illegal ramp stat bit field"); @@ -330,37 +351,42 @@ static int tmc50xx_stepper_disable(const struct device *dev) return tmc50xx_write(config->controller, TMC50XX_CHOPCONF(config->index), reg_value); } -static int tmc50xx_stepper_is_moving(const struct device *dev, bool *is_moving) +static int tmc50xx_stepper_is_moving(const struct device *controller, const uint8_t stepper_index, + bool *is_moving) { - const struct tmc50xx_stepper_config *config = dev->config; + check_stepper_idx(controller, stepper_index); + uint32_t reg_value; int err; - err = tmc50xx_read(config->controller, TMC50XX_DRVSTATUS(config->index), ®_value); + err = tmc50xx_read(controller, TMC50XX_DRVSTATUS(stepper_index), ®_value); if (err != 0) { - LOG_ERR("%s: Failed to read DRVSTATUS register", dev->name); + LOG_ERR("%s: Failed to read DRVSTATUS register", controller->name); return -EIO; } *is_moving = (FIELD_GET(TMC5XXX_DRV_STATUS_STST_BIT, reg_value) != 1U); - LOG_DBG("Stepper motor controller %s is moving: %d", dev->name, *is_moving); + LOG_DBG("Stepper motor controller %s is moving: %d", controller->name, *is_moving); return 0; } -int tmc50xx_stepper_set_max_velocity(const struct device *dev, uint32_t velocity) +int tmc50xx_stepper_set_max_velocity(const struct device *controller, const uint8_t stepper_index, + uint32_t velocity) { - const struct tmc50xx_stepper_config *config = dev->config; - const struct tmc50xx_config *tmc50xx_config = config->controller->config; + const struct tmc50xx_config *tmc50xx_config = controller->config; + + check_stepper_idx(controller, stepper_index); + const uint32_t clock_frequency = tmc50xx_config->clock_frequency; uint32_t velocity_fclk; int err; velocity_fclk = tmc5xxx_calculate_velocity_from_hz_to_fclk(velocity, clock_frequency); - err = tmc50xx_write(config->controller, TMC50XX_VMAX(config->index), velocity_fclk); + err = tmc50xx_write(controller, TMC50XX_VMAX(stepper_index), velocity_fclk); if (err != 0) { - LOG_ERR("%s: Failed to set max velocity", dev->name); + LOG_ERR("%s: Failed to set max velocity", controller->name); return -EIO; } return 0; @@ -410,66 +436,77 @@ static int tmc50xx_stepper_get_micro_step_res(const struct device *dev, return 0; } -static int tmc50xx_stepper_set_reference_position(const struct device *dev, const int32_t position) +static int tmc50xx_stepper_set_reference_position(const struct device *controller, + const uint8_t stepper_index, + const int32_t position) { - const struct tmc50xx_stepper_config *config = dev->config; + check_stepper_idx(controller, stepper_index); int err; - err = tmc50xx_write(config->controller, TMC50XX_RAMPMODE(config->index), + err = tmc50xx_write(controller, TMC50XX_RAMPMODE(stepper_index), TMC5XXX_RAMPMODE_HOLD_MODE); if (err != 0) { return -EIO; } - err = tmc50xx_write(config->controller, TMC50XX_XACTUAL(config->index), position); + err = tmc50xx_write(controller, TMC50XX_XACTUAL(stepper_index), position); if (err != 0) { return -EIO; } - LOG_DBG("Stepper motor controller %s set actual position to %d", dev->name, position); + LOG_DBG("Stepper motor controller %s set actual position to %d", controller->name, + position); return 0; } -static int read_actual_position(const struct tmc50xx_stepper_config *config, int32_t *position) +static int read_actual_position(const struct device *controller, const uint8_t stepper_index, + int32_t *position) { int err; - err = tmc50xx_read(config->controller, TMC50XX_XACTUAL(config->index), position); + err = tmc50xx_read(controller, TMC50XX_XACTUAL(stepper_index), position); if (err != 0) { return -EIO; } return 0; } -static int tmc50xx_stepper_get_actual_position(const struct device *dev, int32_t *position) +static int tmc50xx_stepper_get_actual_position(const struct device *controller, + const uint8_t stepper_index, int32_t *position) { - const struct tmc50xx_stepper_config *config = dev->config; + check_stepper_idx(controller, stepper_index); int err; - err = read_actual_position(config, position); + err = read_actual_position(controller, stepper_index, position); if (err != 0) { return -EIO; } - LOG_DBG("%s actual position: %d", dev->name, *position); + LOG_DBG("%s actual position %d: %d", controller->name, stepper_index, *position); return 0; } -static int tmc50xx_stepper_move_to(const struct device *dev, const int32_t micro_steps) +static int tmc50xx_stepper_move_to(const struct device *controller, const uint8_t stepper_index, + const int32_t micro_steps) { - LOG_DBG("%s set target position to %d", dev->name, micro_steps); - const struct tmc50xx_stepper_config *config = dev->config; - struct tmc50xx_stepper_data *data = dev->data; + const struct tmc50xx_config *controller_config = controller->config; + + check_stepper_idx(controller, stepper_index); + LOG_DBG("%s set target position to %d", controller->name, micro_steps); + + const struct tmc50xx_stepper_config *config = + controller_config->steppers[stepper_index]->config; + struct tmc50xx_stepper_data *data = controller_config->steppers[stepper_index]->data; int err; if (config->is_sg_enabled) { - stallguard_enable(dev, false); + stallguard_enable(controller, stepper_index, false); } - err = tmc50xx_write(config->controller, TMC50XX_RAMPMODE(config->index), + err = tmc50xx_write(controller, TMC50XX_RAMPMODE(stepper_index), TMC5XXX_RAMPMODE_POSITIONING_MODE); if (err != 0) { return -EIO; } - err = tmc50xx_write(config->controller, TMC50XX_XTARGET(config->index), micro_steps); + err = tmc50xx_write(controller, TMC50XX_XTARGET(stepper_index), micro_steps); if (err != 0) { return -EIO; } @@ -484,31 +521,39 @@ static int tmc50xx_stepper_move_to(const struct device *dev, const int32_t micro return 0; } -static int tmc50xx_stepper_move_by(const struct device *dev, const int32_t micro_steps) +static int tmc50xx_stepper_move_by(const struct device *controller, const uint8_t stepper_index, + const int32_t micro_steps) { - int err; + check_stepper_idx(controller, stepper_index); int32_t position; + int err; - err = stepper_get_actual_position(dev, &position); + err = stepper_get_actual_position(controller, stepper_index, &position); if (err != 0) { return -EIO; } int32_t target_position = position + micro_steps; - LOG_DBG("%s moved to %d by steps: %d", dev->name, target_position, micro_steps); + LOG_DBG("%s moved to %d by steps: %d", controller->name, target_position, micro_steps); - return tmc50xx_stepper_move_to(dev, target_position); + return tmc50xx_stepper_move_to(controller, stepper_index, target_position); } -static int tmc50xx_stepper_run(const struct device *dev, const enum stepper_direction direction) +static int tmc50xx_stepper_run(const struct device *controller, const uint8_t stepper_index, + const enum stepper_direction direction) { - LOG_DBG("Stepper motor controller %s run", dev->name); - const struct tmc50xx_stepper_config *config = dev->config; - struct tmc50xx_stepper_data *data = dev->data; + const struct tmc50xx_config *tmc50xx_config = controller->config; + + check_stepper_idx(controller, stepper_index); + LOG_DBG("Stepper motor controller %s run", controller->name); + + const struct tmc50xx_stepper_config *config = + tmc50xx_config->steppers[stepper_index]->config; + struct tmc50xx_stepper_data *data = tmc50xx_config->steppers[stepper_index]->data; int err; if (config->is_sg_enabled) { - err = stallguard_enable(dev, false); + err = stallguard_enable(controller, stepper_index, false); if (err != 0) { return -EIO; } @@ -516,7 +561,7 @@ static int tmc50xx_stepper_run(const struct device *dev, const enum stepper_dire switch (direction) { case STEPPER_DIRECTION_POSITIVE: - err = tmc50xx_write(config->controller, TMC50XX_RAMPMODE(config->index), + err = tmc50xx_write(controller, TMC50XX_RAMPMODE(stepper_index), TMC5XXX_RAMPMODE_POSITIVE_VELOCITY_MODE); if (err != 0) { return -EIO; @@ -524,7 +569,7 @@ static int tmc50xx_stepper_run(const struct device *dev, const enum stepper_dire break; case STEPPER_DIRECTION_NEGATIVE: - err = tmc50xx_write(config->controller, TMC50XX_RAMPMODE(config->index), + err = tmc50xx_write(controller, TMC50XX_RAMPMODE(stepper_index), TMC5XXX_RAMPMODE_NEGATIVE_VELOCITY_MODE); if (err != 0) { return -EIO; @@ -542,18 +587,18 @@ static int tmc50xx_stepper_run(const struct device *dev, const enum stepper_dire return 0; } -static int tmc50xx_stepper_stop(const struct device *dev) +static int tmc50xx_stepper_stop(const struct device *controller, const uint8_t stepper_index) { - const struct tmc50xx_stepper_config *config = dev->config; + check_stepper_idx(controller, stepper_index); int err; - err = tmc50xx_write(config->controller, TMC50XX_RAMPMODE(config->index), + err = tmc50xx_write(controller, TMC50XX_RAMPMODE(stepper_index), TMC5XXX_RAMPMODE_POSITIVE_VELOCITY_MODE); if (err != 0) { return -EIO; } - err = tmc50xx_write(config->controller, TMC50XX_VMAX(config->index), 0); + err = tmc50xx_write(controller, TMC50XX_VMAX(stepper_index), 0); if (err != 0) { return -EIO; } @@ -563,61 +608,57 @@ static int tmc50xx_stepper_stop(const struct device *dev) #ifdef CONFIG_STEPPER_ADI_TMC50XX_RAMP_GEN -int tmc50xx_stepper_set_ramp(const struct device *dev, +int tmc50xx_stepper_set_ramp(const struct device *controller, const uint8_t stepper_index, const struct tmc_ramp_generator_data *ramp_data) { - LOG_DBG("Stepper motor controller %s set ramp", dev->name); - const struct tmc50xx_stepper_config *config = dev->config; + LOG_DBG("Stepper motor controller %s set ramp", controller->name); int err; - err = tmc50xx_write(config->controller, TMC50XX_VSTART(config->index), ramp_data->vstart); + err = tmc50xx_write(controller, TMC50XX_VSTART(stepper_index), ramp_data->vstart); if (err != 0) { return -EIO; } - err = tmc50xx_write(config->controller, TMC50XX_A1(config->index), ramp_data->a1); + err = tmc50xx_write(controller, TMC50XX_A1(stepper_index), ramp_data->a1); if (err != 0) { return -EIO; } - err = tmc50xx_write(config->controller, TMC50XX_AMAX(config->index), ramp_data->amax); + err = tmc50xx_write(controller, TMC50XX_AMAX(stepper_index), ramp_data->amax); if (err != 0) { return -EIO; } - err = tmc50xx_write(config->controller, TMC50XX_D1(config->index), ramp_data->d1); + err = tmc50xx_write(controller, TMC50XX_D1(stepper_index), ramp_data->d1); if (err != 0) { return -EIO; } - err = tmc50xx_write(config->controller, TMC50XX_DMAX(config->index), ramp_data->dmax); + err = tmc50xx_write(controller, TMC50XX_DMAX(stepper_index), ramp_data->dmax); if (err != 0) { return -EIO; } - err = tmc50xx_write(config->controller, TMC50XX_V1(config->index), ramp_data->v1); + err = tmc50xx_write(controller, TMC50XX_V1(stepper_index), ramp_data->v1); if (err != 0) { return -EIO; } - err = tmc50xx_write(config->controller, TMC50XX_VMAX(config->index), ramp_data->vmax); + err = tmc50xx_write(controller, TMC50XX_VMAX(stepper_index), ramp_data->vmax); if (err != 0) { return -EIO; } - err = tmc50xx_write(config->controller, TMC50XX_VSTOP(config->index), ramp_data->vstop); + err = tmc50xx_write(controller, TMC50XX_VSTOP(stepper_index), ramp_data->vstop); if (err != 0) { return -EIO; } - err = tmc50xx_write(config->controller, TMC50XX_TZEROWAIT(config->index), - ramp_data->tzerowait); + err = tmc50xx_write(controller, TMC50XX_TZEROWAIT(stepper_index), ramp_data->tzerowait); if (err != 0) { return -EIO; } - err = tmc50xx_write(config->controller, TMC50XX_VHIGH(config->index), ramp_data->vhigh); + err = tmc50xx_write(controller, TMC50XX_VHIGH(stepper_index), ramp_data->vhigh); if (err != 0) { return -EIO; } - err = tmc50xx_write(config->controller, TMC50XX_VCOOLTHRS(config->index), - ramp_data->vcoolthrs); + err = tmc50xx_write(controller, TMC50XX_VCOOLTHRS(stepper_index), ramp_data->vcoolthrs); if (err != 0) { return -EIO; } - err = tmc50xx_write(config->controller, TMC50XX_IHOLD_IRUN(config->index), - ramp_data->iholdrun); + err = tmc50xx_write(controller, TMC50XX_IHOLD_IRUN(stepper_index), ramp_data->iholdrun); if (err != 0) { return -EIO; } @@ -628,11 +669,11 @@ int tmc50xx_stepper_set_ramp(const struct device *dev, static int tmc50xx_init(const struct device *dev) { - LOG_DBG("TMC50XX stepper motor controller %s initialized", dev->name); struct tmc50xx_data *data = dev->data; const struct tmc50xx_config *config = dev->config; int err; + LOG_DBG("Initializing TMC50XX stepper motor controller %s", dev->name); k_sem_init(&data->sem, 1, 1); if (!spi_is_ready_dt(&config->spi)) { @@ -696,14 +737,14 @@ static int tmc50xx_stepper_init(const struct device *dev) } #ifdef CONFIG_STEPPER_ADI_TMC50XX_RAMP_GEN - err = tmc50xx_stepper_set_ramp(dev, &stepper_config->default_ramp_config); + err = tmc50xx_stepper_set_ramp(stepper_config->controller, stepper_config->index, + &stepper_config->default_ramp_config); if (err != 0) { return -EIO; } #endif k_work_init_delayable(&data->rampstat_callback_dwork, rampstat_work_handler); - rampstat_work_reschedule(&data->rampstat_callback_dwork); err = tmc50xx_stepper_set_micro_step_res(dev, stepper_config->default_micro_step_res); if (err != 0) { return -EIO; @@ -711,21 +752,26 @@ static int tmc50xx_stepper_init(const struct device *dev) return 0; } -static DEVICE_API(stepper, tmc50xx_stepper_api) = { +static DEVICE_API(stepper_drv, tmc50xx_stepper_drv_api) = { .enable = tmc50xx_stepper_enable, .disable = tmc50xx_stepper_disable, - .is_moving = tmc50xx_stepper_is_moving, - .move_by = tmc50xx_stepper_move_by, .set_micro_step_res = tmc50xx_stepper_set_micro_step_res, .get_micro_step_res = tmc50xx_stepper_get_micro_step_res, +}; + +static DEVICE_API(stepper, tmc50xx_stepper_api) = { + .is_moving = tmc50xx_stepper_is_moving, + .move_by = tmc50xx_stepper_move_by, .set_reference_position = tmc50xx_stepper_set_reference_position, .get_actual_position = tmc50xx_stepper_get_actual_position, .move_to = tmc50xx_stepper_move_to, .run = tmc50xx_stepper_run, .stop = tmc50xx_stepper_stop, - .set_event_callback = tmc50xx_stepper_set_event_callback, + .set_event_callback = tmc50xx_set_event_callback, }; +#define TMC50XX_STEPPER_PTRS(child) DEVICE_DT_GET(child), + #define TMC50XX_SHAFT_CONFIG(child) \ (DT_PROP(child, invert_direction) << TMC50XX_GCONF_SHAFT_SHIFT(DT_REG_ADDR(child))) | @@ -740,20 +786,21 @@ static DEVICE_API(stepper, tmc50xx_stepper_api) = { .index = DT_REG_ADDR(child), \ .sg_threshold = DT_PROP(child, stallguard2_threshold), \ .sg_threshold_velocity = DT_PROP(child, stallguard_threshold_velocity), \ - .sg_velocity_check_interval_ms = DT_PROP(child, \ - stallguard_velocity_check_interval_ms), \ + .sg_velocity_check_interval_ms = \ + DT_PROP(child, stallguard_velocity_check_interval_ms), \ .is_sg_enabled = DT_PROP(child, activate_stallguard2), \ IF_ENABLED(CONFIG_STEPPER_ADI_TMC50XX_RAMP_GEN, \ (.default_ramp_config = TMC_RAMP_DT_SPEC_GET_TMC50XX(child))) }; #define TMC50XX_STEPPER_DATA_DEFINE(child) \ static struct tmc50xx_stepper_data tmc50xx_stepper_data_##child = { \ - .stepper = DEVICE_DT_GET(child),}; + .stepper = DEVICE_DT_GET(child), \ + }; #define TMC50XX_STEPPER_DEFINE(child) \ DEVICE_DT_DEFINE(child, tmc50xx_stepper_init, NULL, &tmc50xx_stepper_data_##child, \ &tmc50xx_stepper_config_##child, POST_KERNEL, \ - CONFIG_STEPPER_INIT_PRIORITY, &tmc50xx_stepper_api); + CONFIG_STEPPER_INIT_PRIORITY, &tmc50xx_stepper_drv_api); #define TMC50XX_DEFINE(inst) \ BUILD_ASSERT(DT_INST_CHILD_NUM(inst) <= 2, "tmc50xx can drive two steppers at max"); \ @@ -761,19 +808,25 @@ static DEVICE_API(stepper, tmc50xx_stepper_api) = { "clock frequency must be non-zero positive value"); \ static struct tmc50xx_data tmc50xx_data_##inst; \ static const struct tmc50xx_config tmc50xx_config_##inst = { \ - .gconf = ( \ - (DT_INST_PROP(inst, poscmp_enable) << TMC50XX_GCONF_POSCMP_ENABLE_SHIFT) | \ - (DT_INST_PROP(inst, test_mode) << TMC50XX_GCONF_TEST_MODE_SHIFT) | \ - DT_INST_FOREACH_CHILD(inst, TMC50XX_SHAFT_CONFIG) \ - (DT_INST_PROP(inst, lock_gconf) << TMC50XX_LOCK_GCONF_SHIFT)), \ - .spi = SPI_DT_SPEC_INST_GET(inst, (SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | \ - SPI_MODE_CPOL | SPI_MODE_CPHA | SPI_WORD_SET(8)), 0), \ - .clock_frequency = DT_INST_PROP(inst, clock_frequency),}; \ + .gconf = ((DT_INST_PROP(inst, poscmp_enable) \ + << TMC50XX_GCONF_POSCMP_ENABLE_SHIFT) | \ + (DT_INST_PROP(inst, test_mode) << TMC50XX_GCONF_TEST_MODE_SHIFT) | \ + DT_INST_FOREACH_CHILD(inst, TMC50XX_SHAFT_CONFIG)( \ + DT_INST_PROP(inst, lock_gconf) << TMC50XX_LOCK_GCONF_SHIFT)), \ + .spi = SPI_DT_SPEC_INST_GET(inst, \ + (SPI_OP_MODE_MASTER | SPI_TRANSFER_MSB | \ + SPI_MODE_CPOL | SPI_MODE_CPHA | SPI_WORD_SET(8)), \ + 0), \ + .steppers = (const struct device *[]){DT_INST_FOREACH_CHILD( \ + inst, TMC50XX_STEPPER_PTRS)}, \ + .num_steppers = DT_INST_CHILD_NUM(inst), \ + .clock_frequency = DT_INST_PROP(inst, clock_frequency), \ + }; \ DT_INST_FOREACH_CHILD(inst, TMC50XX_STEPPER_CONFIG_DEFINE); \ DT_INST_FOREACH_CHILD(inst, TMC50XX_STEPPER_DATA_DEFINE); \ DT_INST_FOREACH_CHILD(inst, TMC50XX_STEPPER_DEFINE); \ DEVICE_DT_INST_DEFINE(inst, tmc50xx_init, NULL, &tmc50xx_data_##inst, \ &tmc50xx_config_##inst, POST_KERNEL, CONFIG_STEPPER_INIT_PRIORITY,\ - NULL); + &tmc50xx_stepper_api); DT_INST_FOREACH_STATUS_OKAY(TMC50XX_DEFINE) diff --git a/drivers/stepper/adi_tmc/tmc51xx/tmc51xx.c b/drivers/stepper/adi_tmc/tmc51xx/tmc51xx.c index ec334965e3dd5..2e0512451a7d2 100644 --- a/drivers/stepper/adi_tmc/tmc51xx/tmc51xx.c +++ b/drivers/stepper/adi_tmc/tmc51xx/tmc51xx.c @@ -15,6 +15,8 @@ #include LOG_MODULE_REGISTER(tmc51xx, CONFIG_STEPPER_LOG_LEVEL); +#define TMC51XX_MAX_SUPPORTED_STEPPER 1 + static inline int tmc51xx_bus_check(const struct device *dev) { const struct tmc51xx_config *config = dev->config; @@ -66,16 +68,19 @@ static int tmc51xx_read(const struct device *dev, const uint8_t reg_addr, uint32 return 0; } -static int tmc51xx_stepper_set_event_callback(const struct device *dev, +static int tmc51xx_stepper_set_event_callback(const struct device *controller, + const uint8_t stepper_idx, stepper_event_callback_t callback, void *user_data) { - struct tmc51xx_data *data = dev->data; - __maybe_unused const struct tmc51xx_config *config = dev->config; + CHECK_STEPPER_IDX(controller, stepper_idx, TMC51XX_MAX_SUPPORTED_STEPPER); + __maybe_unused struct tmc51xx_data *data = controller->data; + const struct tmc51xx_config *config = controller->config; + struct tmc51xx_stepper_data *stepper_data = config->steppers[0]->data; __maybe_unused int err; - data->callback = callback; - data->event_cb_user_data = user_data; + stepper_data->callback = callback; + stepper_data->event_cb_user_data = user_data; /* Configure DIAG0 GPIO interrupt pin */ IF_ENABLED(TMC51XX_BUS_SPI, ({ @@ -112,7 +117,7 @@ static int tmc51xx_stepper_set_event_callback(const struct device *dev, /* Clear any pending interrupts */ uint32_t rampstat_value; - err = rampstat_read_clear(dev, &rampstat_value); + err = rampstat_read_clear(controller, &rampstat_value); if (err != 0) { return -EIO; } @@ -121,12 +126,13 @@ static int tmc51xx_stepper_set_event_callback(const struct device *dev, return 0; } -static int read_vactual(const struct device *dev, int32_t *actual_velocity) +static int read_vactual(const struct device *controller, int32_t *actual_velocity) { + __ASSERT(actual_velocity != NULL, "actual_velocity pointer must not be NULL"); int err; uint32_t raw_value; - err = tmc51xx_read(dev, TMC51XX_VACTUAL, &raw_value); + err = tmc51xx_read(controller, TMC51XX_VACTUAL, &raw_value); if (err) { LOG_ERR("Failed to read VACTUAL register"); return err; @@ -139,13 +145,14 @@ static int read_vactual(const struct device *dev, int32_t *actual_velocity) return 0; } -static int stallguard_enable(const struct device *dev, const bool enable) +static int stallguard_enable(const struct device *controller, const bool enable) { - const struct tmc51xx_config *config = dev->config; + const struct tmc51xx_config *tmc51xx_config = controller->config; + const struct tmc51xx_stepper_config *config = tmc51xx_config->steppers[0]->config; uint32_t reg_value; int err; - err = tmc51xx_read(dev, TMC51XX_SWMODE, ®_value); + err = tmc51xx_read(controller, TMC51XX_SWMODE, ®_value); if (err) { LOG_ERR("Failed to read SWMODE register"); return -EIO; @@ -156,7 +163,7 @@ static int stallguard_enable(const struct device *dev, const bool enable) int32_t actual_velocity; - err = read_vactual(dev, &actual_velocity); + err = read_vactual(controller, &actual_velocity); if (err) { return -EIO; } @@ -166,7 +173,7 @@ static int stallguard_enable(const struct device *dev, const bool enable) } else { reg_value &= ~TMC5XXX_SW_MODE_SG_STOP_ENABLE; } - err = tmc51xx_write(dev, TMC51XX_SWMODE, reg_value); + err = tmc51xx_write(controller, TMC51XX_SWMODE, reg_value); if (err) { LOG_ERR("Failed to write SWMODE register"); return -EIO; @@ -179,15 +186,14 @@ static int stallguard_enable(const struct device *dev, const bool enable) static void stallguard_work_handler(struct k_work *work) { struct k_work_delayable *dwork = k_work_delayable_from_work(work); - struct tmc51xx_data const *stepper_data = - CONTAINER_OF(dwork, struct tmc51xx_data, stallguard_dwork); - const struct device *dev = stepper_data->stepper; - const struct tmc51xx_config *config = dev->config; + struct tmc51xx_stepper_data const *stepper_data = + CONTAINER_OF(dwork, struct tmc51xx_stepper_data, stallguard_dwork); + const struct tmc51xx_stepper_config *stepper_config = stepper_data->stepper->config; int err; - err = stallguard_enable(dev, true); + err = stallguard_enable(stepper_config->controller, true); if (err == -EAGAIN) { - k_work_reschedule(dwork, K_MSEC(config->sg_velocity_check_interval_ms)); + k_work_reschedule(dwork, K_MSEC(stepper_config->sg_velocity_check_interval_ms)); } if (err == -EIO) { LOG_ERR("Failed to enable stallguard because of I/O error"); @@ -196,13 +202,15 @@ static void stallguard_work_handler(struct k_work *work) static void stepper_trigger_callback(const struct device *dev, const enum stepper_event event) { - struct tmc51xx_data *data = dev->data; + const struct tmc51xx_config *config = dev->config; + const struct device *stepper = config->steppers[0]; + struct tmc51xx_stepper_data *data = stepper->data; if (!data->callback) { LOG_WRN_ONCE("No callback registered"); return; } - data->callback(dev, event, data->event_cb_user_data); + data->callback(dev, 0, event, data->event_cb_user_data); } #ifdef CONFIG_STEPPER_ADI_TMC51XX_RAMPSTAT_POLL_STALLGUARD_LOG @@ -227,13 +235,13 @@ static void log_stallguard(const struct device *dev, const uint32_t drv_status) #endif /* CONFIG_STEPPER_ADI_TMC51XX_RAMPSTAT_POLL_STALLGUARD_LOG */ -static int rampstat_read_clear(const struct device *dev, uint32_t *rampstat_value) +static int rampstat_read_clear(const struct device *controller, uint32_t *rampstat_value) { int err; - err = tmc51xx_read(dev, TMC51XX_RAMPSTAT, rampstat_value); + err = tmc51xx_read(controller, TMC51XX_RAMPSTAT, rampstat_value); if (err == 0) { - err = tmc51xx_write(dev, TMC51XX_RAMPSTAT, *rampstat_value); + err = tmc51xx_write(controller, TMC51XX_RAMPSTAT, *rampstat_value); } return err; } @@ -242,10 +250,10 @@ static void rampstat_work_handler(struct k_work *work) { struct k_work_delayable *dwork = k_work_delayable_from_work(work); - struct tmc51xx_data *stepper_data = + struct tmc51xx_data *data = CONTAINER_OF(dwork, struct tmc51xx_data, rampstat_callback_dwork); - const struct device *dev = stepper_data->stepper; - __maybe_unused const struct tmc51xx_config *config = dev->config; + const struct device *dev = data->dev; + const struct tmc51xx_config *config = dev->config; __ASSERT_NO_MSG(dev); @@ -319,7 +327,7 @@ static void rampstat_work_handler(struct k_work *work) /* For UART or SPI without DIAG0, reschedule RAMPSTAT polling */ #ifdef CONFIG_STEPPER_ADI_TMC51XX_RAMPSTAT_POLL_INTERVAL_IN_MSEC k_work_reschedule( - &stepper_data->rampstat_callback_dwork, + &data->rampstat_callback_dwork, K_MSEC(CONFIG_STEPPER_ADI_TMC51XX_RAMPSTAT_POLL_INTERVAL_IN_MSEC)); #endif /* CONFIG_STEPPER_ADI_TMC51XX_RAMPSTAT_POLL_INTERVAL_IN_MSEC */ } @@ -332,45 +340,49 @@ static void __maybe_unused tmc51xx_diag0_gpio_callback_handler(const struct devi ARG_UNUSED(port); ARG_UNUSED(pins); - struct tmc51xx_data *stepper_data = CONTAINER_OF(cb, struct tmc51xx_data, diag0_cb); + struct tmc51xx_data *data = CONTAINER_OF(cb, struct tmc51xx_data, diag0_cb); - k_work_reschedule(&stepper_data->rampstat_callback_dwork, K_NO_WAIT); + k_work_reschedule(&data->rampstat_callback_dwork, K_NO_WAIT); } static int tmc51xx_stepper_enable(const struct device *dev) { LOG_DBG("Enabling Stepper motor controller %s", dev->name); + const struct tmc51xx_stepper_config *config = dev->config; uint32_t reg_value; int err; - err = tmc51xx_read(dev, TMC51XX_CHOPCONF, ®_value); + err = tmc51xx_read(config->controller, TMC51XX_CHOPCONF, ®_value); if (err != 0) { return -EIO; } reg_value |= TMC5XXX_CHOPCONF_DRV_ENABLE_MASK; - return tmc51xx_write(dev, TMC51XX_CHOPCONF, reg_value); + return tmc51xx_write(config->controller, TMC51XX_CHOPCONF, reg_value); } static int tmc51xx_stepper_disable(const struct device *dev) { LOG_DBG("Disabling Stepper motor controller %s", dev->name); + const struct tmc51xx_stepper_config *config = dev->config; uint32_t reg_value; int err; - err = tmc51xx_read(dev, TMC51XX_CHOPCONF, ®_value); + err = tmc51xx_read(config->controller, TMC51XX_CHOPCONF, ®_value); if (err != 0) { return -EIO; } reg_value &= ~TMC5XXX_CHOPCONF_DRV_ENABLE_MASK; - return tmc51xx_write(dev, TMC51XX_CHOPCONF, reg_value); + return tmc51xx_write(config->controller, TMC51XX_CHOPCONF, reg_value); } -static int tmc51xx_stepper_is_moving(const struct device *dev, bool *is_moving) +static int tmc51xx_stepper_is_moving(const struct device *dev, const uint8_t stepper_index, + bool *is_moving) { + CHECK_STEPPER_IDX(dev, stepper_index, TMC51XX_MAX_SUPPORTED_STEPPER); uint32_t reg_value; int err; @@ -406,10 +418,11 @@ int tmc51xx_stepper_set_max_velocity(const struct device *dev, uint32_t velocity static int tmc51xx_stepper_set_micro_step_res(const struct device *dev, enum stepper_micro_step_resolution res) { + const struct tmc51xx_stepper_config *config = dev->config; uint32_t reg_value; int err; - err = tmc51xx_read(dev, TMC51XX_CHOPCONF, ®_value); + err = tmc51xx_read(config->controller, TMC51XX_CHOPCONF, ®_value); if (err != 0) { return -EIO; } @@ -418,7 +431,7 @@ static int tmc51xx_stepper_set_micro_step_res(const struct device *dev, reg_value |= ((MICRO_STEP_RES_INDEX(STEPPER_MICRO_STEP_256) - LOG2(res)) << TMC5XXX_CHOPCONF_MRES_SHIFT); - err = tmc51xx_write(dev, TMC51XX_CHOPCONF, reg_value); + err = tmc51xx_write(config->controller, TMC51XX_CHOPCONF, reg_value); if (err != 0) { return -EIO; } @@ -431,10 +444,11 @@ static int tmc51xx_stepper_set_micro_step_res(const struct device *dev, static int tmc51xx_stepper_get_micro_step_res(const struct device *dev, enum stepper_micro_step_resolution *res) { + const struct tmc51xx_stepper_config *config = dev->config; uint32_t reg_value; int err; - err = tmc51xx_read(dev, TMC51XX_CHOPCONF, ®_value); + err = tmc51xx_read(config->controller, TMC51XX_CHOPCONF, ®_value); if (err != 0) { return -EIO; } @@ -445,8 +459,10 @@ static int tmc51xx_stepper_get_micro_step_res(const struct device *dev, return 0; } -static int tmc51xx_stepper_set_reference_position(const struct device *dev, const int32_t position) +static int tmc51xx_stepper_set_reference_position(const struct device *dev, + const uint8_t stepper_idx, const int32_t position) { + CHECK_STEPPER_IDX(dev, stepper_idx, TMC51XX_MAX_SUPPORTED_STEPPER); int err; err = tmc51xx_write(dev, TMC51XX_RAMPMODE, TMC5XXX_RAMPMODE_HOLD_MODE); @@ -473,7 +489,7 @@ static int read_actual_position(const struct device *dev, int32_t *position) if (config->comm_type == TMC_COMM_UART) { bool is_moving; - err = tmc51xx_stepper_is_moving(dev, &is_moving); + err = tmc51xx_stepper_is_moving(dev, 0, &is_moving); if (err != 0) { return -EIO; } @@ -494,8 +510,10 @@ static int read_actual_position(const struct device *dev, int32_t *position) return 0; } -static int tmc51xx_stepper_get_actual_position(const struct device *dev, int32_t *position) +static int tmc51xx_stepper_get_actual_position(const struct device *dev, const uint8_t stepper_idx, + int32_t *position) { + CHECK_STEPPER_IDX(dev, stepper_idx, TMC51XX_MAX_SUPPORTED_STEPPER); int err; err = read_actual_position(dev, position); @@ -506,31 +524,35 @@ static int tmc51xx_stepper_get_actual_position(const struct device *dev, int32_t return 0; } -static int tmc51xx_stepper_move_to(const struct device *dev, const int32_t micro_steps) +static int tmc51xx_stepper_move_to(const struct device *controller, const uint8_t stepper_idx, + const int32_t micro_steps) { - LOG_DBG("%s set target position to %d", dev->name, micro_steps); - const struct tmc51xx_config *config = dev->config; - struct tmc51xx_data *data = dev->data; + CHECK_STEPPER_IDX(controller, stepper_idx, TMC51XX_MAX_SUPPORTED_STEPPER); + LOG_DBG("%s set target position to %d", controller->name, micro_steps); + const struct tmc51xx_config *config = controller->config; + struct tmc51xx_data *data = controller->data; + const struct tmc51xx_stepper_config *stepper_config = config->steppers[0]->config; + struct tmc51xx_stepper_data *stepper_data = config->steppers[0]->data; int err; - if (config->is_sg_enabled) { - stallguard_enable(dev, false); + if (stepper_config->is_sg_enabled) { + stallguard_enable(controller, false); } - err = tmc51xx_write(dev, TMC51XX_RAMPMODE, TMC5XXX_RAMPMODE_POSITIONING_MODE); + err = tmc51xx_write(controller, TMC51XX_RAMPMODE, TMC5XXX_RAMPMODE_POSITIONING_MODE); if (err != 0) { return -EIO; } - err = tmc51xx_write(dev, TMC51XX_XTARGET, micro_steps); + err = tmc51xx_write(controller, TMC51XX_XTARGET, micro_steps); if (err != 0) { return -EIO; } - if (config->is_sg_enabled) { - k_work_reschedule(&data->stallguard_dwork, - K_MSEC(config->sg_velocity_check_interval_ms)); + if (stepper_config->is_sg_enabled) { + k_work_reschedule(&stepper_data->stallguard_dwork, + K_MSEC(stepper_config->sg_velocity_check_interval_ms)); } - if (data->callback) { + if (stepper_data->callback) { /* For SPI with DIAG0 pin, we use interrupt-driven approach */ IF_ENABLED(TMC51XX_BUS_SPI, ({ if (config->comm_type == TMC_COMM_SPI && config->diag0_gpio.port) { @@ -549,31 +571,37 @@ static int tmc51xx_stepper_move_to(const struct device *dev, const int32_t micro return 0; } -static int tmc51xx_stepper_move_by(const struct device *dev, const int32_t micro_steps) +static int tmc51xx_stepper_move_by(const struct device *controller, const uint8_t stepper_idx, + const int32_t micro_steps) { + CHECK_STEPPER_IDX(controller, stepper_idx, TMC51XX_MAX_SUPPORTED_STEPPER); int err; int32_t position; - err = tmc51xx_stepper_get_actual_position(dev, &position); + err = tmc51xx_stepper_get_actual_position(controller, stepper_idx, &position); if (err != 0) { return -EIO; } int32_t target_position = position + micro_steps; - LOG_DBG("%s moved to %d by steps: %d", dev->name, target_position, micro_steps); + LOG_DBG("%s moved to %d by steps: %d", controller->name, target_position, micro_steps); - return tmc51xx_stepper_move_to(dev, target_position); + return tmc51xx_stepper_move_to(controller, stepper_idx, target_position); } -static int tmc51xx_stepper_run(const struct device *dev, const enum stepper_direction direction) +static int tmc51xx_stepper_run(const struct device *controller, const uint8_t stepper_index, + const enum stepper_direction direction) { - LOG_DBG("Stepper motor controller %s run", dev->name); - const struct tmc51xx_config *config = dev->config; - struct tmc51xx_data *data = dev->data; + CHECK_STEPPER_IDX(controller, stepper_index, TMC51XX_MAX_SUPPORTED_STEPPER); + LOG_DBG("Stepper motor controller %s run", controller->name); + const struct tmc51xx_config *tmc51xx_config = controller->config; + struct tmc51xx_data *data = controller->data; + const struct tmc51xx_stepper_config *config = tmc51xx_config->steppers[0]->config; + struct tmc51xx_stepper_data *stepper_data = tmc51xx_config->steppers[0]->data; int err; if (config->is_sg_enabled) { - err = stallguard_enable(dev, false); + err = stallguard_enable(controller, false); if (err != 0) { return -EIO; } @@ -581,14 +609,16 @@ static int tmc51xx_stepper_run(const struct device *dev, const enum stepper_dire switch (direction) { case STEPPER_DIRECTION_POSITIVE: - err = tmc51xx_write(dev, TMC51XX_RAMPMODE, TMC5XXX_RAMPMODE_POSITIVE_VELOCITY_MODE); + err = tmc51xx_write(controller, TMC51XX_RAMPMODE, + TMC5XXX_RAMPMODE_POSITIVE_VELOCITY_MODE); if (err != 0) { return -EIO; } break; case STEPPER_DIRECTION_NEGATIVE: - err = tmc51xx_write(dev, TMC51XX_RAMPMODE, TMC5XXX_RAMPMODE_NEGATIVE_VELOCITY_MODE); + err = tmc51xx_write(controller, TMC51XX_RAMPMODE, + TMC5XXX_RAMPMODE_NEGATIVE_VELOCITY_MODE); if (err != 0) { return -EIO; } @@ -596,13 +626,13 @@ static int tmc51xx_stepper_run(const struct device *dev, const enum stepper_dire } if (config->is_sg_enabled) { - k_work_reschedule(&data->stallguard_dwork, + k_work_reschedule(&stepper_data->stallguard_dwork, K_MSEC(config->sg_velocity_check_interval_ms)); } - if (data->callback) { + if (stepper_data->callback) { /* For SPI with DIAG0 pin, we use interrupt-driven approach */ IF_ENABLED(TMC51XX_BUS_SPI, ({ - if (config->comm_type == TMC_COMM_SPI && config->diag0_gpio.port) { + if (tmc51xx_config->comm_type == TMC_COMM_SPI && tmc51xx_config->diag0_gpio.port) { /* Using interrupt-driven approach - no polling needed */ return 0; } @@ -620,9 +650,10 @@ static int tmc51xx_stepper_run(const struct device *dev, const enum stepper_dire #ifdef CONFIG_STEPPER_ADI_TMC51XX_RAMP_GEN -int tmc51xx_stepper_set_ramp(const struct device *dev, +int tmc51xx_stepper_set_ramp(const struct device *dev, const uint8_t stepper_index, const struct tmc_ramp_generator_data *ramp_data) { + CHECK_STEPPER_IDX(dev, stepper_index, TMC51XX_MAX_SUPPORTED_STEPPER); LOG_DBG("Stepper motor controller %s set ramp", dev->name); int err; @@ -737,26 +768,40 @@ static int tmc51xx_init(const struct device *dev) return -EIO; } - if (config->is_sg_enabled) { + k_work_init_delayable(&data->rampstat_callback_dwork, rampstat_work_handler); + return 0; +} + +static int tmc51xx_stepper_init(const struct device *dev) +{ + const struct tmc51xx_stepper_config *stepper_config = dev->config; + struct tmc51xx_stepper_data *data = dev->data; + int err; + + LOG_DBG("Controller: %s, Stepper: %s", stepper_config->controller->name, dev->name); + + if (stepper_config->is_sg_enabled) { k_work_init_delayable(&data->stallguard_dwork, stallguard_work_handler); - err = tmc51xx_write(dev, TMC51XX_SWMODE, BIT(10)); + err = tmc51xx_write(stepper_config->controller, + TMC50XX_SWMODE(stepper_config->index), BIT(10)); if (err != 0) { return -EIO; } - LOG_DBG("Setting stall guard to %d with delay %d ms", config->sg_threshold, - config->sg_velocity_check_interval_ms); - if (!IN_RANGE(config->sg_threshold, TMC5XXX_SG_MIN_VALUE, TMC5XXX_SG_MAX_VALUE)) { + LOG_DBG("Setting stall guard to %d with delay %d ms", stepper_config->sg_threshold, + stepper_config->sg_velocity_check_interval_ms); + if (!IN_RANGE(stepper_config->sg_threshold, TMC5XXX_SG_MIN_VALUE, + TMC5XXX_SG_MAX_VALUE)) { LOG_ERR("Stallguard threshold out of range"); return -EINVAL; } - int32_t stall_guard_threshold = (int32_t)config->sg_threshold; + int32_t stall_guard_threshold = (int32_t)stepper_config->sg_threshold; - err = tmc51xx_write(dev, TMC51XX_COOLCONF, - stall_guard_threshold - << TMC5XXX_COOLCONF_SG2_THRESHOLD_VALUE_SHIFT); + err = tmc51xx_write( + stepper_config->controller, TMC50XX_COOLCONF(stepper_config->index), + stall_guard_threshold << TMC5XXX_COOLCONF_SG2_THRESHOLD_VALUE_SHIFT); if (err != 0) { return -EIO; } @@ -764,25 +809,22 @@ static int tmc51xx_init(const struct device *dev) } #ifdef CONFIG_STEPPER_ADI_TMC51XX_RAMP_GEN - err = tmc51xx_stepper_set_ramp(dev, &config->default_ramp_config); + err = tmc51xx_stepper_set_ramp(stepper_config->controller, stepper_config->index, + &stepper_config->default_ramp_config); if (err != 0) { return -EIO; } #endif - - k_work_init_delayable(&data->rampstat_callback_dwork, rampstat_work_handler); - uint32_t rampstat_value; - (void)rampstat_read_clear(dev, &rampstat_value); - - err = tmc51xx_stepper_set_micro_step_res(dev, config->default_micro_step_res); + err = tmc51xx_stepper_set_micro_step_res(dev, stepper_config->default_micro_step_res); if (err != 0) { return -EIO; } return 0; } -static int tmc51xx_stepper_stop(const struct device *dev) +static int tmc51xx_stepper_stop(const struct device *dev, const uint8_t stepper_idx) { + CHECK_STEPPER_IDX(dev, stepper_idx, TMC51XX_MAX_SUPPORTED_STEPPER); int err; err = tmc51xx_write(dev, TMC51XX_RAMPMODE, TMC5XXX_RAMPMODE_POSITIVE_VELOCITY_MODE); @@ -798,13 +840,16 @@ static int tmc51xx_stepper_stop(const struct device *dev) return 0; } -static DEVICE_API(stepper, tmc51xx_api) = { +static DEVICE_API(stepper_drv, tmc51xx_drv_api) = { .enable = tmc51xx_stepper_enable, .disable = tmc51xx_stepper_disable, - .is_moving = tmc51xx_stepper_is_moving, - .move_by = tmc51xx_stepper_move_by, .set_micro_step_res = tmc51xx_stepper_set_micro_step_res, .get_micro_step_res = tmc51xx_stepper_get_micro_step_res, +}; + +static DEVICE_API(stepper, tmc51xx_api) = { + .is_moving = tmc51xx_stepper_is_moving, + .move_by = tmc51xx_stepper_move_by, .set_reference_position = tmc51xx_stepper_set_reference_position, .get_actual_position = tmc51xx_stepper_get_actual_position, .move_to = tmc51xx_stepper_move_to, @@ -813,6 +858,8 @@ static DEVICE_API(stepper, tmc51xx_api) = { .set_event_callback = tmc51xx_stepper_set_event_callback, }; +#define TMC51XX_STEPPER_PTRS(child) DEVICE_DT_GET(child), + /* Initializes a struct tmc51xx_config for an instance on a SPI bus. */ #define TMC51XX_CONFIG_SPI(inst) \ .comm_type = TMC_COMM_SPI, \ @@ -829,15 +876,46 @@ static DEVICE_API(stepper, tmc51xx_api) = { .bus_io = &tmc51xx_uart_bus_io, .uart_addr = DT_INST_PROP_OR(inst, uart_device_addr, 1U), \ .sw_sel_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, sw_sel_gpios, {0}) +#define TMC51XX_STEPPER_CONFIG_DEFINE(child) \ + COND_CODE_1(DT_PROP_EXISTS(child, stallguard_threshold_velocity), \ + BUILD_ASSERT(DT_PROP(child, stallguard_threshold_velocity), \ + "stallguard threshold velocity must be a positive value"), ()); \ + IF_ENABLED(CONFIG_STEPPER_ADI_TMC51XX_RAMP_GEN, (CHECK_RAMP_DT_DATA(child))); \ + static const struct tmc51xx_stepper_config tmc51xx_stepper_config_##child = { \ + .controller = DEVICE_DT_GET(DT_PARENT(child)), \ + .default_micro_step_res = DT_PROP(child, micro_step_res), \ + .index = DT_REG_ADDR(child), \ + .sg_threshold = DT_PROP(child, stallguard2_threshold), \ + .sg_threshold_velocity = DT_PROP(child, stallguard_threshold_velocity), \ + .sg_velocity_check_interval_ms = \ + DT_PROP(child, stallguard_velocity_check_interval_ms), \ + .is_sg_enabled = DT_PROP(child, activate_stallguard2), \ + IF_ENABLED(CONFIG_STEPPER_ADI_TMC51XX_RAMP_GEN, \ + (.default_ramp_config = TMC_RAMP_DT_SPEC_GET_TMC51XX(child))) }; + +#define TMC51XX_STEPPER_DATA_DEFINE(child) \ + static struct tmc51xx_stepper_data tmc51xx_stepper_data_##child = { \ + .stepper = DEVICE_DT_GET(child), \ + }; + +#define TMC51XX_STEPPER_DEFINE(child) \ + DEVICE_DT_DEFINE(child, tmc51xx_stepper_init, NULL, &tmc51xx_stepper_data_##child, \ + &tmc51xx_stepper_config_##child, POST_KERNEL, \ + CONFIG_STEPPER_INIT_PRIORITY, &tmc51xx_drv_api); + +#define TMC51XX_SHAFT_CONFIG(child) DT_PROP(child, invert_direction) << TMC51XX_GCONF_SHAFT_SHIFT + /* Device initialization macros */ #define TMC51XX_DEFINE(inst) \ + BUILD_ASSERT(DT_INST_CHILD_NUM(inst) <= 1, "tmc51xx can drive two steppers at max"); \ BUILD_ASSERT((DT_INST_PROP(inst, clock_frequency) > 0), \ "clock frequency must be non-zero positive value"); \ - static struct tmc51xx_data tmc51xx_data_##inst = { \ - .stepper = DEVICE_DT_GET(DT_DRV_INST(inst))}; \ + static struct tmc51xx_data tmc51xx_data_##inst = { \ + .dev = DEVICE_DT_INST_GET(inst), \ + }; \ COND_CODE_1(DT_PROP_EXISTS(inst, stallguard_threshold_velocity), \ BUILD_ASSERT(DT_PROP(inst, stallguard_threshold_velocity), \ - "stallguard threshold velocity must be a positive value"), ()); \ + "stallguard threshold velocity must be a positive value"), ()); \ IF_ENABLED(CONFIG_STEPPER_ADI_TMC51XX_RAMP_GEN, (CHECK_RAMP_DT_DATA(inst))); \ static const struct tmc51xx_config tmc51xx_config_##inst = {COND_CODE_1 \ (DT_INST_ON_BUS(inst, spi), \ @@ -845,19 +923,17 @@ static DEVICE_API(stepper, tmc51xx_api) = { (TMC51XX_CONFIG_UART(inst))), \ .gconf = ((DT_INST_PROP(inst, en_pwm_mode) << TMC51XX_GCONF_EN_PWM_MODE_SHIFT) | \ (DT_INST_PROP(inst, test_mode) << TMC51XX_GCONF_TEST_MODE_SHIFT) | \ - (DT_INST_PROP(inst, invert_direction) << TMC51XX_GCONF_SHAFT_SHIFT) | \ + DT_INST_FOREACH_CHILD(inst, TMC51XX_SHAFT_CONFIG) | \ (DT_INST_NODE_HAS_PROP(inst, diag0_gpios) \ ? BIT(TMC51XX_GCONF_DIAG0_INT_PUSHPULL_SHIFT) \ : 0)), \ - .clock_frequency = DT_INST_PROP(inst, clock_frequency), \ - .default_micro_step_res = DT_INST_PROP(inst, micro_step_res), \ - .sg_threshold = DT_INST_PROP(inst, stallguard2_threshold), \ - .sg_threshold_velocity = DT_INST_PROP(inst, stallguard_threshold_velocity), \ - .sg_velocity_check_interval_ms = \ - DT_INST_PROP(inst, stallguard_velocity_check_interval_ms), \ - .is_sg_enabled = DT_INST_PROP(inst, activate_stallguard2), \ - IF_ENABLED(CONFIG_STEPPER_ADI_TMC51XX_RAMP_GEN, \ - (.default_ramp_config = TMC_RAMP_DT_SPEC_GET_TMC51XX(inst)))}; \ + .steppers = (const struct device *[]){DT_INST_FOREACH_CHILD( \ + inst, TMC51XX_STEPPER_PTRS)}, \ + .clock_frequency = DT_INST_PROP(inst, clock_frequency), \ + }; \ + DT_INST_FOREACH_CHILD(inst, TMC51XX_STEPPER_CONFIG_DEFINE); \ + DT_INST_FOREACH_CHILD(inst, TMC51XX_STEPPER_DATA_DEFINE); \ + DT_INST_FOREACH_CHILD(inst, TMC51XX_STEPPER_DEFINE); \ DEVICE_DT_INST_DEFINE(inst, tmc51xx_init, NULL, &tmc51xx_data_##inst, \ &tmc51xx_config_##inst, POST_KERNEL, CONFIG_STEPPER_INIT_PRIORITY, \ &tmc51xx_api); diff --git a/drivers/stepper/adi_tmc/tmc51xx/tmc51xx.h b/drivers/stepper/adi_tmc/tmc51xx/tmc51xx.h index 43c26255dfd85..fe339d501c359 100644 --- a/drivers/stepper/adi_tmc/tmc51xx/tmc51xx.h +++ b/drivers/stepper/adi_tmc/tmc51xx/tmc51xx.h @@ -25,14 +25,7 @@ struct tmc51xx_config { uint8_t comm_type; const uint32_t gconf; const uint32_t clock_frequency; - const uint16_t default_micro_step_res; - const int8_t sg_threshold; - const bool is_sg_enabled; - const uint32_t sg_velocity_check_interval_ms; - const uint32_t sg_threshold_velocity; -#ifdef CONFIG_STEPPER_ADI_TMC51XX_RAMP_GEN - const struct tmc_ramp_generator_data default_ramp_config; -#endif + const struct device **steppers; #if TMC51XX_BUS_UART const struct gpio_dt_spec sw_sel_gpio; uint8_t uart_addr; @@ -44,9 +37,27 @@ struct tmc51xx_config { struct tmc51xx_data { struct k_sem sem; - struct k_work_delayable stallguard_dwork; - struct k_work_delayable rampstat_callback_dwork; + const struct device *dev; struct gpio_callback diag0_cb; + struct k_work_delayable rampstat_callback_dwork; +}; + +struct tmc51xx_stepper_config { + const uint8_t index; + const uint16_t default_micro_step_res; + const int8_t sg_threshold; + const bool is_sg_enabled; + const uint32_t sg_velocity_check_interval_ms; + const uint32_t sg_threshold_velocity; + /* parent controller required for bus communication */ + const struct device *controller; +#ifdef CONFIG_STEPPER_ADI_TMC51XX_RAMP_GEN + const struct tmc_ramp_generator_data default_ramp_config; +#endif +}; + +struct tmc51xx_stepper_data { + struct k_work_delayable stallguard_dwork; const struct device *stepper; stepper_event_callback_t callback; void *event_cb_user_data; diff --git a/drivers/stepper/fake_stepper_controller.c b/drivers/stepper/fake_stepper_controller.c index 3abce9726c504..72a0aed20d281 100644 --- a/drivers/stepper/fake_stepper_controller.c +++ b/drivers/stepper/fake_stepper_controller.c @@ -12,74 +12,51 @@ #include #endif /* CONFIG_ZTEST */ -#define DT_DRV_COMPAT zephyr_fake_stepper +#define DT_DRV_COMPAT zephyr_fake_stepper_controller -struct fake_stepper_data { - enum stepper_micro_step_resolution micro_step_res; +struct fake_stepper_controller_data { int32_t actual_position; }; -DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_enable, const struct device *); +DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_is_moving, const struct device *, uint8_t, bool *); -DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_disable, const struct device *); +DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_move_by, const struct device *, uint8_t, int32_t); -DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_is_moving, const struct device *, bool *); +DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_set_microstep_interval, const struct device *, uint8_t, + uint64_t); -DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_move_by, const struct device *, int32_t); +DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_set_reference_position, const struct device *, uint8_t, + int32_t); -DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_set_microstep_interval, const struct device *, uint64_t); +DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_get_actual_position, const struct device *, uint8_t, + int32_t *); -DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_set_micro_step_res, const struct device *, - enum stepper_micro_step_resolution); +DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_move_to, const struct device *, uint8_t, int32_t); -DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_get_micro_step_res, const struct device *, - enum stepper_micro_step_resolution *); +DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_run, const struct device *, uint8_t, + enum stepper_direction); -DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_set_reference_position, const struct device *, int32_t); +DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_stop, const struct device *, uint8_t); -DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_get_actual_position, const struct device *, int32_t *); - -DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_move_to, const struct device *, int32_t); - -DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_run, const struct device *, enum stepper_direction); - -DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_stop, const struct device *); - -DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_set_event_callback, const struct device *, +DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_set_event_callback, const struct device *, uint8_t, stepper_event_callback_t, void *); -static int fake_stepper_set_micro_step_res_delegate(const struct device *dev, - const enum stepper_micro_step_resolution res) -{ - struct fake_stepper_data *data = dev->data; - - data->micro_step_res = res; - - return 0; -} - -static int fake_stepper_get_micro_step_res_delegate(const struct device *dev, - enum stepper_micro_step_resolution *res) -{ - struct fake_stepper_data *data = dev->data; - - *res = data->micro_step_res; - - return 0; -} - -static int fake_stepper_set_reference_position_delegate(const struct device *dev, const int32_t pos) +static int fake_stepper_set_reference_position_delegate(const struct device *dev, + uint8_t stepper_idx, const int32_t pos) { - struct fake_stepper_data *data = dev->data; + ARG_UNUSED(stepper_idx); + struct fake_stepper_controller_data *data = dev->data; data->actual_position = pos; return 0; } -static int fake_stepper_get_actual_position_delegate(const struct device *dev, int32_t *pos) +static int fake_stepper_get_actual_position_delegate(const struct device *dev, uint8_t stepper_idx, + int32_t *pos) { - struct fake_stepper_data *data = dev->data; + ARG_UNUSED(stepper_idx); + struct fake_stepper_controller_data *data = dev->data; *pos = data->actual_position; @@ -87,18 +64,15 @@ static int fake_stepper_get_actual_position_delegate(const struct device *dev, i } #ifdef CONFIG_ZTEST -static void fake_stepper_reset_rule_before(const struct ztest_unit_test *test, void *fixture) +static void fake_stepper_controller_reset_rule_before(const struct ztest_unit_test *test, + void *fixture) { ARG_UNUSED(test); ARG_UNUSED(fixture); - RESET_FAKE(fake_stepper_enable); - RESET_FAKE(fake_stepper_disable); RESET_FAKE(fake_stepper_move_by); RESET_FAKE(fake_stepper_is_moving); RESET_FAKE(fake_stepper_set_microstep_interval); - RESET_FAKE(fake_stepper_set_micro_step_res); - RESET_FAKE(fake_stepper_get_micro_step_res); RESET_FAKE(fake_stepper_set_reference_position); RESET_FAKE(fake_stepper_get_actual_position); RESET_FAKE(fake_stepper_move_to); @@ -106,21 +80,17 @@ static void fake_stepper_reset_rule_before(const struct ztest_unit_test *test, v RESET_FAKE(fake_stepper_stop); /* Install custom fakes for the setter and getter functions */ - fake_stepper_set_micro_step_res_fake.custom_fake = fake_stepper_set_micro_step_res_delegate; - fake_stepper_get_micro_step_res_fake.custom_fake = fake_stepper_get_micro_step_res_delegate; fake_stepper_set_reference_position_fake.custom_fake = fake_stepper_set_reference_position_delegate; fake_stepper_get_actual_position_fake.custom_fake = fake_stepper_get_actual_position_delegate; } -ZTEST_RULE(fake_stepper_reset_rule, fake_stepper_reset_rule_before, NULL); +ZTEST_RULE(fake_stepper_controller_reset_rule, fake_stepper_controller_reset_rule_before, NULL); #endif /* CONFIG_ZTEST */ -static int fake_stepper_init(const struct device *dev) +static int fake_stepper_controller_init(const struct device *dev) { - fake_stepper_set_micro_step_res_fake.custom_fake = fake_stepper_set_micro_step_res_delegate; - fake_stepper_get_micro_step_res_fake.custom_fake = fake_stepper_get_micro_step_res_delegate; fake_stepper_set_reference_position_fake.custom_fake = fake_stepper_set_reference_position_delegate; fake_stepper_get_actual_position_fake.custom_fake = @@ -129,14 +99,10 @@ static int fake_stepper_init(const struct device *dev) return 0; } -static DEVICE_API(stepper, fake_stepper_driver_api) = { - .enable = fake_stepper_enable, - .disable = fake_stepper_disable, +static DEVICE_API(stepper, fake_stepper_controller_api) = { .move_by = fake_stepper_move_by, .is_moving = fake_stepper_is_moving, .set_microstep_interval = fake_stepper_set_microstep_interval, - .set_micro_step_res = fake_stepper_set_micro_step_res, - .get_micro_step_res = fake_stepper_get_micro_step_res, .set_reference_position = fake_stepper_set_reference_position, .get_actual_position = fake_stepper_get_actual_position, .move_to = fake_stepper_move_to, @@ -145,12 +111,10 @@ static DEVICE_API(stepper, fake_stepper_driver_api) = { .set_event_callback = fake_stepper_set_event_callback, }; -#define FAKE_STEPPER_INIT(inst) \ - \ - static struct fake_stepper_data fake_stepper_data_##inst; \ - \ - DEVICE_DT_INST_DEFINE(inst, fake_stepper_init, NULL, &fake_stepper_data_##inst, NULL, \ - POST_KERNEL, CONFIG_STEPPER_INIT_PRIORITY, \ - &fake_stepper_driver_api); +#define FAKE_STEPPER_CONTROLLER_INIT(inst) \ + static struct fake_stepper_controller_data fake_stepper_controller_data_##inst; \ + DEVICE_DT_INST_DEFINE(inst, fake_stepper_controller_init, NULL, \ + &fake_stepper_controller_data_##inst, NULL, POST_KERNEL, \ + CONFIG_STEPPER_INIT_PRIORITY, &fake_stepper_controller_api); -DT_INST_FOREACH_STATUS_OKAY(FAKE_STEPPER_INIT) +DT_INST_FOREACH_STATUS_OKAY(FAKE_STEPPER_CONTROLLER_INIT) diff --git a/drivers/stepper/fake_stepper_drv.c b/drivers/stepper/fake_stepper_drv.c new file mode 100644 index 0000000000000..b3738df66d9db --- /dev/null +++ b/drivers/stepper/fake_stepper_drv.c @@ -0,0 +1,92 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2024 Fabian Blatz + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#ifdef CONFIG_ZTEST +#include +#endif /* CONFIG_ZTEST */ + +#define DT_DRV_COMPAT zephyr_fake_stepper + +struct fake_stepper_data { + enum stepper_micro_step_resolution micro_step_res; +}; + +DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_enable, const struct device *); + +DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_disable, const struct device *); + +DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_set_micro_step_res, const struct device *, + enum stepper_micro_step_resolution); + +DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_get_micro_step_res, const struct device *, + enum stepper_micro_step_resolution *); + +static int fake_stepper_set_micro_step_res_delegate(const struct device *dev, + const enum stepper_micro_step_resolution res) +{ + struct fake_stepper_data *data = dev->data; + + data->micro_step_res = res; + + return 0; +} + +static int fake_stepper_get_micro_step_res_delegate(const struct device *dev, + enum stepper_micro_step_resolution *res) +{ + struct fake_stepper_data *data = dev->data; + + *res = data->micro_step_res; + + return 0; +} + +#ifdef CONFIG_ZTEST +static void fake_stepper_reset_rule_before(const struct ztest_unit_test *test, void *fixture) +{ + ARG_UNUSED(test); + ARG_UNUSED(fixture); + + RESET_FAKE(fake_stepper_enable); + RESET_FAKE(fake_stepper_disable); + RESET_FAKE(fake_stepper_set_micro_step_res); + RESET_FAKE(fake_stepper_get_micro_step_res); + + /* Install custom fakes for the setter and getter functions */ + fake_stepper_set_micro_step_res_fake.custom_fake = fake_stepper_set_micro_step_res_delegate; + fake_stepper_get_micro_step_res_fake.custom_fake = fake_stepper_get_micro_step_res_delegate; +} + +ZTEST_RULE(fake_stepper_reset_rule, fake_stepper_reset_rule_before, NULL); +#endif /* CONFIG_ZTEST */ + +static int fake_stepper_init(const struct device *dev) +{ + fake_stepper_set_micro_step_res_fake.custom_fake = fake_stepper_set_micro_step_res_delegate; + fake_stepper_get_micro_step_res_fake.custom_fake = fake_stepper_get_micro_step_res_delegate; + return 0; +} + +static DEVICE_API(stepper_drv, fake_stepper_driver_api) = { + .enable = fake_stepper_enable, + .disable = fake_stepper_disable, + .set_micro_step_res = fake_stepper_set_micro_step_res, + .get_micro_step_res = fake_stepper_get_micro_step_res, +}; + +#define FAKE_STEPPER_INIT(inst) \ + \ + static struct fake_stepper_data fake_stepper_data_##inst; \ + \ + DEVICE_DT_INST_DEFINE(inst, fake_stepper_init, NULL, &fake_stepper_data_##inst, NULL, \ + POST_KERNEL, CONFIG_STEPPER_INIT_PRIORITY, \ + &fake_stepper_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(FAKE_STEPPER_INIT) diff --git a/drivers/stepper/stepper_shell.c b/drivers/stepper/stepper_shell.c index e18da9acb553c..fe800f252cc62 100644 --- a/drivers/stepper/stepper_shell.c +++ b/drivers/stepper/stepper_shell.c @@ -14,8 +14,9 @@ LOG_MODULE_REGISTER(stepper_shell, CONFIG_STEPPER_LOG_LEVEL); enum { ARG_IDX_DEV = 1, - ARG_IDX_PARAM = 2, - ARG_IDX_VALUE = 3, + ARG_IDX_DEV_IDX = 2, + ARG_IDX_PARAM = 3, + ARG_IDX_VALUE = 4, }; struct stepper_microstep_map { @@ -40,8 +41,8 @@ struct stepper_direction_map { .microstep = _microstep, \ } -static void print_callback(const struct device *dev, const enum stepper_event event, - void *user_data) +static void print_callback(const struct device *dev, const uint8_t stepper_idx, + const enum stepper_event event, void *user_data) { const struct shell *sh = user_data; if (!sh) { @@ -185,7 +186,7 @@ static int cmd_stepper_enable(const struct shell *sh, size_t argc, char **argv) return err; } - err = stepper_enable(dev); + err = stepper_drv_enable(dev); if (err) { shell_error(sh, "Error: %d", err); } @@ -203,7 +204,7 @@ static int cmd_stepper_disable(const struct shell *sh, size_t argc, char **argv) return err; } - err = stepper_disable(dev); + err = stepper_drv_disable(dev); if (err) { shell_error(sh, "Error: %d", err); } @@ -240,6 +241,7 @@ static int cmd_stepper_move_by(const struct shell *sh, size_t argc, char **argv) const struct device *dev; int err = 0; + const uint8_t stepper_index = shell_strtol(argv[ARG_IDX_DEV_IDX], 10, &err); int32_t micro_steps = shell_strtol(argv[ARG_IDX_PARAM], 10, &err); if (err < 0) { @@ -251,12 +253,12 @@ static int cmd_stepper_move_by(const struct shell *sh, size_t argc, char **argv) return err; } - err = stepper_set_event_callback(dev, print_callback, (void *)sh); + err = stepper_set_event_callback(dev, stepper_index, print_callback, (void *)sh); if (err != 0) { shell_error(sh, "Failed to set callback: %d", err); } - err = stepper_move_by(dev, micro_steps); + err = stepper_move_by(dev, stepper_index, micro_steps); if (err) { shell_error(sh, "Error: %d", err); } @@ -268,6 +270,7 @@ static int cmd_stepper_set_microstep_interval(const struct shell *sh, size_t arg { const struct device *dev; int err = 0; + const uint8_t stepper_index = shell_strtol(argv[ARG_IDX_DEV_IDX], 10, &err); uint64_t step_interval = shell_strtoull(argv[ARG_IDX_PARAM], 10, &err); if (err < 0) { @@ -279,7 +282,7 @@ static int cmd_stepper_set_microstep_interval(const struct shell *sh, size_t arg return err; } - err = stepper_set_microstep_interval(dev, step_interval); + err = stepper_set_microstep_interval(dev, stepper_index, step_interval); if (err) { shell_error(sh, "Error: %d", err); } @@ -310,7 +313,7 @@ static int cmd_stepper_set_micro_step_res(const struct shell *sh, size_t argc, c return err; } - err = stepper_set_micro_step_res(dev, resolution); + err = stepper_drv_set_micro_stepper_res(dev, resolution); if (err) { shell_error(sh, "Error: %d", err); } @@ -329,7 +332,7 @@ static int cmd_stepper_get_micro_step_res(const struct shell *sh, size_t argc, c return err; } - err = stepper_get_micro_step_res(dev, µ_step_res); + err = stepper_drv_get_micro_step_res(dev, µ_step_res); if (err < 0) { shell_warn(sh, "Failed to get micro-step resolution: %d", err); } else { @@ -343,6 +346,7 @@ static int cmd_stepper_set_reference_position(const struct shell *sh, size_t arg { const struct device *dev; int err = 0; + const uint8_t stepper_index = shell_strtol(argv[ARG_IDX_DEV_IDX], 10, &err); int32_t position = shell_strtol(argv[ARG_IDX_PARAM], 10, &err); if (err < 0) { @@ -354,7 +358,7 @@ static int cmd_stepper_set_reference_position(const struct shell *sh, size_t arg return err; } - err = stepper_set_reference_position(dev, position); + err = stepper_set_reference_position(dev, stepper_index, position); if (err) { shell_error(sh, "Error: %d", err); } @@ -366,6 +370,7 @@ static int cmd_stepper_get_actual_position(const struct shell *sh, size_t argc, { const struct device *dev; int err; + const uint8_t stepper_index = shell_strtol(argv[ARG_IDX_DEV_IDX], 10, &err); int32_t actual_position; err = parse_device_arg(sh, argv, &dev); @@ -373,7 +378,7 @@ static int cmd_stepper_get_actual_position(const struct shell *sh, size_t argc, return err; } - err = stepper_get_actual_position(dev, &actual_position); + err = stepper_get_actual_position(dev, stepper_index, &actual_position); if (err < 0) { shell_warn(sh, "Failed to get actual position: %d", err); } else { @@ -387,6 +392,7 @@ static int cmd_stepper_move_to(const struct shell *sh, size_t argc, char **argv) { const struct device *dev; int err = 0; + const uint8_t stepper_index = shell_strtol(argv[ARG_IDX_DEV_IDX], 10, &err); const int32_t position = shell_strtol(argv[ARG_IDX_PARAM], 10, &err); if (err < 0) { @@ -398,12 +404,12 @@ static int cmd_stepper_move_to(const struct shell *sh, size_t argc, char **argv) return err; } - err = stepper_set_event_callback(dev, print_callback, (void *)sh); + err = stepper_set_event_callback(dev, stepper_index, print_callback, (void *)sh); if (err != 0) { shell_error(sh, "Failed to set callback: %d", err); } - err = stepper_move_to(dev, position); + err = stepper_move_to(dev, stepper_index, position); if (err) { shell_error(sh, "Error: %d", err); } @@ -415,6 +421,7 @@ static int cmd_stepper_run(const struct shell *sh, size_t argc, char **argv) { const struct device *dev; int err = -EINVAL; + const uint8_t stepper_index = shell_strtol(argv[ARG_IDX_DEV_IDX], 10, &err); enum stepper_direction direction = STEPPER_DIRECTION_POSITIVE; for (int i = 0; i < ARRAY_SIZE(stepper_direction_map); i++) { @@ -434,12 +441,12 @@ static int cmd_stepper_run(const struct shell *sh, size_t argc, char **argv) return err; } - err = stepper_set_event_callback(dev, print_callback, (void *)sh); + err = stepper_set_event_callback(dev, stepper_index, print_callback, (void *)sh); if (err != 0) { shell_error(sh, "Failed to set callback: %d", err); } - err = stepper_run(dev, direction); + err = stepper_run(dev, stepper_index, direction); if (err) { shell_error(sh, "Error: %d", err); return err; @@ -448,13 +455,13 @@ static int cmd_stepper_run(const struct shell *sh, size_t argc, char **argv) return 0; } -static int cmd_stepper_info(const struct shell *sh, size_t argc, char **argv) +static int cmd_stepper_control_info(const struct shell *sh, size_t argc, char **argv) { const struct device *dev; int err; bool is_moving; int32_t actual_position; - enum stepper_micro_step_resolution micro_step_res; + const uint8_t stepper_index = shell_strtol(argv[ARG_IDX_DEV_IDX], 10, &err); err = parse_device_arg(sh, argv, &dev); if (err < 0) { @@ -464,25 +471,44 @@ static int cmd_stepper_info(const struct shell *sh, size_t argc, char **argv) shell_print(sh, "Stepper Info:"); shell_print(sh, "Device: %s", dev->name); - err = stepper_get_actual_position(dev, &actual_position); + err = stepper_get_actual_position(dev, stepper_index, &actual_position); if (err < 0) { shell_warn(sh, "Failed to get actual position: %d", err); } else { shell_print(sh, "Actual Position: %d", actual_position); } - err = stepper_get_micro_step_res(dev, µ_step_res); + err = stepper_is_moving(dev, stepper_index, &is_moving); if (err < 0) { - shell_warn(sh, "Failed to get micro-step resolution: %d", err); + shell_warn(sh, "Failed to check if the motor is moving: %d", err); } else { - shell_print(sh, "Micro-step Resolution: %d", micro_step_res); + shell_print(sh, "Is Moving: %s", is_moving ? "Yes" : "No"); + } + + return 0; +} + +static int cmd_stepper_info(const struct shell *sh, size_t argc, char **argv) +{ + const struct device *dev; + int err; + bool is_moving; + int32_t actual_position; + enum stepper_micro_step_resolution micro_step_res; + + err = parse_device_arg(sh, argv, &dev); + if (err < 0) { + return err; } - err = stepper_is_moving(dev, &is_moving); + shell_print(sh, "Stepper Info:"); + shell_print(sh, "Device: %s", dev->name); + + err = stepper_drv_get_micro_step_res(dev, µ_step_res); if (err < 0) { - shell_warn(sh, "Failed to check if the motor is moving: %d", err); + shell_warn(sh, "Failed to get micro-step resolution: %d", err); } else { - shell_print(sh, "Is Moving: %s", is_moving ? "Yes" : "No"); + shell_print(sh, "Micro-step Resolution: %d", micro_step_res); } return 0; @@ -497,19 +523,21 @@ SHELL_STATIC_SUBCMD_SET_CREATE( SHELL_CMD_ARG(get_micro_step_res, &dsub_pos_stepper_motor_name, "", cmd_stepper_get_micro_step_res, 2, 0), SHELL_CMD_ARG(set_reference_position, &dsub_pos_stepper_motor_name, " ", - cmd_stepper_set_reference_position, 3, 0), + cmd_stepper_set_reference_position, 4, 0), SHELL_CMD_ARG(get_actual_position, &dsub_pos_stepper_motor_name, "", - cmd_stepper_get_actual_position, 2, 0), + cmd_stepper_get_actual_position, 3, 0), SHELL_CMD_ARG(set_microstep_interval, &dsub_pos_stepper_motor_name, - " ", cmd_stepper_set_microstep_interval, 3, 0), + " ", cmd_stepper_set_microstep_interval, 4, 0), SHELL_CMD_ARG(move_by, &dsub_pos_stepper_motor_name, " ", - cmd_stepper_move_by, 3, 0), + cmd_stepper_move_by, 4, 0), SHELL_CMD_ARG(move_to, &dsub_pos_stepper_motor_name, " ", - cmd_stepper_move_to, 3, 0), + cmd_stepper_move_to, 4, 0), SHELL_CMD_ARG(run, &dsub_pos_stepper_motor_name_dir, " ", - cmd_stepper_run, 3, 0), - SHELL_CMD_ARG(stop, &dsub_pos_stepper_motor_name, "", cmd_stepper_stop, 2, 0), - SHELL_CMD_ARG(info, &dsub_pos_stepper_motor_name, "", cmd_stepper_info, 2, 0), + cmd_stepper_run, 4, 0), + SHELL_CMD_ARG(stop, &dsub_pos_stepper_motor_name, "", cmd_stepper_stop, 3, 0), + SHELL_CMD_ARG(info, &dsub_pos_stepper_motor_name, "", cmd_stepper_control_info, 3, + 0), + SHELL_CMD_ARG(info, &dsub_pos_stepper_motor_name, "", cmd_stepper_info, 3, 0), SHELL_SUBCMD_SET_END); SHELL_CMD_REGISTER(stepper, &stepper_cmds, "Stepper motor commands", NULL); diff --git a/drivers/stepper/zephyr_stepper_motion_controller.c b/drivers/stepper/zephyr_stepper_motion_controller.c index a6b65124c4efe..12cc89a73f910 100644 --- a/drivers/stepper/zephyr_stepper_motion_controller.c +++ b/drivers/stepper/zephyr_stepper_motion_controller.c @@ -14,6 +14,9 @@ #include LOG_MODULE_REGISTER(stepper_motion_control, CONFIG_STEPPER_LOG_LEVEL); +/* Currently only one stepper is supported by the motion controller */ +#define MAX_SUPPORTED_STEPPER 1 + struct stepper_motion_control_config { const struct device *stepper; const struct timing_source_config timing_config; @@ -48,7 +51,7 @@ void stepper_trigger_callback(const struct device *dev, enum stepper_event event } if (!k_is_in_isr()) { - data->callback(dev, event, data->event_cb_user_data); + data->callback(dev, 0, event, data->event_cb_user_data); return; } @@ -61,6 +64,7 @@ void stepper_trigger_callback(const struct device *dev, enum stepper_event event } ret = k_work_submit(&data->event_callback_work); + if (ret < 0) { LOG_ERR("Failed to submit work item: %d", ret); } @@ -84,7 +88,8 @@ static void stepper_work_event_handler(struct k_work *work) /* Run the callback */ if (data->callback != NULL) { - data->callback(data->dev, event, data->event_cb_user_data); + /* using MAX_SUPPORTED_STEPPER as currently only one stepper is supported */ + data->callback(data->dev, 0, event, data->event_cb_user_data); } /* If there are more pending events, resubmit this work item to handle them */ @@ -107,8 +112,11 @@ static void update_direction_from_step_count(const struct device *dev) } } -static int z_stepper_motion_control_move_by(const struct device *dev, int32_t micro_steps) +static int z_stepper_motion_control_move_by(const struct device *dev, const uint8_t stepper_idx, + int32_t micro_steps) { + CHECK_STEPPER_IDX(dev, stepper_idx, MAX_SUPPORTED_STEPPER); + const struct stepper_motion_control_config *config = dev->config; struct stepper_motion_control_data *data = dev->data; @@ -135,20 +143,25 @@ static int z_stepper_motion_control_move_by(const struct device *dev, int32_t mi return 0; } -static int z_stepper_motion_control_move_to(const struct device *dev, int32_t micro_steps) +static int z_stepper_motion_control_move_to(const struct device *dev, const uint8_t stepper_idx, + int32_t micro_steps) { + CHECK_STEPPER_IDX(dev, stepper_idx, MAX_SUPPORTED_STEPPER); + struct stepper_motion_control_data *data = dev->data; int32_t steps_to_move; K_SPINLOCK(&data->lock) { steps_to_move = micro_steps - data->actual_position; } - return z_stepper_motion_control_move_by(dev, steps_to_move); + return z_stepper_motion_control_move_by(dev, stepper_idx, steps_to_move); } -static int z_stepper_motion_control_run(const struct device *dev, +static int z_stepper_motion_control_run(const struct device *dev, const uint8_t stepper_idx, const enum stepper_direction direction) { + CHECK_STEPPER_IDX(dev, stepper_idx, MAX_SUPPORTED_STEPPER); + const struct stepper_motion_control_config *config = dev->config; struct stepper_motion_control_data *data = dev->data; @@ -163,8 +176,10 @@ static int z_stepper_motion_control_run(const struct device *dev, return 0; } -static int z_stepper_motion_control_stop(const struct device *dev) +static int z_stepper_motion_control_stop(const struct device *dev, const uint8_t stepper_idx) { + CHECK_STEPPER_IDX(dev, stepper_idx, MAX_SUPPORTED_STEPPER); + const struct stepper_motion_control_config *config = dev->config; struct stepper_motion_control_data *data = dev->data; int ret; @@ -180,8 +195,11 @@ static int z_stepper_motion_control_stop(const struct device *dev) } static int z_stepper_motion_control_set_reference_position(const struct device *dev, + const uint8_t stepper_idx, int32_t position) { + CHECK_STEPPER_IDX(dev, stepper_idx, MAX_SUPPORTED_STEPPER); + struct stepper_motion_control_data *data = dev->data; K_SPINLOCK(&data->lock) { @@ -190,8 +208,12 @@ static int z_stepper_motion_control_set_reference_position(const struct device * return 0; } -static int z_stepper_motion_control_get_actual_position(const struct device *dev, int32_t *position) +static int z_stepper_motion_control_get_actual_position(const struct device *dev, + const uint8_t stepper_idx, + int32_t *position) { + CHECK_STEPPER_IDX(dev, stepper_idx, MAX_SUPPORTED_STEPPER); + struct stepper_motion_control_data *data = dev->data; K_SPINLOCK(&data->lock) { @@ -201,8 +223,11 @@ static int z_stepper_motion_control_get_actual_position(const struct device *dev } static int z_stepper_motion_control_set_step_interval(const struct device *dev, + const uint8_t stepper_idx, uint64_t microstep_interval_ns) { + CHECK_STEPPER_IDX(dev, stepper_idx, MAX_SUPPORTED_STEPPER); + const struct stepper_motion_control_config *config = dev->config; struct stepper_motion_control_data *data = dev->data; @@ -220,8 +245,11 @@ static int z_stepper_motion_control_set_step_interval(const struct device *dev, return 0; } -static int z_stepper_motion_control_is_moving(const struct device *dev, bool *is_moving) +static int z_stepper_motion_control_is_moving(const struct device *dev, const uint8_t stepper_idx, + bool *is_moving) { + CHECK_STEPPER_IDX(dev, stepper_idx, MAX_SUPPORTED_STEPPER); + const struct stepper_motion_control_config *config = dev->config; struct stepper_motion_control_data *data = dev->data; @@ -283,8 +311,11 @@ static void velocity_mode_task(const struct device *dev) } static int z_stepper_motion_control_set_event_callback(const struct device *dev, + const uint8_t stepper_idx, stepper_event_callback_t cb, void *user_data) { + CHECK_STEPPER_IDX(dev, stepper_idx, MAX_SUPPORTED_STEPPER); + struct stepper_motion_control_data *data = dev->data; K_SPINLOCK(&data->lock) { @@ -313,36 +344,6 @@ void stepper_handle_timing_signal(const struct device *dev) } } -static int z_stepper_motion_control_enable(const struct device *dev) -{ - const struct stepper_motion_control_config *config = dev->config; - - return stepper_drv_enable(config->stepper); -} - -static int z_stepper_motion_control_disable(const struct device *dev) -{ - const struct stepper_motion_control_config *config = dev->config; - - return stepper_drv_disable(config->stepper); -} - -static int z_stepper_motion_control_set_micro_step_res(const struct device *dev, - const enum stepper_micro_step_resolution res) -{ - const struct stepper_motion_control_config *config = dev->config; - - return stepper_drv_set_micro_stepper_res(config->stepper, res); -} - -static int z_stepper_motion_control_get_micro_step_res(const struct device *dev, - enum stepper_micro_step_resolution *res) -{ - const struct stepper_motion_control_config *config = dev->config; - - return stepper_drv_get_micro_step_res(config->stepper, res); -} - static int stepper_motion_control_init(const struct device *dev) { const struct stepper_motion_control_config *config = dev->config; @@ -371,10 +372,6 @@ static int stepper_motion_control_init(const struct device *dev) } static DEVICE_API(stepper, zephyr_stepper_motion_control_api) = { - .enable = z_stepper_motion_control_enable, - .disable = z_stepper_motion_control_disable, - .set_micro_step_res = z_stepper_motion_control_set_micro_step_res, - .get_micro_step_res = z_stepper_motion_control_get_micro_step_res, .move_to = z_stepper_motion_control_move_to, .move_by = z_stepper_motion_control_move_by, .run = z_stepper_motion_control_run, diff --git a/dts/bindings/stepper/adi/adi,tmc51xx-base.yaml b/dts/bindings/stepper/adi/adi,tmc51xx-base.yaml index 62def383cbaf0..01b040eaef7bf 100644 --- a/dts/bindings/stepper/adi/adi,tmc51xx-base.yaml +++ b/dts/bindings/stepper/adi/adi,tmc51xx-base.yaml @@ -9,31 +9,6 @@ include: property-allowlist: - en-pwm-mode - test-mode - - name: stepper.yaml - - name: adi,trinamic-ramp-generator.yaml - property-allowlist: - - vstart - - a1 - - v1 - - amax - - vmax - - dmax - - d1 - - vstop - - tzerowait - - thigh - - tcoolthrs - - tpwmthrs - - tpowerdown - - ihold - - irun - - iholddelay - - name: adi,trinamic-stallguard.yaml - property-allowlist: - - activate-stallguard2 - - stallguard2-threshold - - stallguard-threshold-velocity - - stallguard-velocity-check-interval-ms properties: "#address-cells": @@ -53,3 +28,34 @@ properties: Hint: µstep velocity v[Hz] µsteps / s v[Hz] = v[51xx] * ( fCLK[Hz]/2 / 2^23 ) where v[51xx] is the value written to the TMC51XX. + +child-binding: + include: + - name: stepper.yaml + - name: base.yaml + property-allowlist: + - reg + - name: adi,trinamic-ramp-generator.yaml + property-allowlist: + - vstart + - a1 + - v1 + - amax + - vmax + - dmax + - d1 + - vstop + - tzerowait + - thigh + - tcoolthrs + - tpwmthrs + - tpowerdown + - ihold + - irun + - iholddelay + - name: adi,trinamic-stallguard.yaml + property-allowlist: + - activate-stallguard2 + - stallguard2-threshold + - stallguard-threshold-velocity + - stallguard-velocity-check-interval-ms diff --git a/dts/bindings/stepper/zephyr,fake-stepper-controller.yaml b/dts/bindings/stepper/zephyr,fake-stepper-controller.yaml new file mode 100644 index 0000000000000..d7feab7961328 --- /dev/null +++ b/dts/bindings/stepper/zephyr,fake-stepper-controller.yaml @@ -0,0 +1,7 @@ +# Copyright (c) 2025 Jilay Sandeep Pandya +# SPDX-License-Identifier: Apache-2.0 + +description: | + A fake stepper controller for use as either a stub or a mock in testing. + +compatible: "zephyr,fake-stepper-controller" diff --git a/dts/bindings/stepper/zephyr,fake-stepper.yaml b/dts/bindings/stepper/zephyr,fake-stepper.yaml index 3c6d01ff08593..a32df4efd320a 100644 --- a/dts/bindings/stepper/zephyr,fake-stepper.yaml +++ b/dts/bindings/stepper/zephyr,fake-stepper.yaml @@ -2,8 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 description: | - This binding provides a fake stepper controller for use as either a stub or a mock in - Zephyr testing. + A fake stepper for use as either a stub or a mock in Zephyr testing. compatible: "zephyr,fake-stepper" diff --git a/dts/bindings/stepper/zephyr,stepper-motion-control.yaml b/dts/bindings/stepper/zephyr,stepper-motion-control.yaml index 85146f4e2eb00..89f7e96111290 100644 --- a/dts/bindings/stepper/zephyr,stepper-motion-control.yaml +++ b/dts/bindings/stepper/zephyr,stepper-motion-control.yaml @@ -16,7 +16,12 @@ compatible: "zephyr,stepper-motion-control" properties: counter: type: phandle - description: Counter used for generating step-accurate pulse signals. + description: | + Counter used for generating step-accurate pulse signals. + This should be a reference to a counter node in the device tree. + When this property is present, the stepper motion controller will use + the counter as a timing source instead of the default work queue timing. + This enables more precise step timing control. stepper: type: phandle diff --git a/include/zephyr/drivers/stepper.h b/include/zephyr/drivers/stepper.h index 34b3dfc5244e6..f22b6b7179ccb 100644 --- a/include/zephyr/drivers/stepper.h +++ b/include/zephyr/drivers/stepper.h @@ -66,6 +66,17 @@ enum stepper_micro_step_resolution { (res) == STEPPER_MICRO_STEP_64 || (res) == STEPPER_MICRO_STEP_128 || \ (res) == STEPPER_MICRO_STEP_256) +/** + * @brief Check if the stepper index is valid + * @param stepper_idx Stepper index to check + * @param max_num_stepper Maximum number of steppers supported by the device + * @param dev Pointer to the device structure + */ +#define CHECK_STEPPER_IDX(dev, stepper_idx, max_num_stepper) \ + __ASSERT(stepper_idx < max_num_stepper, \ + "Invalid stepper index %d, max is %d for device %s", stepper_idx, \ + max_num_stepper, dev->name) + /** * @brief Stepper Motor direction options */ @@ -111,62 +122,34 @@ enum stepper_event { * */ -/** - * @brief Enable the stepper driver. - * - * @see stepper_enable() for details. - */ -typedef int (*stepper_enable_t)(const struct device *dev); - -/** - * @brief Disable the stepper driver. - * - * @see stepper_disable() for details. - */ -typedef int (*stepper_disable_t)(const struct device *dev); - -/** - * @brief Set the micro-step resolution - * - * @see stepper_set_micro_step_res() for details. - */ -typedef int (*stepper_set_micro_step_res_t)(const struct device *dev, - const enum stepper_micro_step_resolution resolution); - -/** - * @brief Get the micro-step resolution - * - * @see stepper_get_micro_step_res() for details. - */ -typedef int (*stepper_get_micro_step_res_t)(const struct device *dev, - enum stepper_micro_step_resolution *resolution); - /** * @brief Set the reference position of the stepper * * @see stepper_set_actual_position() for details. */ -typedef int (*stepper_set_reference_position_t)(const struct device *dev, const int32_t value); +typedef int (*stepper_set_reference_position_t)(const struct device *dev, + const uint8_t stepper_index, const int32_t value); /** * @brief Get the actual a.k.a reference position of the stepper * * @see stepper_get_actual_position() for details. */ -typedef int (*stepper_get_actual_position_t)(const struct device *dev, int32_t *value); +typedef int (*stepper_get_actual_position_t)(const struct device *dev, const uint8_t stepper_index, + int32_t *value); /** * @brief Callback function for stepper events */ -typedef void (*stepper_event_callback_t)(const struct device *dev, const enum stepper_event event, - void *user_data); +typedef void (*stepper_event_callback_t)(const struct device *dev, const uint8_t stepper_index, + const enum stepper_event event, void *user_data); /** * @brief Set the callback function to be called when a stepper event occurs * * @see stepper_set_event_callback() for details. */ -typedef int (*stepper_set_event_callback_t)(const struct device *dev, +typedef int (*stepper_set_event_callback_t)(const struct device *dev, const uint8_t stepper_index, stepper_event_callback_t callback, void *user_data); /** @@ -175,50 +158,51 @@ typedef int (*stepper_set_event_callback_t)(const struct device *dev, * @see stepper_set_microstep_interval() for details. */ typedef int (*stepper_set_microstep_interval_t)(const struct device *dev, + const uint8_t stepper_index, const uint64_t microstep_interval_ns); /** * @brief Move the stepper relatively by a given number of micro-steps. * * @see stepper_move_by() for details. */ -typedef int (*stepper_move_by_t)(const struct device *dev, const int32_t micro_steps); +typedef int (*stepper_move_by_t)(const struct device *dev, const uint8_t stepper_index, + const int32_t micro_steps); /** * @brief Move the stepper to an absolute position in micro-steps. * * @see stepper_move_to() for details. */ -typedef int (*stepper_move_to_t)(const struct device *dev, const int32_t micro_steps); +typedef int (*stepper_move_to_t)(const struct device *dev, const uint8_t stepper_index, + const int32_t micro_steps); /** * @brief Run the stepper with a given step interval in a given direction * * @see stepper_run() for details. */ -typedef int (*stepper_run_t)(const struct device *dev, const enum stepper_direction direction); +typedef int (*stepper_run_t)(const struct device *dev, const uint8_t stepper_index, + const enum stepper_direction direction); /** * @brief Stop the stepper * * @see stepper_stop() for details. */ -typedef int (*stepper_stop_t)(const struct device *dev); +typedef int (*stepper_stop_t)(const struct device *dev, const uint8_t stepper_index); /** * @brief Is the target position fo the stepper reached * * @see stepper_is_moving() for details. */ -typedef int (*stepper_is_moving_t)(const struct device *dev, bool *is_moving); +typedef int (*stepper_is_moving_t)(const struct device *dev, const uint8_t stepper_index, + bool *is_moving); /** * @brief Stepper Driver API */ __subsystem struct stepper_driver_api { - stepper_enable_t enable; - stepper_disable_t disable; - stepper_set_micro_step_res_t set_micro_step_res; - stepper_get_micro_step_res_t get_micro_step_res; stepper_set_reference_position_t set_reference_position; stepper_get_actual_position_t get_actual_position; stepper_set_event_callback_t set_event_callback; @@ -234,150 +218,65 @@ __subsystem struct stepper_driver_api { * @endcond */ -/** - * @brief Enable stepper driver - * - * @details Enabling the driver shall switch on the power stage and energize the coils. - * - * @param dev pointer to the stepper driver instance - * - * @retval -EIO Error during Enabling - * @retval 0 Success - */ -__syscall int stepper_enable(const struct device *dev); - -static inline int z_impl_stepper_enable(const struct device *dev) -{ - const struct stepper_driver_api *api = (const struct stepper_driver_api *)dev->api; - - return api->enable(dev); -} - -/** - * @brief Disable stepper driver - * - * @details Disabling the driver shall switch off the power stage and de-energize the coils. - * Disabling the stepper does not implicitly stop the stepper. If the motor shall not move after - * re-enabling the stepper than consider calling stepper_stop() before. - * - * @param dev pointer to the stepper driver instance - * - * @retval -ENOTSUP Disabling of driver is not supported. - * @retval -EIO Error during Disabling - * @retval 0 Success - */ -__syscall int stepper_disable(const struct device *dev); - -static inline int z_impl_stepper_disable(const struct device *dev) -{ - const struct stepper_driver_api *api = (const struct stepper_driver_api *)dev->api; - - return api->disable(dev); -} - -/** - * @brief Set the micro-step resolution in stepper driver - * - * @param dev pointer to the stepper driver instance - * @param resolution micro-step resolution - * - * @retval -EIO General input / output error - * @retval -ENOSYS If not implemented by device driver - * @retval -EINVAL If the requested resolution is invalid - * @retval -ENOTSUP If the requested resolution is not supported - * @retval 0 Success - */ -__syscall int stepper_set_micro_step_res(const struct device *dev, - enum stepper_micro_step_resolution resolution); - -static inline int z_impl_stepper_set_micro_step_res(const struct device *dev, - enum stepper_micro_step_resolution resolution) -{ - const struct stepper_driver_api *api = (const struct stepper_driver_api *)dev->api; - - if (api->set_micro_step_res == NULL) { - return -ENOSYS; - } - - if (!VALID_MICRO_STEP_RES(resolution)) { - return -EINVAL; - } - return api->set_micro_step_res(dev, resolution); -} - -/** - * @brief Get the micro-step resolution in stepper driver - * - * @param dev pointer to the stepper driver instance - * @param resolution micro-step resolution - * - * @retval -EIO General input / output error - * @retval -ENOSYS If not implemented by device driver - * @retval 0 Success - */ -__syscall int stepper_get_micro_step_res(const struct device *dev, - enum stepper_micro_step_resolution *resolution); - -static inline int z_impl_stepper_get_micro_step_res(const struct device *dev, - enum stepper_micro_step_resolution *resolution) -{ - const struct stepper_driver_api *api = (const struct stepper_driver_api *)dev->api; - - if (api->get_micro_step_res == NULL) { - return -ENOSYS; - } - return api->get_micro_step_res(dev, resolution); -} - /** * @brief Set the reference position of the stepper * - * @param dev Pointer to the stepper driver instance. + * @param dev Pointer to the stepper controller instance. + * @param stepper_index The index of the stepper motor to set the reference position for. * @param value The reference position to set in micro-steps. * * @retval -EIO General input / output error * @retval -ENOSYS If not implemented by device driver * @retval 0 Success */ -__syscall int stepper_set_reference_position(const struct device *dev, int32_t value); +__syscall int stepper_set_reference_position(const struct device *dev, const uint8_t stepper_index, + int32_t value); static inline int z_impl_stepper_set_reference_position(const struct device *dev, + const uint8_t stepper_index, const int32_t value) { + __ASSERT_NO_MSG(dev != NULL); const struct stepper_driver_api *api = (const struct stepper_driver_api *)dev->api; if (api->set_reference_position == NULL) { return -ENOSYS; } - return api->set_reference_position(dev, value); + return api->set_reference_position(dev, stepper_index, value); } /** * @brief Get the actual a.k.a reference position of the stepper * - * @param dev pointer to the stepper driver instance + * @param dev pointer to the stepper controller instance + * @param stepper_index The index of the stepper motor to get the actual position for. * @param value The actual position to get in micro-steps * * @retval -EIO General input / output error * @retval -ENOSYS If not implemented by device driver * @retval 0 Success */ -__syscall int stepper_get_actual_position(const struct device *dev, int32_t *value); +__syscall int stepper_get_actual_position(const struct device *dev, const uint8_t stepper_index, + int32_t *value); -static inline int z_impl_stepper_get_actual_position(const struct device *dev, int32_t *value) +static inline int z_impl_stepper_get_actual_position(const struct device *dev, + const uint8_t stepper_index, int32_t *value) { + __ASSERT_NO_MSG(dev != NULL); + __ASSERT_NO_MSG(value != NULL); const struct stepper_driver_api *api = (const struct stepper_driver_api *)dev->api; if (api->get_actual_position == NULL) { return -ENOSYS; } - return api->get_actual_position(dev, value); + return api->get_actual_position(dev, stepper_index, value); } /** * @brief Set the callback function to be called when a stepper event occurs * - * @param dev pointer to the stepper driver instance + * @param dev pointer to the stepper controller instance + * @param stepper_index The index of the stepper motor to set the event callback for. * @param callback Callback function to be called when a stepper event occurs * passing NULL will disable the callback * @param user_data User data to be passed to the callback function @@ -385,19 +284,21 @@ static inline int z_impl_stepper_get_actual_position(const struct device *dev, i * @retval -ENOSYS If not implemented by device driver * @retval 0 Success */ -__syscall int stepper_set_event_callback(const struct device *dev, +__syscall int stepper_set_event_callback(const struct device *dev, const uint8_t stepper_index, stepper_event_callback_t callback, void *user_data); static inline int z_impl_stepper_set_event_callback(const struct device *dev, + const uint8_t stepper_index, stepper_event_callback_t callback, void *user_data) { + __ASSERT_NO_MSG(dev != NULL); const struct stepper_driver_api *api = (const struct stepper_driver_api *)dev->api; if (api->set_event_callback == NULL) { return -ENOSYS; } - return api->set_event_callback(dev, callback, user_data); + return api->set_event_callback(dev, stepper_index, callback, user_data); } /** @@ -406,7 +307,8 @@ static inline int z_impl_stepper_set_event_callback(const struct device *dev, * @note Setting step interval does not set the stepper into motion, a combination of * set_microstep_interval and move is required to set the stepper into motion. * - * @param dev pointer to the stepper driver instance + * @param dev pointer to the stepper controller instance + * @param stepper_index The index of the stepper motor to set the microstep interval for. * @param microstep_interval_ns time interval between steps in nanoseconds * * @retval -EIO General input / output error @@ -414,18 +316,20 @@ static inline int z_impl_stepper_set_event_callback(const struct device *dev, * @retval -ENOSYS If not implemented by device driver * @retval 0 Success */ -__syscall int stepper_set_microstep_interval(const struct device *dev, +__syscall int stepper_set_microstep_interval(const struct device *dev, const uint8_t stepper_index, uint64_t microstep_interval_ns); static inline int z_impl_stepper_set_microstep_interval(const struct device *dev, + const uint8_t stepper_index, const uint64_t microstep_interval_ns) { + __ASSERT_NO_MSG(dev != NULL); const struct stepper_driver_api *api = (const struct stepper_driver_api *)dev->api; if (api->set_microstep_interval == NULL) { return -ENOSYS; } - return api->set_microstep_interval(dev, microstep_interval_ns); + return api->set_microstep_interval(dev, stepper_index, microstep_interval_ns); } /** @@ -434,19 +338,23 @@ static inline int z_impl_stepper_set_microstep_interval(const struct device *dev * @details The stepper will move by the given number of micro-steps from the current position. * This function is non-blocking. * - * @param dev pointer to the stepper driver instance + * @param dev pointer to the stepper controller instance + * @param stepper_index The index of the stepper motor to set the micro-steps for. * @param micro_steps target micro-steps to be moved from the current position * * @retval -EIO General input / output error * @retval 0 Success */ -__syscall int stepper_move_by(const struct device *dev, int32_t micro_steps); +__syscall int stepper_move_by(const struct device *dev, const uint8_t stepper_index, + int32_t micro_steps); -static inline int z_impl_stepper_move_by(const struct device *dev, const int32_t micro_steps) +static inline int z_impl_stepper_move_by(const struct device *dev, const uint8_t stepper_index, + const int32_t micro_steps) { + __ASSERT_NO_MSG(dev != NULL); const struct stepper_driver_api *api = (const struct stepper_driver_api *)dev->api; - return api->move_by(dev, micro_steps); + return api->move_by(dev, stepper_index, micro_steps); } /** @@ -455,23 +363,27 @@ static inline int z_impl_stepper_move_by(const struct device *dev, const int32_t * @details The stepper will move to the given micro-steps position from the reference position. * This function is non-blocking. * - * @param dev pointer to the stepper driver instance + * @param dev pointer to the stepper controller instance + * @param stepper_index The index of the stepper motor to set the target position for. * @param micro_steps target position to set in micro-steps * * @retval -EIO General input / output error * @retval -ENOSYS If not implemented by device driver * @retval 0 Success */ -__syscall int stepper_move_to(const struct device *dev, int32_t micro_steps); +__syscall int stepper_move_to(const struct device *dev, const uint8_t stepper_index, + int32_t micro_steps); -static inline int z_impl_stepper_move_to(const struct device *dev, const int32_t micro_steps) +static inline int z_impl_stepper_move_to(const struct device *dev, const uint8_t stepper_index, + const int32_t micro_steps) { + __ASSERT_NO_MSG(dev != NULL); const struct stepper_driver_api *api = (const struct stepper_driver_api *)dev->api; if (api->move_to == NULL) { return -ENOSYS; } - return api->move_to(dev, micro_steps); + return api->move_to(dev, stepper_index, micro_steps); } /** @@ -481,68 +393,78 @@ static inline int z_impl_stepper_move_to(const struct device *dev, const int32_t * stalled or stopped using some other command, for instance, stepper_stop(). This * function is non-blocking. * - * @param dev pointer to the stepper driver instance + * @param dev pointer to the stepper controller instance + * @param stepper_index The index of the stepper motor to run. * @param direction The direction to set * * @retval -EIO General input / output error * @retval -ENOSYS If not implemented by device driver * @retval 0 Success */ -__syscall int stepper_run(const struct device *dev, enum stepper_direction direction); +__syscall int stepper_run(const struct device *dev, const uint8_t stepper_index, + enum stepper_direction direction); -static inline int z_impl_stepper_run(const struct device *dev, +static inline int z_impl_stepper_run(const struct device *dev, const uint8_t stepper_index, const enum stepper_direction direction) { + __ASSERT_NO_MSG(dev != NULL); const struct stepper_driver_api *api = (const struct stepper_driver_api *)dev->api; if (api->run == NULL) { return -ENOSYS; } - return api->run(dev, direction); + return api->run(dev, stepper_index, direction); } /** * @brief Stop the stepper * @details Cancel all active movements. * - * @param dev pointer to the stepper driver instance + * @param dev pointer to the stepper controller instance + * @param stepper_index The index of the stepper motor to stop. * * @retval -EIO General input / output error * @retval -ENOSYS If not implemented by device driver * @retval 0 Success */ -__syscall int stepper_stop(const struct device *dev); +__syscall int stepper_stop(const struct device *dev, const uint8_t stepper_index); -static inline int z_impl_stepper_stop(const struct device *dev) +static inline int z_impl_stepper_stop(const struct device *dev, const uint8_t stepper_index) { + __ASSERT_NO_MSG(dev != NULL); const struct stepper_driver_api *api = (const struct stepper_driver_api *)dev->api; if (api->stop == NULL) { return -ENOSYS; } - return api->stop(dev); + return api->stop(dev, stepper_index); } /** * @brief Check if the stepper is currently moving * - * @param dev pointer to the stepper driver instance + * @param dev pointer to the stepper controller instance + * @param stepper_index The index of the stepper motor to check if it is moving. * @param is_moving Pointer to a boolean to store the moving status of the stepper * * @retval -EIO General input / output error * @retval -ENOSYS If not implemented by device driver * @retval 0 Success */ -__syscall int stepper_is_moving(const struct device *dev, bool *is_moving); +__syscall int stepper_is_moving(const struct device *dev, const uint8_t stepper_index, + bool *is_moving); -static inline int z_impl_stepper_is_moving(const struct device *dev, bool *is_moving) +static inline int z_impl_stepper_is_moving(const struct device *dev, const uint8_t stepper_index, + bool *is_moving) { + __ASSERT_NO_MSG(dev != NULL); + __ASSERT_NO_MSG(is_moving != NULL); const struct stepper_driver_api *api = (const struct stepper_driver_api *)dev->api; if (api->is_moving == NULL) { return -ENOSYS; } - return api->is_moving(dev, is_moving); + return api->is_moving(dev, stepper_index, is_moving); } /** @@ -654,6 +576,7 @@ __syscall int stepper_drv_enable(const struct device *dev); static inline int z_impl_stepper_drv_enable(const struct device *dev) { + __ASSERT_NO_MSG(dev != NULL); const struct stepper_drv_driver_api *api = (const struct stepper_drv_driver_api *)dev->api; return api->enable(dev); @@ -674,6 +597,7 @@ __syscall int stepper_drv_disable(const struct device *dev); static inline int z_impl_stepper_drv_disable(const struct device *dev) { + __ASSERT_NO_MSG(dev != NULL); const struct stepper_drv_driver_api *api = (const struct stepper_drv_driver_api *)dev->api; return api->disable(dev); @@ -696,6 +620,7 @@ __syscall int stepper_drv_step(const struct device *dev); static inline int z_impl_stepper_drv_step(const struct device *dev) { + __ASSERT_NO_MSG(dev != NULL); const struct stepper_drv_driver_api *api = (const struct stepper_drv_driver_api *)dev->api; return api->step(dev); @@ -718,6 +643,7 @@ __syscall int stepper_drv_set_direction(const struct device *dev, static inline int z_impl_stepper_drv_set_direction(const struct device *dev, const enum stepper_direction direction) { + __ASSERT_NO_MSG(dev != NULL); const struct stepper_drv_driver_api *api = (const struct stepper_drv_driver_api *)dev->api; if ((direction != STEPPER_DIRECTION_POSITIVE) && @@ -731,7 +657,7 @@ static inline int z_impl_stepper_drv_set_direction(const struct device *dev, * @brief Set the micro-step resolution in stepper driver * * @param dev pointer to the step dir driver instance - * @param resolution micro-step resolution + * @param res micro-step resolution * * @retval -EIO General input / output error * @retval -ENOSYS If not implemented by device driver @@ -739,41 +665,46 @@ static inline int z_impl_stepper_drv_set_direction(const struct device *dev, * @retval -ENOTSUP If the requested resolution is not supported * @retval 0 Success */ -__syscall int stepper_drv_set_micro_stepper_res(const struct device *dev, - enum stepper_micro_step_resolution resolution); +__syscall int stepper_drv_set_micro_step_res(const struct device *dev, + enum stepper_micro_step_resolution res); -static inline int -z_impl_stepper_drv_set_micro_stepper_res(const struct device *dev, - enum stepper_micro_step_resolution resolution) +static inline int z_impl_stepper_drv_set_micro_step_res(const struct device *dev, + enum stepper_micro_step_resolution res) { + __ASSERT_NO_MSG(dev != NULL); const struct stepper_drv_driver_api *api = (const struct stepper_drv_driver_api *)dev->api; - if (!VALID_MICRO_STEP_RES(resolution)) { + if (api->set_micro_step_res == NULL) { + return -ENOSYS; + } + + if (!VALID_MICRO_STEP_RES(res)) { return -EINVAL; } - return api->set_micro_step_res(dev, resolution); + return api->set_micro_step_res(dev, res); } /** * @brief Get the micro-step resolution in stepper driver * * @param dev pointer to the stepper_drv driver instance - * @param resolution micro-step resolution + * @param res micro-step resolution * * @retval -EIO General input / output error * @retval -ENOSYS If not implemented by device driver * @retval 0 Success */ __syscall int stepper_drv_get_micro_step_res(const struct device *dev, - enum stepper_micro_step_resolution *resolution); + enum stepper_micro_step_resolution *res); -static inline int -z_impl_stepper_drv_get_micro_step_res(const struct device *dev, - enum stepper_micro_step_resolution *resolution) +static inline int z_impl_stepper_drv_get_micro_step_res(const struct device *dev, + enum stepper_micro_step_resolution *res) { + __ASSERT_NO_MSG(dev != NULL); + __ASSERT_NO_MSG(res != NULL); const struct stepper_drv_driver_api *api = (const struct stepper_drv_driver_api *)dev->api; - return api->get_micro_step_res(dev, resolution); + return api->get_micro_step_res(dev, res); } /** @@ -793,6 +724,7 @@ __syscall int stepper_drv_set_fault_cb(const struct device *dev, stepper_drv_fau static inline int z_impl_stepper_drv_set_fault_cb(const struct device *dev, stepper_drv_fault_cb_t callback, void *user_data) { + __ASSERT_NO_MSG(dev != NULL); const struct stepper_drv_driver_api *api = (const struct stepper_drv_driver_api *)dev->api; if (api->set_fault_cb == NULL) { diff --git a/include/zephyr/drivers/stepper/stepper_fake.h b/include/zephyr/drivers/stepper/stepper_fake.h index 0dad709dc61ac..722504b24949a 100644 --- a/include/zephyr/drivers/stepper/stepper_fake.h +++ b/include/zephyr/drivers/stepper/stepper_fake.h @@ -18,29 +18,33 @@ DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_enable, const struct device *); DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_disable, const struct device *); -DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_move_by, const struct device *, int32_t); - -DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_set_microstep_interval, const struct device *, uint64_t); - DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_set_micro_step_res, const struct device *, enum stepper_micro_step_resolution); DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_get_micro_step_res, const struct device *, enum stepper_micro_step_resolution *); -DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_set_reference_position, const struct device *, int32_t); +DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_move_by, const struct device *, uint8_t, int32_t); + +DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_set_microstep_interval, const struct device *, uint8_t, + uint64_t); + +DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_set_reference_position, const struct device *, uint8_t, + int32_t); -DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_get_actual_position, const struct device *, int32_t *); +DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_get_actual_position, const struct device *, uint8_t, + int32_t *); -DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_move_to, const struct device *, int32_t); +DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_move_to, const struct device *, uint8_t, int32_t); -DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_is_moving, const struct device *, bool *); +DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_is_moving, const struct device *, uint8_t, bool *); -DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_run, const struct device *, enum stepper_direction); +DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_run, const struct device *, uint8_t, + enum stepper_direction); -DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_stop, const struct device *); +DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_stop, const struct device *, uint8_t); -DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_set_event_callback, const struct device *, +DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_set_event_callback, const struct device *, uint8_t, stepper_event_callback_t, void *); #ifdef __cplusplus diff --git a/include/zephyr/drivers/stepper/stepper_trinamic.h b/include/zephyr/drivers/stepper/stepper_trinamic.h index 7a27ef8de9c87..c4f7e5382a32d 100644 --- a/include/zephyr/drivers/stepper/stepper_trinamic.h +++ b/include/zephyr/drivers/stepper/stepper_trinamic.h @@ -194,36 +194,39 @@ struct tmc_ramp_generator_data { #define TMC_RAMP_DT_SPEC_GET_TMC51XX(node) \ { \ - TMC_RAMP_DT_SPEC_GET_COMMON(DT_DRV_INST(node)) \ - .tpowerdown = DT_INST_PROP(node, tpowerdown), \ - .tpwmthrs = DT_INST_PROP(node, tpwmthrs), \ - .tcoolthrs = DT_INST_PROP(node, tcoolthrs), \ - .thigh = DT_INST_PROP(node, thigh), \ + TMC_RAMP_DT_SPEC_GET_COMMON(node) \ + .tpowerdown = DT_PROP(node, tpowerdown), \ + .tpwmthrs = DT_PROP(node, tpwmthrs), \ + .tcoolthrs = DT_PROP(node, tcoolthrs), \ + .thigh = DT_PROP(node, thigh), \ } /** * @brief Configure Trinamic Stepper Ramp Generator * * @param dev Pointer to the stepper motor controller instance + * @param stepper_index The index of the stepper to set the ramp for. * @param ramp_data Pointer to a struct containing the required ramp parameters * * @retval -EIO General input / output error * @retval -ENOSYS If not implemented by device driver * @retval 0 Success */ -int tmc50xx_stepper_set_ramp(const struct device *dev, +int tmc50xx_stepper_set_ramp(const struct device *dev, const uint8_t stepper_index, const struct tmc_ramp_generator_data *ramp_data); /** * @brief Set the maximum velocity of the stepper motor * * @param dev Pointer to the stepper motor controller instance + * @param stepper_index The index of the stepper to set the maximum velocity for. * @param velocity Maximum velocity in microsteps per second. * * @retval -EIO General input / output error * @retval 0 Success */ -int tmc50xx_stepper_set_max_velocity(const struct device *dev, uint32_t velocity); +int tmc50xx_stepper_set_max_velocity(const struct device *dev, const uint8_t stepper_index, + const uint32_t velocity); /** * @} diff --git a/samples/drivers/stepper/generic/src/main.c b/samples/drivers/stepper/generic/src/main.c index c81798fa8224d..ad58e9255d6de 100644 --- a/samples/drivers/stepper/generic/src/main.c +++ b/samples/drivers/stepper/generic/src/main.c @@ -12,6 +12,10 @@ #include LOG_MODULE_REGISTER(stepper, CONFIG_STEPPER_LOG_LEVEL); +/* Every motion controller must support at least one stepper */ +#define STEPPER_INDEX 0 + +static const struct device *stepper = DEVICE_DT_GET(DT_ALIAS(stepper)); static const struct device *stepper_motion_controller = DEVICE_DT_GET(DT_ALIAS(stepper_motion_control)); @@ -32,8 +36,8 @@ static int32_t ping_pong_target_position = static K_SEM_DEFINE(stepper_generic_sem, 0, 1); -static void stepper_callback(const struct device *dev, const enum stepper_event event, - void *user_data) +static void stepper_callback(const struct device *dev, const uint8_t stepper_index, + const enum stepper_event event, void *user_data) { switch (event) { case STEPPER_EVENT_STEPS_COMPLETED: @@ -70,49 +74,56 @@ int main(void) LOG_ERR("Device %s is not ready\n", stepper_motion_controller->name); return -ENODEV; } - LOG_DBG("stepper is %p, name is %s\n", stepper_motion_controller, - stepper_motion_controller->name); - if (!device_is_ready(stepper_motion_controller)) { - LOG_ERR("Device %s is not ready\n", stepper_motion_controller->name); + if (!device_is_ready(stepper)) { + LOG_ERR("Device %s is not ready\n", stepper->name); return -ENODEV; } - stepper_set_event_callback(stepper_motion_controller, stepper_callback, NULL); - stepper_set_reference_position(stepper_motion_controller, 0); - stepper_set_microstep_interval(stepper_motion_controller, CONFIG_STEP_INTERVAL_NS); + LOG_DBG("Motion Controller is %s, Stepper is %s", stepper_motion_controller->name, + stepper->name); + + stepper_set_event_callback(stepper_motion_controller, STEPPER_INDEX, stepper_callback, + NULL); + stepper_set_reference_position(stepper_motion_controller, STEPPER_INDEX, 0); + stepper_set_microstep_interval(stepper_motion_controller, STEPPER_INDEX, + CONFIG_STEP_INTERVAL_NS); for (;;) { k_sem_take(&stepper_generic_sem, K_FOREVER); switch (atomic_get(&stepper_mode)) { case STEPPER_MODE_ENABLE: - stepper_enable(stepper_motion_controller); + stepper_drv_enable(stepper); LOG_INF("mode: enable\n"); break; case STEPPER_MODE_STOP: - stepper_stop(stepper_motion_controller); + stepper_stop(stepper_motion_controller, STEPPER_INDEX); LOG_INF("mode: stop\n"); break; case STEPPER_MODE_ROTATE_CW: - stepper_run(stepper_motion_controller, STEPPER_DIRECTION_POSITIVE); + stepper_run(stepper_motion_controller, STEPPER_INDEX, + STEPPER_DIRECTION_POSITIVE); LOG_INF("mode: rotate cw\n"); break; case STEPPER_MODE_ROTATE_CCW: - stepper_run(stepper_motion_controller, STEPPER_DIRECTION_NEGATIVE); + stepper_run(stepper_motion_controller, STEPPER_INDEX, + STEPPER_DIRECTION_NEGATIVE); LOG_INF("mode: rotate ccw\n"); break; case STEPPER_MODE_PING_PONG_RELATIVE: ping_pong_target_position *= -1; - stepper_move_by(stepper_motion_controller, ping_pong_target_position); + stepper_move_by(stepper_motion_controller, STEPPER_INDEX, + ping_pong_target_position); LOG_INF("mode: ping pong relative\n"); break; case STEPPER_MODE_PING_PONG_ABSOLUTE: ping_pong_target_position *= -1; - stepper_move_to(stepper_motion_controller, ping_pong_target_position); + stepper_move_to(stepper_motion_controller, STEPPER_INDEX, + ping_pong_target_position); LOG_INF("mode: ping pong absolute\n"); break; case STEPPER_MODE_DISABLE: - stepper_disable(stepper_motion_controller); + stepper_drv_disable(stepper); LOG_INF("mode: disable\n"); break; } @@ -125,7 +136,8 @@ static void monitor_thread(void) for (;;) { int32_t actual_position; - stepper_get_actual_position(stepper_motion_controller, &actual_position); + stepper_get_actual_position(stepper_motion_controller, STEPPER_INDEX, + &actual_position); LOG_DBG("Actual position: %d\n", actual_position); k_sleep(K_MSEC(CONFIG_MONITOR_THREAD_TIMEOUT_MS)); } diff --git a/samples/drivers/stepper/tmc50xx/boards/nucleo_g071rb.overlay b/samples/drivers/stepper/tmc50xx/boards/nucleo_g071rb.overlay index e3b70a3553140..fe0be5b0db366 100644 --- a/samples/drivers/stepper/tmc50xx/boards/nucleo_g071rb.overlay +++ b/samples/drivers/stepper/tmc50xx/boards/nucleo_g071rb.overlay @@ -1,6 +1,7 @@ / { aliases { stepper = &tmc_stepper; + stepper-motion-control = &tmc50xx; }; }; diff --git a/samples/drivers/stepper/tmc50xx/src/main.c b/samples/drivers/stepper/tmc50xx/src/main.c index 8604f610c9670..451c9195fdf25 100644 --- a/samples/drivers/stepper/tmc50xx/src/main.c +++ b/samples/drivers/stepper/tmc50xx/src/main.c @@ -10,14 +10,19 @@ #include const struct device *stepper = DEVICE_DT_GET(DT_ALIAS(stepper)); +const struct device *stepper_controller = DEVICE_DT_GET(DT_ALIAS(stepper_motion_control)); +const uint8_t stepper_index = DT_REG_ADDR(DT_ALIAS(stepper)); -int32_t ping_pong_target_position = CONFIG_STEPS_PER_REV * CONFIG_PING_PONG_N_REV * - DT_PROP(DT_ALIAS(stepper), micro_step_res); +int32_t ping_pong_target_position = + CONFIG_STEPS_PER_REV * CONFIG_PING_PONG_N_REV * DT_PROP(DT_ALIAS(stepper), micro_step_res); K_SEM_DEFINE(steps_completed_sem, 0, 1); -void stepper_callback(const struct device *dev, const enum stepper_event event, void *user_data) +void stepper_callback(const struct device *dev, const uint8_t stepper_idx, + const enum stepper_event event, void *user_data) { + ARG_UNUSED(stepper_idx); + ARG_UNUSED(user_data); switch (event) { case STEPPER_EVENT_STEPS_COMPLETED: k_sem_give(&steps_completed_sem); @@ -36,19 +41,35 @@ int main(void) } printf("stepper is %p, name is %s\n", stepper, stepper->name); - stepper_set_event_callback(stepper, stepper_callback, NULL); - stepper_enable(stepper); - stepper_set_reference_position(stepper, 0); - stepper_move_by(stepper, ping_pong_target_position); + stepper_set_event_callback(stepper_controller, stepper_index, stepper_callback, NULL); + stepper_drv_enable(stepper); + + enum stepper_micro_step_resolution micro_step_res; + + stepper_drv_get_micro_step_res(stepper, µ_step_res); + printf("Microstep resolution is %d\n", micro_step_res); + + stepper_set_reference_position(stepper_controller, stepper_index, 0); + stepper_move_by(stepper_controller, stepper_index, ping_pong_target_position); /* Change Max Velocity during runtime */ int32_t tmc_velocity = DT_PROP(DT_ALIAS(stepper), vmax) * CONFIG_MAX_VELOCITY_MULTIPLIER; - (void)tmc50xx_stepper_set_max_velocity(stepper, tmc_velocity); + (void)tmc50xx_stepper_set_max_velocity(stepper_controller, stepper_index, tmc_velocity); for (;;) { if (k_sem_take(&steps_completed_sem, K_FOREVER) == 0) { ping_pong_target_position *= -1; - stepper_move_by(stepper, ping_pong_target_position); + stepper_move_by(stepper_controller, stepper_index, + ping_pong_target_position); + + int32_t actual_position; + + if (stepper_get_actual_position(stepper_controller, stepper_index, + &actual_position) == 0) { + printf("Actual position: %d\n", actual_position); + } else { + printf("Failed to get actual position\n"); + } } } return 0; diff --git a/tests/drivers/build_all/stepper/spi.dtsi b/tests/drivers/build_all/stepper/spi.dtsi index 0f6bfbc13db3e..4d848a5d9931c 100644 --- a/tests/drivers/build_all/stepper/spi.dtsi +++ b/tests/drivers/build_all/stepper/spi.dtsi @@ -6,6 +6,8 @@ * PLEASE KEEP REG ADDRESSES SEQUENTIAL * ***************************************/ +#include + adi_tmc50xx: adi_tmc50xx@0 { compatible = "adi,tmc50xx"; status = "okay"; @@ -94,31 +96,37 @@ adi_tmc51xx_1: adi_tmc51xx@1 { clock-frequency = <16000000>; /* Internal/External Clock frequency */ /* common stepper controller settings */ - invert-direction; - micro-step-res = <256>; - - /* ADI TMC stallguard settings specific to TMC5160 */ - activate-stallguard2; - stallguard-velocity-check-interval-ms = <100>; - stallguard2-threshold = <9>; - stallguard-threshold-velocity = <50000>; - - /* ADI TMC ramp generator as well as current settings */ - vstart = <10>; - a1 = <20>; - v1 = <30>; - d1 = <40>; - vmax = <50>; - amax = <60>; - dmax = <70>; - tzerowait = <80>; - thigh = <90>; - tcoolthrs = <100>; - tpwmthrs = <110>; - tpowerdown = <120>; - ihold = <1>; - irun = <2>; - iholddelay = <3>; + tmc51xx_1: tmc51xx_1@0 { + status = "okay"; + reg = <0>; + + /* common stepper controller settings */ + invert-direction; + micro-step-res = <256>; + + /* ADI TMC stallguard settings specific to TMC5160 */ + activate-stallguard2; + stallguard-velocity-check-interval-ms = <100>; + stallguard2-threshold = <9>; + stallguard-threshold-velocity = <50000>; + + /* ADI TMC ramp generator as well as current settings */ + vstart = <10>; + a1 = <20>; + v1 = <30>; + d1 = <40>; + vmax = <50>; + amax = <60>; + dmax = <70>; + tzerowait = <80>; + thigh = <90>; + tcoolthrs = <100>; + tpwmthrs = <110>; + tpowerdown = <120>; + ihold = <1>; + irun = <2>; + iholddelay = <3>; + }; }; adi_tmc51xx_2: adi_tmc51xx@2 { @@ -135,30 +143,33 @@ adi_tmc51xx_2: adi_tmc51xx@2 { en-pwm-mode; test-mode; /* ADI TMC Global configuration flags */ clock-frequency = <16000000>; /* Internal/External Clock frequency */ - /* common stepper controller settings */ - invert-direction; - micro-step-res = <256>; - - /* ADI TMC stallguard settings specific to TMC5160 */ - activate-stallguard2; - stallguard-velocity-check-interval-ms = <100>; - stallguard2-threshold = <9>; - stallguard-threshold-velocity = <50000>; - - /* ADI TMC ramp generator as well as current settings */ - vstart = <10>; - a1 = <20>; - v1 = <30>; - d1 = <40>; - vmax = <50>; - amax = <60>; - dmax = <70>; - tzerowait = <80>; - thigh = <90>; - tcoolthrs = <100>; - tpwmthrs = <110>; - tpowerdown = <120>; - ihold = <1>; - irun = <2>; - iholddelay = <3>; + tmc51xx_2: tmc51xx_2@0 { + status = "okay"; + reg = <0>; + micro-step-res = <256>; + invert-direction; + + /* ADI TMC stallguard settings specific to TMC5160 */ + activate-stallguard2; + stallguard-velocity-check-interval-ms = <100>; + stallguard2-threshold = <9>; + stallguard-threshold-velocity = <50000>; + + /* ADI TMC ramp generator as well as current settings */ + vstart = <10>; + a1 = <20>; + v1 = <30>; + d1 = <40>; + vmax = <50>; + amax = <60>; + dmax = <70>; + tzerowait = <80>; + thigh = <90>; + tcoolthrs = <100>; + tpwmthrs = <110>; + tpowerdown = <120>; + ihold = <1>; + irun = <2>; + iholddelay = <3>; + }; }; diff --git a/tests/drivers/build_all/stepper/uart.dtsi b/tests/drivers/build_all/stepper/uart.dtsi index e2116bae1f423..8af9beb9c5c54 100644 --- a/tests/drivers/build_all/stepper/uart.dtsi +++ b/tests/drivers/build_all/stepper/uart.dtsi @@ -14,31 +14,37 @@ adi_tmc51xx_uart: adi_tmc51xx { en-pwm-mode; test-mode; /* ADI TMC Global configuration flags */ clock-frequency = <16000000>; /* Internal/External Clock frequency */ + #address-cells = <1>; + #size-cells = <0>; - /* common stepper controller settings */ - invert-direction; - micro-step-res = <256>; + tmc5160_1: tmc5160_1@0 { + status = "okay"; + reg = <0>; + /* micro-stepping resolution */ + micro-step-res = <256>; + invert-direction; - /* ADI TMC stallguard settings specific to TMC5160 */ - activate-stallguard2; - stallguard-velocity-check-interval-ms = <100>; - stallguard2-threshold = <9>; - stallguard-threshold-velocity = <50000>; + /* ADI TMC stallguard settings specific to TMC5160 */ + activate-stallguard2; + stallguard-velocity-check-interval-ms = <100>; + stallguard2-threshold = <9>; + stallguard-threshold-velocity = <50000>; - /* ADI TMC ramp generator as well as current settings */ - vstart = <10>; - a1 = <20>; - v1 = <30>; - d1 = <40>; - vmax = <50>; - amax = <60>; - dmax = <70>; - tzerowait = <80>; - thigh = <90>; - tcoolthrs = <100>; - tpwmthrs = <110>; - tpowerdown = <120>; - ihold = <1>; - irun = <2>; - iholddelay = <3>; + /* ADI TMC ramp generator as well as current settings */ + vstart = <10>; + a1 = <20>; + v1 = <30>; + d1 = <40>; + vmax = <50>; + amax = <60>; + dmax = <70>; + tzerowait = <80>; + thigh = <90>; + tcoolthrs = <100>; + tpwmthrs = <110>; + tpowerdown = <120>; + ihold = <1>; + irun = <2>; + iholddelay = <3>; + }; }; diff --git a/tests/drivers/stepper/drv84xx/api/Kconfig b/tests/drivers/stepper/drv84xx/api/Kconfig index 40415c3edbca8..af5d21ee62ab5 100644 --- a/tests/drivers/stepper/drv84xx/api/Kconfig +++ b/tests/drivers/stepper/drv84xx/api/Kconfig @@ -6,4 +6,10 @@ config ENTROPY_GENERATOR bool "Shuffle the order of tests and suites" default y +config STEPPER_IDX + int "Stepper index" + default 0 + help + Index of the stepper device to test. + source "Kconfig.zephyr" diff --git a/tests/drivers/stepper/drv84xx/api/src/main.c b/tests/drivers/stepper/drv84xx/api/src/main.c index d8f0aa69028e9..142b70af782de 100644 --- a/tests/drivers/stepper/drv84xx/api/src/main.c +++ b/tests/drivers/stepper/drv84xx/api/src/main.c @@ -20,8 +20,8 @@ struct drv84xx_api_fixture { struct k_poll_signal stepper_signal; struct k_poll_event stepper_event; -static void drv84xx_api_print_event_callback(const struct device *dev, enum stepper_event event, - void *dummy) +static void drv84xx_api_print_event_callback(const struct device *dev, const uint8_t stepper_idx, + enum stepper_event event, void *dummy) { switch (event) { case STEPPER_EVENT_STEPS_COMPLETED: @@ -59,42 +59,18 @@ static void *drv84xx_api_setup(void) static void drv84xx_api_before(void *f) { struct drv84xx_api_fixture *fixture = f; - (void)stepper_set_reference_position(fixture->dev, 0); - (void)stepper_set_micro_step_res(fixture->dev, 1); + (void)stepper_set_reference_position(fixture->dev, CONFIG_STEPPER_IDX, 0); + (void)stepper_set_microstep_interval(fixture->dev, CONFIG_STEPPER_IDX, 20000000); k_poll_signal_reset(&stepper_signal); } -static void drv84xx_api_after(void *f) -{ - struct drv84xx_api_fixture *fixture = f; - (void)stepper_disable(fixture->dev); -} - -ZTEST_F(drv84xx_api, test_micro_step_res_set) -{ - (void)stepper_set_micro_step_res(fixture->dev, 4); - enum stepper_micro_step_resolution res; - (void)stepper_get_micro_step_res(fixture->dev, &res); - zassert_equal(res, 4, "Micro step resolution not set correctly, should be %d but is %d", 4, - res); -} - -ZTEST_F(drv84xx_api, test_actual_position_set) -{ - int32_t pos = 100u; - (void)stepper_set_reference_position(fixture->dev, pos); - (void)stepper_get_actual_position(fixture->dev, &pos); - zassert_equal(pos, 100u, "Actual position should be %u but is %u", 100u, pos); -} - ZTEST_F(drv84xx_api, test_move_to_positive_direction_movement) { int32_t pos = 50; - (void)stepper_enable(fixture->dev); - (void)stepper_set_microstep_interval(fixture->dev, 20000000); - (void)stepper_set_event_callback(fixture->dev, fixture->callback, NULL); - (void)stepper_move_to(fixture->dev, pos); + (void)stepper_set_microstep_interval(fixture->dev, CONFIG_STEPPER_IDX, 20000000); + (void)stepper_set_event_callback(fixture->dev, CONFIG_STEPPER_IDX, fixture->callback, NULL); + (void)stepper_move_to(fixture->dev, CONFIG_STEPPER_IDX, pos); (void)k_poll(&stepper_event, 1, K_SECONDS(5)); unsigned int signaled; int result; @@ -103,7 +79,7 @@ ZTEST_F(drv84xx_api, test_move_to_positive_direction_movement) zassert_equal(signaled, 1, "No event detected"); zassert_equal(result, STEPPER_EVENT_STEPS_COMPLETED, "Event was not STEPPER_EVENT_STEPS_COMPLETED event"); - (void)stepper_get_actual_position(fixture->dev, &pos); + (void)stepper_get_actual_position(fixture->dev, CONFIG_STEPPER_IDX, &pos); zassert_equal(pos, 50u, "Target position should be %d but is %d", 50u, pos); } @@ -111,10 +87,9 @@ ZTEST_F(drv84xx_api, test_move_to_negative_direction_movement) { int32_t pos = -50; - (void)stepper_enable(fixture->dev); - (void)stepper_set_microstep_interval(fixture->dev, 20000000); - (void)stepper_set_event_callback(fixture->dev, fixture->callback, NULL); - (void)stepper_move_to(fixture->dev, pos); + (void)stepper_set_microstep_interval(fixture->dev, CONFIG_STEPPER_IDX, 20000000); + (void)stepper_set_event_callback(fixture->dev, CONFIG_STEPPER_IDX, fixture->callback, NULL); + (void)stepper_move_to(fixture->dev, CONFIG_STEPPER_IDX, pos); (void)k_poll(&stepper_event, 1, K_SECONDS(5)); unsigned int signaled; int result; @@ -123,7 +98,7 @@ ZTEST_F(drv84xx_api, test_move_to_negative_direction_movement) zassert_equal(signaled, 1, "No event detected"); zassert_equal(result, STEPPER_EVENT_STEPS_COMPLETED, "Event was not STEPPER_EVENT_STEPS_COMPLETED event"); - (void)stepper_get_actual_position(fixture->dev, &pos); + (void)stepper_get_actual_position(fixture->dev, CONFIG_STEPPER_IDX, &pos); zassert_equal(pos, -50, "Target position should be %d but is %d", -50, pos); } @@ -131,10 +106,9 @@ ZTEST_F(drv84xx_api, test_move_to_identical_current_and_target_position) { int32_t pos = 0; - (void)stepper_enable(fixture->dev); - (void)stepper_set_microstep_interval(fixture->dev, 20000000); - (void)stepper_set_event_callback(fixture->dev, fixture->callback, NULL); - (void)stepper_move_to(fixture->dev, pos); + (void)stepper_set_microstep_interval(fixture->dev, CONFIG_STEPPER_IDX, 20000000); + (void)stepper_set_event_callback(fixture->dev, CONFIG_STEPPER_IDX, fixture->callback, NULL); + (void)stepper_move_to(fixture->dev, CONFIG_STEPPER_IDX, pos); (void)k_poll(&stepper_event, 1, K_SECONDS(5)); unsigned int signaled; int result; @@ -143,7 +117,7 @@ ZTEST_F(drv84xx_api, test_move_to_identical_current_and_target_position) zassert_equal(signaled, 1, "No event detected"); zassert_equal(result, STEPPER_EVENT_STEPS_COMPLETED, "Event was not STEPPER_EVENT_STEPS_COMPLETED event"); - (void)stepper_get_actual_position(fixture->dev, &pos); + (void)stepper_get_actual_position(fixture->dev, CONFIG_STEPPER_IDX, &pos); zassert_equal(pos, 0, "Target position should not have changed from %d but is %d", 0, pos); } @@ -152,11 +126,10 @@ ZTEST_F(drv84xx_api, test_move_to_is_moving_true_while_moving) int32_t pos = 50; bool moving = false; - (void)stepper_enable(fixture->dev); - (void)stepper_set_microstep_interval(fixture->dev, 20000000); - (void)stepper_set_event_callback(fixture->dev, fixture->callback, NULL); - (void)stepper_move_to(fixture->dev, pos); - (void)stepper_is_moving(fixture->dev, &moving); + (void)stepper_set_microstep_interval(fixture->dev, CONFIG_STEPPER_IDX, 20000000); + (void)stepper_set_event_callback(fixture->dev, CONFIG_STEPPER_IDX, fixture->callback, NULL); + (void)stepper_move_to(fixture->dev, CONFIG_STEPPER_IDX, pos); + (void)stepper_is_moving(fixture->dev, CONFIG_STEPPER_IDX, &moving); zassert_true(moving, "Driver should be in state is_moving while moving"); } @@ -165,10 +138,9 @@ ZTEST_F(drv84xx_api, test_move_to_is_moving_false_when_completed) int32_t pos = 50; bool moving = false; - (void)stepper_enable(fixture->dev); - (void)stepper_set_microstep_interval(fixture->dev, 20000000); - (void)stepper_set_event_callback(fixture->dev, fixture->callback, NULL); - (void)stepper_move_to(fixture->dev, pos); + (void)stepper_set_microstep_interval(fixture->dev, CONFIG_STEPPER_IDX, 20000000); + (void)stepper_set_event_callback(fixture->dev, CONFIG_STEPPER_IDX, fixture->callback, NULL); + (void)stepper_move_to(fixture->dev, CONFIG_STEPPER_IDX, pos); (void)k_poll(&stepper_event, 1, K_SECONDS(5)); unsigned int signaled; int result; @@ -177,7 +149,7 @@ ZTEST_F(drv84xx_api, test_move_to_is_moving_false_when_completed) zassert_equal(signaled, 1, "No event detected"); zassert_equal(result, STEPPER_EVENT_STEPS_COMPLETED, "Event was not STEPPER_EVENT_STEPS_COMPLETED event"); - (void)stepper_is_moving(fixture->dev, &moving); + (void)stepper_is_moving(fixture->dev, CONFIG_STEPPER_IDX, &moving); zassert_false(moving, "Driver should not be in state is_moving after finishing"); } @@ -185,10 +157,9 @@ ZTEST_F(drv84xx_api, test_move_by_zero_steps_no_movement) { int32_t steps = 0; - (void)stepper_enable(fixture->dev); - (void)stepper_set_microstep_interval(fixture->dev, 20000000); - (void)stepper_set_event_callback(fixture->dev, fixture->callback, NULL); - (void)stepper_move_by(fixture->dev, steps); + (void)stepper_set_microstep_interval(fixture->dev, CONFIG_STEPPER_IDX, 20000000); + (void)stepper_set_event_callback(fixture->dev, CONFIG_STEPPER_IDX, fixture->callback, NULL); + (void)stepper_move_by(fixture->dev, CONFIG_STEPPER_IDX, steps); (void)k_poll(&stepper_event, 1, K_SECONDS(5)); unsigned int signaled; int result; @@ -197,7 +168,7 @@ ZTEST_F(drv84xx_api, test_move_by_zero_steps_no_movement) zassert_equal(signaled, 1, "No event detected"); zassert_equal(result, STEPPER_EVENT_STEPS_COMPLETED, "Event was not STEPPER_EVENT_STEPS_COMPLETED event"); - (void)stepper_get_actual_position(fixture->dev, &steps); + (void)stepper_get_actual_position(fixture->dev, CONFIG_STEPPER_IDX, &steps); zassert_equal(steps, 0, "Target position should be %d but is %d", 0, steps); } @@ -206,23 +177,21 @@ ZTEST_F(drv84xx_api, test_move_by_is_moving_true_while_moving) int32_t steps = 50; bool moving = false; - (void)stepper_enable(fixture->dev); - (void)stepper_set_microstep_interval(fixture->dev, 20000000); - (void)stepper_set_event_callback(fixture->dev, fixture->callback, NULL); - (void)stepper_move_by(fixture->dev, steps); - (void)stepper_is_moving(fixture->dev, &moving); + (void)stepper_set_microstep_interval(fixture->dev, CONFIG_STEPPER_IDX, 20000000); + (void)stepper_set_event_callback(fixture->dev, CONFIG_STEPPER_IDX, fixture->callback, NULL); + (void)stepper_move_by(fixture->dev, CONFIG_STEPPER_IDX, steps); + (void)stepper_is_moving(fixture->dev, CONFIG_STEPPER_IDX, &moving); zassert_true(moving, "Driver should be in state is_moving"); } ZTEST_F(drv84xx_api, test_move_by_is_moving_false_when_completed) { - int32_t steps = 50; + int32_t steps = 10; bool moving = true; - (void)stepper_enable(fixture->dev); - (void)stepper_set_microstep_interval(fixture->dev, 20000000); - (void)stepper_set_event_callback(fixture->dev, fixture->callback, NULL); - (void)stepper_move_by(fixture->dev, steps); + (void)stepper_set_microstep_interval(fixture->dev, CONFIG_STEPPER_IDX, 20000000); + (void)stepper_set_event_callback(fixture->dev, CONFIG_STEPPER_IDX, fixture->callback, NULL); + (void)stepper_move_by(fixture->dev, CONFIG_STEPPER_IDX, steps); (void)k_poll(&stepper_event, 1, K_SECONDS(5)); unsigned int signaled; int result; @@ -231,7 +200,7 @@ ZTEST_F(drv84xx_api, test_move_by_is_moving_false_when_completed) zassert_equal(signaled, 1, "No event detected"); zassert_equal(result, STEPPER_EVENT_STEPS_COMPLETED, "Event was not STEPPER_EVENT_STEPS_COMPLETED event"); - (void)stepper_is_moving(fixture->dev, &moving); + (void)stepper_is_moving(fixture->dev, CONFIG_STEPPER_IDX, &moving); zassert_false(moving, "Driver should not be in state is_moving after completion"); } @@ -240,12 +209,11 @@ ZTEST_F(drv84xx_api, test_run_positive_direction_correct_position) uint64_t step_interval = 20000000; int32_t steps = 0; - (void)stepper_enable(fixture->dev); - (void)stepper_set_microstep_interval(fixture->dev, step_interval); - (void)stepper_run(fixture->dev, STEPPER_DIRECTION_POSITIVE); + (void)stepper_set_microstep_interval(fixture->dev, CONFIG_STEPPER_IDX, step_interval); + (void)stepper_run(fixture->dev, CONFIG_STEPPER_IDX, STEPPER_DIRECTION_POSITIVE); k_busy_wait(110000); - (void)stepper_get_actual_position(fixture->dev, &steps); + (void)stepper_get_actual_position(fixture->dev, CONFIG_STEPPER_IDX, &steps); zassert_true(IN_RANGE(steps, 4, 6), "Current position should be between 4 and 6 but is %d", steps); } @@ -255,13 +223,12 @@ ZTEST_F(drv84xx_api, test_run_negative_direction_correct_position) uint64_t step_interval = 20000000; int32_t steps = 0; - (void)stepper_enable(fixture->dev); - (void)stepper_set_microstep_interval(fixture->dev, step_interval); - (void)stepper_run(fixture->dev, STEPPER_DIRECTION_NEGATIVE); + (void)stepper_set_microstep_interval(fixture->dev, CONFIG_STEPPER_IDX, step_interval); + (void)stepper_run(fixture->dev, CONFIG_STEPPER_IDX, STEPPER_DIRECTION_NEGATIVE); k_busy_wait(110000); - (void)stepper_get_actual_position(fixture->dev, &steps); - zassert_true(IN_RANGE(steps, -6, 4), + (void)stepper_get_actual_position(fixture->dev, CONFIG_STEPPER_IDX, &steps); + zassert_true(IN_RANGE(steps, -6, -4), "Current position should be between -6 and -4 but is %d", steps); } @@ -270,9 +237,8 @@ ZTEST_F(drv84xx_api, test_run_zero_step_interval_correct_position) uint64_t step_interval = 0; int32_t steps = 0; - (void)stepper_enable(fixture->dev); - (void)stepper_set_microstep_interval(fixture->dev, step_interval); - (void)stepper_run(fixture->dev, STEPPER_DIRECTION_POSITIVE); + (void)stepper_set_microstep_interval(fixture->dev, CONFIG_STEPPER_IDX, step_interval); + (void)stepper_run(fixture->dev, CONFIG_STEPPER_IDX, STEPPER_DIRECTION_POSITIVE); k_msleep(100); zassert_equal(steps, 0, "Current position should not have changed from %d but is %d", 0, @@ -284,12 +250,10 @@ ZTEST_F(drv84xx_api, test_run_is_moving_true_when_step_interval_greater_zero) uint64_t step_interval = 20000000; bool moving = false; - (void)stepper_enable(fixture->dev); - (void)stepper_set_microstep_interval(fixture->dev, step_interval); - (void)stepper_run(fixture->dev, STEPPER_DIRECTION_POSITIVE); - (void)stepper_is_moving(fixture->dev, &moving); + (void)stepper_set_microstep_interval(fixture->dev, CONFIG_STEPPER_IDX, step_interval); + (void)stepper_run(fixture->dev, CONFIG_STEPPER_IDX, STEPPER_DIRECTION_POSITIVE); + (void)stepper_is_moving(fixture->dev, CONFIG_STEPPER_IDX, &moving); zassert_true(moving, "Driver should be in state is_moving"); - (void)stepper_disable(fixture->dev); } -ZTEST_SUITE(drv84xx_api, NULL, drv84xx_api_setup, drv84xx_api_before, drv84xx_api_after, NULL); +ZTEST_SUITE(drv84xx_api, NULL, drv84xx_api_setup, drv84xx_api_before, NULL, NULL); diff --git a/tests/drivers/stepper/drv84xx/emul/src/main.c b/tests/drivers/stepper/drv84xx/emul/src/main.c index 3fbb23b36746e..96d4b19004a76 100644 --- a/tests/drivers/stepper/drv84xx/emul/src/main.c +++ b/tests/drivers/stepper/drv84xx/emul/src/main.c @@ -33,7 +33,7 @@ static void *drv84xx_emul_setup(void) static void drv84xx_emul_before(void *f) { struct drv84xx_emul_fixture *fixture = f; - (void)stepper_drv_set_micro_stepper_res(fixture->dev, 1); + (void)stepper_drv_set_micro_step_res(fixture->dev, 1); } static void drv84xx_emul_after(void *f) diff --git a/tests/drivers/stepper/shell/CMakeLists.txt b/tests/drivers/stepper/shell/CMakeLists.txt index 7a7892c68da6e..7afca16e6238d 100644 --- a/tests/drivers/stepper/shell/CMakeLists.txt +++ b/tests/drivers/stepper/shell/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.20.0) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) -project(can_shell) +project(stepper_shell) FILE(GLOB app_sources src/*.c) target_sources(app PRIVATE ${app_sources}) diff --git a/tests/drivers/stepper/shell/app.overlay b/tests/drivers/stepper/shell/app.overlay index ee0a78de9b784..c815460c5fb39 100644 --- a/tests/drivers/stepper/shell/app.overlay +++ b/tests/drivers/stepper/shell/app.overlay @@ -9,4 +9,9 @@ compatible = "zephyr,fake-stepper"; status = "okay"; }; + + fake_stepper_controller: fake_stepper_controller { + compatible = "zephyr,fake-stepper-controller"; + status = "okay"; + }; }; diff --git a/tests/drivers/stepper/shell/src/main.c b/tests/drivers/stepper/shell/src/main.c index c4c9c5796272e..6e22b614c3c64 100644 --- a/tests/drivers/stepper/shell/src/main.c +++ b/tests/drivers/stepper/shell/src/main.c @@ -14,18 +14,22 @@ #include #include -#define FAKE_STEPPER_NAME DEVICE_DT_NAME(DT_NODELABEL(fake_stepper)) +#define FAKE_STEPPER_CONTROLLER DEVICE_DT_NAME(DT_NODELABEL(fake_stepper_controller)) +#define FAKE_STEPPER_NAME DEVICE_DT_NAME(DT_NODELABEL(fake_stepper)) +#define FAKE_STEPPER_INDEX STRINGIFY(0) /* Global variables */ static const struct device *const fake_stepper_dev = DEVICE_DT_GET(DT_NODELABEL(fake_stepper)); +static const struct device *const fake_stepper_controller_dev = + DEVICE_DT_GET(DT_NODELABEL(fake_stepper_controller)); DEFINE_FFF_GLOBALS; -#define ASSERT_STEPPER_FUNC_CALLED(stepper_fake_func, retval) \ +#define ASSERT_STEPPER_FUNC_CALLED(stepper_fake_func, stepper_fake_dev, retval) \ zassert_ok(retval, "failed to execute shell command (err %d)", retval); \ zassert_equal(stepper_fake_func.call_count, 1, \ STRINGIFY(stepper_fake_func) " function not called"); \ - zassert_equal(stepper_fake_func.arg0_val, fake_stepper_dev, "wrong device pointer") + zassert_equal(stepper_fake_func.arg0_val, stepper_fake_dev, "wrong device pointer") static void *stepper_shell_setup(void) { @@ -45,7 +49,7 @@ ZTEST(stepper_shell, test_stepper_enable) const struct shell *sh = shell_backend_dummy_get_ptr(); int err = shell_execute_cmd(sh, "stepper enable " FAKE_STEPPER_NAME); - ASSERT_STEPPER_FUNC_CALLED(fake_stepper_enable_fake, err); + ASSERT_STEPPER_FUNC_CALLED(fake_stepper_enable_fake, fake_stepper_dev, err); zassert_equal(err, 0, "stepper enable could not be executed"); } @@ -54,26 +58,30 @@ ZTEST(stepper_shell, test_stepper_disable) const struct shell *sh = shell_backend_dummy_get_ptr(); int err = shell_execute_cmd(sh, "stepper disable " FAKE_STEPPER_NAME); - ASSERT_STEPPER_FUNC_CALLED(fake_stepper_disable_fake, err); + ASSERT_STEPPER_FUNC_CALLED(fake_stepper_disable_fake, fake_stepper_dev, err); zassert_equal(err, 0, "stepper disable could not be executed"); } ZTEST(stepper_shell, test_stepper_move_by) { const struct shell *sh = shell_backend_dummy_get_ptr(); - int err = shell_execute_cmd(sh, "stepper move_by " FAKE_STEPPER_NAME " 1000"); + int err = shell_execute_cmd( + sh, "stepper move_by " FAKE_STEPPER_CONTROLLER FAKE_STEPPER_INDEX " 1000"); - ASSERT_STEPPER_FUNC_CALLED(fake_stepper_move_by_fake, err); - zassert_equal(fake_stepper_move_by_fake.arg1_val, 1000, "wrong microsteps value"); + ASSERT_STEPPER_FUNC_CALLED(fake_stepper_move_by_fake, fake_stepper_controller_dev, err); + zassert_equal(fake_stepper_move_by_fake.arg2_val, 1000, "wrong microsteps value"); } ZTEST(stepper_shell, test_stepper_set_microstep_interval) { const struct shell *sh = shell_backend_dummy_get_ptr(); - int err = shell_execute_cmd(sh, "stepper set_microstep_interval " FAKE_STEPPER_NAME " 200"); + int err = shell_execute_cmd( + sh, "stepper set_microstep_interval " FAKE_STEPPER_CONTROLLER FAKE_STEPPER_INDEX + " 200"); - ASSERT_STEPPER_FUNC_CALLED(fake_stepper_set_microstep_interval_fake, err); - zassert_equal(fake_stepper_set_microstep_interval_fake.arg1_val, 200, + ASSERT_STEPPER_FUNC_CALLED(fake_stepper_set_microstep_interval_fake, + fake_stepper_controller_dev, err); + zassert_equal(fake_stepper_set_microstep_interval_fake.arg2_val, 200, "wrong step_interval value"); } @@ -82,7 +90,7 @@ ZTEST(stepper_shell, test_stepper_set_micro_step_res) const struct shell *sh = shell_backend_dummy_get_ptr(); int err = shell_execute_cmd(sh, "stepper set_micro_step_res " FAKE_STEPPER_NAME " 64"); - ASSERT_STEPPER_FUNC_CALLED(fake_stepper_set_micro_step_res_fake, err); + ASSERT_STEPPER_FUNC_CALLED(fake_stepper_set_micro_step_res_fake, fake_stepper_dev, err); zassert_equal(fake_stepper_set_micro_step_res_fake.arg1_val, 64, "wrong micro steps resolution value"); } @@ -100,50 +108,58 @@ ZTEST(stepper_shell, test_stepper_get_micro_step_res) const struct shell *sh = shell_backend_dummy_get_ptr(); int err = shell_execute_cmd(sh, "stepper get_micro_step_res " FAKE_STEPPER_NAME); - ASSERT_STEPPER_FUNC_CALLED(fake_stepper_get_micro_step_res_fake, err); + ASSERT_STEPPER_FUNC_CALLED(fake_stepper_get_micro_step_res_fake, fake_stepper_dev, err); } ZTEST(stepper_shell, test_stepper_set_reference_position) { const struct shell *sh = shell_backend_dummy_get_ptr(); - int err = shell_execute_cmd(sh, "stepper set_reference_position " FAKE_STEPPER_NAME " 100"); + int err = shell_execute_cmd( + sh, "stepper set_reference_position " FAKE_STEPPER_CONTROLLER FAKE_STEPPER_INDEX + " 100"); - ASSERT_STEPPER_FUNC_CALLED(fake_stepper_set_reference_position_fake, err); - zassert_equal(fake_stepper_set_reference_position_fake.arg1_val, 100, + ASSERT_STEPPER_FUNC_CALLED(fake_stepper_set_reference_position_fake, + fake_stepper_controller_dev, err); + zassert_equal(fake_stepper_set_reference_position_fake.arg2_val, 100, "wrong actual position value"); } ZTEST(stepper_shell, test_stepper_get_actual_position) { const struct shell *sh = shell_backend_dummy_get_ptr(); - int err = shell_execute_cmd(sh, "stepper get_actual_position " FAKE_STEPPER_NAME); + int err = shell_execute_cmd( + sh, "stepper get_actual_position " FAKE_STEPPER_CONTROLLER FAKE_STEPPER_INDEX); - ASSERT_STEPPER_FUNC_CALLED(fake_stepper_get_actual_position_fake, err); + ASSERT_STEPPER_FUNC_CALLED(fake_stepper_get_actual_position_fake, + fake_stepper_controller_dev, err); } ZTEST(stepper_shell, test_stepper_move_to) { const struct shell *sh = shell_backend_dummy_get_ptr(); - int err = shell_execute_cmd(sh, "stepper move_to " FAKE_STEPPER_NAME " 200"); + int err = shell_execute_cmd( + sh, "stepper move_to " FAKE_STEPPER_CONTROLLER FAKE_STEPPER_INDEX " 200"); - ASSERT_STEPPER_FUNC_CALLED(fake_stepper_move_to_fake, err); - zassert_equal(fake_stepper_move_to_fake.arg1_val, 200, "wrong target position value"); + ASSERT_STEPPER_FUNC_CALLED(fake_stepper_move_to_fake, fake_stepper_controller_dev, err); + zassert_equal(fake_stepper_move_to_fake.arg2_val, 200, "wrong target position value"); } ZTEST(stepper_shell, test_stepper_run) { const struct shell *sh = shell_backend_dummy_get_ptr(); - int err = shell_execute_cmd(sh, "stepper run " FAKE_STEPPER_NAME " positive"); + int err = shell_execute_cmd(sh, "stepper run " FAKE_STEPPER_CONTROLLER FAKE_STEPPER_INDEX + " positive"); - ASSERT_STEPPER_FUNC_CALLED(fake_stepper_run_fake, err); - zassert_equal(fake_stepper_run_fake.arg1_val, STEPPER_DIRECTION_POSITIVE, + ASSERT_STEPPER_FUNC_CALLED(fake_stepper_run_fake, fake_stepper_controller_dev, err); + zassert_equal(fake_stepper_run_fake.arg2_val, STEPPER_DIRECTION_POSITIVE, "wrong direction value"); } ZTEST(stepper_shell, test_stepper_run_invalid_direction) { const struct shell *sh = shell_backend_dummy_get_ptr(); - int err = shell_execute_cmd(sh, "stepper run " FAKE_STEPPER_NAME " foo"); + int err = shell_execute_cmd(sh, "stepper run " FAKE_STEPPER_CONTROLLER FAKE_STEPPER_INDEX + " foo"); zassert_not_equal(err, 0, " executed run with invalid direction value"); } @@ -151,22 +167,32 @@ ZTEST(stepper_shell, test_stepper_run_invalid_direction) ZTEST(stepper_shell, test_stepper_stop) { const struct shell *sh = shell_backend_dummy_get_ptr(); - int err = shell_execute_cmd(sh, "stepper stop " FAKE_STEPPER_NAME); + int err = shell_execute_cmd(sh, "stepper stop " FAKE_STEPPER_CONTROLLER FAKE_STEPPER_INDEX); - ASSERT_STEPPER_FUNC_CALLED(fake_stepper_stop_fake, err); + ASSERT_STEPPER_FUNC_CALLED(fake_stepper_stop_fake, fake_stepper_controller_dev, err); zassert_equal(err, 0, "stepper stop could not be executed"); } -ZTEST(stepper_shell, test_stepper_info) +ZTEST(stepper_shell, test_stepper_controller_info) { const struct shell *sh = shell_backend_dummy_get_ptr(); - int err = shell_execute_cmd(sh, "stepper info " FAKE_STEPPER_NAME); + int err = shell_execute_cmd( + sh, "stepper control info " FAKE_STEPPER_CONTROLLER FAKE_STEPPER_INDEX); zassert_ok(err, "failed to execute shell command (err %d)", err); zassert_equal(fake_stepper_is_moving_fake.call_count, 1, "is_moving function not called"); zassert_equal(fake_stepper_get_actual_position_fake.call_count, 1, "get_actual_position function not called"); +} + +ZTEST(stepper_shell, test_stepper_info) +{ + const struct shell *sh = shell_backend_dummy_get_ptr(); + int err = shell_execute_cmd(sh, "stepper info " FAKE_STEPPER_NAME); + + zassert_ok(err, "failed to execute shell command (err %d)", err); + zassert_equal(fake_stepper_get_micro_step_res_fake.call_count, 1, "get_micro_step_res function not called"); } diff --git a/tests/drivers/stepper/stepper_api/Kconfig b/tests/drivers/stepper/stepper_api/Kconfig index 6cc7b5be7e0bc..5c09ba90b5f31 100644 --- a/tests/drivers/stepper/stepper_api/Kconfig +++ b/tests/drivers/stepper/stepper_api/Kconfig @@ -11,3 +11,9 @@ config STEPPER_TEST_TIMING_TIMEOUT_TOLERANCE_PCT help Additional margin (%) added to step timing during test timeouts. Accounts for execution and scheduling jitter. + +config STEPPER_IDX + int "Stepper index" + default 0 + help + Index of the stepper device to test. diff --git a/tests/drivers/stepper/stepper_api/boards/native_sim_adi_tmc2209.overlay b/tests/drivers/stepper/stepper_api/boards/native_sim_adi_tmc2209.overlay index b1aad6a6d4079..b3f1b00ec5325 100644 --- a/tests/drivers/stepper/stepper_api/boards/native_sim_adi_tmc2209.overlay +++ b/tests/drivers/stepper/stepper_api/boards/native_sim_adi_tmc2209.overlay @@ -16,7 +16,6 @@ adi_tmc2209: adi_tmc2209 { status = "okay"; compatible = "adi,tmc2209"; - micro-step-res = <32>; dir-gpios = <&gpio1 0 0>; step-gpios = <&gpio1 1 0>; en-gpios = <&gpio2 1 0>; diff --git a/tests/drivers/stepper/stepper_api/boards/native_sim_adi_tmc2209_work_q.overlay b/tests/drivers/stepper/stepper_api/boards/native_sim_adi_tmc2209_work_q.overlay index 5b0b71ae6762c..3769b908c521e 100644 --- a/tests/drivers/stepper/stepper_api/boards/native_sim_adi_tmc2209_work_q.overlay +++ b/tests/drivers/stepper/stepper_api/boards/native_sim_adi_tmc2209_work_q.overlay @@ -16,7 +16,6 @@ adi_tmc2209: adi_tmc2209 { status = "okay"; compatible = "adi,tmc2209"; - micro-step-res = <32>; dir-gpios = <&gpio1 0 0>; step-gpios = <&gpio1 1 0>; en-gpios = <&gpio2 1 0>; diff --git a/tests/drivers/stepper/stepper_api/boards/native_sim_allegro_a4979.overlay b/tests/drivers/stepper/stepper_api/boards/native_sim_allegro_a4979.overlay index 2652c194f213f..72b91ae42735d 100644 --- a/tests/drivers/stepper/stepper_api/boards/native_sim_allegro_a4979.overlay +++ b/tests/drivers/stepper/stepper_api/boards/native_sim_allegro_a4979.overlay @@ -16,7 +16,6 @@ allegro_a4979: allegro_a4979 { status = "okay"; compatible = "allegro,a4979"; - micro-step-res = <1>; reset-gpios = <&gpio4 0 0>; dir-gpios = <&gpio1 0 0>; step-gpios = <&gpio1 1 0>; diff --git a/tests/drivers/stepper/stepper_api/boards/native_sim_allegro_a4979_work_q.overlay b/tests/drivers/stepper/stepper_api/boards/native_sim_allegro_a4979_work_q.overlay index b2e477fbed3be..64000ba9691b8 100644 --- a/tests/drivers/stepper/stepper_api/boards/native_sim_allegro_a4979_work_q.overlay +++ b/tests/drivers/stepper/stepper_api/boards/native_sim_allegro_a4979_work_q.overlay @@ -16,7 +16,6 @@ allegro_a4979: allegro_a4979 { status = "okay"; compatible = "allegro,a4979"; - micro-step-res = <1>; reset-gpios = <&gpio4 0 0>; dir-gpios = <&gpio1 0 0>; step-gpios = <&gpio1 1 0>; diff --git a/tests/drivers/stepper/stepper_api/boards/native_sim_ti_drv84xx.overlay b/tests/drivers/stepper/stepper_api/boards/native_sim_ti_drv84xx.overlay index 90b9fc584ece6..b234ce1bfca9d 100644 --- a/tests/drivers/stepper/stepper_api/boards/native_sim_ti_drv84xx.overlay +++ b/tests/drivers/stepper/stepper_api/boards/native_sim_ti_drv84xx.overlay @@ -16,7 +16,6 @@ ti_drv84xx: ti_drv84xx { status = "okay"; compatible = "ti,drv84xx"; - micro-step-res = <8>; dir-gpios = <&gpio1 0 0>; step-gpios = <&gpio1 1 0>; sleep-gpios = <&gpio2 0 GPIO_ACTIVE_LOW>; diff --git a/tests/drivers/stepper/stepper_api/boards/native_sim_ti_drv84xx_work_q.overlay b/tests/drivers/stepper/stepper_api/boards/native_sim_ti_drv84xx_work_q.overlay index 55740f8576bda..01de085d6a4fe 100644 --- a/tests/drivers/stepper/stepper_api/boards/native_sim_ti_drv84xx_work_q.overlay +++ b/tests/drivers/stepper/stepper_api/boards/native_sim_ti_drv84xx_work_q.overlay @@ -16,7 +16,6 @@ ti_drv84xx: ti_drv84xx { status = "okay"; compatible = "ti,drv84xx"; - micro-step-res = <8>; dir-gpios = <&gpio1 0 0>; step-gpios = <&gpio1 1 0>; sleep-gpios = <&gpio2 0 GPIO_ACTIVE_LOW>; diff --git a/tests/drivers/stepper/stepper_api/src/main.c b/tests/drivers/stepper/stepper_api/src/main.c index e16155e162093..f461f7177b3cd 100644 --- a/tests/drivers/stepper/stepper_api/src/main.c +++ b/tests/drivers/stepper/stepper_api/src/main.c @@ -30,8 +30,8 @@ void *user_data_received; } while (0); \ }) -static void stepper_print_event_callback(const struct device *dev, enum stepper_event event, - void *user_data) +static void stepper_print_event_callback(const struct device *dev, const uint8_t stepper_idx, + enum stepper_event event, void *user_data) { const struct device *dev_callback = user_data; user_data_received = user_data; @@ -72,14 +72,13 @@ static void *stepper_setup(void) &stepper_signal); zassert_not_null(fixture.dev); - (void)stepper_enable(fixture.dev); return &fixture; } static void stepper_before(void *f) { struct stepper_fixture *fixture = f; - (void)stepper_set_reference_position(fixture->dev, 0); + (void)stepper_set_reference_position(fixture->dev, CONFIG_STEPPER_IDX, 0); k_poll_signal_reset(&stepper_signal); @@ -88,24 +87,9 @@ static void stepper_before(void *f) ZTEST_SUITE(stepper, NULL, stepper_setup, stepper_before, NULL, NULL); -ZTEST_F(stepper, test_set_micro_step_res_invalid) -{ - int ret = stepper_set_micro_step_res(fixture->dev, 127); - - zassert_equal(ret, -EINVAL, "Invalid micro step resolution should return -EINVAL"); -} - -ZTEST_F(stepper, test_get_micro_step_res) -{ - enum stepper_micro_step_resolution res; - (void)stepper_get_micro_step_res(fixture->dev, &res); - zassert_equal(res, DT_PROP(DT_ALIAS(stepper), micro_step_res), - "Micro step resolution not set correctly"); -} - ZTEST_F(stepper, test_set_micro_step_interval_invalid_zero) { - int err = stepper_set_microstep_interval(fixture->dev, 0); + int err = stepper_set_microstep_interval(fixture->dev, CONFIG_STEPPER_IDX, 0); if (err == -ENOSYS) { ztest_test_skip(); } @@ -117,10 +101,10 @@ ZTEST_F(stepper, test_actual_position) int32_t pos = 100u; int ret; - ret = stepper_set_reference_position(fixture->dev, pos); + ret = stepper_set_reference_position(fixture->dev, CONFIG_STEPPER_IDX, pos); zassert_equal(ret, 0, "Failed to set reference position"); - ret = stepper_get_actual_position(fixture->dev, &pos); + ret = stepper_get_actual_position(fixture->dev, CONFIG_STEPPER_IDX, &pos); zassert_equal(ret, 0, "Failed to get actual position"); zassert_equal(pos, 100u, "Actual position not set correctly"); } @@ -130,21 +114,22 @@ ZTEST_F(stepper, test_target_position_w_fixed_step_interval) int32_t pos = 10u; int ret; - ret = stepper_set_microstep_interval(fixture->dev, 100 * USEC_PER_SEC); + ret = stepper_set_microstep_interval(fixture->dev, CONFIG_STEPPER_IDX, 100 * USEC_PER_SEC); if (ret == -ENOSYS) { ztest_test_skip(); } /* Pass the function name as user data */ - (void)stepper_set_event_callback(fixture->dev, fixture->callback, (void *)fixture->dev); + (void)stepper_set_event_callback(fixture->dev, CONFIG_STEPPER_IDX, fixture->callback, + (void *)fixture->dev); - (void)stepper_move_to(fixture->dev, pos); + (void)stepper_move_to(fixture->dev, CONFIG_STEPPER_IDX, pos); POLL_AND_CHECK_SIGNAL( stepper_signal, stepper_event, STEPPER_EVENT_STEPS_COMPLETED, K_MSEC(pos * (100 + CONFIG_STEPPER_TEST_TIMING_TIMEOUT_TOLERANCE_PCT))); - (void)stepper_get_actual_position(fixture->dev, &pos); + (void)stepper_get_actual_position(fixture->dev, CONFIG_STEPPER_IDX, &pos); zassert_equal(pos, 10u, "Target position should be %d but is %d", 10u, pos); zassert_equal(user_data_received, fixture->dev, "User data not received"); } @@ -153,14 +138,15 @@ ZTEST_F(stepper, test_move_by_positive_step_count) { int32_t steps = 20; - (void)stepper_set_microstep_interval(fixture->dev, 100 * USEC_PER_SEC); - (void)stepper_set_event_callback(fixture->dev, fixture->callback, (void *)fixture->dev); - (void)stepper_move_by(fixture->dev, steps); + (void)stepper_set_microstep_interval(fixture->dev, CONFIG_STEPPER_IDX, 100 * USEC_PER_SEC); + (void)stepper_set_event_callback(fixture->dev, CONFIG_STEPPER_IDX, fixture->callback, + (void *)fixture->dev); + (void)stepper_move_by(fixture->dev, CONFIG_STEPPER_IDX, steps); POLL_AND_CHECK_SIGNAL( stepper_signal, stepper_event, STEPPER_EVENT_STEPS_COMPLETED, K_MSEC(steps * (100 + CONFIG_STEPPER_TEST_TIMING_TIMEOUT_TOLERANCE_PCT))); - (void)stepper_get_actual_position(fixture->dev, &steps); + (void)stepper_get_actual_position(fixture->dev, CONFIG_STEPPER_IDX, &steps); zassert_equal(steps, 20u, "Target position should be %d but is %d", 20u, steps); } @@ -168,14 +154,15 @@ ZTEST_F(stepper, test_move_by_negative_step_count) { int32_t steps = -20; - (void)stepper_set_microstep_interval(fixture->dev, 100 * USEC_PER_SEC); - (void)stepper_set_event_callback(fixture->dev, fixture->callback, (void *)fixture->dev); - (void)stepper_move_by(fixture->dev, steps); + (void)stepper_set_microstep_interval(fixture->dev, CONFIG_STEPPER_IDX, 100 * USEC_PER_SEC); + (void)stepper_set_event_callback(fixture->dev, CONFIG_STEPPER_IDX, fixture->callback, + (void *)fixture->dev); + (void)stepper_move_by(fixture->dev, CONFIG_STEPPER_IDX, steps); POLL_AND_CHECK_SIGNAL( stepper_signal, stepper_event, STEPPER_EVENT_STEPS_COMPLETED, K_MSEC(-steps * (100 + CONFIG_STEPPER_TEST_TIMING_TIMEOUT_TOLERANCE_PCT))); - (void)stepper_get_actual_position(fixture->dev, &steps); + (void)stepper_get_actual_position(fixture->dev, CONFIG_STEPPER_IDX, &steps); zassert_equal(steps, -20u, "Target position should be %d but is %d", -20u, steps); } @@ -184,26 +171,28 @@ ZTEST_F(stepper, test_move_by_zero_steps) { bool is_moving; - (void)stepper_set_microstep_interval(fixture->dev, 100 * USEC_PER_SEC); - (void)stepper_set_event_callback(fixture->dev, fixture->callback, (void *)fixture->dev); - (void)stepper_move_by(fixture->dev, 0); + (void)stepper_set_microstep_interval(fixture->dev, CONFIG_STEPPER_IDX, 100 * USEC_PER_SEC); + (void)stepper_set_event_callback(fixture->dev, CONFIG_STEPPER_IDX, fixture->callback, + (void *)fixture->dev); + (void)stepper_move_by(fixture->dev, CONFIG_STEPPER_IDX, 0); POLL_AND_CHECK_SIGNAL(stepper_signal, stepper_event, STEPPER_EVENT_STEPS_COMPLETED, K_MSEC(100)); - stepper_is_moving(fixture->dev, &is_moving); + stepper_is_moving(fixture->dev, CONFIG_STEPPER_IDX, &is_moving); zassert_equal(is_moving, false, "Stepper is still moving"); } ZTEST_F(stepper, test_stop) { - (void)stepper_set_event_callback(fixture->dev, fixture->callback, (void *)fixture->dev); + (void)stepper_set_event_callback(fixture->dev, CONFIG_STEPPER_IDX, fixture->callback, + (void *)fixture->dev); /* Run the stepper in positive direction */ - (void)stepper_run(fixture->dev, STEPPER_DIRECTION_POSITIVE); + (void)stepper_run(fixture->dev, CONFIG_STEPPER_IDX, STEPPER_DIRECTION_POSITIVE); /* Stop the stepper */ - int ret = stepper_stop(fixture->dev); + int ret = stepper_stop(fixture->dev, CONFIG_STEPPER_IDX); bool is_moving; if (ret == 0) { @@ -212,10 +201,10 @@ ZTEST_F(stepper, test_stop) zassert_equal(user_data_received, fixture->dev, "User data not received"); /* Check if the stepper is stopped */ - stepper_is_moving(fixture->dev, &is_moving); + stepper_is_moving(fixture->dev, CONFIG_STEPPER_IDX, &is_moving); zassert_equal(is_moving, false, "Stepper is still moving"); } else if (ret == -ENOSYS) { - stepper_is_moving(fixture->dev, &is_moving); + stepper_is_moving(fixture->dev, CONFIG_STEPPER_IDX, &is_moving); zassert_equal(is_moving, true, "Stepper should be moving since stop is not implemented"); } else { From 7fb6aa2751cef768808a160612fd3e3aa35319a6 Mon Sep 17 00:00:00 2001 From: Jilay Pandya Date: Sun, 29 Jun 2025 16:06:19 +0200 Subject: [PATCH 3/6] drivers: stepper: shell: add stepper index selection functionality Add stepper index selection functionality in stepper shell to select different steppers for the same stepper motion controller Signed-off-by: Jilay Pandya --- drivers/stepper/stepper_shell.c | 195 ++++++++++++++++++++----- tests/drivers/stepper/shell/src/main.c | 41 +++--- 2 files changed, 182 insertions(+), 54 deletions(-) diff --git a/drivers/stepper/stepper_shell.c b/drivers/stepper/stepper_shell.c index fe800f252cc62..ad896aee2429f 100644 --- a/drivers/stepper/stepper_shell.c +++ b/drivers/stepper/stepper_shell.c @@ -29,6 +29,13 @@ struct stepper_direction_map { enum stepper_direction direction; }; +struct stepper_control_idx_map { + const char *name; + uint8_t stepper_idx; +}; + +#define STEPPER_DRV_MICROSTEP_PARAM_IDX 2 + #define STEPPER_DIRECTION_MAP_ENTRY(_name, _dir) \ { \ .name = _name, \ @@ -41,6 +48,12 @@ struct stepper_direction_map { .microstep = _microstep, \ } +#define STEPPER_CONTROL_IDX_MAP_ENTRY(_name, _idx) \ + { \ + .name = _name, \ + .stepper_idx = _idx, \ + } + static void print_callback(const struct device *dev, const uint8_t stepper_idx, const enum stepper_event event, void *user_data) { @@ -71,7 +84,12 @@ static void print_callback(const struct device *dev, const uint8_t stepper_idx, } } -static bool device_is_stepper(const struct device *dev) +static bool device_is_stepper_drv(const struct device *dev) +{ + return DEVICE_API_IS(stepper_drv, dev); +} + +static bool device_is_stepper_controller(const struct device *dev) { return DEVICE_API_IS(stepper, dev); } @@ -81,6 +99,12 @@ static const struct stepper_direction_map stepper_direction_map[] = { STEPPER_DIRECTION_MAP_ENTRY("negative", STEPPER_DIRECTION_NEGATIVE), }; +static const struct stepper_control_idx_map stepper_control_idx_map[] = { + STEPPER_CONTROL_IDX_MAP_ENTRY("0", 0), + STEPPER_CONTROL_IDX_MAP_ENTRY("1", 1), + STEPPER_CONTROL_IDX_MAP_ENTRY("2", 2), +}; + static const struct stepper_microstep_map stepper_microstep_map[] = { STEPPER_MICROSTEP_MAP("1", STEPPER_MICRO_STEP_1), STEPPER_MICROSTEP_MAP("2", STEPPER_MICRO_STEP_2), @@ -107,6 +131,34 @@ static void cmd_stepper_direction(size_t idx, struct shell_static_entry *entry) SHELL_DYNAMIC_CMD_CREATE(dsub_stepper_direction, cmd_stepper_direction); +static void cmd_stepper_idx_dir(size_t idx, struct shell_static_entry *entry) +{ + if (idx < ARRAY_SIZE(stepper_control_idx_map)) { + entry->syntax = stepper_control_idx_map[idx].name; + } else { + entry->syntax = NULL; + } + entry->handler = NULL; + entry->help = "Stepper direction"; + entry->subcmd = &dsub_stepper_direction; +} + +SHELL_DYNAMIC_CMD_CREATE(dsub_stepper_idx_dir, cmd_stepper_idx_dir); + +static void cmd_stepper_idx(size_t idx, struct shell_static_entry *entry) +{ + if (idx < ARRAY_SIZE(stepper_control_idx_map)) { + entry->syntax = stepper_control_idx_map[idx].name; + } else { + entry->syntax = NULL; + } + entry->handler = NULL; + entry->help = "Stepper direction"; + entry->subcmd = NULL; +} + +SHELL_DYNAMIC_CMD_CREATE(dsub_stepper_idx, cmd_stepper_idx); + static void cmd_stepper_microstep(size_t idx, struct shell_static_entry *entry) { if (idx < ARRAY_SIZE(stepper_microstep_map)) { @@ -123,7 +175,7 @@ SHELL_DYNAMIC_CMD_CREATE(dsub_stepper_microstep, cmd_stepper_microstep); static void cmd_pos_stepper_motor_name(size_t idx, struct shell_static_entry *entry) { - const struct device *dev = shell_device_filter(idx, device_is_stepper); + const struct device *dev = shell_device_filter(idx, device_is_stepper_drv); entry->syntax = (dev != NULL) ? dev->name : NULL; entry->handler = NULL; @@ -133,9 +185,21 @@ static void cmd_pos_stepper_motor_name(size_t idx, struct shell_static_entry *en SHELL_DYNAMIC_CMD_CREATE(dsub_pos_stepper_motor_name, cmd_pos_stepper_motor_name); -static void cmd_pos_stepper_motor_name_dir(size_t idx, struct shell_static_entry *entry) +static void cmd_pos_stepper_controller_name(size_t idx, struct shell_static_entry *entry) { - const struct device *dev = shell_device_filter(idx, device_is_stepper); + const struct device *dev = shell_device_filter(idx, device_is_stepper_controller); + + entry->syntax = (dev != NULL) ? dev->name : NULL; + entry->handler = NULL; + entry->help = "List Devices"; + entry->subcmd = &dsub_stepper_idx; +} + +SHELL_DYNAMIC_CMD_CREATE(dsub_pos_stepper_controller_name, cmd_pos_stepper_controller_name); + +static void cmd_pos_stepper_controller_name_dir(size_t idx, struct shell_static_entry *entry) +{ + const struct device *dev = shell_device_filter(idx, device_is_stepper_controller); if (dev != NULL) { entry->syntax = dev->name; @@ -144,14 +208,14 @@ static void cmd_pos_stepper_motor_name_dir(size_t idx, struct shell_static_entry } entry->handler = NULL; entry->help = "List Devices"; - entry->subcmd = &dsub_stepper_direction; + entry->subcmd = &dsub_stepper_idx_dir; } -SHELL_DYNAMIC_CMD_CREATE(dsub_pos_stepper_motor_name_dir, cmd_pos_stepper_motor_name_dir); +SHELL_DYNAMIC_CMD_CREATE(dsub_pos_stepper_controller_name_dir, cmd_pos_stepper_controller_name_dir); static void cmd_pos_stepper_motor_name_microstep(size_t idx, struct shell_static_entry *entry) { - const struct device *dev = shell_device_filter(idx, device_is_stepper); + const struct device *dev = shell_device_filter(idx, device_is_stepper_drv); if (dev != NULL) { entry->syntax = dev->name; @@ -212,23 +276,40 @@ static int cmd_stepper_disable(const struct shell *sh, size_t argc, char **argv) return err; } +static int get_stepper_index(char **argv, uint8_t *stepper_idx) +{ + for (int i = 0; i < ARRAY_SIZE(stepper_control_idx_map); i++) { + if (strcmp(argv[ARG_IDX_DEV_IDX], stepper_control_idx_map[i].name) == 0) { + *stepper_idx = stepper_control_idx_map[i].stepper_idx; + return 0; + } + } + return -EINVAL; +} static int cmd_stepper_stop(const struct shell *sh, size_t argc, char **argv) { const struct device *dev; + uint8_t stepper_index; int err = 0; + err = get_stepper_index(argv, &stepper_index); + if (err < 0) { + shell_error(sh, "Invalid stepper index: %s", argv[ARG_IDX_DEV_IDX]); + return err; + } + err = parse_device_arg(sh, argv, &dev); if (err < 0) { return err; } - err = stepper_stop(dev); + err = stepper_stop(dev, stepper_index); if (err) { shell_error(sh, "Error: %d", err); return err; } - err = stepper_set_event_callback(dev, print_callback, (void *)sh); + err = stepper_set_event_callback(dev, stepper_index, print_callback, (void *)sh); if (err != 0) { shell_error(sh, "Failed to set callback: %d", err); } @@ -239,9 +320,15 @@ static int cmd_stepper_stop(const struct shell *sh, size_t argc, char **argv) static int cmd_stepper_move_by(const struct shell *sh, size_t argc, char **argv) { const struct device *dev; + uint8_t stepper_index; int err = 0; - const uint8_t stepper_index = shell_strtol(argv[ARG_IDX_DEV_IDX], 10, &err); + err = get_stepper_index(argv, &stepper_index); + if (err < 0) { + shell_error(sh, "Invalid stepper index: %s", argv[ARG_IDX_DEV_IDX]); + return err; + } + int32_t micro_steps = shell_strtol(argv[ARG_IDX_PARAM], 10, &err); if (err < 0) { @@ -269,8 +356,15 @@ static int cmd_stepper_move_by(const struct shell *sh, size_t argc, char **argv) static int cmd_stepper_set_microstep_interval(const struct shell *sh, size_t argc, char **argv) { const struct device *dev; + uint8_t stepper_index; int err = 0; - const uint8_t stepper_index = shell_strtol(argv[ARG_IDX_DEV_IDX], 10, &err); + + err = get_stepper_index(argv, &stepper_index); + if (err < 0) { + shell_error(sh, "Invalid stepper index: %s", argv[ARG_IDX_DEV_IDX]); + return err; + } + uint64_t step_interval = shell_strtoull(argv[ARG_IDX_PARAM], 10, &err); if (err < 0) { @@ -297,14 +391,16 @@ static int cmd_stepper_set_micro_step_res(const struct shell *sh, size_t argc, c int err = -EINVAL; for (int i = 0; i < ARRAY_SIZE(stepper_microstep_map); i++) { - if (strcmp(argv[ARG_IDX_PARAM], stepper_microstep_map[i].name) == 0) { + if (strcmp(argv[STEPPER_DRV_MICROSTEP_PARAM_IDX], stepper_microstep_map[i].name) == + 0) { resolution = stepper_microstep_map[i].microstep; err = 0; break; } } if (err != 0) { - shell_error(sh, "Invalid microstep value %s", argv[ARG_IDX_PARAM]); + shell_error(sh, "Invalid microstep value %s", + argv[STEPPER_DRV_MICROSTEP_PARAM_IDX]); return err; } @@ -313,7 +409,7 @@ static int cmd_stepper_set_micro_step_res(const struct shell *sh, size_t argc, c return err; } - err = stepper_drv_set_micro_stepper_res(dev, resolution); + err = stepper_drv_set_micro_step_res(dev, resolution); if (err) { shell_error(sh, "Error: %d", err); } @@ -345,8 +441,15 @@ static int cmd_stepper_get_micro_step_res(const struct shell *sh, size_t argc, c static int cmd_stepper_set_reference_position(const struct shell *sh, size_t argc, char **argv) { const struct device *dev; + uint8_t stepper_index; int err = 0; - const uint8_t stepper_index = shell_strtol(argv[ARG_IDX_DEV_IDX], 10, &err); + + err = get_stepper_index(argv, &stepper_index); + if (err < 0) { + shell_error(sh, "Invalid stepper index: %s", argv[ARG_IDX_DEV_IDX]); + return err; + } + int32_t position = shell_strtol(argv[ARG_IDX_PARAM], 10, &err); if (err < 0) { @@ -369,8 +472,15 @@ static int cmd_stepper_set_reference_position(const struct shell *sh, size_t arg static int cmd_stepper_get_actual_position(const struct shell *sh, size_t argc, char **argv) { const struct device *dev; + uint8_t stepper_index; int err; - const uint8_t stepper_index = shell_strtol(argv[ARG_IDX_DEV_IDX], 10, &err); + + err = get_stepper_index(argv, &stepper_index); + if (err < 0) { + shell_error(sh, "Invalid stepper index: %s", argv[ARG_IDX_DEV_IDX]); + return err; + } + int32_t actual_position; err = parse_device_arg(sh, argv, &dev); @@ -391,8 +501,15 @@ static int cmd_stepper_get_actual_position(const struct shell *sh, size_t argc, static int cmd_stepper_move_to(const struct shell *sh, size_t argc, char **argv) { const struct device *dev; + uint8_t stepper_index; int err = 0; - const uint8_t stepper_index = shell_strtol(argv[ARG_IDX_DEV_IDX], 10, &err); + + err = get_stepper_index(argv, &stepper_index); + if (err < 0) { + shell_error(sh, "Invalid stepper index: %s", argv[ARG_IDX_DEV_IDX]); + return err; + } + const int32_t position = shell_strtol(argv[ARG_IDX_PARAM], 10, &err); if (err < 0) { @@ -420,10 +537,18 @@ static int cmd_stepper_move_to(const struct shell *sh, size_t argc, char **argv) static int cmd_stepper_run(const struct shell *sh, size_t argc, char **argv) { const struct device *dev; - int err = -EINVAL; - const uint8_t stepper_index = shell_strtol(argv[ARG_IDX_DEV_IDX], 10, &err); + uint8_t stepper_index; + int err; + + err = get_stepper_index(argv, &stepper_index); + if (err < 0) { + shell_error(sh, "Invalid stepper index: %s", argv[ARG_IDX_DEV_IDX]); + return err; + } + enum stepper_direction direction = STEPPER_DIRECTION_POSITIVE; + err = -EINVAL; for (int i = 0; i < ARRAY_SIZE(stepper_direction_map); i++) { if (strcmp(argv[ARG_IDX_PARAM], stepper_direction_map[i].name) == 0) { direction = stepper_direction_map[i].direction; @@ -458,10 +583,16 @@ static int cmd_stepper_run(const struct shell *sh, size_t argc, char **argv) static int cmd_stepper_control_info(const struct shell *sh, size_t argc, char **argv) { const struct device *dev; + uint8_t stepper_index; int err; bool is_moving; int32_t actual_position; - const uint8_t stepper_index = shell_strtol(argv[ARG_IDX_DEV_IDX], 10, &err); + + err = get_stepper_index(argv, &stepper_index); + if (err < 0) { + shell_error(sh, "Invalid stepper index: %s", argv[ARG_IDX_DEV_IDX]); + return err; + } err = parse_device_arg(sh, argv, &dev); if (err < 0) { @@ -492,8 +623,6 @@ static int cmd_stepper_info(const struct shell *sh, size_t argc, char **argv) { const struct device *dev; int err; - bool is_moving; - int32_t actual_position; enum stepper_micro_step_resolution micro_step_res; err = parse_device_arg(sh, argv, &dev); @@ -522,22 +651,22 @@ SHELL_STATIC_SUBCMD_SET_CREATE( " ", cmd_stepper_set_micro_step_res, 3, 0), SHELL_CMD_ARG(get_micro_step_res, &dsub_pos_stepper_motor_name, "", cmd_stepper_get_micro_step_res, 2, 0), - SHELL_CMD_ARG(set_reference_position, &dsub_pos_stepper_motor_name, " ", - cmd_stepper_set_reference_position, 4, 0), - SHELL_CMD_ARG(get_actual_position, &dsub_pos_stepper_motor_name, "", + SHELL_CMD_ARG(set_reference_position, &dsub_pos_stepper_controller_name, + " ", cmd_stepper_set_reference_position, 4, 0), + SHELL_CMD_ARG(get_actual_position, &dsub_pos_stepper_controller_name, "", cmd_stepper_get_actual_position, 3, 0), - SHELL_CMD_ARG(set_microstep_interval, &dsub_pos_stepper_motor_name, + SHELL_CMD_ARG(set_microstep_interval, &dsub_pos_stepper_controller_name, " ", cmd_stepper_set_microstep_interval, 4, 0), - SHELL_CMD_ARG(move_by, &dsub_pos_stepper_motor_name, " ", + SHELL_CMD_ARG(move_by, &dsub_pos_stepper_controller_name, " ", cmd_stepper_move_by, 4, 0), - SHELL_CMD_ARG(move_to, &dsub_pos_stepper_motor_name, " ", + SHELL_CMD_ARG(move_to, &dsub_pos_stepper_controller_name, " ", cmd_stepper_move_to, 4, 0), - SHELL_CMD_ARG(run, &dsub_pos_stepper_motor_name_dir, " ", + SHELL_CMD_ARG(run, &dsub_pos_stepper_controller_name_dir, " ", cmd_stepper_run, 4, 0), - SHELL_CMD_ARG(stop, &dsub_pos_stepper_motor_name, "", cmd_stepper_stop, 3, 0), - SHELL_CMD_ARG(info, &dsub_pos_stepper_motor_name, "", cmd_stepper_control_info, 3, - 0), - SHELL_CMD_ARG(info, &dsub_pos_stepper_motor_name, "", cmd_stepper_info, 3, 0), + SHELL_CMD_ARG(stop, &dsub_pos_stepper_controller_name, "", cmd_stepper_stop, 3, 0), + SHELL_CMD_ARG(control_info, &dsub_pos_stepper_controller_name, "", + cmd_stepper_control_info, 3, 0), + SHELL_CMD_ARG(info, &dsub_pos_stepper_motor_name, "", cmd_stepper_info, 2, 0), SHELL_SUBCMD_SET_END); SHELL_CMD_REGISTER(stepper, &stepper_cmds, "Stepper motor commands", NULL); diff --git a/tests/drivers/stepper/shell/src/main.c b/tests/drivers/stepper/shell/src/main.c index 6e22b614c3c64..018fb43212245 100644 --- a/tests/drivers/stepper/shell/src/main.c +++ b/tests/drivers/stepper/shell/src/main.c @@ -16,7 +16,7 @@ #define FAKE_STEPPER_CONTROLLER DEVICE_DT_NAME(DT_NODELABEL(fake_stepper_controller)) #define FAKE_STEPPER_NAME DEVICE_DT_NAME(DT_NODELABEL(fake_stepper)) -#define FAKE_STEPPER_INDEX STRINGIFY(0) +#define FAKE_STEPPER_IDX STRINGIFY(0) /* Global variables */ static const struct device *const fake_stepper_dev = DEVICE_DT_GET(DT_NODELABEL(fake_stepper)); @@ -65,8 +65,8 @@ ZTEST(stepper_shell, test_stepper_disable) ZTEST(stepper_shell, test_stepper_move_by) { const struct shell *sh = shell_backend_dummy_get_ptr(); - int err = shell_execute_cmd( - sh, "stepper move_by " FAKE_STEPPER_CONTROLLER FAKE_STEPPER_INDEX " 1000"); + int err = shell_execute_cmd(sh, "stepper move_by " FAKE_STEPPER_CONTROLLER + " " FAKE_STEPPER_IDX " 1000"); ASSERT_STEPPER_FUNC_CALLED(fake_stepper_move_by_fake, fake_stepper_controller_dev, err); zassert_equal(fake_stepper_move_by_fake.arg2_val, 1000, "wrong microsteps value"); @@ -75,12 +75,11 @@ ZTEST(stepper_shell, test_stepper_move_by) ZTEST(stepper_shell, test_stepper_set_microstep_interval) { const struct shell *sh = shell_backend_dummy_get_ptr(); - int err = shell_execute_cmd( - sh, "stepper set_microstep_interval " FAKE_STEPPER_CONTROLLER FAKE_STEPPER_INDEX - " 200"); + int err = shell_execute_cmd(sh, "stepper set_microstep_interval " FAKE_STEPPER_CONTROLLER + " " FAKE_STEPPER_IDX " 200"); ASSERT_STEPPER_FUNC_CALLED(fake_stepper_set_microstep_interval_fake, - fake_stepper_controller_dev, err); + fake_stepper_controller_dev, err); zassert_equal(fake_stepper_set_microstep_interval_fake.arg2_val, 200, "wrong step_interval value"); } @@ -114,12 +113,11 @@ ZTEST(stepper_shell, test_stepper_get_micro_step_res) ZTEST(stepper_shell, test_stepper_set_reference_position) { const struct shell *sh = shell_backend_dummy_get_ptr(); - int err = shell_execute_cmd( - sh, "stepper set_reference_position " FAKE_STEPPER_CONTROLLER FAKE_STEPPER_INDEX - " 100"); + int err = shell_execute_cmd(sh, "stepper set_reference_position " FAKE_STEPPER_CONTROLLER + " " FAKE_STEPPER_IDX " 100"); ASSERT_STEPPER_FUNC_CALLED(fake_stepper_set_reference_position_fake, - fake_stepper_controller_dev, err); + fake_stepper_controller_dev, err); zassert_equal(fake_stepper_set_reference_position_fake.arg2_val, 100, "wrong actual position value"); } @@ -127,18 +125,18 @@ ZTEST(stepper_shell, test_stepper_set_reference_position) ZTEST(stepper_shell, test_stepper_get_actual_position) { const struct shell *sh = shell_backend_dummy_get_ptr(); - int err = shell_execute_cmd( - sh, "stepper get_actual_position " FAKE_STEPPER_CONTROLLER FAKE_STEPPER_INDEX); + int err = shell_execute_cmd(sh, "stepper get_actual_position " FAKE_STEPPER_CONTROLLER + " " FAKE_STEPPER_IDX); ASSERT_STEPPER_FUNC_CALLED(fake_stepper_get_actual_position_fake, - fake_stepper_controller_dev, err); + fake_stepper_controller_dev, err); } ZTEST(stepper_shell, test_stepper_move_to) { const struct shell *sh = shell_backend_dummy_get_ptr(); - int err = shell_execute_cmd( - sh, "stepper move_to " FAKE_STEPPER_CONTROLLER FAKE_STEPPER_INDEX " 200"); + int err = shell_execute_cmd(sh, "stepper move_to " FAKE_STEPPER_CONTROLLER + " " FAKE_STEPPER_IDX " 200"); ASSERT_STEPPER_FUNC_CALLED(fake_stepper_move_to_fake, fake_stepper_controller_dev, err); zassert_equal(fake_stepper_move_to_fake.arg2_val, 200, "wrong target position value"); @@ -147,7 +145,7 @@ ZTEST(stepper_shell, test_stepper_move_to) ZTEST(stepper_shell, test_stepper_run) { const struct shell *sh = shell_backend_dummy_get_ptr(); - int err = shell_execute_cmd(sh, "stepper run " FAKE_STEPPER_CONTROLLER FAKE_STEPPER_INDEX + int err = shell_execute_cmd(sh, "stepper run " FAKE_STEPPER_CONTROLLER " " FAKE_STEPPER_IDX " positive"); ASSERT_STEPPER_FUNC_CALLED(fake_stepper_run_fake, fake_stepper_controller_dev, err); @@ -158,7 +156,7 @@ ZTEST(stepper_shell, test_stepper_run) ZTEST(stepper_shell, test_stepper_run_invalid_direction) { const struct shell *sh = shell_backend_dummy_get_ptr(); - int err = shell_execute_cmd(sh, "stepper run " FAKE_STEPPER_CONTROLLER FAKE_STEPPER_INDEX + int err = shell_execute_cmd(sh, "stepper run " FAKE_STEPPER_CONTROLLER " " FAKE_STEPPER_IDX " foo"); zassert_not_equal(err, 0, " executed run with invalid direction value"); @@ -167,7 +165,8 @@ ZTEST(stepper_shell, test_stepper_run_invalid_direction) ZTEST(stepper_shell, test_stepper_stop) { const struct shell *sh = shell_backend_dummy_get_ptr(); - int err = shell_execute_cmd(sh, "stepper stop " FAKE_STEPPER_CONTROLLER FAKE_STEPPER_INDEX); + int err = shell_execute_cmd(sh, "stepper stop " FAKE_STEPPER_CONTROLLER + " " FAKE_STEPPER_IDX); ASSERT_STEPPER_FUNC_CALLED(fake_stepper_stop_fake, fake_stepper_controller_dev, err); zassert_equal(err, 0, "stepper stop could not be executed"); @@ -176,8 +175,8 @@ ZTEST(stepper_shell, test_stepper_stop) ZTEST(stepper_shell, test_stepper_controller_info) { const struct shell *sh = shell_backend_dummy_get_ptr(); - int err = shell_execute_cmd( - sh, "stepper control info " FAKE_STEPPER_CONTROLLER FAKE_STEPPER_INDEX); + int err = shell_execute_cmd(sh, "stepper control_info " FAKE_STEPPER_CONTROLLER + " " FAKE_STEPPER_IDX); zassert_ok(err, "failed to execute shell command (err %d)", err); From 848fa135799b922f51188bf407c26a420e4979f3 Mon Sep 17 00:00:00 2001 From: Jilay Pandya Date: Fri, 25 Jul 2025 19:03:37 +0200 Subject: [PATCH 4/6] drivers: stepper: introduce event cb mechanism for stepper_drv stall detection is a stepper_drv functionality and hence stepper_drv api needs an event callback mechanism to notify about the stepper driver related events such as fault, stall and so on. Signed-off-by: Jilay Pandya --- drivers/stepper/adi_tmc/tmc50xx.c | 29 +++++++++++- drivers/stepper/adi_tmc/tmc51xx/tmc51xx.c | 25 ++++++++++- drivers/stepper/adi_tmc/tmc51xx/tmc51xx.h | 2 + drivers/stepper/fake_stepper_drv.c | 4 ++ drivers/stepper/stepper_shell.c | 30 +++++++++++-- drivers/stepper/ti/drv84xx.c | 9 ++-- include/zephyr/drivers/stepper.h | 44 +++++++++++-------- include/zephyr/drivers/stepper/stepper_fake.h | 3 ++ tests/drivers/stepper/drv84xx/api/src/main.c | 3 -- tests/drivers/stepper/stepper_api/src/main.c | 3 -- 10 files changed, 117 insertions(+), 35 deletions(-) diff --git a/drivers/stepper/adi_tmc/tmc50xx.c b/drivers/stepper/adi_tmc/tmc50xx.c index 8a999cc08139b..4c37f02dc5d2e 100644 --- a/drivers/stepper/adi_tmc/tmc50xx.c +++ b/drivers/stepper/adi_tmc/tmc50xx.c @@ -37,6 +37,8 @@ struct tmc50xx_stepper_data { const struct device *stepper; stepper_event_callback_t callback; void *event_cb_user_data; + stepper_drv_event_cb_t drv_event_cb; + void *drv_event_cb_user_data; }; struct tmc50xx_stepper_config { @@ -116,6 +118,17 @@ static int tmc50xx_set_event_callback(const struct device *controller, const uin return 0; } +static int tmc50xx_set_stepper_drv_event_callback(const struct device *stepper, + stepper_drv_event_cb_t callback, void *user_data) +{ + struct tmc50xx_stepper_data *data = stepper->data; + + data->drv_event_cb = callback; + data->drv_event_cb_user_data = user_data; + + return 0; +} + static int read_vactual(const struct device *controller, const uint8_t stepper_index, int32_t *actual_velocity) { @@ -206,6 +219,17 @@ static void execute_callback(const struct device *controller, const uint8_t step data->callback(controller, stepper_index, event, data->event_cb_user_data); } +static void execute_stepper_drv_cb(const struct device *stepper, const enum stepper_drv_event event) +{ + struct tmc50xx_stepper_data *stepper_data = stepper->data; + + if (stepper_data->drv_event_cb) { + stepper_data->drv_event_cb(stepper, event, stepper_data->drv_event_cb_user_data); + } else { + LOG_WRN_ONCE("%s: No callback registered", stepper->name); + } +} + #ifdef CONFIG_STEPPER_ADI_TMC50XX_RAMPSTAT_POLL_STALLGUARD_LOG static void log_stallguard(struct tmc50xx_stepper_data *stepper_data, const uint32_t drv_status) @@ -305,8 +329,8 @@ static void rampstat_work_handler(struct k_work *work) case TMC5XXX_STOP_SG_EVENT: LOG_DBG("RAMPSTAT %s:Stall detected", stepper_data->stepper->name); stallguard_enable(stepper_config->controller, stepper_config->index, false); - execute_callback(stepper_config->controller, stepper_config->index, - STEPPER_EVENT_STALL_DETECTED); + execute_stepper_drv_cb(stepper_data->stepper, + STEPPER_DRV_EVENT_STALL_DETETCTED); break; default: LOG_ERR("Illegal ramp stat bit field"); @@ -757,6 +781,7 @@ static DEVICE_API(stepper_drv, tmc50xx_stepper_drv_api) = { .disable = tmc50xx_stepper_disable, .set_micro_step_res = tmc50xx_stepper_set_micro_step_res, .get_micro_step_res = tmc50xx_stepper_get_micro_step_res, + .set_event_cb = tmc50xx_set_stepper_drv_event_callback, }; static DEVICE_API(stepper, tmc50xx_stepper_api) = { diff --git a/drivers/stepper/adi_tmc/tmc51xx/tmc51xx.c b/drivers/stepper/adi_tmc/tmc51xx/tmc51xx.c index 2e0512451a7d2..49cfe2d618de9 100644 --- a/drivers/stepper/adi_tmc/tmc51xx/tmc51xx.c +++ b/drivers/stepper/adi_tmc/tmc51xx/tmc51xx.c @@ -68,6 +68,17 @@ static int tmc51xx_read(const struct device *dev, const uint8_t reg_addr, uint32 return 0; } +static int tmc51xx_stepper_drv_set_event_cb(const struct device *stepper, + stepper_drv_event_cb_t callback, void *user_data) +{ + struct tmc51xx_stepper_data *data = stepper->data; + + data->drv_event_cb = callback; + data->drv_event_cb_user_data = user_data; + + return 0; +} + static int tmc51xx_stepper_set_event_callback(const struct device *controller, const uint8_t stepper_idx, stepper_event_callback_t callback, void *user_data) @@ -213,6 +224,17 @@ static void stepper_trigger_callback(const struct device *dev, const enum steppe data->callback(dev, 0, event, data->event_cb_user_data); } +static void trigger_stepper_drv_cb(const struct device *stepper, const enum stepper_drv_event event) +{ + struct tmc51xx_stepper_data *stepper_data = stepper->data; + + if (stepper_data->drv_event_cb) { + stepper_data->drv_event_cb(stepper, event, stepper_data->drv_event_cb_user_data); + } else { + LOG_WRN_ONCE("%s: No callback registered", stepper->name); + } +} + #ifdef CONFIG_STEPPER_ADI_TMC51XX_RAMPSTAT_POLL_STALLGUARD_LOG static void log_stallguard(const struct device *dev, const uint32_t drv_status) @@ -309,7 +331,7 @@ static void rampstat_work_handler(struct k_work *work) case TMC5XXX_STOP_SG_EVENT: LOG_DBG("RAMPSTAT %s:Stall detected", dev->name); stallguard_enable(dev, false); - stepper_trigger_callback(dev, STEPPER_EVENT_STALL_DETECTED); + trigger_stepper_drv_cb(dev, STEPPER_DRV_EVENT_STALL_DETETCTED); break; default: LOG_ERR("Illegal ramp stat bit field 0x%x", ramp_stat_values); @@ -845,6 +867,7 @@ static DEVICE_API(stepper_drv, tmc51xx_drv_api) = { .disable = tmc51xx_stepper_disable, .set_micro_step_res = tmc51xx_stepper_set_micro_step_res, .get_micro_step_res = tmc51xx_stepper_get_micro_step_res, + .set_event_cb = tmc51xx_stepper_drv_set_event_cb, }; static DEVICE_API(stepper, tmc51xx_api) = { diff --git a/drivers/stepper/adi_tmc/tmc51xx/tmc51xx.h b/drivers/stepper/adi_tmc/tmc51xx/tmc51xx.h index fe339d501c359..588fda58d3fef 100644 --- a/drivers/stepper/adi_tmc/tmc51xx/tmc51xx.h +++ b/drivers/stepper/adi_tmc/tmc51xx/tmc51xx.h @@ -61,6 +61,8 @@ struct tmc51xx_stepper_data { const struct device *stepper; stepper_event_callback_t callback; void *event_cb_user_data; + stepper_drv_event_cb_t drv_event_cb; + void *drv_event_cb_user_data; }; #if TMC51XX_BUS_SPI diff --git a/drivers/stepper/fake_stepper_drv.c b/drivers/stepper/fake_stepper_drv.c index b3738df66d9db..dad759a103f39 100644 --- a/drivers/stepper/fake_stepper_drv.c +++ b/drivers/stepper/fake_stepper_drv.c @@ -28,6 +28,9 @@ DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_set_micro_step_res, const struct device DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_get_micro_step_res, const struct device *, enum stepper_micro_step_resolution *); +DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_drv_set_event_callback, const struct device *, + stepper_drv_event_cb_t, void *); + static int fake_stepper_set_micro_step_res_delegate(const struct device *dev, const enum stepper_micro_step_resolution res) { @@ -79,6 +82,7 @@ static DEVICE_API(stepper_drv, fake_stepper_driver_api) = { .disable = fake_stepper_disable, .set_micro_step_res = fake_stepper_set_micro_step_res, .get_micro_step_res = fake_stepper_get_micro_step_res, + .set_event_cb = fake_stepper_drv_set_event_callback, }; #define FAKE_STEPPER_INIT(inst) \ diff --git a/drivers/stepper/stepper_shell.c b/drivers/stepper/stepper_shell.c index ad896aee2429f..4448e1fcbd622 100644 --- a/drivers/stepper/stepper_shell.c +++ b/drivers/stepper/stepper_shell.c @@ -54,10 +54,33 @@ struct stepper_control_idx_map { .stepper_idx = _idx, \ } +static void print_stepper_drv_event_cb(const struct device *dev, const enum stepper_drv_event event, + void *user_data) +{ + const struct shell *sh = user_data; + + if (!sh) { + return; + } + + switch (event) { + case STEPPER_DRV_EVENT_STALL_DETETCTED: + shell_info(sh, "%s: Stall detected.", dev->name); + break; + case STEPPER_DRV_EVENT_FAULT_DETECTED: + shell_info(sh, "%s: Fault detected.", dev->name); + break; + default: + shell_info(sh, "%s: Unknown event.", dev->name); + break; + } +} + static void print_callback(const struct device *dev, const uint8_t stepper_idx, const enum stepper_event event, void *user_data) { const struct shell *sh = user_data; + if (!sh) { return; } @@ -66,9 +89,6 @@ static void print_callback(const struct device *dev, const uint8_t stepper_idx, case STEPPER_EVENT_STEPS_COMPLETED: shell_info(sh, "%s: Steps completed.", dev->name); break; - case STEPPER_EVENT_STALL_DETECTED: - shell_info(sh, "%s: Stall detected.", dev->name); - break; case STEPPER_EVENT_LEFT_END_STOP_DETECTED: shell_info(sh, "%s: Left limit switch pressed.", dev->name); break; @@ -255,6 +275,10 @@ static int cmd_stepper_enable(const struct shell *sh, size_t argc, char **argv) shell_error(sh, "Error: %d", err); } + err = stepper_drv_set_event_cb(dev, print_stepper_drv_event_cb, (void *)sh); + if (err) { + shell_error(sh, "Failed to set stepper driver event callback: %d", err); + } return err; } diff --git a/drivers/stepper/ti/drv84xx.c b/drivers/stepper/ti/drv84xx.c index 7f24affefa987..a1a89b060abc3 100644 --- a/drivers/stepper/ti/drv84xx.c +++ b/drivers/stepper/ti/drv84xx.c @@ -50,7 +50,7 @@ struct drv84xx_data { struct drv84xx_pin_states pin_states; enum stepper_micro_step_resolution ustep_res; struct gpio_callback fault_cb_data; - stepper_drv_fault_cb_t fault_cb; + stepper_drv_event_cb_t fault_cb; void *fault_cb_user_data; }; @@ -255,7 +255,7 @@ static int drv84xx_disable(const struct device *dev) return ret; } -static int drv84xx_set_fault_cb(const struct device *dev, stepper_drv_fault_cb_t fault_cb, +static int drv84xx_set_fault_cb(const struct device *dev, stepper_drv_event_cb_t fault_cb, void *user_data) { struct drv84xx_data *data = dev->data; @@ -360,7 +360,8 @@ void fault_event(const struct device *dev, struct gpio_callback *cb, uint32_t pi struct drv84xx_data *data = CONTAINER_OF(cb, struct drv84xx_data, fault_cb_data); if (data->fault_cb != NULL) { - data->fault_cb(data->dev, data->fault_cb_user_data); + data->fault_cb(data->dev, STEPPER_DRV_EVENT_FAULT_DETECTED, + data->fault_cb_user_data); } else { LOG_WRN_ONCE("%s: Fault pin triggered but no callback is set", dev->name); } @@ -450,7 +451,7 @@ static int drv84xx_init(const struct device *dev) static DEVICE_API(stepper_drv, drv84xx_stepper_api) = { .enable = drv84xx_enable, .disable = drv84xx_disable, - .set_fault_cb = drv84xx_set_fault_cb, + .set_event_cb = drv84xx_set_fault_cb, .set_micro_step_res = drv84xx_set_micro_step_res, .get_micro_step_res = drv84xx_get_micro_step_res, .step = step_dir_stepper_common_step, diff --git a/include/zephyr/drivers/stepper.h b/include/zephyr/drivers/stepper.h index f22b6b7179ccb..dcfa255524be2 100644 --- a/include/zephyr/drivers/stepper.h +++ b/include/zephyr/drivers/stepper.h @@ -105,14 +105,12 @@ enum stepper_run_mode { enum stepper_event { /** Steps set using move_by or move_to have been executed */ STEPPER_EVENT_STEPS_COMPLETED = 0, - /** Stall detected */ - STEPPER_EVENT_STALL_DETECTED = 1, /** Left end switch status changes to pressed */ - STEPPER_EVENT_LEFT_END_STOP_DETECTED = 2, + STEPPER_EVENT_LEFT_END_STOP_DETECTED = 1, /** Right end switch status changes to pressed */ - STEPPER_EVENT_RIGHT_END_STOP_DETECTED = 3, + STEPPER_EVENT_RIGHT_END_STOP_DETECTED = 2, /** Stepper has stopped */ - STEPPER_EVENT_STOPPED = 4, + STEPPER_EVENT_STOPPED = 3, }; /** @@ -480,6 +478,13 @@ static inline int z_impl_stepper_is_moving(const struct device *dev, const uint8 * @{ */ +enum stepper_drv_event { + /** Stepper driver stall detected */ + STEPPER_DRV_EVENT_STALL_DETETCTED = 0, + /** Stepper driver fault detected */ + STEPPER_DRV_EVENT_FAULT_DETECTED = 1, +}; + /** * @cond INTERNAL_HIDDEN * @@ -533,17 +538,18 @@ typedef int (*stepper_drv_get_micro_step_res_t)(const struct device *dev, enum stepper_micro_step_resolution *resolution); /** - * @brief Callback function for stepper fault events + * @brief Callback function for stepper driver events */ -typedef int (*stepper_drv_fault_cb_t)(const struct device *dev, void *user_data); +typedef void (*stepper_drv_event_cb_t)(const struct device *dev, const enum stepper_drv_event event, + void *user_data); /** - * @brief Set the callback function to be called when a stepper driver fault occurs + * @brief Set the callback function to be called when a stepper_drv_event occurs * - * @see stepper_drv_set_fault_callback() for details. + * @see stepper_drv_set_event_callback() for details. */ -typedef int (*stepper_drv_set_fault_callback_t)(const struct device *dev, - stepper_drv_fault_cb_t callback, void *user_data); +typedef int (*stepper_drv_set_event_callback_t)(const struct device *dev, + stepper_drv_event_cb_t callback, void *user_data); /** * @brief Stepper DRV Driver API @@ -555,7 +561,7 @@ __subsystem struct stepper_drv_driver_api { stepper_drv_step_t step; stepper_drv_set_micro_step_res_t set_micro_step_res; stepper_drv_get_micro_step_res_t get_micro_step_res; - stepper_drv_set_fault_callback_t set_fault_cb; + stepper_drv_set_event_callback_t set_event_cb; }; /** @@ -708,30 +714,30 @@ static inline int z_impl_stepper_drv_get_micro_step_res(const struct device *dev } /** - * @brief Set the callback function to be called when a stepper fault occurs + * @brief Set the callback function to be called when a stepper_drv_event occurs * * @param dev pointer to the stepper_drv driver instance - * @param callback Callback function to be called when a stepper fault occurs + * @param callback Callback function to be called when a stepper_drv_event occurs * passing NULL will disable the callback * @param user_data User data to be passed to the callback function * * @retval -ENOSYS If not implemented by device driver * @retval 0 Success */ -__syscall int stepper_drv_set_fault_cb(const struct device *dev, stepper_drv_fault_cb_t callback, +__syscall int stepper_drv_set_event_cb(const struct device *dev, stepper_drv_event_cb_t callback, void *user_data); -static inline int z_impl_stepper_drv_set_fault_cb(const struct device *dev, - stepper_drv_fault_cb_t callback, void *user_data) +static inline int z_impl_stepper_drv_set_event_cb(const struct device *dev, + stepper_drv_event_cb_t cb, void *user_data) { __ASSERT_NO_MSG(dev != NULL); const struct stepper_drv_driver_api *api = (const struct stepper_drv_driver_api *)dev->api; - if (api->set_fault_cb == NULL) { + if (api->set_event_cb == NULL) { return -ENOSYS; } - return api->set_fault_cb(dev, callback, user_data); + return api->set_event_cb(dev, cb, user_data); } /** diff --git a/include/zephyr/drivers/stepper/stepper_fake.h b/include/zephyr/drivers/stepper/stepper_fake.h index 722504b24949a..ca15ee9d55d9f 100644 --- a/include/zephyr/drivers/stepper/stepper_fake.h +++ b/include/zephyr/drivers/stepper/stepper_fake.h @@ -24,6 +24,9 @@ DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_set_micro_step_res, const struct devic DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_get_micro_step_res, const struct device *, enum stepper_micro_step_resolution *); +DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_drv_set_event_callback, const struct device *, + stepper_drv_event_cb_t, void *); + DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_move_by, const struct device *, uint8_t, int32_t); DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_set_microstep_interval, const struct device *, uint8_t, diff --git a/tests/drivers/stepper/drv84xx/api/src/main.c b/tests/drivers/stepper/drv84xx/api/src/main.c index 142b70af782de..5033f928e70fb 100644 --- a/tests/drivers/stepper/drv84xx/api/src/main.c +++ b/tests/drivers/stepper/drv84xx/api/src/main.c @@ -33,9 +33,6 @@ static void drv84xx_api_print_event_callback(const struct device *dev, const uin case STEPPER_EVENT_RIGHT_END_STOP_DETECTED: k_poll_signal_raise(&stepper_signal, STEPPER_EVENT_RIGHT_END_STOP_DETECTED); break; - case STEPPER_EVENT_STALL_DETECTED: - k_poll_signal_raise(&stepper_signal, STEPPER_EVENT_STALL_DETECTED); - break; default: break; } diff --git a/tests/drivers/stepper/stepper_api/src/main.c b/tests/drivers/stepper/stepper_api/src/main.c index f461f7177b3cd..c7219a909858c 100644 --- a/tests/drivers/stepper/stepper_api/src/main.c +++ b/tests/drivers/stepper/stepper_api/src/main.c @@ -46,9 +46,6 @@ static void stepper_print_event_callback(const struct device *dev, const uint8_t case STEPPER_EVENT_RIGHT_END_STOP_DETECTED: k_poll_signal_raise(&stepper_signal, STEPPER_EVENT_RIGHT_END_STOP_DETECTED); break; - case STEPPER_EVENT_STALL_DETECTED: - k_poll_signal_raise(&stepper_signal, STEPPER_EVENT_STALL_DETECTED); - break; case STEPPER_EVENT_STOPPED: k_poll_signal_raise(&stepper_signal, STEPPER_EVENT_STOPPED); break; From 91ab71c521c22dc65512239434a45dab3ec73a68 Mon Sep 17 00:00:00 2001 From: Jilay Pandya Date: Tue, 29 Jul 2025 11:51:28 +0200 Subject: [PATCH 5/6] drivers: stepper: change namespacing for stepper_micro_step_resolution refactor stepper_micro_step_resolution to stepper_drv_micro_step_resolution Signed-off-by: Jilay Pandya --- drivers/stepper/adi_tmc/tmc22xx.c | 18 ++-- drivers/stepper/adi_tmc/tmc50xx.c | 8 +- drivers/stepper/adi_tmc/tmc51xx/tmc51xx.c | 8 +- drivers/stepper/allegro/a4979.c | 14 +-- drivers/stepper/fake_stepper_drv.c | 10 +-- drivers/stepper/h_bridge_stepper.c | 12 +-- drivers/stepper/stepper_shell.c | 26 +++--- drivers/stepper/ti/drv84xx.c | 24 ++--- include/zephyr/drivers/stepper.h | 88 +++++++++---------- include/zephyr/drivers/stepper/stepper_fake.h | 4 +- samples/drivers/stepper/tmc50xx/src/main.c | 2 +- 11 files changed, 107 insertions(+), 107 deletions(-) diff --git a/drivers/stepper/adi_tmc/tmc22xx.c b/drivers/stepper/adi_tmc/tmc22xx.c index 4283516e7f0ba..93ff2269d37bd 100644 --- a/drivers/stepper/adi_tmc/tmc22xx.c +++ b/drivers/stepper/adi_tmc/tmc22xx.c @@ -15,11 +15,11 @@ struct tmc22xx_config { struct step_dir_stepper_common_config common; const struct gpio_dt_spec enable_pin; const struct gpio_dt_spec *msx_pins; - enum stepper_micro_step_resolution *msx_resolutions; + enum stepper_drv_micro_step_resolution *msx_resolutions; }; struct tmc22xx_data { - enum stepper_micro_step_resolution resolution; + enum stepper_drv_micro_step_resolution resolution; }; STEP_DIR_STEPPER_STRUCT_CHECK(struct tmc22xx_config); @@ -41,7 +41,7 @@ static int tmc22xx_stepper_disable(const struct device *dev) } static int tmc22xx_stepper_set_micro_step_res(const struct device *dev, - enum stepper_micro_step_resolution micro_step_res) + enum stepper_drv_micro_step_resolution micro_step_res) { struct tmc22xx_data *data = dev->data; const struct tmc22xx_config *config = dev->config; @@ -78,7 +78,7 @@ static int tmc22xx_stepper_set_micro_step_res(const struct device *dev, } static int tmc22xx_stepper_get_micro_step_res(const struct device *dev, - enum stepper_micro_step_resolution *micro_step_res) + enum stepper_drv_micro_step_resolution *micro_step_res) { struct tmc22xx_data *data = dev->data; @@ -182,11 +182,11 @@ static DEVICE_API(stepper_drv, tmc22xx_stepper_api) = { &tmc22xx_stepper_api); #define DT_DRV_COMPAT adi_tmc2209 -static enum stepper_micro_step_resolution tmc2209_msx_resolutions[MSX_PIN_STATE_COUNT] = { - STEPPER_MICRO_STEP_8, - STEPPER_MICRO_STEP_32, - STEPPER_MICRO_STEP_64, - STEPPER_MICRO_STEP_16, +static enum stepper_drv_micro_step_resolution tmc2209_msx_resolutions[MSX_PIN_STATE_COUNT] = { + STEPPER_DRV_MICRO_STEP_8, + STEPPER_DRV_MICRO_STEP_32, + STEPPER_DRV_MICRO_STEP_64, + STEPPER_DRV_MICRO_STEP_16, }; DT_INST_FOREACH_STATUS_OKAY_VARGS(TMC22XX_STEPPER_DEFINE, tmc2209_msx_resolutions) #undef DT_DRV_COMPAT diff --git a/drivers/stepper/adi_tmc/tmc50xx.c b/drivers/stepper/adi_tmc/tmc50xx.c index 4c37f02dc5d2e..9d483903226b7 100644 --- a/drivers/stepper/adi_tmc/tmc50xx.c +++ b/drivers/stepper/adi_tmc/tmc50xx.c @@ -417,7 +417,7 @@ int tmc50xx_stepper_set_max_velocity(const struct device *controller, const uint } static int tmc50xx_stepper_set_micro_step_res(const struct device *dev, - enum stepper_micro_step_resolution res) + enum stepper_drv_micro_step_resolution res) { const struct tmc50xx_stepper_config *config = dev->config; uint32_t reg_value; @@ -429,7 +429,7 @@ static int tmc50xx_stepper_set_micro_step_res(const struct device *dev, } reg_value &= ~TMC5XXX_CHOPCONF_MRES_MASK; - reg_value |= ((MICRO_STEP_RES_INDEX(STEPPER_MICRO_STEP_256) - LOG2(res)) + reg_value |= ((MICRO_STEP_RES_INDEX(STEPPER_DRV_MICRO_STEP_256) - LOG2(res)) << TMC5XXX_CHOPCONF_MRES_SHIFT); err = tmc50xx_write(config->controller, TMC50XX_CHOPCONF(config->index), reg_value); @@ -443,7 +443,7 @@ static int tmc50xx_stepper_set_micro_step_res(const struct device *dev, } static int tmc50xx_stepper_get_micro_step_res(const struct device *dev, - enum stepper_micro_step_resolution *res) + enum stepper_drv_micro_step_resolution *res) { const struct tmc50xx_stepper_config *config = dev->config; uint32_t reg_value; @@ -455,7 +455,7 @@ static int tmc50xx_stepper_get_micro_step_res(const struct device *dev, } reg_value &= TMC5XXX_CHOPCONF_MRES_MASK; reg_value >>= TMC5XXX_CHOPCONF_MRES_SHIFT; - *res = (1 << (MICRO_STEP_RES_INDEX(STEPPER_MICRO_STEP_256) - reg_value)); + *res = (1 << (MICRO_STEP_RES_INDEX(STEPPER_DRV_MICRO_STEP_256) - reg_value)); LOG_DBG("Stepper motor controller %s get micro step resolution: %d", dev->name, *res); return 0; } diff --git a/drivers/stepper/adi_tmc/tmc51xx/tmc51xx.c b/drivers/stepper/adi_tmc/tmc51xx/tmc51xx.c index 49cfe2d618de9..9dfc199d36b98 100644 --- a/drivers/stepper/adi_tmc/tmc51xx/tmc51xx.c +++ b/drivers/stepper/adi_tmc/tmc51xx/tmc51xx.c @@ -438,7 +438,7 @@ int tmc51xx_stepper_set_max_velocity(const struct device *dev, uint32_t velocity } static int tmc51xx_stepper_set_micro_step_res(const struct device *dev, - enum stepper_micro_step_resolution res) + enum stepper_drv_micro_step_resolution res) { const struct tmc51xx_stepper_config *config = dev->config; uint32_t reg_value; @@ -450,7 +450,7 @@ static int tmc51xx_stepper_set_micro_step_res(const struct device *dev, } reg_value &= ~TMC5XXX_CHOPCONF_MRES_MASK; - reg_value |= ((MICRO_STEP_RES_INDEX(STEPPER_MICRO_STEP_256) - LOG2(res)) + reg_value |= ((MICRO_STEP_RES_INDEX(STEPPER_DRV_MICRO_STEP_256) - LOG2(res)) << TMC5XXX_CHOPCONF_MRES_SHIFT); err = tmc51xx_write(config->controller, TMC51XX_CHOPCONF, reg_value); @@ -464,7 +464,7 @@ static int tmc51xx_stepper_set_micro_step_res(const struct device *dev, } static int tmc51xx_stepper_get_micro_step_res(const struct device *dev, - enum stepper_micro_step_resolution *res) + enum stepper_drv_micro_step_resolution *res) { const struct tmc51xx_stepper_config *config = dev->config; uint32_t reg_value; @@ -476,7 +476,7 @@ static int tmc51xx_stepper_get_micro_step_res(const struct device *dev, } reg_value &= TMC5XXX_CHOPCONF_MRES_MASK; reg_value >>= TMC5XXX_CHOPCONF_MRES_SHIFT; - *res = (1 << (MICRO_STEP_RES_INDEX(STEPPER_MICRO_STEP_256) - reg_value)); + *res = (1 << (MICRO_STEP_RES_INDEX(STEPPER_DRV_MICRO_STEP_256) - reg_value)); LOG_DBG("Stepper motor controller %s get micro step resolution: %d", dev->name, *res); return 0; } diff --git a/drivers/stepper/allegro/a4979.c b/drivers/stepper/allegro/a4979.c index ea710d87f77c7..360cd7e5451f9 100644 --- a/drivers/stepper/allegro/a4979.c +++ b/drivers/stepper/allegro/a4979.c @@ -21,7 +21,7 @@ struct a4979_config { }; struct a4979_data { - enum stepper_micro_step_resolution micro_step_res; + enum stepper_drv_micro_step_resolution micro_step_res; }; STEP_DIR_STEPPER_STRUCT_CHECK(struct a4979_config); @@ -90,7 +90,7 @@ static int a4979_stepper_disable(const struct device *dev) } static int a4979_stepper_set_micro_step_res(const struct device *dev, - const enum stepper_micro_step_resolution micro_step_res) + const enum stepper_drv_micro_step_resolution micro_step_res) { const struct a4979_config *config = dev->config; struct a4979_data *data = dev->data; @@ -100,19 +100,19 @@ static int a4979_stepper_set_micro_step_res(const struct device *dev, uint8_t m1_value = 0; switch (micro_step_res) { - case STEPPER_MICRO_STEP_1: + case STEPPER_DRV_MICRO_STEP_1: m0_value = 0; m1_value = 0; break; - case STEPPER_MICRO_STEP_2: + case STEPPER_DRV_MICRO_STEP_2: m0_value = 1; m1_value = 0; break; - case STEPPER_MICRO_STEP_4: + case STEPPER_DRV_MICRO_STEP_4: m0_value = 0; m1_value = 1; break; - case STEPPER_MICRO_STEP_16: + case STEPPER_DRV_MICRO_STEP_16: m0_value = 1; m1_value = 1; break; @@ -135,7 +135,7 @@ static int a4979_stepper_set_micro_step_res(const struct device *dev, } static int a4979_stepper_get_micro_step_res(const struct device *dev, - enum stepper_micro_step_resolution *micro_step_res) + enum stepper_drv_micro_step_resolution *micro_step_res) { struct a4979_data *data = dev->data; diff --git a/drivers/stepper/fake_stepper_drv.c b/drivers/stepper/fake_stepper_drv.c index dad759a103f39..efaba5a51ec9d 100644 --- a/drivers/stepper/fake_stepper_drv.c +++ b/drivers/stepper/fake_stepper_drv.c @@ -15,7 +15,7 @@ #define DT_DRV_COMPAT zephyr_fake_stepper struct fake_stepper_data { - enum stepper_micro_step_resolution micro_step_res; + enum stepper_drv_micro_step_resolution micro_step_res; }; DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_enable, const struct device *); @@ -23,16 +23,16 @@ DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_enable, const struct device *); DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_disable, const struct device *); DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_set_micro_step_res, const struct device *, - enum stepper_micro_step_resolution); + enum stepper_drv_micro_step_resolution); DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_get_micro_step_res, const struct device *, - enum stepper_micro_step_resolution *); + enum stepper_drv_micro_step_resolution *); DEFINE_FAKE_VALUE_FUNC(int, fake_stepper_drv_set_event_callback, const struct device *, stepper_drv_event_cb_t, void *); static int fake_stepper_set_micro_step_res_delegate(const struct device *dev, - const enum stepper_micro_step_resolution res) + const enum stepper_drv_micro_step_resolution res) { struct fake_stepper_data *data = dev->data; @@ -42,7 +42,7 @@ static int fake_stepper_set_micro_step_res_delegate(const struct device *dev, } static int fake_stepper_get_micro_step_res_delegate(const struct device *dev, - enum stepper_micro_step_resolution *res) + enum stepper_drv_micro_step_resolution *res) { struct fake_stepper_data *data = dev->data; diff --git a/drivers/stepper/h_bridge_stepper.c b/drivers/stepper/h_bridge_stepper.c index 11d6f57173038..1d4d0407d0b9e 100644 --- a/drivers/stepper/h_bridge_stepper.c +++ b/drivers/stepper/h_bridge_stepper.c @@ -13,7 +13,7 @@ #include LOG_MODULE_REGISTER(h_bridge_stepper, CONFIG_STEPPER_LOG_LEVEL); -#define MAX_MICRO_STEP_RES STEPPER_MICRO_STEP_2 +#define MAX_MICRO_STEP_RES STEPPER_DRV_MICRO_STEP_2 #define NUM_CONTROL_PINS 4 static const uint8_t @@ -88,15 +88,15 @@ static void update_coil_charge(const struct device *dev) } static int h_bridge_stepper_set_micro_step_res(const struct device *dev, - enum stepper_micro_step_resolution micro_step_res) + enum stepper_drv_micro_step_resolution micro_step_res) { struct h_bridge_stepper_data *data = dev->data; int err = 0; K_SPINLOCK(&data->lock) { switch (micro_step_res) { - case STEPPER_MICRO_STEP_1: - case STEPPER_MICRO_STEP_2: + case STEPPER_DRV_MICRO_STEP_1: + case STEPPER_DRV_MICRO_STEP_2: data->step_gap = MAX_MICRO_STEP_RES >> (micro_step_res - 1); break; default: @@ -108,7 +108,7 @@ static int h_bridge_stepper_set_micro_step_res(const struct device *dev, } static int h_bridge_stepper_get_micro_step_res(const struct device *dev, - enum stepper_micro_step_resolution *micro_step_res) + enum stepper_drv_micro_step_resolution *micro_step_res) { struct h_bridge_stepper_data *data = dev->data; *micro_step_res = MAX_MICRO_STEP_RES >> (data->step_gap - 1); @@ -230,7 +230,7 @@ static DEVICE_API(stepper_drv, h_bridge_stepper_api) = { static struct h_bridge_stepper_data h_bridge_stepper_data_##inst = { \ .step_gap = MAX_MICRO_STEP_RES >> (DT_INST_PROP(inst, micro_step_res) - 1), \ }; \ - BUILD_ASSERT(DT_INST_PROP(inst, micro_step_res) <= STEPPER_MICRO_STEP_2, \ + BUILD_ASSERT(DT_INST_PROP(inst, micro_step_res) <= STEPPER_DRV_MICRO_STEP_2, \ "h_bridge stepper driver supports up to 2 micro steps"); \ DEVICE_DT_INST_DEFINE(inst, h_bridge_stepper_init, NULL, &h_bridge_stepper_data_##inst, \ &h_bridge_stepper_config_##inst, POST_KERNEL, \ diff --git a/drivers/stepper/stepper_shell.c b/drivers/stepper/stepper_shell.c index 4448e1fcbd622..2608c456f2e52 100644 --- a/drivers/stepper/stepper_shell.c +++ b/drivers/stepper/stepper_shell.c @@ -21,7 +21,7 @@ enum { struct stepper_microstep_map { const char *name; - enum stepper_micro_step_resolution microstep; + enum stepper_drv_micro_step_resolution microstep; }; struct stepper_direction_map { @@ -126,15 +126,15 @@ static const struct stepper_control_idx_map stepper_control_idx_map[] = { }; static const struct stepper_microstep_map stepper_microstep_map[] = { - STEPPER_MICROSTEP_MAP("1", STEPPER_MICRO_STEP_1), - STEPPER_MICROSTEP_MAP("2", STEPPER_MICRO_STEP_2), - STEPPER_MICROSTEP_MAP("4", STEPPER_MICRO_STEP_4), - STEPPER_MICROSTEP_MAP("8", STEPPER_MICRO_STEP_8), - STEPPER_MICROSTEP_MAP("16", STEPPER_MICRO_STEP_16), - STEPPER_MICROSTEP_MAP("32", STEPPER_MICRO_STEP_32), - STEPPER_MICROSTEP_MAP("64", STEPPER_MICRO_STEP_64), - STEPPER_MICROSTEP_MAP("128", STEPPER_MICRO_STEP_128), - STEPPER_MICROSTEP_MAP("256", STEPPER_MICRO_STEP_256), + STEPPER_MICROSTEP_MAP("1", STEPPER_DRV_MICRO_STEP_1), + STEPPER_MICROSTEP_MAP("2", STEPPER_DRV_MICRO_STEP_2), + STEPPER_MICROSTEP_MAP("4", STEPPER_DRV_MICRO_STEP_4), + STEPPER_MICROSTEP_MAP("8", STEPPER_DRV_MICRO_STEP_8), + STEPPER_MICROSTEP_MAP("16", STEPPER_DRV_MICRO_STEP_16), + STEPPER_MICROSTEP_MAP("32", STEPPER_DRV_MICRO_STEP_32), + STEPPER_MICROSTEP_MAP("64", STEPPER_DRV_MICRO_STEP_64), + STEPPER_MICROSTEP_MAP("128", STEPPER_DRV_MICRO_STEP_128), + STEPPER_MICROSTEP_MAP("256", STEPPER_DRV_MICRO_STEP_256), }; static void cmd_stepper_direction(size_t idx, struct shell_static_entry *entry) @@ -411,7 +411,7 @@ static int cmd_stepper_set_microstep_interval(const struct shell *sh, size_t arg static int cmd_stepper_set_micro_step_res(const struct shell *sh, size_t argc, char **argv) { const struct device *dev; - enum stepper_micro_step_resolution resolution; + enum stepper_drv_micro_step_resolution resolution; int err = -EINVAL; for (int i = 0; i < ARRAY_SIZE(stepper_microstep_map); i++) { @@ -445,7 +445,7 @@ static int cmd_stepper_get_micro_step_res(const struct shell *sh, size_t argc, c { const struct device *dev; int err; - enum stepper_micro_step_resolution micro_step_res; + enum stepper_drv_micro_step_resolution micro_step_res; err = parse_device_arg(sh, argv, &dev); if (err < 0) { @@ -647,7 +647,7 @@ static int cmd_stepper_info(const struct shell *sh, size_t argc, char **argv) { const struct device *dev; int err; - enum stepper_micro_step_resolution micro_step_res; + enum stepper_drv_micro_step_resolution micro_step_res; err = parse_device_arg(sh, argv, &dev); if (err < 0) { diff --git a/drivers/stepper/ti/drv84xx.c b/drivers/stepper/ti/drv84xx.c index a1a89b060abc3..d9b84aed8af6c 100644 --- a/drivers/stepper/ti/drv84xx.c +++ b/drivers/stepper/ti/drv84xx.c @@ -48,7 +48,7 @@ struct drv84xx_pin_states { struct drv84xx_data { const struct device *dev; struct drv84xx_pin_states pin_states; - enum stepper_micro_step_resolution ustep_res; + enum stepper_drv_micro_step_resolution ustep_res; struct gpio_callback fault_cb_data; stepper_drv_event_cb_t fault_cb; void *fault_cb_user_data; @@ -267,7 +267,7 @@ static int drv84xx_set_fault_cb(const struct device *dev, stepper_drv_event_cb_t } static int drv84xx_set_micro_step_res(const struct device *dev, - enum stepper_micro_step_resolution micro_step_res) + enum stepper_drv_micro_step_resolution micro_step_res) { const struct drv84xx_config *config = dev->config; struct drv84xx_data *data = dev->data; @@ -290,39 +290,39 @@ static int drv84xx_set_micro_step_res(const struct device *dev, * 3: 330kΩ */ switch (micro_step_res) { - case STEPPER_MICRO_STEP_1: + case STEPPER_DRV_MICRO_STEP_1: m0_value = 0; m1_value = 0; break; - case STEPPER_MICRO_STEP_2: + case STEPPER_DRV_MICRO_STEP_2: m0_value = 2; m1_value = 0; break; - case STEPPER_MICRO_STEP_4: + case STEPPER_DRV_MICRO_STEP_4: m0_value = 0; m1_value = 1; break; - case STEPPER_MICRO_STEP_8: + case STEPPER_DRV_MICRO_STEP_8: m0_value = 1; m1_value = 1; break; - case STEPPER_MICRO_STEP_16: + case STEPPER_DRV_MICRO_STEP_16: m0_value = 2; m1_value = 1; break; - case STEPPER_MICRO_STEP_32: + case STEPPER_DRV_MICRO_STEP_32: m0_value = 0; m1_value = 2; break; - case STEPPER_MICRO_STEP_64: + case STEPPER_DRV_MICRO_STEP_64: m0_value = 2; m1_value = 3; break; - case STEPPER_MICRO_STEP_128: + case STEPPER_DRV_MICRO_STEP_128: m0_value = 2; m1_value = 2; break; - case STEPPER_MICRO_STEP_256: + case STEPPER_DRV_MICRO_STEP_256: m0_value = 1; m1_value = 2; break; @@ -348,7 +348,7 @@ static int drv84xx_set_micro_step_res(const struct device *dev, } static int drv84xx_get_micro_step_res(const struct device *dev, - enum stepper_micro_step_resolution *micro_step_res) + enum stepper_drv_micro_step_resolution *micro_step_res) { struct drv84xx_data *data = dev->data; *micro_step_res = data->ustep_res; diff --git a/include/zephyr/drivers/stepper.h b/include/zephyr/drivers/stepper.h index dcfa255524be2..5798ba15fab40 100644 --- a/include/zephyr/drivers/stepper.h +++ b/include/zephyr/drivers/stepper.h @@ -29,43 +29,6 @@ extern "C" { #endif -/** - * @brief Stepper Motor micro-step resolution options - */ -enum stepper_micro_step_resolution { - /** Full step resolution */ - STEPPER_MICRO_STEP_1 = 1, - /** 2 micro-steps per full step */ - STEPPER_MICRO_STEP_2 = 2, - /** 4 micro-steps per full step */ - STEPPER_MICRO_STEP_4 = 4, - /** 8 micro-steps per full step */ - STEPPER_MICRO_STEP_8 = 8, - /** 16 micro-steps per full step */ - STEPPER_MICRO_STEP_16 = 16, - /** 32 micro-steps per full step */ - STEPPER_MICRO_STEP_32 = 32, - /** 64 micro-steps per full step */ - STEPPER_MICRO_STEP_64 = 64, - /** 128 micro-steps per full step */ - STEPPER_MICRO_STEP_128 = 128, - /** 256 micro-steps per full step */ - STEPPER_MICRO_STEP_256 = 256, -}; - -/** - * @brief Macro to calculate the index of the microstep resolution - * @param res Microstep resolution - */ -#define MICRO_STEP_RES_INDEX(res) LOG2(res) - -#define VALID_MICRO_STEP_RES(res) \ - ((res) == STEPPER_MICRO_STEP_1 || (res) == STEPPER_MICRO_STEP_2 || \ - (res) == STEPPER_MICRO_STEP_4 || (res) == STEPPER_MICRO_STEP_8 || \ - (res) == STEPPER_MICRO_STEP_16 || (res) == STEPPER_MICRO_STEP_32 || \ - (res) == STEPPER_MICRO_STEP_64 || (res) == STEPPER_MICRO_STEP_128 || \ - (res) == STEPPER_MICRO_STEP_256) - /** * @brief Check if the stepper index is valid * @param stepper_idx Stepper index to check @@ -478,6 +441,43 @@ static inline int z_impl_stepper_is_moving(const struct device *dev, const uint8 * @{ */ +/** + * @brief Stepper Motor micro-step resolution options + */ +enum stepper_drv_micro_step_resolution { + /** Full step resolution */ + STEPPER_DRV_MICRO_STEP_1 = 1, + /** 2 micro-steps per full step */ + STEPPER_DRV_MICRO_STEP_2 = 2, + /** 4 micro-steps per full step */ + STEPPER_DRV_MICRO_STEP_4 = 4, + /** 8 micro-steps per full step */ + STEPPER_DRV_MICRO_STEP_8 = 8, + /** 16 micro-steps per full step */ + STEPPER_DRV_MICRO_STEP_16 = 16, + /** 32 micro-steps per full step */ + STEPPER_DRV_MICRO_STEP_32 = 32, + /** 64 micro-steps per full step */ + STEPPER_DRV_MICRO_STEP_64 = 64, + /** 128 micro-steps per full step */ + STEPPER_DRV_MICRO_STEP_128 = 128, + /** 256 micro-steps per full step */ + STEPPER_DRV_MICRO_STEP_256 = 256, +}; + +/** + * @brief Macro to calculate the index of the microstep resolution + * @param res Microstep resolution + */ +#define MICRO_STEP_RES_INDEX(res) LOG2(res) + +#define VALID_MICRO_STEP_RES(res) \ + ((res) == STEPPER_DRV_MICRO_STEP_1 || (res) == STEPPER_DRV_MICRO_STEP_2 || \ + (res) == STEPPER_DRV_MICRO_STEP_4 || (res) == STEPPER_DRV_MICRO_STEP_8 || \ + (res) == STEPPER_DRV_MICRO_STEP_16 || (res) == STEPPER_DRV_MICRO_STEP_32 || \ + (res) == STEPPER_DRV_MICRO_STEP_64 || (res) == STEPPER_DRV_MICRO_STEP_128 || \ + (res) == STEPPER_DRV_MICRO_STEP_256) + enum stepper_drv_event { /** Stepper driver stall detected */ STEPPER_DRV_EVENT_STALL_DETETCTED = 0, @@ -527,7 +527,7 @@ typedef int (*stepper_drv_step_t)(const struct device *dev); * @see stepper_drv_set_micro_step_res() for details. */ typedef int (*stepper_drv_set_micro_step_res_t)( - const struct device *dev, const enum stepper_micro_step_resolution resolution); + const struct device *dev, const enum stepper_drv_micro_step_resolution resolution); /** * @brief Get the stepper micro-step resolution @@ -535,13 +535,13 @@ typedef int (*stepper_drv_set_micro_step_res_t)( * @see stepper_drv_get_micro_step_res() for details. */ typedef int (*stepper_drv_get_micro_step_res_t)(const struct device *dev, - enum stepper_micro_step_resolution *resolution); + enum stepper_drv_micro_step_resolution *resolution); /** * @brief Callback function for stepper driver events */ typedef void (*stepper_drv_event_cb_t)(const struct device *dev, const enum stepper_drv_event event, - void *user_data); + void *user_data); /** * @brief Set the callback function to be called when a stepper_drv_event occurs @@ -672,10 +672,10 @@ static inline int z_impl_stepper_drv_set_direction(const struct device *dev, * @retval 0 Success */ __syscall int stepper_drv_set_micro_step_res(const struct device *dev, - enum stepper_micro_step_resolution res); + enum stepper_drv_micro_step_resolution res); static inline int z_impl_stepper_drv_set_micro_step_res(const struct device *dev, - enum stepper_micro_step_resolution res) + enum stepper_drv_micro_step_resolution res) { __ASSERT_NO_MSG(dev != NULL); const struct stepper_drv_driver_api *api = (const struct stepper_drv_driver_api *)dev->api; @@ -701,10 +701,10 @@ static inline int z_impl_stepper_drv_set_micro_step_res(const struct device *dev * @retval 0 Success */ __syscall int stepper_drv_get_micro_step_res(const struct device *dev, - enum stepper_micro_step_resolution *res); + enum stepper_drv_micro_step_resolution *res); static inline int z_impl_stepper_drv_get_micro_step_res(const struct device *dev, - enum stepper_micro_step_resolution *res) + enum stepper_drv_micro_step_resolution *res) { __ASSERT_NO_MSG(dev != NULL); __ASSERT_NO_MSG(res != NULL); diff --git a/include/zephyr/drivers/stepper/stepper_fake.h b/include/zephyr/drivers/stepper/stepper_fake.h index ca15ee9d55d9f..f4ba69733e8ea 100644 --- a/include/zephyr/drivers/stepper/stepper_fake.h +++ b/include/zephyr/drivers/stepper/stepper_fake.h @@ -19,10 +19,10 @@ DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_enable, const struct device *); DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_disable, const struct device *); DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_set_micro_step_res, const struct device *, - enum stepper_micro_step_resolution); + enum stepper_drv_micro_step_resolution); DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_get_micro_step_res, const struct device *, - enum stepper_micro_step_resolution *); + enum stepper_drv_micro_step_resolution *); DECLARE_FAKE_VALUE_FUNC(int, fake_stepper_drv_set_event_callback, const struct device *, stepper_drv_event_cb_t, void *); diff --git a/samples/drivers/stepper/tmc50xx/src/main.c b/samples/drivers/stepper/tmc50xx/src/main.c index 451c9195fdf25..85d0ba5b9a1e9 100644 --- a/samples/drivers/stepper/tmc50xx/src/main.c +++ b/samples/drivers/stepper/tmc50xx/src/main.c @@ -44,7 +44,7 @@ int main(void) stepper_set_event_callback(stepper_controller, stepper_index, stepper_callback, NULL); stepper_drv_enable(stepper); - enum stepper_micro_step_resolution micro_step_res; + enum stepper_drv_micro_step_resolution micro_step_res; stepper_drv_get_micro_step_res(stepper, µ_step_res); printf("Microstep resolution is %d\n", micro_step_res); From 5e204746de29b1859168c85fdc18eb36dd4a1977 Mon Sep 17 00:00:00 2001 From: Jilay Pandya Date: Tue, 8 Jul 2025 21:24:03 +0200 Subject: [PATCH 6/6] doc: add class diagram for stepper driver subsystem - add class diagram for stepper driver subsystem showing relationship between stepper and stepper_drv api - move stepper.rst in a dedicated stepper folder - add information about stepper_drv api and relevant functions in stepper documentation. Signed-off-by: Jilay Pandya --- MAINTAINERS.yml | 2 +- doc/hardware/peripherals/index.rst | 2 +- .../{stepper.rst => stepper/index.rst} | 56 ++++++++---- .../stepper/individual_controller_driver.rst | 55 +++++++++++ .../stepper/integrated_controller_driver.rst | 86 ++++++++++++++++++ ...tepper_driver_subsystem_class_diagram.webp | Bin 0 -> 247890 bytes 6 files changed, 183 insertions(+), 18 deletions(-) rename doc/hardware/peripherals/{stepper.rst => stepper/index.rst} (72%) create mode 100644 doc/hardware/peripherals/stepper/individual_controller_driver.rst create mode 100644 doc/hardware/peripherals/stepper/integrated_controller_driver.rst create mode 100644 doc/hardware/peripherals/stepper/stepper_driver_subsystem_class_diagram.webp diff --git a/MAINTAINERS.yml b/MAINTAINERS.yml index c825cb30055d9..c90979d7cddc5 100644 --- a/MAINTAINERS.yml +++ b/MAINTAINERS.yml @@ -2312,7 +2312,7 @@ Documentation Infrastructure: - include/zephyr/drivers/stepper/ - include/zephyr/drivers/stepper.h - dts/bindings/stepper/ - - doc/hardware/peripherals/stepper.rst + - doc/hardware/peripherals/stepper/ - samples/drivers/stepper/ - tests/drivers/build_all/stepper/ - tests/drivers/stepper/ diff --git a/doc/hardware/peripherals/index.rst b/doc/hardware/peripherals/index.rst index 1b235d242c33a..e0833a820f8bb 100644 --- a/doc/hardware/peripherals/index.rst +++ b/doc/hardware/peripherals/index.rst @@ -57,7 +57,7 @@ Peripherals sensor/index.rst sent.rst spi.rst - stepper.rst + stepper/index.rst smbus.rst uart.rst usbc_vbus.rst diff --git a/doc/hardware/peripherals/stepper.rst b/doc/hardware/peripherals/stepper/index.rst similarity index 72% rename from doc/hardware/peripherals/stepper.rst rename to doc/hardware/peripherals/stepper/index.rst index 5e399b2d594df..1c0b07436deaf 100644 --- a/doc/hardware/peripherals/stepper.rst +++ b/doc/hardware/peripherals/stepper/index.rst @@ -3,22 +3,33 @@ Steppers ######## -The stepper driver API provides a set of functions for controlling and configuring stepper drivers. +The stepper driver subsystem consists of two device driver APIs: -Configure Stepper Driver -======================== +.. figure:: stepper_driver_subsystem_class_diagram.webp + :align: center + :alt: Stepper driver subsystem class diagram + +Stepper Driver API +****************** + +The stepper driver API provides a common interface for stepper drivers. + +- Configure **micro-stepping resolution** using :c:func:`stepper_drv_set_micro_step_res` + and :c:func:`stepper_drv_get_micro_step_res`. +- **Enable** the stepper driver using :c:func:`stepper_drv_enable`. +- **Disable** the stepper driver using :c:func:`stepper_drv_disable`. +- Set the **direction** of the stepper using :c:func:`stepper_drv_set_direction`. +- **Step** a single step using :c:func:`stepper_drv_step`. +- Register an **event callback** using :c:func:`stepper_drv_set_event_cb`. + +Stepper API +*********** + +The stepper API provides a common interface for stepper controllers. -- Configure **micro-stepping resolution** using :c:func:`stepper_set_micro_step_res` - and :c:func:`stepper_get_micro_step_res`. - Configure **reference position** in microsteps using :c:func:`stepper_set_reference_position` and :c:func:`stepper_get_actual_position`. - Set **step interval** in nanoseconds between steps using :c:func:`stepper_set_microstep_interval` -- **Enable** the stepper driver using :c:func:`stepper_enable`. -- **Disable** the stepper driver using :c:func:`stepper_disable`. - -Control Stepper -=============== - - **Move by** +/- micro-steps also known as **relative movement** using :c:func:`stepper_move_by`. - **Move to** a specific position also known as **absolute movement** using :c:func:`stepper_move_to`. - Run continuously with a **constant step interval** in a specific direction until @@ -27,8 +38,10 @@ Control Stepper - Check if the stepper is **moving** using :c:func:`stepper_is_moving`. - Register an **event callback** using :c:func:`stepper_set_event_callback`. +.. _stepper-device-tree: + Device Tree -=========== +*********** In the context of stepper controllers device tree provides the initial hardware configuration for stepper drivers on a per device level. Each device must specify @@ -36,10 +49,16 @@ a device tree binding in Zephyr, and ideally, a set of hardware configuration op for things such as current settings, ramp parameters and furthermore. These can then be used in a boards devicetree to configure a stepper driver to its initial state. -See examples in: +Driver Composition Scenarios +============================ + +Below are two typical scenarios: -- :dtcompatible:`zephyr,h-bridge-stepper` -- :dtcompatible:`adi,tmc50xx` +.. toctree:: + :maxdepth: 1 + + integrated_controller_driver.rst + individual_controller_driver.rst Discord ======= @@ -48,9 +67,10 @@ Zephyr has a `stepper discord`_ channel for stepper related discussions, which is open to all. .. _stepper-api-reference: +.. _stepper-drv-api-reference: Stepper API Test Suite -====================== +********************** The stepper API test suite provides a set of tests that can be used to verify the functionality of stepper drivers. @@ -93,6 +113,10 @@ API Reference A common set of functions which should be implemented by all stepper drivers. +.. doxygengroup:: stepper_drv_interface + +A common set of functions which should be implemented by all stepper controllers. + .. doxygengroup:: stepper_interface Stepper controller specific APIs diff --git a/doc/hardware/peripherals/stepper/individual_controller_driver.rst b/doc/hardware/peripherals/stepper/individual_controller_driver.rst new file mode 100644 index 0000000000000..f8de81563be7d --- /dev/null +++ b/doc/hardware/peripherals/stepper/individual_controller_driver.rst @@ -0,0 +1,55 @@ +.. _stepper-individual-controller-driver: + +Individual Stepper Motion Controller and Driver +############################################### + +A motion control driver implements ``stepper`` API, for instance, :dtcompatible:`zephyr,stepper-motion-control` +and a hardware driver implements ``stepper_drv`` API, for instance, :dtcompatible:`ti,drv84xx` or +:dtcompatible:`zephyr,h-bridge-stepper`. + +Following is an example of a device tree configuration for a stepper driver with a dedicated stepper motion +controller: + +.. code-block:: dts + + / { + aliases { + x_axis_stepper_motion_controller = &stepper_motion_control; + x_axis_stepper_driver = &drv8424; + }; + }; + + /* DEVICE_API: stepper_drv api */ + drv8424: drv8424 { + status = "okay"; + compatible = "ti,drv84xx"; + + dir-gpios = <&gpio1 0 GPIO_ACTIVE_LOW>; + step-gpios = <&gpio2 1 GPIO_ACTIVE_LOW>; + sleep-gpios = <&gpio3 0 GPIO_ACTIVE_LOW>; + en-gpios = <&gpio4 1 GPIO_ACTIVE_LOW>; + m0-gpios = <&gpio5 0 GPIO_ACTIVE_LOW>; + m1-gpios = <&gpio6 1 GPIO_ACTIVE_LOW>; + }; + + /* DEVICE_API: stepper api */ + stepper_motion_control: stepper_motion_control { + compatible = "zephyr,stepper-motion-control"; + status = "okay"; + counter = <&counter0>; + stepper = <&drv8424>; + }; + +All the stepper api functions need a stepper motor index, since a stepper motion controller can control +multiple motors. However, this can be configured via application specific Kconfig to use a specific index, +for instance, :kconfig:option-regex:`CONFIG_STEPPER_AXIS_*`. + +Following the aforementioned configurations, the stepper driver subsystem can be used in the application code +as follows: + +.. code-block:: c + + /* Configure the index of motor and respective axis via Kconfig */ + stepper_move_to(x_axis_stepper_motion_controller, CONFIG_STEPPER_AXIS_X_*, 200); + stepper_stop(x_axis_stepper_motion_controller, CONFIG_STEPPER_AXIS_X_*); + stepper_drv_disable(x_axis_stepper_driver); diff --git a/doc/hardware/peripherals/stepper/integrated_controller_driver.rst b/doc/hardware/peripherals/stepper/integrated_controller_driver.rst new file mode 100644 index 0000000000000..0bb715b637c77 --- /dev/null +++ b/doc/hardware/peripherals/stepper/integrated_controller_driver.rst @@ -0,0 +1,86 @@ +.. _stepper-integrated-controller-driver: + +Integrated Stepper Motion Control and Driver +############################################ + +Implements both ``stepper`` and ``stepper_drv`` APIs in a single driver. For instance, :dtcompatible:`adi,tmc50xx`. + +Following is an example of a device tree configuration for a stepper driver with integrated motion control: + +.. code-block:: dts + + / { + aliases { + x_y_stepper_motion_controller = &tmc50xx; + x_axis_stepper_driver = &motor_x; + y_axis_stepper_driver = &motor_y; + }; + }; + + &spi0 { + /* SPI bus options here, not shown */ + + /* Dual controller/driver for up to two 2-phase bipolar stepper motors */ + /* DEVICE_API: stepper api */ + tmc50xx: tmc50xx@0 { + compatible = "adi,tmc50xx"; + reg = <0>; + spi-max-frequency = ; /* Maximum SPI bus frequency */ + + #address-cells = <1>; + #size-cells = <0>; + + poscmp-enable; test-mode; lock-gconf; /* ADI TMC Global configuration flags */ + clock-frequency = ; /* Internal/External Clock frequency */ + + /* DEVICE_API: stepper_drv api */ + motor_x: motor@0 { + status = "okay"; + reg = <0>; + + /* common stepper controller settings */ + invert-direction; + micro-step-res = <256>; + + /* ADI TMC stallguard settings specific to TMC50XX */ + activate-stallguard2; + ..................... + + /* ADI TMC ramp generator as well as current settings */ + vstart = <10>; + .............. + }; + + /* DEVICE_API: stepper_drv api */ + motor_y: motor@1 { + status = "okay"; + reg = <1>; + + /* common stepper controller settings */ + micro-step-res = <256>; + + /* ADI TMC stallguard settings specific to TMC50XX */ + activate-stallguard2; + ..................... + + /* ADI TMC ramp generator as well as current settings */ + vstart = <10>; + .............. + }; + }; + }; + +All the stepper api functions need a stepper motor index, since a stepper motion controller can control +multiple motors. However, this can be configured via application specific Kconfig to use a specific index, +for instance, :kconfig:option-regex:`CONFIG_STEPPER_AXIS_*`. + +Following the aforementioned configurations, the stepper driver subsystem can be used in the application code +as follows: + +.. code-block:: c + + /* Configure the index of motor and respective axis via Kconfig */ + stepper_move_to(x_y_stepper_motion_controller, CONFIG_STEPPER_AXIS_X_*, 200); + stepper_stop(x_y_stepper_motion_controller, CONFIG_STEPPER_AXIS_Y_*); + stepper_drv_disable(x_axis_stepper_driver); + stepper_drv_disable(y_axis_stepper_driver); diff --git a/doc/hardware/peripherals/stepper/stepper_driver_subsystem_class_diagram.webp b/doc/hardware/peripherals/stepper/stepper_driver_subsystem_class_diagram.webp new file mode 100644 index 0000000000000000000000000000000000000000..cc2dabeee43330f93c2f3f4bc4196620e0e03894 GIT binary patch literal 247890 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 z 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%0A_z3gq^?8O(kx2&*O<^ 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 U0002O