From 52c6a289f169f806676093b3446a3ee8972ba652 Mon Sep 17 00:00:00 2001 From: Jilay Pandya Date: Thu, 26 Sep 2024 09:45:58 +0200 Subject: [PATCH] drivers: stepper: adi: trinamic tmc5041 This commit introduces initial structure for trinamic drivers TMC5041 is implemented with following features: - StallGuard - RAMPSTAT_POLL - RAMP_GEN Signed-off-by: Dipak Shetty Signed-off-by: Jilay Pandya --- drivers/stepper/CMakeLists.txt | 6 +- drivers/stepper/Kconfig | 1 + drivers/stepper/adi_tmc/CMakeLists.txt | 6 + drivers/stepper/adi_tmc/Kconfig | 56 ++ .../adi_tmc/adi_tmc5041_stepper_controller.c | 762 ++++++++++++++++++ drivers/stepper/adi_tmc/adi_tmc_reg.h | 148 ++++ drivers/stepper/adi_tmc/adi_tmc_spi.c | 118 +++ drivers/stepper/adi_tmc/adi_tmc_spi.h | 63 ++ dts/bindings/stepper/adi/adi,tmc5041.yaml | 67 ++ .../stepper/adi/adi,trinamic-gconf.yaml | 74 ++ .../adi/adi,trinamic-ramp-generator.yaml | 133 +++ .../stepper/adi/adi,trinamic-stallguard.yaml | 36 + dts/bindings/stepper/stepper-controller.yaml | 6 + include/zephyr/drivers/stepper.h | 5 + .../zephyr/drivers/stepper/stepper_trinamic.h | 172 ++++ 15 files changed, 1652 insertions(+), 1 deletion(-) create mode 100644 drivers/stepper/adi_tmc/CMakeLists.txt create mode 100644 drivers/stepper/adi_tmc/Kconfig create mode 100644 drivers/stepper/adi_tmc/adi_tmc5041_stepper_controller.c create mode 100644 drivers/stepper/adi_tmc/adi_tmc_reg.h create mode 100644 drivers/stepper/adi_tmc/adi_tmc_spi.c create mode 100644 drivers/stepper/adi_tmc/adi_tmc_spi.h create mode 100644 dts/bindings/stepper/adi/adi,tmc5041.yaml create mode 100644 dts/bindings/stepper/adi/adi,trinamic-gconf.yaml create mode 100644 dts/bindings/stepper/adi/adi,trinamic-ramp-generator.yaml create mode 100644 dts/bindings/stepper/adi/adi,trinamic-stallguard.yaml create mode 100644 include/zephyr/drivers/stepper/stepper_trinamic.h diff --git a/drivers/stepper/CMakeLists.txt b/drivers/stepper/CMakeLists.txt index f45c0d0d717..a73875a285c 100644 --- a/drivers/stepper/CMakeLists.txt +++ b/drivers/stepper/CMakeLists.txt @@ -3,9 +3,13 @@ zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/drivers/stepper.h) +# zephyr-keep-sorted-start +add_subdirectory_ifdef(CONFIG_STEPPER_ADI_TMC adi_tmc) +# 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_STEPPER_SHELL stepper_shell.c) diff --git a/drivers/stepper/Kconfig b/drivers/stepper/Kconfig index 43e4c2d49ae..f382265b518 100644 --- a/drivers/stepper/Kconfig +++ b/drivers/stepper/Kconfig @@ -49,6 +49,7 @@ config STEPPER_SHELL_THREAD_PRIORITY comment "Stepper Drivers" +rsource "adi_tmc/Kconfig" rsource "Kconfig.fake" rsource "Kconfig.gpio" diff --git a/drivers/stepper/adi_tmc/CMakeLists.txt b/drivers/stepper/adi_tmc/CMakeLists.txt new file mode 100644 index 00000000000..a972c9b54dc --- /dev/null +++ b/drivers/stepper/adi_tmc/CMakeLists.txt @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024 Carl Zeiss Meditec AG +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() +zephyr_library_sources_ifdef(CONFIG_STEPPER_ADI_TMC_SPI adi_tmc_spi.c) +zephyr_library_sources_ifdef(CONFIG_STEPPER_ADI_TMC5041 adi_tmc5041_stepper_controller.c) diff --git a/drivers/stepper/adi_tmc/Kconfig b/drivers/stepper/adi_tmc/Kconfig new file mode 100644 index 00000000000..b787b6e1af3 --- /dev/null +++ b/drivers/stepper/adi_tmc/Kconfig @@ -0,0 +1,56 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024 Carl Zeiss Meditec AG +# SPDX-License-Identifier: Apache-2.0 + +menuconfig STEPPER_ADI_TMC + bool "Trinamic Stepper Controller" + depends on STEPPER + default y + help + Enable trinamic stepper controller + +if STEPPER_ADI_TMC + +config STEPPER_ADI_TMC_RAMP_GEN + bool "Use Trinamic Stepper Controller with Ramp Generator" + depends on STEPPER_ADI_TMC + default y + help + Enable ramp generator for trinamic stepper controller + +config STEPPER_ADI_TMC_SPI + bool "Use Trinamic Stepper Controller with SPI" + depends on STEPPER_ADI_TMC + select SPI + help + A Trinamic Stepper Controller with SPI is enabled + +comment "Trinamic Stepper Drivers" + +config STEPPER_ADI_TMC5041 + bool "Activate trinamic tmc5041 stepper driver" + depends on DT_HAS_ADI_TMC5041_ENABLED && STEPPER_ADI_TMC + select STEPPER_ADI_TMC_SPI + default y + help + Stepper driver for TMC5041. + +config STEPPER_ADI_TMC5041_RAMPSTAT_POLL + bool "TMC5041 poll ramp status" + depends on STEPPER_ADI_TMC5041 + select POLL + default y + help + When enabled, the ramp status will be polled on TMC5041, to check for events: + - TMC5041_POS_REACHED_EVENT + - TMC5041_STOP_SG_EVENT + - TMC5041_STOP_LEFT_EVENT + - TMC5041_STOP_RIGHT_EVENT + +config STEPPER_ADI_TMC5041_RAMPSTAT_POLL_INTERVAL_IN_MSEC + int "TMC5041 poll ramp status interval in ms" + depends on STEPPER_ADI_TMC5041_RAMPSTAT_POLL + default 100 + help + The interval in ms to poll the ramp status on TMC5041. + +endif # STEPPER_ADI_TMC diff --git a/drivers/stepper/adi_tmc/adi_tmc5041_stepper_controller.c b/drivers/stepper/adi_tmc/adi_tmc5041_stepper_controller.c new file mode 100644 index 00000000000..8c7b05492a8 --- /dev/null +++ b/drivers/stepper/adi_tmc/adi_tmc5041_stepper_controller.c @@ -0,0 +1,762 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2024 Carl Zeiss Meditec AG + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT adi_tmc5041 + +#include "stdlib.h" + +#include +#include + +#include "adi_tmc_reg.h" +#include "adi_tmc_spi.h" + +#include + +LOG_MODULE_REGISTER(tmc5041, CONFIG_STEPPER_LOG_LEVEL); + +struct tmc5041_data { + struct k_sem sem; +}; + +struct tmc5041_config { + const uint32_t gconf; + struct spi_dt_spec spi; + const uint32_t clock_frequency; +}; + +struct tmc5041_stepper_data { + struct k_work_delayable stallguard_dwork; + /* Work item to run the callback in a thread context. */ +#ifdef CONFIG_STEPPER_ADI_TMC5041_RAMPSTAT_POLL + struct k_work_delayable rampstat_callback_dwork; +#endif + /* device pointer required to access config in k_work */ + const struct device *stepper; + struct k_poll_signal *async_signal; +}; + +struct tmc5041_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_TMC_RAMP_GEN + const struct tmc_ramp_generator_data default_ramp_config; +#endif +}; + +static int tmc5041_write(const struct device *dev, const uint8_t reg_addr, const uint32_t reg_val) +{ + const struct tmc5041_config *config = dev->config; + struct tmc5041_data *data = dev->data; + const struct spi_dt_spec bus = config->spi; + int err; + + k_sem_take(&data->sem, K_FOREVER); + + err = tmc_spi_write_register(&bus, TMC5041_WRITE_BIT, reg_addr, reg_val); + + k_sem_give(&data->sem); + + if (err) { + LOG_ERR("Failed to write register 0x%x with value 0x%x", reg_addr, reg_val); + return err; + } + return 0; +} + +static int tmc5041_read(const struct device *dev, const uint8_t reg_addr, uint32_t *reg_val) +{ + const struct tmc5041_config *config = dev->config; + struct tmc5041_data *data = dev->data; + const struct spi_dt_spec bus = config->spi; + int err; + + k_sem_take(&data->sem, K_FOREVER); + + err = tmc_spi_read_register(&bus, TMC5041_ADDRESS_MASK, reg_addr, reg_val); + + k_sem_give(&data->sem); + + if (err) { + LOG_ERR("Failed to read register 0x%x", reg_addr); + return err; + } + return 0; +} + +static void calculate_velocity_from_hz_to_fclk(const struct device *dev, const uint32_t velocity_hz, + uint32_t *const velocity_fclk) +{ + const struct tmc5041_config *config = dev->config; + + *velocity_fclk = + ((uint64_t)(velocity_hz) << TMC5041_CLOCK_FREQ_SHIFT) / config->clock_frequency; + LOG_DBG("Stepper motor controller %s velocity: %d Hz, velocity_fclk: %d", dev->name, + velocity_hz, *velocity_fclk); +} + +static void set_async_signal(const struct device *dev, struct k_poll_signal *async) +{ + struct tmc5041_stepper_data *data = dev->data; + + if (!async) { + return; + } + + if (data->async_signal) { + k_poll_signal_reset(data->async_signal); + } + data->async_signal = async; +} + +static int stallguard_enable(const struct device *dev, const bool enable) +{ + const struct tmc5041_stepper_config *config = dev->config; + uint32_t reg_value; + int err; + + err = tmc5041_read(config->controller, TMC5041_SWMODE(config->index), ®_value); + if (err) { + LOG_ERR("Failed to read SWMODE register"); + return -EIO; + } + + if (enable) { + reg_value |= TMC5041_SW_MODE_SG_STOP_ENABLE; + + int32_t actual_velocity; + + err = tmc5041_read(config->controller, TMC5041_VACTUAL(config->index), + &actual_velocity); + if (err) { + LOG_ERR("Failed to read VACTUAL register"); + return -EIO; + } + + actual_velocity = (actual_velocity << (31 - TMC_RAMP_VACTUAL_SHIFT)) >> + (31 - TMC_RAMP_VACTUAL_SHIFT); + LOG_DBG("actual velocity: %d", actual_velocity); + + if (abs(actual_velocity) < config->sg_threshold_velocity) { + return -EAGAIN; + } + } else { + reg_value &= ~TMC5041_SW_MODE_SG_STOP_ENABLE; + } + err = tmc5041_write(config->controller, TMC5041_SWMODE(config->index), reg_value); + if (err) { + LOG_ERR("Failed to write SWMODE register"); + return -EIO; + } + return 0; +} + +static void stallguard_work_handler(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct tmc5041_stepper_data *stepper_data = + CONTAINER_OF(dwork, struct tmc5041_stepper_data, stallguard_dwork); + int err; + const struct tmc5041_stepper_config *stepper_config = stepper_data->stepper->config; + + err = stallguard_enable(stepper_data->stepper, true); + if (err == -EAGAIN) { + LOG_ERR("retrying stallguard activation"); + 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"); + return; + } +} + +#ifdef CONFIG_STEPPER_ADI_TMC5041_RAMPSTAT_POLL + +static void emit_signal(struct k_poll_signal *async_signal, const enum stepper_signal_result signal) +{ + int err; + + if (!async_signal) { + LOG_WRN("Async signal is NULL"); + return; + } + + err = k_poll_signal_raise(async_signal, signal); + if (err != 0) { + LOG_ERR("Failed to raise signal %d error %d", signal, err); + } +} + +static void rampstat_work_handler(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + + struct tmc5041_stepper_data *stepper_data = + CONTAINER_OF(dwork, struct tmc5041_stepper_data, rampstat_callback_dwork); + const struct tmc5041_stepper_config *stepper_config = stepper_data->stepper->config; + + __ASSERT_NO_MSG(stepper_config->controller != NULL); + + uint32_t drv_status; + int err; + + tmc5041_read(stepper_config->controller, TMC5041_DRVSTATUS(stepper_config->index), + &drv_status); + if (FIELD_GET(TMC5041_DRV_STATUS_SG_STATUS_MASK, drv_status) == 1U) { + LOG_INF("%s: Stall detected", stepper_data->stepper->name); + err = tmc5041_write(stepper_config->controller, + TMC5041_RAMPMODE(stepper_config->index), + TMC5041_RAMPMODE_HOLD_MODE); + if (err != 0) { + LOG_ERR("%s: Failed to stop motor", stepper_data->stepper->name); + return; + } + } + + uint32_t rampstat_value; + + err = tmc5041_read(stepper_config->controller, TMC5041_RAMPSTAT(stepper_config->index), + &rampstat_value); + if (err != 0) { + LOG_ERR("%s: Failed to read RAMPSTAT register", stepper_data->stepper->name); + return; + } + + const uint8_t ramp_stat_values = FIELD_GET(TMC5041_RAMPSTAT_INT_MASK, rampstat_value); + + if (ramp_stat_values > 0) { + switch (ramp_stat_values) { + + case TMC5041_STOP_LEFT_EVENT: + LOG_DBG("RAMPSTAT %s:Left end-stop detected", stepper_data->stepper->name); + emit_signal(stepper_data->async_signal, + STEPPER_SIGNAL_LEFT_END_STOP_DETECTED); + break; + + case TMC5041_STOP_RIGHT_EVENT: + LOG_DBG("RAMPSTAT %s:Right end-stop detected", stepper_data->stepper->name); + emit_signal(stepper_data->async_signal, + STEPPER_SIGNAL_RIGHT_END_STOP_DETECTED); + break; + + case TMC5041_POS_REACHED_EVENT: + LOG_DBG("RAMPSTAT %s:Position reached", stepper_data->stepper->name); + emit_signal(stepper_data->async_signal, STEPPER_SIGNAL_STEPS_COMPLETED); + break; + + case TMC5041_STOP_SG_EVENT: + LOG_DBG("RAMPSTAT %s:Stall detected", stepper_data->stepper->name); + stallguard_enable(stepper_data->stepper, false); + emit_signal(stepper_data->async_signal, + STEPPER_SIGNAL_SENSORLESS_STALL_DETECTED); + break; + default: + LOG_ERR("Illegal ramp stat bit field"); + break; + } + } else { + k_work_reschedule( + &stepper_data->rampstat_callback_dwork, + K_MSEC(CONFIG_STEPPER_ADI_TMC5041_RAMPSTAT_POLL_INTERVAL_IN_MSEC)); + } +} + +#endif + +static int tmc5041_stepper_enable(const struct device *dev, const bool enable) +{ + LOG_DBG("Stepper motor controller %s %s", dev->name, enable ? "enabled" : "disabled"); + const struct tmc5041_stepper_config *config = dev->config; + uint32_t reg_value; + int err; + + err = tmc5041_read(config->controller, TMC5041_CHOPCONF(config->index), ®_value); + if (err != 0) { + return -EIO; + } + + if (enable) { + reg_value |= TMC5041_CHOPCONF_DRV_ENABLE_MASK; + } else { + reg_value &= ~TMC5041_CHOPCONF_DRV_ENABLE_MASK; + } + + err = tmc5041_write(config->controller, TMC5041_CHOPCONF(config->index), reg_value); + if (err != 0) { + return -EIO; + } + return 0; +} + +static int tmc5041_stepper_is_moving(const struct device *dev, bool *is_moving) +{ + const struct tmc5041_stepper_config *config = dev->config; + uint32_t reg_value; + int err; + + err = tmc5041_read(config->controller, TMC5041_DRVSTATUS(config->index), ®_value); + + if (err != 0) { + LOG_ERR("%s: Failed to read DRVSTATUS register", dev->name); + return -EIO; + } + + *is_moving = (FIELD_GET(TMC5041_DRV_STATUS_STST_BIT, reg_value) != 1U); + LOG_DBG("Stepper motor controller %s is moving: %d", dev->name, *is_moving); + return 0; +} + +static int tmc5041_stepper_move(const struct device *dev, const int32_t steps, + struct k_poll_signal *async) +{ + const struct tmc5041_stepper_config *config = dev->config; + struct tmc5041_stepper_data *data = dev->data; + int err; + + set_async_signal(dev, async); + + if (config->is_sg_enabled) { + err = stallguard_enable(dev, false); + if (err != 0) { + return -EIO; + } + } + + int32_t position; + + err = stepper_get_actual_position(dev, &position); + if (err != 0) { + return -EIO; + } + int32_t target_position = position + steps; + + err = tmc5041_write(config->controller, TMC5041_RAMPMODE(config->index), + TMC5041_RAMPMODE_POSITIONING_MODE); + if (err != 0) { + return -EIO; + } + LOG_DBG("Stepper motor controller %s moved to %d by steps: %d", dev->name, target_position, + steps); + err = tmc5041_write(config->controller, TMC5041_XTARGET(config->index), target_position); + if (err != 0) { + return -EIO; + } + + if (config->is_sg_enabled) { + k_work_reschedule(&data->stallguard_dwork, + K_MSEC(config->sg_velocity_check_interval_ms)); + } +#ifdef CONFIG_STEPPER_ADI_TMC5041_RAMPSTAT_POLL + k_work_reschedule(&data->rampstat_callback_dwork, + K_MSEC(CONFIG_STEPPER_ADI_TMC5041_RAMPSTAT_POLL_INTERVAL_IN_MSEC)); +#endif + return 0; +} + +static int tmc5041_stepper_set_max_velocity(const struct device *dev, uint32_t velocity) +{ + const struct tmc5041_stepper_config *config = dev->config; + uint32_t velocity_fclk; + int err; + + calculate_velocity_from_hz_to_fclk(config->controller, velocity, &velocity_fclk); + err = tmc5041_write(config->controller, TMC5041_VMAX(config->index), velocity_fclk); + if (err != 0) { + LOG_ERR("%s: Failed to set max velocity", dev->name); + return -EIO; + } + return 0; +} + +static int tmc5041_stepper_set_micro_step_res(const struct device *dev, + enum micro_step_resolution res) +{ + const struct tmc5041_stepper_config *config = dev->config; + uint32_t reg_value; + int err; + + err = tmc5041_read(config->controller, TMC5041_CHOPCONF(config->index), ®_value); + if (err != 0) { + return -EIO; + } + + reg_value &= ~TMC5041_CHOPCONF_MRES_MASK; + reg_value |= ((MICRO_STEP_RES_INDEX(STEPPER_MICRO_STEP_256) - LOG2(res)) + << TMC5041_CHOPCONF_MRES_SHIFT); + + err = tmc5041_write(config->controller, TMC5041_CHOPCONF(config->index), reg_value); + if (err != 0) { + return -EIO; + } + + LOG_DBG("Stepper motor controller %s set micro step resolution to 0x%x", dev->name, + reg_value); + return 0; +} + +static int tmc5041_stepper_get_micro_step_res(const struct device *dev, + enum micro_step_resolution *res) +{ + const struct tmc5041_stepper_config *config = dev->config; + uint32_t reg_value; + int err; + + err = tmc5041_read(config->controller, TMC5041_CHOPCONF(config->index), ®_value); + if (err != 0) { + return -EIO; + } + reg_value &= TMC5041_CHOPCONF_MRES_MASK; + reg_value >>= TMC5041_CHOPCONF_MRES_SHIFT; + *res = (1 << (MICRO_STEP_RES_INDEX(STEPPER_MICRO_STEP_256) - reg_value)); + LOG_DBG("Stepper motor controller %s get micro step resolution: %d", dev->name, *res); + return 0; +} + +static int tmc5041_stepper_set_actual_position(const struct device *dev, const int32_t position) +{ + const struct tmc5041_stepper_config *config = dev->config; + int err; + + err = tmc5041_write(config->controller, TMC5041_RAMPMODE(config->index), + TMC5041_RAMPMODE_HOLD_MODE); + if (err != 0) { + return -EIO; + } + + err = tmc5041_write(config->controller, TMC5041_XACTUAL(config->index), position); + if (err != 0) { + return -EIO; + } + LOG_DBG("Stepper motor controller %s set actual position to %d", dev->name, position); + return 0; +} + +static int tmc5041_stepper_get_actual_position(const struct device *dev, int32_t *position) +{ + const struct tmc5041_stepper_config *config = dev->config; + int err; + + err = tmc5041_read(config->controller, TMC5041_XACTUAL(config->index), position); + if (err != 0) { + return -EIO; + } + LOG_DBG("%s actual position: %d", dev->name, *position); + return 0; +} + +static int tmc5041_stepper_set_target_position(const struct device *dev, const int32_t position, + struct k_poll_signal *async) +{ + LOG_DBG("Stepper motor controller %s set target position to %d", dev->name, position); + const struct tmc5041_stepper_config *config = dev->config; + struct tmc5041_stepper_data *data = dev->data; + int err; + + set_async_signal(dev, async); + + if (config->is_sg_enabled) { + stallguard_enable(dev, false); + } + + err = tmc5041_write(config->controller, TMC5041_RAMPMODE(config->index), + TMC5041_RAMPMODE_POSITIONING_MODE); + if (err != 0) { + return -EIO; + } + tmc5041_write(config->controller, TMC5041_XTARGET(config->index), position); + + if (config->is_sg_enabled) { + k_work_reschedule(&data->stallguard_dwork, + K_MSEC(config->sg_velocity_check_interval_ms)); + } +#ifdef CONFIG_STEPPER_ADI_TMC5041_RAMPSTAT_POLL + k_work_reschedule(&data->rampstat_callback_dwork, + K_MSEC(CONFIG_STEPPER_ADI_TMC5041_RAMPSTAT_POLL_INTERVAL_IN_MSEC)); +#endif + return 0; +} + +static int tmc5041_stepper_enable_constant_velocity_mode(const struct device *dev, + const enum stepper_direction direction, + const uint32_t velocity) +{ + LOG_DBG("Stepper motor controller %s enable constant velocity mode", dev->name); + const struct tmc5041_stepper_config *config = dev->config; + struct tmc5041_stepper_data *data = dev->data; + uint32_t velocity_fclk; + int err; + + calculate_velocity_from_hz_to_fclk(config->controller, velocity, &velocity_fclk); + + if (config->is_sg_enabled) { + err = stallguard_enable(dev, false); + if (err != 0) { + return -EIO; + } + } + + switch (direction) { + case STEPPER_DIRECTION_POSITIVE: + err = tmc5041_write(config->controller, TMC5041_RAMPMODE(config->index), + TMC5041_RAMPMODE_POSITIVE_VELOCITY_MODE); + if (err != 0) { + return -EIO; + } + err = tmc5041_write(config->controller, TMC5041_VMAX(config->index), velocity_fclk); + if (err != 0) { + return -EIO; + } + break; + + case STEPPER_DIRECTION_NEGATIVE: + err = tmc5041_write(config->controller, TMC5041_RAMPMODE(config->index), + TMC5041_RAMPMODE_NEGATIVE_VELOCITY_MODE); + if (err != 0) { + return -EIO; + } + err = tmc5041_write(config->controller, TMC5041_VMAX(config->index), velocity_fclk); + if (err != 0) { + return -EIO; + } + break; + } + + if (config->is_sg_enabled) { + k_work_reschedule(&data->stallguard_dwork, + K_MSEC(config->sg_velocity_check_interval_ms)); + } + return 0; +} + +#ifdef CONFIG_STEPPER_ADI_TMC_RAMP_GEN + +int tmc5041_stepper_set_ramp(const struct device *dev, + const struct tmc_ramp_generator_data *ramp_data) +{ + LOG_DBG("Stepper motor controller %s set ramp", dev->name); + const struct tmc5041_stepper_config *config = dev->config; + int err; + + err = tmc5041_write(config->controller, TMC5041_VSTART(config->index), ramp_data->vstart); + if (err != 0) { + return -EIO; + } + err = tmc5041_write(config->controller, TMC5041_A1(config->index), ramp_data->a1); + if (err != 0) { + return -EIO; + } + err = tmc5041_write(config->controller, TMC5041_AMAX(config->index), ramp_data->amax); + if (err != 0) { + return -EIO; + } + err = tmc5041_write(config->controller, TMC5041_D1(config->index), ramp_data->d1); + if (err != 0) { + return -EIO; + } + err = tmc5041_write(config->controller, TMC5041_DMAX(config->index), ramp_data->dmax); + if (err != 0) { + return -EIO; + } + err = tmc5041_write(config->controller, TMC5041_V1(config->index), ramp_data->v1); + if (err != 0) { + return -EIO; + } + err = tmc5041_write(config->controller, TMC5041_VMAX(config->index), ramp_data->vmax); + if (err != 0) { + return -EIO; + } + err = tmc5041_write(config->controller, TMC5041_VSTOP(config->index), ramp_data->vstop); + if (err != 0) { + return -EIO; + } + err = tmc5041_write(config->controller, TMC5041_TZEROWAIT(config->index), + ramp_data->tzerowait); + if (err != 0) { + return -EIO; + } + err = tmc5041_write(config->controller, TMC5041_VHIGH(config->index), ramp_data->vhigh); + if (err != 0) { + return -EIO; + } + err = tmc5041_write(config->controller, TMC5041_VCOOLTHRS(config->index), + ramp_data->vcoolthrs); + if (err != 0) { + return -EIO; + } + err = tmc5041_write(config->controller, TMC5041_IHOLD_IRUN(config->index), + ramp_data->iholdrun); + if (err != 0) { + return -EIO; + } + return 0; +} + +#endif + +static int tmc5041_init(const struct device *dev) +{ + LOG_DBG("TMC5041 stepper motor controller %s initialized", dev->name); + struct tmc5041_data *data = dev->data; + const struct tmc5041_config *config = dev->config; + int err; + + k_sem_init(&data->sem, 1, 1); + + if (!spi_is_ready_dt(&config->spi)) { + LOG_ERR("SPI bus is not ready"); + return -ENODEV; + } + + /* Init non motor-index specific registers here. */ + LOG_DBG("GCONF: %d", config->gconf); + err = tmc5041_write(dev, TMC5041_GCONF, config->gconf); + if (err != 0) { + return -EIO; + } + + /* Read GSTAT register values to clear any errors SPI Datagram. */ + uint32_t gstat_value; + + err = tmc5041_read(dev, TMC5041_GSTAT, &gstat_value); + if (err != 0) { + return -EIO; + } + + LOG_DBG("Device %s initialized", dev->name); + return 0; +} + +static int tmc5041_stepper_init(const struct device *dev) +{ + const struct tmc5041_stepper_config *stepper_config = dev->config; + struct tmc5041_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 = tmc5041_write(stepper_config->controller, + TMC5041_SWMODE(stepper_config->index), BIT(10)); + if (err != 0) { + return -EIO; + } + + 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, TMC5041_SG_MIN_VALUE, + TMC5041_SG_MAX_VALUE)) { + LOG_ERR("Stallguard threshold out of range"); + return -EINVAL; + } + + int32_t stall_guard_threshold = (int32_t)stepper_config->sg_threshold; + + err = tmc5041_write( + stepper_config->controller, TMC5041_COOLCONF(stepper_config->index), + stall_guard_threshold << TMC5041_COOLCONF_SG2_THRESHOLD_VALUE_SHIFT); + if (err != 0) { + return -EIO; + } + err = stallguard_enable(dev, true); + if (err == -EAGAIN) { + LOG_ERR("retrying stallguard activation"); + k_work_reschedule(&data->stallguard_dwork, + K_MSEC(stepper_config->sg_velocity_check_interval_ms)); + } + } + +#ifdef CONFIG_STEPPER_ADI_TMC_RAMP_GEN + err = tmc5041_stepper_set_ramp(dev, &stepper_config->default_ramp_config); + if (err != 0) { + return -EIO; + } +#endif + +#if CONFIG_STEPPER_ADI_TMC5041_RAMPSTAT_POLL + k_work_init_delayable(&data->rampstat_callback_dwork, rampstat_work_handler); + k_work_reschedule(&data->rampstat_callback_dwork, + K_MSEC(CONFIG_STEPPER_ADI_TMC5041_RAMPSTAT_POLL_INTERVAL_IN_MSEC)); +#endif + err = tmc5041_stepper_set_micro_step_res(dev, stepper_config->default_micro_step_res); + if (err != 0) { + return -EIO; + } + return 0; +} + +#define TMC5041_SHAFT_CONFIG(child) \ + (DT_PROP(child, invert_direction) << TMC5041_GCONF_SHAFT_SHIFT(DT_REG_ADDR(child))) | + +#define TMC5041_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_TMC_RAMP_GEN, (CHECK_RAMP_DT_DATA(child))); \ + static const struct tmc5041_stepper_config tmc5041_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_TMC_RAMP_GEN, \ + (.default_ramp_config = TMC_RAMP_DT_SPEC_GET(child))) }; + +#define TMC5041_STEPPER_DATA_DEFINE(child) \ + static struct tmc5041_stepper_data tmc5041_stepper_data_##child = { \ + .stepper = DEVICE_DT_GET(child),}; + +#define TMC5041_STEPPER_API_DEFINE(child) \ + static const struct stepper_driver_api tmc5041_stepper_api_##child = { \ + .enable = tmc5041_stepper_enable, \ + .is_moving = tmc5041_stepper_is_moving, \ + .move = tmc5041_stepper_move, \ + .set_max_velocity = tmc5041_stepper_set_max_velocity, \ + .set_micro_step_res = tmc5041_stepper_set_micro_step_res, \ + .get_micro_step_res = tmc5041_stepper_get_micro_step_res, \ + .set_actual_position = tmc5041_stepper_set_actual_position, \ + .get_actual_position = tmc5041_stepper_get_actual_position, \ + .set_target_position = tmc5041_stepper_set_target_position, \ + .enable_constant_velocity_mode = tmc5041_stepper_enable_constant_velocity_mode, \ + }; + +#define TMC5041_STEPPER_DEFINE(child) \ + DEVICE_DT_DEFINE(child, tmc5041_stepper_init, NULL, &tmc5041_stepper_data_##child, \ + &tmc5041_stepper_config_##child, POST_KERNEL, \ + CONFIG_STEPPER_INIT_PRIORITY, &tmc5041_stepper_api_##child); + +#define TMC5041_DEFINE(inst) \ + BUILD_ASSERT(DT_INST_CHILD_NUM(inst) <= 2, "tmc5041 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 tmc5041_data tmc5041_data_##inst; \ + static const struct tmc5041_config tmc5041_config_##inst = { \ + .gconf = ( \ + (DT_INST_PROP(inst, poscmp_enable) << TMC5041_GCONF_POSCMP_ENABLE_SHIFT) |\ + (DT_INST_PROP(inst, test_mode) << TMC5041_GCONF_TEST_MODE_SHIFT) | \ + DT_INST_FOREACH_CHILD(inst, TMC5041_SHAFT_CONFIG) \ + (DT_INST_PROP(inst, lock_gconf) << TMC5041_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),}; \ + DT_INST_FOREACH_CHILD(inst, TMC5041_STEPPER_CONFIG_DEFINE); \ + DT_INST_FOREACH_CHILD(inst, TMC5041_STEPPER_DATA_DEFINE); \ + DT_INST_FOREACH_CHILD(inst, TMC5041_STEPPER_API_DEFINE); \ + DT_INST_FOREACH_CHILD(inst, TMC5041_STEPPER_DEFINE); \ + DEVICE_DT_INST_DEFINE(inst, tmc5041_init, NULL, &tmc5041_data_##inst, \ + &tmc5041_config_##inst, POST_KERNEL, CONFIG_STEPPER_INIT_PRIORITY,\ + NULL); + +DT_INST_FOREACH_STATUS_OKAY(TMC5041_DEFINE) diff --git a/drivers/stepper/adi_tmc/adi_tmc_reg.h b/drivers/stepper/adi_tmc/adi_tmc_reg.h new file mode 100644 index 00000000000..1c88020c352 --- /dev/null +++ b/drivers/stepper/adi_tmc/adi_tmc_reg.h @@ -0,0 +1,148 @@ +/** + * @file drivers/stepper/adi/tmc_reg.h + * + * @brief TMC Registers + * + */ + +/* + * SPDX-FileCopyrightText: Copyright (c) 2024 Carl Zeiss Meditec AG + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_STEPPER_ADI_TMC_REG_H_ +#define ZEPHYR_DRIVERS_STEPPER_ADI_TMC_REG_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef CONFIG_STEPPER_ADI_TMC5041 + +#define TMC5041_MOTOR_ADDR(m) (0x20 << (m)) +#define TMC5041_MOTOR_ADDR_DRV(m) ((m) << 4) +#define TMC5041_MOTOR_ADDR_PWM(m) ((m) << 3) + +/** + * @name TMC5041 module registers + * @anchor TMC5041_REGISTERS + * + * @{ + */ + +#define TMC5041_WRITE_BIT 0x80U +#define TMC5041_ADDRESS_MASK 0x7FU + +#define TMC5041_GCONF_POSCMP_ENABLE_SHIFT 3 +#define TMC5041_GCONF_TEST_MODE_SHIFT 7 +#define TMC5041_GCONF_SHAFT_SHIFT(n) ((n) ? 8 : 9) +#define TMC5041_LOCK_GCONF_SHIFT 10 + +#define TMC5041_GCONF 0x00 +#define TMC5041_GSTAT 0x01 +#define TMC5041_INPUT 0x04 +#define TMC5041_X_COMPARE 0x05 + +#define TMC5041_PWMCONF(motor) (0x10 | TMC5041_MOTOR_ADDR_PWM(motor)) +#define TMC5041_PWM_STATUS(motor) (0x11 | TMC5041_MOTOR_ADDR_PWM(motor)) + +#define TMC5041_RAMPMODE(motor) (0x00 | TMC5041_MOTOR_ADDR(motor)) +#define TMC5041_XACTUAL(motor) (0x01 | TMC5041_MOTOR_ADDR(motor)) +#define TMC5041_VACTUAL(motor) (0x02 | TMC5041_MOTOR_ADDR(motor)) +#define TMC5041_VSTART(motor) (0x03 | TMC5041_MOTOR_ADDR(motor)) +#define TMC5041_A1(motor) (0x04 | TMC5041_MOTOR_ADDR(motor)) +#define TMC5041_V1(motor) (0x05 | TMC5041_MOTOR_ADDR(motor)) +#define TMC5041_AMAX(motor) (0x06 | TMC5041_MOTOR_ADDR(motor)) +#define TMC5041_VMAX(motor) (0x07 | TMC5041_MOTOR_ADDR(motor)) +#define TMC5041_DMAX(motor) (0x08 | TMC5041_MOTOR_ADDR(motor)) +#define TMC5041_D1(motor) (0x0A | TMC5041_MOTOR_ADDR(motor)) +#define TMC5041_VSTOP(motor) (0x0B | TMC5041_MOTOR_ADDR(motor)) +#define TMC5041_TZEROWAIT(motor) (0x0C | TMC5041_MOTOR_ADDR(motor)) +#define TMC5041_XTARGET(motor) (0x0D | TMC5041_MOTOR_ADDR(motor)) +#define TMC5041_IHOLD_IRUN(motor) (0x10 | TMC5041_MOTOR_ADDR(motor)) +#define TMC5041_VCOOLTHRS(motor) (0x11 | TMC5041_MOTOR_ADDR(motor)) +#define TMC5041_VHIGH(motor) (0x12 | TMC5041_MOTOR_ADDR(motor)) +#define TMC5041_SWMODE(motor) (0x14 | TMC5041_MOTOR_ADDR(motor)) +#define TMC5041_RAMPSTAT(motor) (0x15 | TMC5041_MOTOR_ADDR(motor)) +#define TMC5041_XLATCH(motor) (0x16 | TMC5041_MOTOR_ADDR(motor)) + +#define TMC5041_MSLUT0(motor) (0x60 | TMC5041_MOTOR_ADDR_DRV(motor)) +#define TMC5041_MSLUT1(motor) (0x61 | TMC5041_MOTOR_ADDR_DRV(motor)) +#define TMC5041_MSLUT2(motor) (0x62 | TMC5041_MOTOR_ADDR_DRV(motor)) +#define TMC5041_MSLUT3(motor) (0x63 | TMC5041_MOTOR_ADDR_DRV(motor)) +#define TMC5041_MSLUT4(motor) (0x64 | TMC5041_MOTOR_ADDR_DRV(motor)) +#define TMC5041_MSLUT5(motor) (0x65 | TMC5041_MOTOR_ADDR_DRV(motor)) +#define TMC5041_MSLUT6(motor) (0x66 | TMC5041_MOTOR_ADDR_DRV(motor)) +#define TMC5041_MSLUT7(motor) (0x67 | TMC5041_MOTOR_ADDR_DRV(motor)) +#define TMC5041_MSLUTSEL(motor) (0x68 | TMC5041_MOTOR_ADDR_DRV(motor)) +#define TMC5041_MSLUTSTART(motor) (0x69 | TMC5041_MOTOR_ADDR_DRV(motor)) +#define TMC5041_MSCNT(motor) (0x6A | TMC5041_MOTOR_ADDR_DRV(motor)) +#define TMC5041_MSCURACT(motor) (0x6B | TMC5041_MOTOR_ADDR_DRV(motor)) +#define TMC5041_CHOPCONF(motor) (0x6C | TMC5041_MOTOR_ADDR_DRV(motor)) +#define TMC5041_COOLCONF(motor) (0x6D | TMC5041_MOTOR_ADDR_DRV(motor)) +#define TMC5041_DRVSTATUS(motor) (0x6F | TMC5041_MOTOR_ADDR_DRV(motor)) + +#define TMC5041_RAMPMODE_POSITIONING_MODE 0 +#define TMC5041_RAMPMODE_POSITIVE_VELOCITY_MODE 1 +#define TMC5041_RAMPMODE_NEGATIVE_VELOCITY_MODE 2 +#define TMC5041_RAMPMODE_HOLD_MODE 3 + +#define TMC5041_SW_MODE_SG_STOP_ENABLE BIT(10) + +#define TMC5041_RAMPSTAT_INT_MASK GENMASK(7, 4) +#define TMC5041_RAMPSTAT_INT_SHIFT 4 + +#define TMC5041_RAMPSTAT_POS_REACHED_EVENT_MASK BIT(7) +#define TMC5041_POS_REACHED_EVENT \ + (TMC5041_RAMPSTAT_POS_REACHED_EVENT_MASK >> TMC5041_RAMPSTAT_INT_SHIFT) + +#define TMC5041_RAMPSTAT_STOP_SG_EVENT_MASK BIT(6) +#define TMC5041_STOP_SG_EVENT (TMC5041_RAMPSTAT_STOP_SG_EVENT_MASK >> TMC5041_RAMPSTAT_INT_SHIFT) + +#define TMC5041_RAMPSTAT_STOP_RIGHT_EVENT_MASK BIT(5) +#define TMC5041_STOP_RIGHT_EVENT \ + (TMC5041_RAMPSTAT_STOP_RIGHT_EVENT_MASK >> TMC5041_RAMPSTAT_INT_SHIFT) + +#define TMC5041_RAMPSTAT_STOP_LEFT_EVENT_MASK BIT(4) +#define TMC5041_STOP_LEFT_EVENT \ + (TMC5041_RAMPSTAT_STOP_LEFT_EVENT_MASK >> TMC5041_RAMPSTAT_INT_SHIFT) + +#define TMC5041_DRV_STATUS_STST_BIT BIT(31) +#define TMC5041_DRV_STATUS_SG_RESULT_MASK GENMASK(9, 0) +#define TMC5041_DRV_STATUS_SG_STATUS_MASK BIT(24) +#define TMC5041_DRV_STATUS_SG_STATUS_SHIFT 24 + +#define TMC5041_SG_MIN_VALUE -64 +#define TMC5041_SG_MAX_VALUE 63 + +#define TMC5041_COOLCONF_SG2_THRESHOLD_VALUE_SHIFT 16 + +#define TMC5041_IHOLD_MASK GENMASK(4, 0) +#define TMC5041_IHOLD_SHIFT 0 +#define TMC5041_IHOLD(n) (((n) << TMC5041_IHOLD_SHIFT) & TMC5041_IHOLD_MASK) + +#define TMC5041_IRUN_MASK GENMASK(12, 8) +#define TMC5041_IRUN_SHIFT 8 +#define TMC5041_IRUN(n) (((n) << TMC5041_IRUN_SHIFT) & TMC5041_IRUN_MASK) + +#define TMC5041_IHOLDDELAY_MASK GENMASK(19, 16) +#define TMC5041_IHOLDDELAY_SHIFT 16 +#define TMC5041_IHOLDDELAY(n) (((n) << TMC5041_IHOLDDELAY_SHIFT) & TMC5041_IHOLDDELAY_MASK) + +#define TMC5041_CHOPCONF_DRV_ENABLE_MASK GENMASK(3, 0) +#define TMC5041_CHOPCONF_MRES_MASK GENMASK(27, 24) +#define TMC5041_CHOPCONF_MRES_SHIFT 24 + +#define TMC5041_CLOCK_FREQ_SHIFT 24 + +#endif + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_DRIVERS_STEPPER_ADI_TMC_REG_H_ */ diff --git a/drivers/stepper/adi_tmc/adi_tmc_spi.c b/drivers/stepper/adi_tmc/adi_tmc_spi.c new file mode 100644 index 00000000000..7aa3bed69d5 --- /dev/null +++ b/drivers/stepper/adi_tmc/adi_tmc_spi.c @@ -0,0 +1,118 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2024 Carl Zeiss Meditec AG + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "adi_tmc_spi.h" + +#define BUFFER_SIZE 5U + +#include + +LOG_MODULE_REGISTER(tmc_spi, CONFIG_SPI_LOG_LEVEL); + +static void parse_tmc_spi_status(const uint8_t status_byte) +{ + if ((status_byte & BIT_MASK(0)) != 0) { + LOG_WRN("spi dataframe: reset_flag detected"); + } + if ((status_byte & BIT_MASK(1)) != 0) { + LOG_WRN("spi dataframe: driver_error(1) detected"); + } + if ((status_byte & BIT_MASK(2)) != 0) { + LOG_WRN("spi dataframe: driver_error(2) detected"); + } +} + +static void print_tx_rx_buffer(const uint8_t *const tx_buffer, const uint8_t *const rx_buffer) +{ + LOG_HEXDUMP_DBG(tx_buffer, BUFFER_SIZE, "TX: "); + LOG_HEXDUMP_DBG(rx_buffer, BUFFER_SIZE, "RX: "); +} + +int tmc_spi_read_register(const struct spi_dt_spec *bus, const uint8_t read_address_mask, + const uint8_t register_address, uint32_t *data) +{ + uint8_t tx_buffer[BUFFER_SIZE] = {read_address_mask & register_address, 0U, 0U, 0U, 0U}; + uint8_t rx_buffer[BUFFER_SIZE]; + int status; + + const struct spi_buf spi_buffer_tx = { + .buf = &tx_buffer, + .len = sizeof(tx_buffer), + }; + struct spi_buf_set spi_buffer_array_tx = { + .buffers = &spi_buffer_tx, + .count = 1U, + }; + + struct spi_buf spi_buffer_rx = { + .buf = &rx_buffer, + .len = sizeof(rx_buffer), + }; + struct spi_buf_set spi_buffer_array_rx = { + .buffers = &spi_buffer_rx, + .count = 1U, + }; + + /** send read with the address byte */ + status = spi_transceive_dt(bus, &spi_buffer_array_tx, &spi_buffer_array_rx); + if (status < 0) { + return status; + } + + print_tx_rx_buffer(tx_buffer, rx_buffer); + parse_tmc_spi_status(rx_buffer[0]); + + /** read the value from the address */ + status = spi_transceive_dt(bus, &spi_buffer_array_tx, &spi_buffer_array_rx); + if (status < 0) { + return status; + } + + *data = ((uint32_t)rx_buffer[1] << 24) + ((uint32_t)rx_buffer[2] << 16) + + ((uint32_t)rx_buffer[3] << 8) + (uint32_t)rx_buffer[4]; + + print_tx_rx_buffer(tx_buffer, rx_buffer); + parse_tmc_spi_status(rx_buffer[0]); + return status; +} + +int tmc_spi_write_register(const struct spi_dt_spec *bus, const uint8_t write_bit, + const uint8_t register_address, const uint32_t data) +{ + uint8_t tx_buffer[BUFFER_SIZE] = {write_bit | register_address, data >> 24, data >> 16, + data >> 8, data}; + uint8_t rx_buffer[BUFFER_SIZE]; + int status; + + const struct spi_buf spi_buffer_tx = { + .buf = &tx_buffer, + .len = sizeof(tx_buffer), + }; + struct spi_buf_set spi_buffer_array_tx = { + .buffers = &spi_buffer_tx, + .count = 1U, + }; + + struct spi_buf spi_buffer_rx = { + .buf = &rx_buffer, + .len = sizeof(rx_buffer), + }; + struct spi_buf_set spi_buffer_array_rx = { + .buffers = &spi_buffer_rx, + .count = 1U, + }; + + status = spi_transceive_dt(bus, &spi_buffer_array_tx, &spi_buffer_array_rx); + if (status < 0) { + return status; + } + + print_tx_rx_buffer(tx_buffer, rx_buffer); + parse_tmc_spi_status(rx_buffer[0]); + + return status; +} diff --git a/drivers/stepper/adi_tmc/adi_tmc_spi.h b/drivers/stepper/adi_tmc/adi_tmc_spi.h new file mode 100644 index 00000000000..c228b6192d4 --- /dev/null +++ b/drivers/stepper/adi_tmc/adi_tmc_spi.h @@ -0,0 +1,63 @@ +/** + * @file drivers/stepper/adi/stepper.h + * + * @brief Private API for Trinamic SPI bus + * + */ + +/* + * SPDX-FileCopyrightText: Copyright (c) 2024 Carl Zeiss Meditec AG + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_STEPPER_ADI_TMC_SPI_H_ +#define ZEPHYR_DRIVERS_STEPPER_ADI_TMC_SPI_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief TMC SPI INTERFACE + * @ingroup io_priv_interfaces + * @{ + * + */ + +#include + +/** + * @brief Read a register from the TMC module using the SPI Bus. + * + * @param bus SPI DT information of the bus. + * @param read_address_mask Address Mask for read operation. + * @param register_address Register. + * @param data Pointer to read value. + * + * @return a value from spi_transceive(). + */ +int tmc_spi_read_register(const struct spi_dt_spec *bus, const uint8_t read_address_mask, + const uint8_t register_address, uint32_t *data); + +/** + * @brief Write into a register in the TMC module using the SPI Bus. + * + * @param bus SPI DT information of the bus. + * @param write_bit Write bit for write operation. + * @param register_address Register. + * @param data Value to be written in the register. + * + * @return a value from spi_transceive(). + */ +int tmc_spi_write_register(const struct spi_dt_spec *bus, const uint8_t write_bit, + const uint8_t register_address, const uint32_t data); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_DRIVERS_STEPPER_ADI_TMC_SPI_H_ */ diff --git a/dts/bindings/stepper/adi/adi,tmc5041.yaml b/dts/bindings/stepper/adi/adi,tmc5041.yaml new file mode 100644 index 00000000000..882a18284dd --- /dev/null +++ b/dts/bindings/stepper/adi/adi,tmc5041.yaml @@ -0,0 +1,67 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024 Carl Zeiss Meditec AG +# SPDX-License-Identifier: Apache-2.0 + +description: Analog Devices TMC5041 Stepper Motor Controller + +compatible: "adi,tmc5041" + +include: + - name: spi-device.yaml + - name: adi,trinamic-gconf.yaml + property-allowlist: + - poscmp_enable + - shaft1 + - shaft2 + - test_mode + - lock_gconf + +properties: + "#address-cells": + default: 1 + const: 1 + + "#size-cells": + default: 0 + const: 0 + + clock-frequency: + type: int + required: true + description: | + The frequency of the clock signal provided to the TMC5041. + This is used for real world conversion. + + Hint: µstep velocity v[Hz] µsteps / s v[Hz] = v[5041] * ( fCLK[Hz]/2 / 2^23 ) + where v[5041] is the value written to the TMC5041. + +child-binding: + include: + - name: base.yaml + property-allowlist: + - reg + - name: stepper-controller.yaml + property-allowlist: + - invert-direction + - micro-step-res + - name: adi,trinamic-ramp-generator.yaml + property-allowlist: + - vstart + - a1 + - v1 + - amax + - vmax + - dmax + - d1 + - vstop + - tzerowait + - vhigh + - vcoolthrs + - 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/adi/adi,trinamic-gconf.yaml b/dts/bindings/stepper/adi/adi,trinamic-gconf.yaml new file mode 100644 index 00000000000..404476b8ee4 --- /dev/null +++ b/dts/bindings/stepper/adi/adi,trinamic-gconf.yaml @@ -0,0 +1,74 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024 Carl Zeiss Meditec AG +# SPDX-License-Identifier: Apache-2.0 + +description: Global configuration flags for Trinamic stepper controller. + +properties: + en_spreadcycle: + type: boolean + description: | + A high level on the pin SPREAD inverts this flag to switch between both chopper modes. + 0: StealthChop mode + 1: SpreadCycle mode enabled + + i_scale_analog: + type: boolean + description: | + 0: Use internal reference derived from 5VOUT + 1: Use voltage supplied to VREF as current reference + + internal_rsense: + type: boolean + description: | + 0: Operation with external sense resistors + 1: Internal sense resistors. Use current supplied into VREF as reference for internal + sense resistor. VREF pin internally is driven to GND in this mode. + + index_otpw: + type: boolean + description: | + 0: INDEX shows the first microstep position of sequencer + 1: INDEX output shows step pulses from internal pulse generator (toggle upon each step) + + index_step: + type: boolean + description: | + 0: INDEX output as selected by index_otpw + 1: INDEX pin shows the current step position of sequencer + + pdn_disable: + type: boolean + description: | + 0: Normal operation + 1: Power down mode + + mstep_reg_select: + type: boolean + description: | + 0: Microstep resolution selected by pins MS1, MS2 + 1: Microstep resolution selected by MRES register + + poscmp_enable: + type: boolean + description: | + Enable position compare feature + 0: Outputs INT and PP are tristated. + 1: Position compare pulse (PP) and interrupt output (INT) are available + + Attention – do not leave the outputs floating in tristate condition, provide an external + pull-up or set poscmp_enable=1 + + test_mode: + type: boolean + description: | + Enable test mode + 0: Normal operation + 1: Enable analog test output on pin REFR2 + TEST_SEL selects the function of REFR2: 0…4: T120, DAC1, VDDH1, DAC2, VDDH2 + + Attention: Not for user, set to 0 for normal operation! + + lock_gconf: + type: boolean + description: | + 1: GCONF is locked against further write access. diff --git a/dts/bindings/stepper/adi/adi,trinamic-ramp-generator.yaml b/dts/bindings/stepper/adi/adi,trinamic-ramp-generator.yaml new file mode 100644 index 00000000000..42d9b9e41fb --- /dev/null +++ b/dts/bindings/stepper/adi/adi,trinamic-ramp-generator.yaml @@ -0,0 +1,133 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024 Carl Zeiss Meditec AG +# SPDX-License-Identifier: Apache-2.0 + +description: Ramp Generator Motion Control Register-Set for Trinamic stepper controller. + +properties: + vstart: + type: int + default: 1 + description: | + Motor start velocity in [µsteps/t](unsigned) + + Normally, set VSTOP ≥ VSTART! VSTART may be + set to a higher value, when motion distance is + sufficient to allow deceleration to VSTOP. + + a1: + type: int + default: 0 + description: | + First acceleration between VSTART and V1 in [µsteps/ta²](unsigned) + + v1: + type: int + default: 0 + description: | + First acceleration / deceleration phase threshold velocity in [µsteps/t] (unsigned) + + 0: Disables A1 and D1 phase, use AMAX, DMAX only + + amax: + type: int + default: 0 + description: | + Second acceleration between V1 and VMAX in [µsteps/ta²](unsigned) + This is the acceleration and deceleration value + for velocity mode. + + vmax: + type: int + default: 0 + description: | + Motion ramp target velocity in [µsteps/t] (for positioning ensure VMAX ≥ VSTART) (unsigned) + This is the target velocity in velocity mode. It can be changed any time during a motion. + + dmax: + type: int + default: 0 + description: | + Deceleration between VMAX and V1 in [µsteps/ta²](unsigned) + + d1: + type: int + default: 1 + description: | + Deceleration between V1 and VSTOP in [µsteps/ta²](unsigned) + + Attention: Do not set 0 in positioning mode, + even if V1=0! + + vstop: + type: int + default: 10 + description: | + Motor stop velocity in [µsteps/t] (unsigned) + + Attention: Set VSTOP ≥ VSTART! + + Attention: Do not set 0 in positioning mode, + minimum 10 recommended! + + tzerowait: + type: int + default: 0 + description: | + Waiting time after ramping down to zero velocity before next movement or direction + inversion can start and before motor power down starts. Time range is about 0 to 2 + seconds. This setting avoids excess acceleration e.g. from VSTOP to -VSTART. + + ihold: + type: int + default: 0 + description: | + Hold current in % of run current (0-100) + Standstill current (0=1/32…31=32/32) + In combination with StealthChop mode, setting IHOLD=0 allows to choose freewheeling or coil + short circuit for motor stand still + + irun: + type: int + default: 0 + description: | + Motor run current (0=1/32…31=32/32) + Hint: Choose sense resistors in a way, that normal + IRUN is 16 to 31 for best microstep performance. + + iholddelay: + type: int + default: 0 + description: | + Controls the number of clock cycles for motor power down after a motion as soon as TZEROWAIT + has expired. The smooth transition avoids a motor jerk upon power down. + 0: instant power down + 1..15: Delay per current reduction step in multiple of 2^18 clocks + + vcoolthrs: + type: int + default: 0 + description: | + This is the lower threshold velocity for switching on smart energy CoolStep and StallGuard + feature. Further it is the upper operation velocity for StealthChop. (unsigned) + + Set this parameter to disable CoolStep at low speeds, where it cannot work reliably. + The stop on stall function (enable with sg_stop when using internal motion controller) + becomes enabled when exceeding this velocity. It becomes disabled again once the velocity + falls below this threshold. This allows for homing procedures with StallGuard by blanking out + the StallGuard signal at low velocities (will not work in combination with StealthChop). + VHIGH ≥ |VACT| ≥ VCOOLTHRS: + - CoolStep and stop on stall are enabled, if configured + - Voltage PWM mode StealthChop is switched off, if configured + + vhigh: + type: int + default: 0 + description: | + This velocity setting allows velocity dependent switching into a different chopper mode and + fullstepping to maximize torque.(unsigned) + |VACT| ≥ VHIGH: + - CoolStep is disabled (motor runs with normal current scale) + - If vhighchm is set, the chopper switches to chm=1 with TFD=0 + (constant off time with slow decay, only). + - If vhighfs is set, the motor operates in fullstep mode. + - Voltage PWM mode StealthChop is switched off, if configured diff --git a/dts/bindings/stepper/adi/adi,trinamic-stallguard.yaml b/dts/bindings/stepper/adi/adi,trinamic-stallguard.yaml new file mode 100644 index 00000000000..2a68273b2f1 --- /dev/null +++ b/dts/bindings/stepper/adi/adi,trinamic-stallguard.yaml @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024 Carl Zeiss Meditec AG +# SPDX-License-Identifier: Apache-2.0 + +description: Stallguard bindings for Trinamic stepper controller. + +properties: + activate-stallguard2: + type: boolean + description: | + Enable StallGuard2 feature, if the driver supports it. + + stallguard2-threshold: + type: int + default: 0 + description: | + This signed value controls StallGuard2 level for stall output and sets the + optimum measurement range for readout. A lower value gives a higher sensitivity. + Zero is the starting value working with most motors. + + -64 to +63: A higher value makes StallGuard2 less sensitive and requires more torque + to indicate a stall. + + stallguard-threshold-velocity: + type: int + default: 1 + description: | + Threshold velocity for StallGuard2 to detect a stall event. + This value should be greater than zero. + + stallguard-velocity-check-interval-ms: + type: int + default: 100 + description: | + Stallguard should not be enabled during motor spin-up. + This delay is used to check if the actual stepper velocity is greater than + stallguard-threshold-velocity before enabling stallguard. diff --git a/dts/bindings/stepper/stepper-controller.yaml b/dts/bindings/stepper/stepper-controller.yaml index b2c8e2b8643..48b4ec7225a 100644 --- a/dts/bindings/stepper/stepper-controller.yaml +++ b/dts/bindings/stepper/stepper-controller.yaml @@ -6,8 +6,14 @@ description: Stepper Controller include: base.yaml properties: + invert-direction: + type: boolean + description: | + Invert motor direction. + micro-step-res: type: int + default: 1 enum: - 1 - 2 diff --git a/include/zephyr/drivers/stepper.h b/include/zephyr/drivers/stepper.h index ba7d49cdf29..958d21216a8 100644 --- a/include/zephyr/drivers/stepper.h +++ b/include/zephyr/drivers/stepper.h @@ -28,6 +28,8 @@ extern "C" { #endif +#define MICRO_STEP_RES_INDEX(res) LOG2(res) + /** * @brief Stepper Motor micro step resolution options */ @@ -80,6 +82,9 @@ enum stepper_run_mode { enum stepper_signal_result { /** Steps set using move or set_target_position have been executed */ STEPPER_SIGNAL_STEPS_COMPLETED = 0, + STEPPER_SIGNAL_SENSORLESS_STALL_DETECTED = 1, + STEPPER_SIGNAL_LEFT_END_STOP_DETECTED = 2, + STEPPER_SIGNAL_RIGHT_END_STOP_DETECTED = 3, }; /** diff --git a/include/zephyr/drivers/stepper/stepper_trinamic.h b/include/zephyr/drivers/stepper/stepper_trinamic.h new file mode 100644 index 00000000000..e9c16491b33 --- /dev/null +++ b/include/zephyr/drivers/stepper/stepper_trinamic.h @@ -0,0 +1,172 @@ +/** + * @file drivers/stepper/stepper_trinamic.h + * + * @brief Public API for Trinamic Stepper Controller Specific Functions + * + */ + +/* + * SPDX-FileCopyrightText: Copyright (c) 2024 Carl Zeiss Meditec AG + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_STEPPER_STEPPER_TRINAMIC_H_ +#define ZEPHYR_INCLUDE_DRIVERS_STEPPER_STEPPER_TRINAMIC_H_ + +/** + * @brief Trinamic Stepper Controller Interface + * @defgroup trinamic_stepper_interface Trinamic Stepper Controller Interface + * @ingroup stepper_interface + * @{ + */ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Trinamic stepper controller ramp generator data limits + */ +#define TMC_RAMP_VSTART_MAX GENMASK(17, 0) +#define TMC_RAMP_VSTART_MIN 0 +#define TMC_RAMP_V1_MAX GENMASK(19, 0) +#define TMC_RAMP_V1_MIN 0 +#define TMC_RAMP_VMAX_MAX (GENMASK(22, 0) - 512) +#define TMC_RAMP_VMAX_MIN 0 +#define TMC_RAMP_A1_MAX GENMASK(15, 0) +#define TMC_RAMP_A1_MIN 0 +#define TMC_RAMP_AMAX_MAX GENMASK(15, 0) +#define TMC_RAMP_AMAX_MIN 0 +#define TMC_RAMP_D1_MAX GENMASK(15, 0) +#define TMC_RAMP_D1_MIN 1 +#define TMC_RAMP_DMAX_MAX GENMASK(15, 0) +#define TMC_RAMP_DMAX_MIN 0 +#define TMC_RAMP_VSTOP_MAX GENMASK(17, 0) +#define TMC_RAMP_VSTOP_MIN 1 +#define TMC_RAMP_TZEROWAIT_MAX (GENMASK(15, 0) - 512) +#define TMC_RAMP_TZEROWAIT_MIN 0 +#define TMC_RAMP_VCOOLTHRS_MAX GENMASK(22, 0) +#define TMC_RAMP_VCOOLTHRS_MIN 0 +#define TMC_RAMP_VHIGH_MAX GENMASK(22, 0) +#define TMC_RAMP_VHIGH_MIN 0 +#define TMC_RAMP_IHOLD_IRUN_MAX GENMASK(4, 0) +#define TMC_RAMP_IHOLD_IRUN_MIN 0 +#define TMC_RAMP_IHOLDDELAY_MAX GENMASK(3, 0) +#define TMC_RAMP_IHOLDDELAY_MIN 0 +#define TMC_RAMP_VACTUAL_SHIFT 22 +/** + * @brief Trinamic Stepper Ramp Generator data + */ +struct tmc_ramp_generator_data { + uint32_t vstart; + uint32_t v1; + uint32_t vmax; + uint16_t a1; + uint16_t amax; + uint16_t d1; + uint16_t dmax; + uint32_t vstop; + uint16_t tzerowait; + uint32_t vcoolthrs; + uint32_t vhigh; + uint32_t iholdrun; +}; + +/** + * @brief Check if Ramp DT data is within limits + */ +#define CHECK_RAMP_DT_DATA(node) \ + COND_CODE_1(DT_PROP_EXISTS(node, vstart), \ + BUILD_ASSERT(IN_RANGE(DT_PROP(node, vstart), TMC_RAMP_VSTART_MIN, \ + TMC_RAMP_VSTART_MAX), "vstart out of range"), ()); \ + COND_CODE_1(DT_PROP_EXISTS(node, v1), \ + BUILD_ASSERT(IN_RANGE(DT_PROP(node, v1), TMC_RAMP_V1_MIN, \ + TMC_RAMP_V1_MAX), "v1 out of range"), ()); \ + COND_CODE_1(DT_PROP_EXISTS(node, vmax), \ + BUILD_ASSERT(IN_RANGE(DT_PROP(node, vmax), TMC_RAMP_VMAX_MIN, \ + TMC_RAMP_VMAX_MAX), "vmax out of range"), ()); \ + COND_CODE_1(DT_PROP_EXISTS(node, a1), \ + BUILD_ASSERT(IN_RANGE(DT_PROP(node, a1), TMC_RAMP_A1_MIN, \ + TMC_RAMP_A1_MAX), "a1 out of range"), ()); \ + COND_CODE_1(DT_PROP_EXISTS(node, amax), \ + BUILD_ASSERT(IN_RANGE(DT_PROP(node, amax), TMC_RAMP_AMAX_MIN, \ + TMC_RAMP_AMAX_MAX), "amax out of range"), ()); \ + COND_CODE_1(DT_PROP_EXISTS(node, d1), \ + BUILD_ASSERT(IN_RANGE(DT_PROP(node, d1), TMC_RAMP_D1_MIN, \ + TMC_RAMP_D1_MAX), "d1 out of range"), ()); \ + COND_CODE_1(DT_PROP_EXISTS(node, dmax), \ + BUILD_ASSERT(IN_RANGE(DT_PROP(node, dmax), TMC_RAMP_DMAX_MIN, \ + TMC_RAMP_DMAX_MAX), "dmax out of range"), ()); \ + COND_CODE_1(DT_PROP_EXISTS(node, vstop), \ + BUILD_ASSERT(IN_RANGE(DT_PROP(node, vstop), TMC_RAMP_VSTOP_MIN, \ + TMC_RAMP_VSTOP_MAX), "vstop out of range"), ()); \ + COND_CODE_1(DT_PROP_EXISTS(node, tzerowait), \ + BUILD_ASSERT(IN_RANGE(DT_PROP(node, tzerowait), TMC_RAMP_TZEROWAIT_MIN, \ + TMC_RAMP_TZEROWAIT_MAX), "tzerowait out of range"), ()); \ + COND_CODE_1(DT_PROP_EXISTS(node, vcoolthrs), \ + BUILD_ASSERT(IN_RANGE(DT_PROP(node, vcoolthrs), TMC_RAMP_VCOOLTHRS_MIN, \ + TMC_RAMP_VCOOLTHRS_MAX), "vcoolthrs out of range"), ()); \ + COND_CODE_1(DT_PROP_EXISTS(node, vhigh), \ + BUILD_ASSERT(IN_RANGE(DT_PROP(node, vhigh), TMC_RAMP_VHIGH_MIN, \ + TMC_RAMP_VHIGH_MAX), "vhigh out of range"), ()); \ + COND_CODE_1(DT_PROP_EXISTS(node, ihold), \ + BUILD_ASSERT(IN_RANGE(DT_PROP(node, ihold), TMC_RAMP_IHOLD_IRUN_MIN, \ + TMC_RAMP_IHOLD_IRUN_MAX), "ihold out of range"), ()); \ + COND_CODE_1(DT_PROP_EXISTS(node, irun), \ + BUILD_ASSERT(IN_RANGE(DT_PROP(node, irun), TMC_RAMP_IHOLD_IRUN_MIN, \ + TMC_RAMP_IHOLD_IRUN_MAX), "irun out of range"), ()); \ + COND_CODE_1(DT_PROP_EXISTS(node, iholddelay), \ + BUILD_ASSERT(IN_RANGE(DT_PROP(node, iholddelay), TMC_RAMP_IHOLDDELAY_MIN, \ + TMC_RAMP_IHOLDDELAY_MAX), "iholddelay out of range"), ()); + +/** + * @brief Get Trinamic Stepper Ramp Generator data from DT + * + * @param node DT node identifier + * + * @return struct tmc_ramp_generator_data + */ +#define TMC_RAMP_DT_SPEC_GET(node) \ + { \ + .vstart = DT_PROP(node, vstart), \ + .v1 = DT_PROP(node, v1), \ + .vmax = DT_PROP(node, vmax), \ + .a1 = DT_PROP(node, a1), \ + .amax = DT_PROP(node, amax), \ + .d1 = DT_PROP(node, d1), \ + .dmax = DT_PROP(node, dmax), \ + .vstop = DT_PROP(node, vstop), \ + .tzerowait = DT_PROP(node, tzerowait), \ + .vcoolthrs = DT_PROP(node, vcoolthrs), \ + .vhigh = DT_PROP(node, vhigh), \ + .iholdrun = (TMC5041_IRUN(DT_PROP(node, irun)) | \ + TMC5041_IHOLD(DT_PROP(node, ihold)) | \ + TMC5041_IHOLDDELAY(DT_PROP(node, iholddelay))), \ + } + +/** + * @brief Configure Trinamic Stepper Ramp Generator + * + * @param dev Pointer to the stepper motor controller instance + * @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 tmc5041_stepper_set_ramp(const struct device *dev, + const struct tmc_ramp_generator_data *ramp_data); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_DRIVERS_STEPPER_STEPPER_TRINAMIC_H_ */