drivers: stepper: Add step direction stepper common binding
Adds a step direction binding that can be used with any stepper that implements said control interface to cut down on boilerplate code. Signed-off-by: Fabian Blatz <fabianblatz@gmail.com>
This commit is contained in:
parent
51d59fa1d1
commit
ba2aee24c9
5 changed files with 474 additions and 1 deletions
|
@ -12,4 +12,5 @@ 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)
|
||||
|
|
|
@ -24,10 +24,17 @@ 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"
|
||||
|
||||
rsource "adi_tmc/Kconfig"
|
||||
# zephyr-keep-sorted-start
|
||||
rsource "Kconfig.fake"
|
||||
rsource "Kconfig.gpio"
|
||||
rsource "adi_tmc/Kconfig"
|
||||
# zephyr-keep-sorted-stop
|
||||
|
||||
endif
|
||||
|
|
263
drivers/stepper/step_dir_stepper_common.c
Normal file
263
drivers/stepper/step_dir_stepper_common.c
Normal file
|
@ -0,0 +1,263 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: Copyright (c) 2024 Fabian Blatz <fabianblatz@gmail.com>
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "step_dir_stepper_common.h"
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(step_dir_stepper, CONFIG_STEPPER_LOG_LEVEL);
|
||||
|
||||
static inline int step_dir_stepper_perform_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);
|
||||
break;
|
||||
case STEPPER_DIRECTION_NEGATIVE:
|
||||
ret = gpio_pin_set_dt(&config->dir_pin, 0);
|
||||
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);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to toggle step pin: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!config->dual_edge) {
|
||||
ret = gpio_pin_toggle_dt(&config->step_pin);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to toggle step pin: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (data->step_count) {
|
||||
(void)step_dir_stepper_perform_step(dev);
|
||||
}
|
||||
update_remaining_steps(dev->data);
|
||||
}
|
||||
|
||||
static void velocity_mode_task(const struct device *dev)
|
||||
{
|
||||
struct step_dir_stepper_common_data *data = dev->data;
|
||||
|
||||
(void)step_dir_stepper_perform_step(dev);
|
||||
(void)k_work_reschedule(&data->stepper_dwork, K_USEC(data->delay_in_us));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)) {
|
||||
LOG_ERR("GPIO pins are not ready");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = gpio_pin_configure_dt(&config->step_pin, GPIO_OUTPUT);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to configure step pin: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = gpio_pin_configure_dt(&config->dir_pin, GPIO_OUTPUT);
|
||||
if (ret < 0) {
|
||||
LOG_ERR("Failed to configure dir pin: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
k_work_init_delayable(&data->stepper_dwork, stepper_work_step_handler);
|
||||
|
||||
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;
|
||||
|
||||
if (data->delay_in_us == 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;
|
||||
update_direction_from_step_count(dev);
|
||||
(void)k_work_reschedule(&data->stepper_dwork, K_NO_WAIT);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (velocity == 0) {
|
||||
LOG_ERR("Velocity cannot be zero");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (velocity > USEC_PER_SEC) {
|
||||
LOG_ERR("Velocity cannot be greater than %d micro steps per second", USEC_PER_SEC);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
K_SPINLOCK(&data->lock) {
|
||||
data->delay_in_us = USEC_PER_SEC / velocity;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (data->delay_in_us == 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;
|
||||
update_direction_from_step_count(dev);
|
||||
(void)k_work_reschedule(&data->stepper_dwork, K_NO_WAIT);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
*is_moving = k_work_delayable_is_pending(&data->stepper_dwork);
|
||||
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;
|
||||
|
||||
K_SPINLOCK(&data->lock) {
|
||||
data->run_mode = STEPPER_RUN_MODE_VELOCITY;
|
||||
data->direction = direction;
|
||||
if (value != 0) {
|
||||
data->delay_in_us = USEC_PER_SEC / velocity;
|
||||
(void)k_work_reschedule(&data->stepper_dwork, K_NO_WAIT);
|
||||
} else {
|
||||
(void)k_work_cancel_delayable(&data->stepper_dwork);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
191
drivers/stepper/step_dir_stepper_common.h
Normal file
191
drivers/stepper/step_dir_stepper_common.h
Normal file
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* Copyright 2024 Fabian Blatz <fabianblatz@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef ZEPHYR_DRIVER_STEPPER_STEP_DIR_STEPPER_COMMON_H_
|
||||
#define ZEPHYR_DRIVER_STEPPER_STEP_DIR_STEPPER_COMMON_H_
|
||||
|
||||
/**
|
||||
* @brief Stepper Driver APIs
|
||||
* @defgroup step_dir_stepper Stepper Driver APIs
|
||||
* @ingroup io_interfaces
|
||||
* @{
|
||||
*/
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/drivers/stepper.h>
|
||||
|
||||
/**
|
||||
* @brief Common step direction stepper config.
|
||||
*
|
||||
* This structure **must** be placed first in the driver's config structure.
|
||||
*/
|
||||
struct step_dir_stepper_common_config {
|
||||
const struct gpio_dt_spec step_pin;
|
||||
const struct gpio_dt_spec dir_pin;
|
||||
bool dual_edge;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Initialize common step direction stepper config from devicetree instance.
|
||||
*
|
||||
* @param node_id The devicetree node identifier.
|
||||
*/
|
||||
#define STEP_DIR_STEPPER_DT_COMMON_CONFIG_INIT(node_id) \
|
||||
{ \
|
||||
.step_pin = GPIO_DT_SPEC_GET(node_id, step_gpios), \
|
||||
.dir_pin = GPIO_DT_SPEC_GET(node_id, direction_gpios), \
|
||||
.dual_edge = DT_PROP_OR(node_id, dual_edge_step, false), \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize common step direction stepper config from devicetree instance.
|
||||
* @param inst Instance.
|
||||
*/
|
||||
#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;
|
||||
struct k_work_delayable stepper_dwork;
|
||||
int32_t actual_position;
|
||||
uint32_t delay_in_us;
|
||||
int32_t step_count;
|
||||
stepper_event_callback_t callback;
|
||||
void *event_cb_user_data;
|
||||
};
|
||||
|
||||
/**
|
||||
* @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) \
|
||||
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");
|
||||
|
||||
/**
|
||||
* @brief Common function to initialize a step direction stepper device at init time.
|
||||
*
|
||||
* This function must be called at the end of the device init function.
|
||||
*
|
||||
* @param dev Step direction stepper device instance.
|
||||
*
|
||||
* @retval 0 If initialized successfully.
|
||||
* @retval -errno Negative errno in case of failure.
|
||||
*/
|
||||
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 maximum velocity in micro_steps per second.
|
||||
*
|
||||
* @param dev Pointer to the device structure.
|
||||
* @param velocity Maximum velocity in micro_steps per second.
|
||||
* @return 0 on success, or a negative error code on failure.
|
||||
*/
|
||||
int step_dir_stepper_common_set_max_velocity(const struct device *dev, const uint32_t velocity);
|
||||
|
||||
/**
|
||||
* @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 velocity in a given direction.
|
||||
*
|
||||
* @param dev Pointer to the device structure.
|
||||
* @param direction The direction of movement (positive or negative).
|
||||
* @param velocity The velocity in micro_steps per second.
|
||||
* @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,
|
||||
const uint32_t velocity);
|
||||
|
||||
/**
|
||||
* @brief Set a callback function for stepper motor events.
|
||||
*
|
||||
* This function sets a user-defined callback that will be invoked when a stepper motor event
|
||||
* occurs.
|
||||
*
|
||||
* @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);
|
||||
|
||||
/** @} */
|
||||
|
||||
#endif /* ZEPHYR_DRIVER_STEPPER_STEP_DIR_STEPPER_COMMON_H_ */
|
|
@ -24,3 +24,14 @@ properties:
|
|||
- 256
|
||||
description: |
|
||||
micro-step resolution to be set while initializing the device driver.
|
||||
|
||||
step-gpios:
|
||||
type: phandle-array
|
||||
description: |
|
||||
The GPIO pins used to send step signals to the stepper motor.
|
||||
|
||||
direction-gpios:
|
||||
type: phandle-array
|
||||
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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue