drivers: stepper: adi_tmc: Add tmc51xx support

Add tmc51xx support based on tmc50xx implementation.

Signed-off-by: Anders Nielsen <anders.nielsen@prevas.dk>
This commit is contained in:
Anders Nielsen 2025-04-15 15:28:05 +02:00 committed by Benjamin Cabé
commit b98bd7c145
6 changed files with 967 additions and 2 deletions

View file

@ -7,3 +7,4 @@ zephyr_library_property(ALLOW_EMPTY TRUE)
zephyr_library_sources_ifdef(CONFIG_STEPPER_ADI_TMC_SPI adi_tmc_spi.c)
zephyr_library_sources_ifdef(CONFIG_STEPPER_ADI_TMC2209 adi_tmc22xx_stepper_controller.c)
zephyr_library_sources_ifdef(CONFIG_STEPPER_ADI_TMC50XX adi_tmc50xx_stepper_controller.c)
zephyr_library_sources_ifdef(CONFIG_STEPPER_ADI_TMC51XX adi_tmc51xx_stepper_controller.c)

View file

@ -0,0 +1,690 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2025 Prevas A/S
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT adi_tmc51xx
#include <stdlib.h>
#include <zephyr/drivers/stepper.h>
#include <zephyr/drivers/stepper/stepper_trinamic.h>
#include "adi_tmc_spi.h"
#include "adi_tmc5xxx_common.h"
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(tmc51xx, CONFIG_STEPPER_LOG_LEVEL);
struct tmc51xx_data {
struct k_sem sem;
struct k_work_delayable stallguard_dwork;
/* Work item to run the callback in a thread context. */
#ifdef CONFIG_STEPPER_ADI_TMC51XX_RAMPSTAT_POLL
struct k_work_delayable rampstat_callback_dwork;
#endif
/* device pointer required to access config in k_work */
const struct device *stepper;
stepper_event_callback_t callback;
void *event_cb_user_data;
};
struct tmc51xx_config {
const uint32_t gconf;
struct spi_dt_spec spi;
const uint32_t clock_frequency;
const uint16_t default_micro_step_res;
const int8_t sg_threshold;
const bool is_sg_enabled;
const uint32_t sg_velocity_check_interval_ms;
const uint32_t sg_threshold_velocity;
#ifdef CONFIG_STEPPER_ADI_TMC51XX_RAMP_GEN
const struct tmc_ramp_generator_data default_ramp_config;
#endif
};
static int tmc51xx_write(const struct device *dev, const uint8_t reg_addr, const uint32_t reg_val)
{
const struct tmc51xx_config *config = dev->config;
struct tmc51xx_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, TMC5XXX_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 tmc51xx_read(const struct device *dev, const uint8_t reg_addr, uint32_t *reg_val)
{
const struct tmc51xx_config *config = dev->config;
struct tmc51xx_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, TMC5XXX_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 int tmc51xx_stepper_set_event_callback(const struct device *dev,
stepper_event_callback_t callback, void *user_data)
{
struct tmc51xx_data *data = dev->data;
data->callback = callback;
data->event_cb_user_data = user_data;
return 0;
}
static int stallguard_enable(const struct device *dev, const bool enable)
{
const struct tmc51xx_config *config = dev->config;
uint32_t reg_value;
int err;
err = tmc51xx_read(dev, TMC51XX_SWMODE, &reg_value);
if (err) {
LOG_ERR("Failed to read SWMODE register");
return -EIO;
}
if (enable) {
reg_value |= TMC5XXX_SW_MODE_SG_STOP_ENABLE;
int32_t actual_velocity;
err = tmc51xx_read(dev, TMC51XX_VACTUAL,
&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 &= ~TMC5XXX_SW_MODE_SG_STOP_ENABLE;
}
err = tmc51xx_write(dev, TMC51XX_SWMODE, 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 tmc51xx_data *stepper_data =
CONTAINER_OF(dwork, struct tmc51xx_data, stallguard_dwork);
const struct device *dev = stepper_data->stepper;
const struct tmc51xx_config *config = dev->config;
int err;
err = stallguard_enable(dev, true);
if (err == -EAGAIN) {
LOG_ERR("retrying stallguard activation");
k_work_reschedule(dwork, K_MSEC(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_TMC51XX_RAMPSTAT_POLL
static void execute_callback(const struct device *dev, const enum stepper_event event)
{
struct tmc51xx_data *data = dev->data;
if (!data->callback) {
LOG_WRN_ONCE("No callback registered");
return;
}
data->callback(dev, event, data->event_cb_user_data);
}
static void rampstat_work_handler(struct k_work *work)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct tmc51xx_data *stepper_data =
CONTAINER_OF(dwork, struct tmc51xx_data, rampstat_callback_dwork);
const struct device *dev = stepper_data->stepper;
__ASSERT_NO_MSG(dev != NULL);
uint32_t drv_status;
int err;
err = tmc51xx_read(dev, TMC51XX_DRVSTATUS,
&drv_status);
if (err != 0) {
LOG_ERR("%s: Failed to read DRVSTATUS register", dev->name);
return;
}
if (FIELD_GET(TMC5XXX_DRV_STATUS_SG_STATUS_MASK, drv_status) == 1U) {
LOG_INF("%s: Stall detected", dev->name);
err = tmc51xx_write(dev,
TMC51XX_RAMPMODE,
TMC5XXX_RAMPMODE_HOLD_MODE);
if (err != 0) {
LOG_ERR("%s: Failed to stop motor", dev->name);
return;
}
}
uint32_t rampstat_value;
err = tmc51xx_read(dev, TMC51XX_RAMPSTAT,
&rampstat_value);
if (err != 0) {
LOG_ERR("%s: Failed to read RAMPSTAT register", dev->name);
return;
}
const uint8_t ramp_stat_values = FIELD_GET(TMC5XXX_RAMPSTAT_INT_MASK, rampstat_value);
if (ramp_stat_values > 0) {
switch (ramp_stat_values) {
case TMC5XXX_STOP_LEFT_EVENT:
LOG_DBG("RAMPSTAT %s:Left end-stop detected", dev->name);
execute_callback(dev,
STEPPER_EVENT_LEFT_END_STOP_DETECTED);
break;
case TMC5XXX_STOP_RIGHT_EVENT:
LOG_DBG("RAMPSTAT %s:Right end-stop detected", dev->name);
execute_callback(dev,
STEPPER_EVENT_RIGHT_END_STOP_DETECTED);
break;
case TMC5XXX_POS_REACHED_EVENT:
LOG_DBG("RAMPSTAT %s:Position reached", dev->name);
execute_callback(dev, STEPPER_EVENT_STEPS_COMPLETED);
break;
case TMC5XXX_STOP_SG_EVENT:
LOG_DBG("RAMPSTAT %s:Stall detected", dev->name);
stallguard_enable(dev, false);
execute_callback(dev, STEPPER_EVENT_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_TMC51XX_RAMPSTAT_POLL_INTERVAL_IN_MSEC));
}
}
#endif
static int tmc51xx_stepper_enable(const struct device *dev)
{
LOG_DBG("Enabling Stepper motor controller %s", dev->name);
uint32_t reg_value;
int err;
err = tmc51xx_read(dev, TMC51XX_CHOPCONF, &reg_value);
if (err != 0) {
return -EIO;
}
reg_value |= TMC5XXX_CHOPCONF_DRV_ENABLE_MASK;
return tmc51xx_write(dev, TMC51XX_CHOPCONF, reg_value);
}
static int tmc51xx_stepper_disable(const struct device *dev)
{
LOG_DBG("Disabling Stepper motor controller %s", dev->name);
uint32_t reg_value;
int err;
err = tmc51xx_read(dev, TMC51XX_CHOPCONF, &reg_value);
if (err != 0) {
return -EIO;
}
reg_value &= ~TMC5XXX_CHOPCONF_DRV_ENABLE_MASK;
return tmc51xx_write(dev, TMC51XX_CHOPCONF, reg_value);
}
static int tmc51xx_stepper_is_moving(const struct device *dev, bool *is_moving)
{
uint32_t reg_value;
int err;
err = tmc51xx_read(dev, TMC51XX_DRVSTATUS, &reg_value);
if (err != 0) {
LOG_ERR("%s: Failed to read DRVSTATUS register", dev->name);
return -EIO;
}
*is_moving = (FIELD_GET(TMC5XXX_DRV_STATUS_STST_BIT, reg_value) == 1U);
LOG_DBG("Stepper motor controller %s is moving: %d", dev->name, *is_moving);
return 0;
}
int tmc51xx_stepper_set_max_velocity(const struct device *dev, uint32_t velocity)
{
const struct tmc51xx_config *config = dev->config;
const uint32_t clock_frequency = config->clock_frequency;
uint32_t velocity_fclk;
int err;
velocity_fclk = tmc5xxx_calculate_velocity_from_hz_to_fclk(velocity, clock_frequency);
err = tmc51xx_write(dev, TMC51XX_VMAX, velocity_fclk);
if (err != 0) {
LOG_ERR("%s: Failed to set max velocity", dev->name);
return -EIO;
}
return 0;
}
static int tmc51xx_stepper_set_micro_step_res(const struct device *dev,
enum stepper_micro_step_resolution res)
{
if (!VALID_MICRO_STEP_RES(res)) {
LOG_ERR("Invalid micro step resolution %d", res);
return -ENOTSUP;
}
uint32_t reg_value;
int err;
err = tmc51xx_read(dev, TMC51XX_CHOPCONF, &reg_value);
if (err != 0) {
return -EIO;
}
reg_value &= ~TMC5XXX_CHOPCONF_MRES_MASK;
reg_value |= ((MICRO_STEP_RES_INDEX(STEPPER_MICRO_STEP_256) - LOG2(res))
<< TMC5XXX_CHOPCONF_MRES_SHIFT);
err = tmc51xx_write(dev, TMC51XX_CHOPCONF, 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 tmc51xx_stepper_get_micro_step_res(const struct device *dev,
enum stepper_micro_step_resolution *res)
{
uint32_t reg_value;
int err;
err = tmc51xx_read(dev, TMC51XX_CHOPCONF, &reg_value);
if (err != 0) {
return -EIO;
}
reg_value &= TMC5XXX_CHOPCONF_MRES_MASK;
reg_value >>= TMC5XXX_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 tmc51xx_stepper_set_reference_position(const struct device *dev, const int32_t position)
{
int err;
err = tmc51xx_write(dev, TMC51XX_RAMPMODE,
TMC5XXX_RAMPMODE_HOLD_MODE);
if (err != 0) {
return -EIO;
}
err = tmc51xx_write(dev, TMC51XX_XACTUAL, position);
if (err != 0) {
return -EIO;
}
LOG_DBG("Stepper motor controller %s set actual position to %d", dev->name, position);
return 0;
}
static int tmc51xx_stepper_get_actual_position(const struct device *dev, int32_t *position)
{
int err;
err = tmc51xx_read(dev, TMC51XX_XACTUAL, position);
if (err != 0) {
return -EIO;
}
LOG_DBG("%s actual position: %d", dev->name, *position);
return 0;
}
static int tmc51xx_stepper_move_to(const struct device *dev, const int32_t micro_steps)
{
LOG_DBG("%s set target position to %d", dev->name, micro_steps);
const struct tmc51xx_config *config = dev->config;
struct tmc51xx_data *data = dev->data;
int err;
if (config->is_sg_enabled) {
stallguard_enable(dev, false);
}
err = tmc51xx_write(dev, TMC51XX_RAMPMODE,
TMC5XXX_RAMPMODE_POSITIONING_MODE);
if (err != 0) {
return -EIO;
}
err = tmc51xx_write(dev, TMC51XX_XTARGET, micro_steps);
if (err != 0) {
return -EIO;
}
if (config->is_sg_enabled) {
k_work_reschedule(&data->stallguard_dwork,
K_MSEC(config->sg_velocity_check_interval_ms));
}
#ifdef CONFIG_STEPPER_ADI_TMC51XX_RAMPSTAT_POLL
if (data->callback) {
k_work_reschedule(
&data->rampstat_callback_dwork,
K_MSEC(CONFIG_STEPPER_ADI_TMC51XX_RAMPSTAT_POLL_INTERVAL_IN_MSEC));
}
#endif
return 0;
}
static int tmc51xx_stepper_move_by(const struct device *dev, const int32_t micro_steps)
{
int err;
int32_t position;
err = stepper_get_actual_position(dev, &position);
if (err != 0) {
return -EIO;
}
int32_t target_position = position + micro_steps;
LOG_DBG("%s moved to %d by steps: %d", dev->name, target_position, micro_steps);
return tmc51xx_stepper_move_to(dev, target_position);
}
static int tmc51xx_stepper_run(const struct device *dev, const enum stepper_direction direction)
{
LOG_DBG("Stepper motor controller %s run", dev->name);
const struct tmc51xx_config *config = dev->config;
struct tmc51xx_data *data = dev->data;
int err;
if (config->is_sg_enabled) {
err = stallguard_enable(dev, false);
if (err != 0) {
return -EIO;
}
}
switch (direction) {
case STEPPER_DIRECTION_POSITIVE:
err = tmc51xx_write(dev, TMC51XX_RAMPMODE,
TMC5XXX_RAMPMODE_POSITIVE_VELOCITY_MODE);
if (err != 0) {
return -EIO;
}
break;
case STEPPER_DIRECTION_NEGATIVE:
err = tmc51xx_write(dev, TMC51XX_RAMPMODE,
TMC5XXX_RAMPMODE_NEGATIVE_VELOCITY_MODE);
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));
}
#ifdef CONFIG_STEPPER_ADI_TMC51XX_RAMPSTAT_POLL
if (data->callback) {
k_work_reschedule(
&data->rampstat_callback_dwork,
K_MSEC(CONFIG_STEPPER_ADI_TMC51XX_RAMPSTAT_POLL_INTERVAL_IN_MSEC));
}
#endif
return 0;
}
#ifdef CONFIG_STEPPER_ADI_TMC51XX_RAMP_GEN
int tmc51xx_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);
int err;
err = tmc51xx_write(dev, TMC51XX_VSTART, ramp_data->vstart);
if (err != 0) {
return -EIO;
}
err = tmc51xx_write(dev, TMC51XX_A1, ramp_data->a1);
if (err != 0) {
return -EIO;
}
err = tmc51xx_write(dev, TMC51XX_AMAX, ramp_data->amax);
if (err != 0) {
return -EIO;
}
err = tmc51xx_write(dev, TMC51XX_D1, ramp_data->d1);
if (err != 0) {
return -EIO;
}
err = tmc51xx_write(dev, TMC51XX_DMAX, ramp_data->dmax);
if (err != 0) {
return -EIO;
}
err = tmc51xx_write(dev, TMC51XX_V1, ramp_data->v1);
if (err != 0) {
return -EIO;
}
err = tmc51xx_write(dev, TMC51XX_VMAX, ramp_data->vmax);
if (err != 0) {
return -EIO;
}
err = tmc51xx_write(dev, TMC51XX_VSTOP, ramp_data->vstop);
if (err != 0) {
return -EIO;
}
err = tmc51xx_write(dev, TMC51XX_TZEROWAIT,
ramp_data->tzerowait);
if (err != 0) {
return -EIO;
}
err = tmc51xx_write(dev, TMC51XX_THIGH, ramp_data->thigh);
if (err != 0) {
return -EIO;
}
err = tmc51xx_write(dev, TMC51XX_TCOOLTHRS, ramp_data->tcoolthrs);
if (err != 0) {
return -EIO;
}
err = tmc51xx_write(dev, TMC51XX_TPWMTHRS, ramp_data->tpwmthrs);
if (err != 0) {
return -EIO;
}
err = tmc51xx_write(dev, TMC51XX_TPOWER_DOWN, ramp_data->tpowerdown);
if (err != 0) {
return -EIO;
}
err = tmc51xx_write(dev, TMC51XX_IHOLD_IRUN, ramp_data->iholdrun);
if (err != 0) {
return -EIO;
}
return 0;
}
#endif
static int tmc51xx_init(const struct device *dev)
{
LOG_DBG("TMC51XX stepper motor controller %s initialized", dev->name);
struct tmc51xx_data *data = dev->data;
const struct tmc51xx_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;
}
LOG_DBG("GCONF: %d", config->gconf);
err = tmc51xx_write(dev, TMC5XXX_GCONF, config->gconf);
if (err != 0) {
return -EIO;
}
/* Read and write GSTAT register to clear any SPI Datagram errors. */
uint32_t gstat_value;
err = tmc51xx_read(dev, TMC5XXX_GSTAT, &gstat_value);
if (err != 0) {
return -EIO;
}
err = tmc51xx_write(dev, TMC5XXX_GSTAT, gstat_value);
if (err != 0) {
return -EIO;
}
if (config->is_sg_enabled) {
k_work_init_delayable(&data->stallguard_dwork, stallguard_work_handler);
err = tmc51xx_write(dev,
TMC51XX_SWMODE, BIT(10));
if (err != 0) {
return -EIO;
}
LOG_DBG("Setting stall guard to %d with delay %d ms", config->sg_threshold,
config->sg_velocity_check_interval_ms);
if (!IN_RANGE(config->sg_threshold, TMC5XXX_SG_MIN_VALUE,
TMC5XXX_SG_MAX_VALUE)) {
LOG_ERR("Stallguard threshold out of range");
return -EINVAL;
}
int32_t stall_guard_threshold = (int32_t)config->sg_threshold;
err = tmc51xx_write(
dev, TMC51XX_COOLCONF,
stall_guard_threshold << TMC5XXX_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(config->sg_velocity_check_interval_ms));
}
}
#ifdef CONFIG_STEPPER_ADI_TMC51XX_RAMP_GEN
err = tmc51xx_stepper_set_ramp(dev, &config->default_ramp_config);
if (err != 0) {
return -EIO;
}
#endif
#if CONFIG_STEPPER_ADI_TMC51XX_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_TMC51XX_RAMPSTAT_POLL_INTERVAL_IN_MSEC));
#endif
err = tmc51xx_stepper_set_micro_step_res(dev, config->default_micro_step_res);
if (err != 0) {
return -EIO;
}
return 0;
}
static DEVICE_API(stepper, tmc51xx_api) = {
.enable = tmc51xx_stepper_enable,
.disable = tmc51xx_stepper_disable,
.is_moving = tmc51xx_stepper_is_moving,
.move_by = tmc51xx_stepper_move_by,
.set_micro_step_res = tmc51xx_stepper_set_micro_step_res,
.get_micro_step_res = tmc51xx_stepper_get_micro_step_res,
.set_reference_position = tmc51xx_stepper_set_reference_position,
.get_actual_position = tmc51xx_stepper_get_actual_position,
.move_to = tmc51xx_stepper_move_to,
.run = tmc51xx_stepper_run,
.set_event_callback = tmc51xx_stepper_set_event_callback,
};
#define TMC51XX_DEFINE(inst) \
BUILD_ASSERT((DT_INST_PROP(inst, clock_frequency) > 0), \
"clock frequency must be non-zero positive value"); \
static struct tmc51xx_data tmc51xx_data_##inst = { \
.stepper = DEVICE_DT_GET(DT_DRV_INST(inst))}; \
COND_CODE_1(DT_PROP_EXISTS(inst, stallguard_threshold_velocity), \
BUILD_ASSERT(DT_PROP(inst, stallguard_threshold_velocity), \
"stallguard threshold velocity must be a positive value"), ()); \
IF_ENABLED(CONFIG_STEPPER_ADI_TMC51XX_RAMP_GEN, (CHECK_RAMP_DT_DATA(inst))); \
static const struct tmc51xx_config tmc51xx_config_##inst = { \
.gconf = ( \
(DT_INST_PROP(inst, en_pwm_mode) << TMC51XX_GCONF_EN_PWM_MODE_SHIFT) | \
(DT_INST_PROP(inst, test_mode) << TMC51XX_GCONF_TEST_MODE_SHIFT) | \
(DT_INST_PROP(inst, invert_direction) << TMC51XX_GCONF_SHAFT_SHIFT)), \
.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), \
.default_micro_step_res = DT_INST_PROP(inst, micro_step_res), \
.sg_threshold = DT_INST_PROP(inst, stallguard2_threshold), \
.sg_threshold_velocity = DT_INST_PROP(inst, stallguard_threshold_velocity), \
.sg_velocity_check_interval_ms = DT_INST_PROP(inst, \
stallguard_velocity_check_interval_ms), \
.is_sg_enabled = DT_INST_PROP(inst, activate_stallguard2), \
IF_ENABLED(CONFIG_STEPPER_ADI_TMC51XX_RAMP_GEN, \
(.default_ramp_config = TMC_RAMP_DT_SPEC_GET_TMC51XX(inst)))}; \
DEVICE_DT_INST_DEFINE(inst, tmc51xx_init, NULL, &tmc51xx_data_##inst, \
&tmc51xx_config_##inst, POST_KERNEL, CONFIG_STEPPER_INIT_PRIORITY,\
&tmc51xx_api);
DT_INST_FOREACH_STATUS_OKAY(TMC51XX_DEFINE)

View file

@ -0,0 +1,108 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 Prevas A/S
# SPDX-License-Identifier: Apache-2.0
description: |
Analog Devices TMC51XX Stepper Motor Controller
Example:
&spi0 {
/* SPI bus options here, not shown */
/* Controller/driver for one 2-phase bipolar stepper motor */
tmc51xx: tmc51xx@0 {
compatible = "adi,tmc51xx";
reg = <0>;
spi-max-frequency = <DT_FREQ_M(8)>; /* Maximum SPI bus frequency */
#address-cells = <1>;
#size-cells = <0>;
en-pwm-mode; test-mode; /* ADI TMC Global configuration flags */
clock-frequency = <DT_FREQ_M(16)>; /* Internal/External Clock frequency */
/* common stepper controller settings */
invert-direction;
micro-step-res = <256>;
/* ADI TMC stallguard settings specific to TMC51XX */
activate-stallguard2;
stallguard-velocity-check-interval-ms=<100>;
stallguard2-threshold=<9>;
stallguard-threshold-velocity=<500000>;
/* ADI TMC ramp generator as well as current settings */
vstart = <10>;
a1 = <20>;
v1 = <30>;
d1 = <40>;
vmax = <50>;
amax = <60>;
dmax = <70>;
tzerowait = <80>;
thigh = <90>;
tcoolthrs = <100>;
tpwmthrs = <110>;
tpowerdown = <120>;
ihold = <1>;
irun = <2>;
iholddelay = <3>;
};
};
compatible: "adi,tmc51xx"
include:
- name: spi-device.yaml
- name: adi,trinamic-gconf.yaml
property-allowlist:
- en-pwm-mode
- test-mode
- name: stepper-controller.yaml
- name: base.yaml
property-allowlist:
- reg
- name: adi,trinamic-ramp-generator.yaml
property-allowlist:
- vstart
- a1
- v1
- amax
- vmax
- dmax
- d1
- vstop
- tzerowait
- thigh
- tcoolthrs
- tpwmthrs
- tpowerdown
- ihold
- irun
- iholddelay
- name: adi,trinamic-stallguard.yaml
property-allowlist:
- activate-stallguard2
- stallguard2-threshold
- stallguard-threshold-velocity
- stallguard-velocity-check-interval-ms
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 TMC51XX.
This is used for real world conversion.
Hint: µstep velocity v[Hz] µsteps / s v[Hz] = v[51xx] * ( fCLK[Hz]/2 / 2^23 )
where v[51xx] is the value written to the TMC51XX.

View file

@ -1,4 +1,5 @@
# SPDX-FileCopyrightText: Copyright (c) 2024 Carl Zeiss Meditec AG
# SPDX-FileCopyrightText: Copyright (c) 2025 Prevas A/S
# SPDX-License-Identifier: Apache-2.0
description: Global configuration flags for Trinamic stepper controller.
@ -72,3 +73,11 @@ properties:
type: boolean
description: |
1: GCONF is locked against further write access.
en-pwm-mode:
type: boolean
description: |
1: StealthChop voltage PWM mode enabled
(depending on velocity thresholds). Switch from
off to on state while in stand-still and at IHOLD=
nominal IRUN current, only.

View file

@ -1,4 +1,5 @@
# SPDX-FileCopyrightText: Copyright (c) 2024 Carl Zeiss Meditec AG
# SPDX-FileCopyrightText: Copyright (c) 2025 Prevas A/S
# SPDX-License-Identifier: Apache-2.0
description: Ramp Generator Motion Control Register-Set for Trinamic stepper controller.
@ -131,3 +132,63 @@ properties:
(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
tcoolthrs:
type: int
default: 0
description: |
This is the lower threshold velocity for switching on smart
energy CoolStep and StallGuard feature. (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) and the stall
output signal become enabled when exceeding this velocity. In
non-DcStep mode, it becomes disabled again once the velocity
falls below this threshold.
TCOOLTHRS ≥ TSTEP ≥ THIGH:
- CoolStep is enabled, if configured
- StealthChop voltage PWM mode is disabled
TCOOLTHRS ≥ TSTEP
- Stop on stall is enabled, if configured
- Stall output signal (DIAG0/1) is enabled, if configured
thigh:
type: int
default: 0
description: |
This velocity setting allows velocity dependent switching into
a different chopper mode and fullstepping to maximize torque.
(unsigned)
The stall detection feature becomes switched off for 2-3
electrical periods whenever passing THIGH threshold to
compensate for the effect of switching modes.
TSTEP ≤ THIGH:
- CoolStep is disabled (motor runs with normal current
scale)
- StealthChop voltage PWM mode is disabled
- 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,
and the stall detection becomes switched over to
DcStep stall detection.
tpwmthrs:
type: int
default: 0
description: |
This is the upper velocity for StealthChop voltage PWM mode.
TSTEP ≥ TPWMTHRS
- StealthChop PWM mode is enabled, if configured
- DcStep is disabled
tpowerdown:
type: int
default: 10
description: |
TPOWERDOWN sets the delay time after stand still (stst) of the
motor to motor current power down. Time range is about 0 to
4 seconds.
Attention: A minimum setting of 2 is required to allow
automatic tuning of StealthChop PWM_OFS_AUTO.
Reset Default = 10
0…((2^8)-1) * 2^18 tCLK

View file

@ -8,19 +8,115 @@
adi_tmc50xx: adi_tmc50xx@0 {
compatible = "adi,tmc50xx";
status = "okay";
reg = <0x0>;
spi-max-frequency = <0>;
spi-max-frequency = <8000000>;
label = "tmc5041_0";
#address-cells = <1>;
#size-cells = <0>;
clock-frequency = <1>;
poscmp-enable; test-mode; lock-gconf; /* ADI TMC Global configuration flags */
clock-frequency = <16000000>; /* Internal/External Clock frequency */
tmc50xx_0: tmc50xx_0@0 {
status = "okay";
reg = <0>;
/* common stepper controller settings */
invert-direction;
micro-step-res = <256>;
/* ADI TMC stallguard settings specific to TMC50XX */
activate-stallguard2;
stallguard-velocity-check-interval-ms=<100>;
stallguard2-threshold=<9>;
stallguard-threshold-velocity=<500000>;
/* ADI TMC ramp generator as well as current settings */
vstart = <10>;
a1 = <20>;
v1 = <30>;
d1 = <40>;
vmax = <50>;
amax = <60>;
dmax = <70>;
tzerowait = <80>;
vhigh = <90>;
vcoolthrs = <100>;
ihold = <1>;
irun = <2>;
iholddelay = <3>;
};
tmc50xx_1: tmc50xx_1@1 {
status = "okay";
reg = <1>;
/* common stepper controller settings */
invert-direction;
micro-step-res = <256>;
/* ADI TMC stallguard settings specific to TMC50XX */
activate-stallguard2;
stallguard-velocity-check-interval-ms=<100>;
stallguard2-threshold=<9>;
stallguard-threshold-velocity=<500000>;
/* ADI TMC ramp generator as well as current settings */
vstart = <10>;
a1 = <20>;
v1 = <30>;
d1 = <40>;
vmax = <50>;
amax = <60>;
dmax = <70>;
tzerowait = <80>;
vhigh = <90>;
vcoolthrs = <100>;
ihold = <1>;
irun = <2>;
iholddelay = <3>;
};
};
adi_tmc51xx: adi_tmc51xx@1 {
compatible = "adi,tmc51xx";
status = "okay";
reg = <0x01>;
spi-max-frequency = <8000000>;
label = "tmc5160_1";
#address-cells = <1>;
#size-cells = <0>;
en-pwm-mode; test-mode; /* ADI TMC Global configuration flags */
clock-frequency = <16000000>; /* Internal/External Clock frequency */
/* common stepper controller settings */
invert-direction;
micro-step-res = <256>;
/* ADI TMC stallguard settings specific to TMC5160 */
activate-stallguard2;
stallguard-velocity-check-interval-ms=<100>;
stallguard2-threshold=<9>;
stallguard-threshold-velocity=<50000>;
/* ADI TMC ramp generator as well as current settings */
vstart = <10>;
a1 = <20>;
v1 = <30>;
d1 = <40>;
vmax = <50>;
amax = <60>;
dmax = <70>;
tzerowait = <80>;
thigh = <90>;
tcoolthrs = <100>;
tpwmthrs = <110>;
tpowerdown = <120>;
ihold = <1>;
irun = <2>;
iholddelay = <3>;
};