Skip to content

drivers: stepper: ADI TMC2130 Stepper Driver #90677

New issue

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

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

Already on GitHub? Sign in to your account

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions boards/shields/mikroe_silent_step_2_click/Kconfig.shield
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Copyright (c) 2025 Navimatix GmbH
# SPDX-License-Identifier: Apache-2.0

config SHIELD_MIKROE_SILENT_STEP_2_CLICK
def_bool $(shields_list_contains,mikroe_silent_step_2_click)
43 changes: 43 additions & 0 deletions boards/shields/mikroe_silent_step_2_click/doc/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
.. _mikroe_silent_step_2_click:

MikroElektronika Silent Step 2 Click
####################################

Overview
********

The MikroElektronika `Silent Step 2 Click`_ features the `ADI TMC2130`_ stepper driver with step-dir
control via gpio and configuration via spi. A `NXP PCA9538A`_ gpio expander accessed via i2c offers
access to additional pins of th stepper driver.

.. figure:: silent_step_2_click.webp
:align: center
:alt: MikroElektronika Silent Step 2 Click

MikroElektronika Silent Step 2 Click (Credit: MikroElektronika)

Requirements
************

This shield can only be used with a board that provides a mikroBUS
socket and defines a ``mikrobus_i2c`` node label for the mikroBUS I2C
interface, a ``mikrobus_spi`` node label for the mikroBUS SPI
interface and a ``mikrobus_header`` node label (see :ref:`shields` for more details).

Programming
***********

.. zephyr-app-commands::
:zephyr-app: samples/drivers/stepper/generic/
:board: <board>
:shield: mikroe_silent_step_2_click
:goals: build

.. _Silent Step 2 Click:
https://www.mikroe.com/silent-step-2-click

.. _ADI TMC2130:
https://www.analog.com/en/products/tmc2130.html

.. _NXP PCA9538A:
https://www.nxp.com/products/interfaces/ic-spi-i3c-interface-devices/general-purpose-i-o-gpio/low-voltage-8-bit-ic-bus-i-o-port-with-interrupt-and-reset:PCA9538A
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2025 Navimatix GmbH
* SPDX-License-Identifier: Apache-2.0
*/

&mikrobus_i2c {
status = "okay";
pca9538a_mikroe_silent_step_2_click: pca9538a@70 {
status = "okay";
compatible = "nxp,pca9538";

reg = <0x70>;

gpio-controller;
ngpios = <8>;
#gpio-cells = <2>;

gpio-reserved-ranges = <3 1>;
Copy link
Contributor

Choose a reason for hiding this comment

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

Setting gpio-reserved-ranges on nxp,pca9538 has no effect - the driver does not handle this.


gpio-line-names =
"EN",
"FT1",
"FT2";
};
};

&mikrobus_spi {
cs-gpios = <&mikrobus_header 2 GPIO_ACTIVE_LOW>;
status = "okay";
tmc2130_mikroe_silent_step_2_click: tmc2130@0 {
compatible = "adi,tmc2130";
reg = <0>;
spi-max-frequency = <DT_FREQ_M(1)>;

dir-gpios = <&mikrobus_header 0 0>;
step-gpios = <&mikrobus_header 6 0>;
en-gpios = <&pca9538a_mikroe_silent_step_2_click 0 GPIO_ACTIVE_LOW>;
irun = <20>;
ihold = <20>;
en-pwm-mode;
};
};
6 changes: 6 additions & 0 deletions boards/shields/mikroe_silent_step_2_click/shield.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
shield:
name: mikroe_silent_step_2_click
full_name: Silent Step 2 Click
vendor: mikroe
supported_features:
- stepper
1 change: 1 addition & 0 deletions drivers/stepper/adi_tmc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
zephyr_library()
zephyr_library_property(ALLOW_EMPTY TRUE)

zephyr_library_sources_ifdef(CONFIG_STEPPER_ADI_TMC2130 tmc2130.c)
zephyr_library_sources_ifdef(CONFIG_STEPPER_ADI_TMC2209 tmc22xx.c)
zephyr_library_sources_ifdef(CONFIG_STEPPER_ADI_TMC50XX tmc50xx.c)
add_subdirectory_ifdef(CONFIG_STEPPER_ADI_TMC51XX tmc51xx)
Expand Down
1 change: 1 addition & 0 deletions drivers/stepper/adi_tmc/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ config STEPPER_ADI_TMC_UART

comment "Trinamic Stepper Drivers"

rsource "Kconfig.tmc2130"
rsource "Kconfig.tmc22xx"
rsource "Kconfig.tmc50xx"
rsource "Kconfig.tmc51xx"
Expand Down
11 changes: 11 additions & 0 deletions drivers/stepper/adi_tmc/Kconfig.tmc2130
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 Navimatix GmbH
# SPDX-License-Identifier: Apache-2.0

config STEPPER_ADI_TMC2130
bool "Activate trinamic tmc2209 stepper driver"
depends on DT_HAS_ADI_TMC2130_ENABLED
select STEP_DIR_STEPPER
select STEPPER_ADI_TMC_SPI
default y
help
Stepper driver for TMC2130, using the step-dir interface with spi configuration.
243 changes: 243 additions & 0 deletions drivers/stepper/adi_tmc/tmc2130.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2025 Navimatix GmbH
* SPDX-License-Identifier: Apache-2.0
*/

#define DT_DRV_COMPAT adi_tmc2130

#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/drivers/stepper.h>
#include "../step_dir/step_dir_stepper_common.h"
#include "tmc2130_reg.h"
#include "adi_tmc_spi.h"

#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(tmc2130, CONFIG_STEPPER_LOG_LEVEL);

struct tmc2130_config {
struct step_dir_stepper_common_config common;
struct gpio_dt_spec en_pin;
struct spi_dt_spec spi;
bool stealth_chop_enabled;
Copy link
Member

@jilaypandya jilaypandya Jul 11, 2025

Choose a reason for hiding this comment

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

This is a gconf property. Please use the appropriate register name i.e. gconf. Follow the pattern as done in other trinamic drivers where this register gets configured during startup. Thanks.

uint32_t tpwmthrs;
uint8_t tpowerdown;
uint32_t ihold_irun;
enum stepper_micro_step_resolution default_ustep_res;
};

struct tmc2130_data {
struct step_dir_stepper_common_data common;
struct k_sem sem;
};

STEP_DIR_STEPPER_STRUCT_CHECK(struct tmc2130_config, struct tmc2130_data);

static int tmc2130_stepper_enable(const struct device *dev)
{
const struct tmc2130_config *config = dev->config;
int ret;

/* Check availability of the enable pin, as it might be hardwired. */
if (config->en_pin.port == NULL) {
LOG_WRN_ONCE("%s: Enable pin undefined.", dev->name);
return 0;
}

ret = gpio_pin_set_dt(&config->en_pin, 1);
if (ret != 0) {
LOG_ERR("%s: Failed to set en_pin (error: %d)", dev->name, ret);
}

return ret;
}

static int tmc2130_stepper_disable(const struct device *dev)
{
const struct tmc2130_config *config = dev->config;
int ret;

/* Check availability of the enable pin, as it might be hardwired. */
if (config->en_pin.port == NULL) {
LOG_WRN_ONCE("%s: Enable pin undefined.", dev->name);
return -ENOTSUP;
}

ret = gpio_pin_set_dt(&config->en_pin, 0);
if (ret != 0) {
LOG_ERR("%s: Failed to set en_pin (error: %d)", dev->name, ret);
}

return ret;
}

static int tmc2130_stepper_set_micro_step_res(const struct device *dev,
enum stepper_micro_step_resolution micro_step_res)
{
const struct tmc2130_config *config = dev->config;
struct tmc2130_data *data = dev->data;
int ret;
uint32_t reg_value;

k_sem_take(&data->sem, K_FOREVER);

ret = tmc_spi_read_register(&config->spi, TMC2130_ADDRESS_MASK, TMC2130_CHOPCONF,
&reg_value);
if (ret != 0) {
LOG_ERR("%s: Failed to read register 0x%x (error code: %d)", dev->name,
TMC2130_CHOPCONF, ret);
goto set_micro_step_res_end;
}
reg_value &= ~TMC2130_CHOPCONF_MRES_MASK;
reg_value |= ((MICRO_STEP_RES_INDEX(STEPPER_MICRO_STEP_256) - LOG2(micro_step_res))
<< TMC2130_CHOPCONF_MRES_SHIFT);

ret = tmc_spi_write_register(&config->spi, TMC2130_WRITE_BIT, TMC2130_CHOPCONF, reg_value);
if (ret != 0) {
LOG_ERR("%s: Failed to write register 0x%x (error code: %d)", dev->name,
TMC2130_CHOPCONF, ret);
}

set_micro_step_res_end:
k_sem_give(&data->sem);
Copy link
Member

Choose a reason for hiding this comment

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

Use a wrapper for bus communication as done in other trinamic drivers.


return ret;
}

static int tmc2130_stepper_get_micro_step_res(const struct device *dev,
enum stepper_micro_step_resolution *micro_step_res)
{
const struct tmc2130_config *config = dev->config;
struct tmc2130_data *data = dev->data;
uint32_t reg_value;
int err;

k_sem_take(&data->sem, K_FOREVER);

err = tmc_spi_read_register(&config->spi, TMC2130_ADDRESS_MASK, TMC2130_CHOPCONF,
&reg_value);
if (err != 0) {
return -EIO;
}

k_sem_give(&data->sem);

reg_value &= TMC2130_CHOPCONF_MRES_MASK;
reg_value >>= TMC2130_CHOPCONF_MRES_SHIFT;
*micro_step_res = (1 << (MICRO_STEP_RES_INDEX(STEPPER_MICRO_STEP_256) - reg_value));

return 0;
}

static int tmc2130_stepper_init(const struct device *dev)
{
const struct tmc2130_config *config = dev->config;
struct tmc2130_data *data = dev->data;
uint32_t gstat_data;
int ret;

uint8_t ustep_res_reg_value =
(MICRO_STEP_RES_INDEX(STEPPER_MICRO_STEP_256) - LOG2(config->default_ustep_res));

/* Read GSTAT register to clear any errors. */
if (!spi_is_ready_dt(&config->spi)) {
LOG_ERR("SPI bus is not ready");
return -ENODEV;
}
tmc_spi_read_register(&config->spi, TMC2130_ADDRESS_MASK, TMC2130_GSTAT, &gstat_data);
LOG_DBG("GSTAT: %x", gstat_data);

/* Configuration registers and their intended values. */
uint32_t reg_combined[][2] = {
{TMC2130_CHOPCONF,
TMC2130_CHOPCONF_INIT(ustep_res_reg_value, config->common.dual_edge)},
{TMC2130_IHOLD_IRUN, config->ihold_irun},
{TMC2130_TPOWERDOWN, TMC2130_TPOWERDOWN_INIT(config->tpowerdown)},
{TMC2130_GCONF, TMC2130_GCONF_INIT(config->stealth_chop_enabled)},
{TMC2130_TPWMTHRS, TMC2130_TPWMTHRS_INIT(config->tpwmthrs)},
{TMC2130_PWMCONF, TMC2130_PWMCONF_INIT}};

/*Write configuration. */
for (int i = 0; i < ARRAY_SIZE(reg_combined); i++) {
ret = tmc_spi_write_register(&config->spi, TMC2130_WRITE_BIT, reg_combined[i][0],
reg_combined[i][1]);
if (ret != 0) {
LOG_ERR("%s: Failed to write register 0x%x (error code: %d)", dev->name,
reg_combined[i][0], ret);
return ret;
}
}

/* Configure enable pin if it is available */
if (config->en_pin.port != NULL) {
ret = gpio_pin_configure_dt(&config->en_pin, GPIO_OUTPUT_INACTIVE);
if (ret != 0) {
LOG_ERR("%s: Failed to configure en_pin (error: %d)", dev->name, ret);
return ret;
}
}

k_sem_init(&data->sem, 1, 1);

ret = step_dir_stepper_common_init(dev);
if (ret != 0) {
LOG_ERR("%s: Failed to initialize common step direction stepper (error: %d)",
dev->name, ret);
return ret;
}

return 0;
}

static DEVICE_API(stepper, tmc2130_stepper_api) = {
.enable = tmc2130_stepper_enable,
.disable = tmc2130_stepper_disable,
.move_by = step_dir_stepper_common_move_by,
.is_moving = step_dir_stepper_common_is_moving,
.set_reference_position = step_dir_stepper_common_set_reference_position,
.get_actual_position = step_dir_stepper_common_get_actual_position,
.move_to = step_dir_stepper_common_move_to,
.set_microstep_interval = step_dir_stepper_common_set_microstep_interval,
.run = step_dir_stepper_common_run,
.stop = step_dir_stepper_common_stop,
.set_event_callback = step_dir_stepper_common_set_event_callback,
.set_micro_step_res = tmc2130_stepper_set_micro_step_res,
.get_micro_step_res = tmc2130_stepper_get_micro_step_res,
};

#define TMC2130_CHECK_CONFIGURATION(inst) \
BUILD_ASSERT(DT_INST_PROP(inst, tpwmthrs) <= TMC2130_TPWMTHRS_MAX_VALUE, \
"tpwthrs is too large"); \
BUILD_ASSERT(DT_INST_PROP(inst, iholddelay) <= TMC2130_IHOLDDELAY_MAX_VALUE, \
"iholddelay is too large"); \
BUILD_ASSERT(DT_INST_PROP(inst, tpowerdown) <= TMC2130_TPOWERDOWN_MAX_VALUE, \
"tpowerdown is too large"); \
BUILD_ASSERT(DT_INST_PROP(inst, ihold) <= TMC2130_IHOLD_MAX_VALUE, "ihold is too large"); \
BUILD_ASSERT(DT_INST_PROP(inst, irun) <= TMC2130_IRUN_MAX_VALUE, "irun is too large");

#define TMC2130_STEPPER_DEVICE(inst) \
TMC2130_CHECK_CONFIGURATION(inst); \
static const struct tmc2130_config tmc2130_config_##inst = { \
.common = STEP_DIR_STEPPER_DT_INST_COMMON_CONFIG_INIT(inst), \
.en_pin = GPIO_DT_SPEC_INST_GET_OR(inst, en_gpios, {0}), \
.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), \
.tpwmthrs = DT_INST_PROP(inst, tpwmthrs), \
.tpowerdown = DT_INST_PROP(inst, tpowerdown), \
.stealth_chop_enabled = DT_INST_PROP(inst, en_pwm_mode), \
.default_ustep_res = DT_INST_PROP(inst, micro_step_res), \
.ihold_irun = TMC2130_IHOLD_IRUN_INIT(DT_INST_PROP(inst, iholddelay), \
DT_INST_PROP(inst, irun), \
DT_INST_PROP(inst, ihold)), \
}; \
static struct tmc2130_data tmc2130_data_##inst = { \
.common = STEP_DIR_STEPPER_DT_INST_COMMON_DATA_INIT(inst), \
}; \
DEVICE_DT_INST_DEFINE(inst, tmc2130_stepper_init, NULL, &tmc2130_data_##inst, \
&tmc2130_config_##inst, POST_KERNEL, CONFIG_STEPPER_INIT_PRIORITY, \
&tmc2130_stepper_api);

DT_INST_FOREACH_STATUS_OKAY(TMC2130_STEPPER_DEVICE)
Loading