Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

drivers: stepper: Add timing source API #83342

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion drivers/stepper/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/drivers/stepper.h)
# zephyr-keep-sorted-start
add_subdirectory_ifdef(CONFIG_STEPPER_ADI_TMC adi_tmc)
add_subdirectory_ifdef(CONFIG_STEPPER_TI ti)
add_subdirectory_ifdef(CONFIG_STEP_DIR_STEPPER step_dir)
# zephyr-keep-sorted-stop

zephyr_library()
zephyr_library_property(ALLOW_EMPTY TRUE)

zephyr_library_sources_ifdef(CONFIG_FAKE_STEPPER fake_stepper_controller.c)
zephyr_library_sources_ifdef(CONFIG_GPIO_STEPPER gpio_stepper_controller.c)
zephyr_library_sources_ifdef(CONFIG_STEP_DIR_STEPPER step_dir_stepper_common.c)
zephyr_library_sources_ifdef(CONFIG_STEPPER_SHELL stepper_shell.c)
6 changes: 1 addition & 5 deletions drivers/stepper/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,13 @@ config STEPPER_SHELL
help
Enable stepper shell for testing.

config STEP_DIR_STEPPER
bool
help
Enable library used for step direction stepper drivers.

comment "Stepper Drivers"

# zephyr-keep-sorted-start
rsource "Kconfig.fake"
rsource "Kconfig.gpio"
rsource "adi_tmc/Kconfig"
rsource "step_dir/Kconfig"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: probably let's just keep this area only for drivers and place all the lib functionalities above comment "Stepper Drivers"

rsource "ti/Kconfig"
# zephyr-keep-sorted-stop

Expand Down
2 changes: 1 addition & 1 deletion drivers/stepper/adi_tmc/adi_tmc22xx_stepper_controller.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

#include "../step_dir_stepper_common.h"
#include "../step_dir/step_dir_stepper_common.h"

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(tmc22xx, CONFIG_STEPPER_LOG_LEVEL);
Expand Down
9 changes: 9 additions & 0 deletions drivers/stepper/step_dir/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# SPDX-FileCopyrightText: Copyright (c) 2024 Fabian Blatz
# SPDX-License-Identifier: Apache-2.0

zephyr_library()
zephyr_library_property(ALLOW_EMPTY TRUE)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is probably not needed since there's some files enabled unconditionally


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)
32 changes: 32 additions & 0 deletions drivers/stepper/step_dir/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Copyright (c) 2024 Fabian Blatz <[email protected]>
# SPDX-License-Identifier: Apache-2.0

config STEP_DIR_STEPPER
bool
help
Enable library used for step direction stepper drivers.

config STEP_DIR_STEPPER_GENERATE_ISR_SAFE_EVENTS
Copy link
Contributor

@jilaypandya jilaypandya Dec 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this feature could be used by other drivers as well, hence could be probably refactored out to Kconfig.stepper_event_template

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be then included over here with module called step_dir

bool "Guarantee non ISR callbacks upon stepper events"
depends on STEP_DIR_STEPPER
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 STEP_DIR_STEPPER_EVENT_QUEUE_LEN
int "Maximum number of pending stepper events"
default 4
depends on STEP_DIR_STEPPER_GENERATE_ISR_SAFE_EVENTS
help
The maximum number of stepper events that can be pending before new events
are dropped.

config STEP_DIR_STEPPER_COUNTER_TIMING
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the idea was to go via device tree instead of Kconfig, in order to be able to select on per instance basis. #82843 (comment)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I added the Kconfig to remove the source file from compilation if you are sure you only need the delayed work.

bool "Counter use for stepping"
depends on STEP_DIR_STEPPER
depends on COUNTER
default y
help
Enable usage of a counter device for accurate stepping.
Original file line number Diff line number Diff line change
Expand Up @@ -47,20 +47,70 @@ static inline int step_dir_stepper_perform_step(const struct device *dev)
return 0;
}

static 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_STEP_DIR_STEPPER_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
}

#ifdef CONFIG_STEP_DIR_STEPPER_GENERATE_ISR_SAFE_EVENTS
static void stepper_work_event_handler(struct k_work *work)
{
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_STEP_DIR_STEPPER_GENERATE_ISR_SAFE_EVENTS */

static void update_remaining_steps(struct step_dir_stepper_common_data *data)
{
if (data->step_count > 0) {
data->step_count--;
(void)k_work_reschedule(&data->stepper_dwork, K_USEC(data->delay_in_us));
} else if (data->step_count < 0) {
data->step_count++;
(void)k_work_reschedule(&data->stepper_dwork, K_USEC(data->delay_in_us));
} else {
if (!data->callback) {
LOG_WRN_ONCE("No callback set");
return;
}
data->callback(data->dev, STEPPER_EVENT_STEPS_COMPLETED, data->event_cb_user_data);
stepper_trigger_callback(data->dev, STEPPER_EVENT_STEPS_COMPLETED);
}
}

Expand All @@ -80,34 +130,41 @@ static void update_direction_from_step_count(const struct device *dev)
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;

if (data->step_count) {
(void)step_dir_stepper_perform_step(dev);
}

update_remaining_steps(dev->data);

if (config->timing_source->needs_reschedule(dev) && data->step_count != 0) {
(void)config->timing_source->start(dev);
}
}

static void velocity_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;

(void)step_dir_stepper_perform_step(dev);
(void)k_work_reschedule(&data->stepper_dwork, K_USEC(data->delay_in_us));

if (config->timing_source->needs_reschedule(dev)) {
(void)config->timing_source->start(dev);
}
}

static void stepper_work_step_handler(struct k_work *work)
void stepper_handle_timing_signal(const struct device *dev)
{
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 step_dir_stepper_common_data *data = dev->data;

K_SPINLOCK(&data->lock) {
switch (data->run_mode) {
case STEPPER_RUN_MODE_POSITION:
position_mode_task(data->dev);
position_mode_task(dev);
break;
case STEPPER_RUN_MODE_VELOCITY:
velocity_mode_task(data->dev);
velocity_mode_task(dev);
break;
default:
LOG_WRN("Unsupported run mode: %d", data->run_mode);
Expand All @@ -119,7 +176,6 @@ static void stepper_work_step_handler(struct k_work *work)
int step_dir_stepper_common_init(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;

if (!gpio_is_ready_dt(&config->step_pin) || !gpio_is_ready_dt(&config->dir_pin)) {
Expand All @@ -139,25 +195,41 @@ int step_dir_stepper_common_init(const struct device *dev)
return ret;
}

k_work_init_delayable(&data->stepper_dwork, stepper_work_step_handler);
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_STEP_DIR_STEPPER_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_STEP_DIR_STEPPER_EVENT_QUEUE_LEN);
k_work_init(&data->event_callback_work, stepper_work_event_handler);
#endif

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->delay_in_us == 0) {
if (data->max_velocity == 0) {
LOG_ERR("Velocity not set or invalid velocity 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->max_velocity);
update_direction_from_step_count(dev);
(void)k_work_reschedule(&data->stepper_dwork, K_NO_WAIT);
config->timing_source->start(dev);
}

return 0;
Expand All @@ -166,6 +238,7 @@ int step_dir_stepper_common_move_by(const struct device *dev, const int32_t micr
int step_dir_stepper_common_set_max_velocity(const struct device *dev, const uint32_t velocity)
{
struct step_dir_stepper_common_data *data = dev->data;
const struct step_dir_stepper_common_config *config = dev->config;

if (velocity == 0) {
LOG_ERR("Velocity cannot be zero");
Expand All @@ -178,7 +251,8 @@ int step_dir_stepper_common_set_max_velocity(const struct device *dev, const uin
}

K_SPINLOCK(&data->lock) {
data->delay_in_us = USEC_PER_SEC / velocity;
data->max_velocity = velocity;
config->timing_source->update(dev, velocity);
}

return 0;
Expand Down Expand Up @@ -209,43 +283,47 @@ int step_dir_stepper_common_get_actual_position(const struct device *dev, int32_
int step_dir_stepper_common_move_to(const struct device *dev, const int32_t value)
{
struct step_dir_stepper_common_data *data = dev->data;
const struct step_dir_stepper_common_config *config = dev->config;

if (data->delay_in_us == 0) {
if (data->max_velocity == 0) {
LOG_ERR("Velocity not set or invalid velocity set");
return -EINVAL;
}

K_SPINLOCK(&data->lock) {
data->run_mode = STEPPER_RUN_MODE_POSITION;
data->step_count = value - data->actual_position;
config->timing_source->update(dev, data->max_velocity);
update_direction_from_step_count(dev);
(void)k_work_reschedule(&data->stepper_dwork, K_NO_WAIT);
config->timing_source->start(dev);
}

return 0;
}

int step_dir_stepper_common_is_moving(const struct device *dev, bool *is_moving)
{
struct step_dir_stepper_common_data *data = dev->data;
const struct step_dir_stepper_common_config *config = dev->config;

*is_moving = k_work_delayable_is_pending(&data->stepper_dwork);
*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,
const uint32_t velocity)
{
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;
data->max_velocity = velocity;
config->timing_source->update(dev, velocity);
if (velocity != 0) {
data->delay_in_us = USEC_PER_SEC / velocity;
(void)k_work_reschedule(&data->stepper_dwork, K_NO_WAIT);
config->timing_source->start(dev);
} else {
(void)k_work_cancel_delayable(&data->stepper_dwork);
config->timing_source->stop(dev);
}
}

Expand Down
Loading
Loading