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 <dipak.shetty@zeiss.com>
Signed-off-by: Jilay Pandya <jilay.pandya@zeiss.com>
This commit is contained in:
Jilay Pandya 2024-09-26 09:45:58 +02:00 committed by Fabio Baltieri
parent 42c43b9f9c
commit 52c6a289f1
15 changed files with 1652 additions and 1 deletions

View file

@ -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)

View file

@ -49,6 +49,7 @@ config STEPPER_SHELL_THREAD_PRIORITY
comment "Stepper Drivers"
rsource "adi_tmc/Kconfig"
rsource "Kconfig.fake"
rsource "Kconfig.gpio"

View file

@ -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)

View file

@ -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

View file

@ -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 <zephyr/drivers/stepper.h>
#include <zephyr/drivers/stepper/stepper_trinamic.h>
#include "adi_tmc_reg.h"
#include "adi_tmc_spi.h"
#include <zephyr/logging/log.h>
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), &reg_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), &reg_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), &reg_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), &reg_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), &reg_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)

View file

@ -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_ */

View file

@ -0,0 +1,118 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2024 Carl Zeiss Meditec AG
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/sys/util.h>
#include "adi_tmc_spi.h"
#define BUFFER_SIZE 5U
#include <zephyr/logging/log.h>
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;
}

View file

@ -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 <zephyr/drivers/spi.h>
/**
* @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_ */

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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,
};
/**

View file

@ -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 <stdint.h>
#include <zephyr/drivers/stepper.h>
#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_ */