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

Conversation

jbehrensnx
Copy link
Contributor

This PR adds a driver for the ADI TMC2130 stepper driver. The driver only supports the step-dir-spi interface (simply called step-dir in official documentation) of the TMC2130. The pure gpio (called standalone, also a step-dir interface) and pure spi interfaces are not supported.

I moved some devicetree bindings from adi,trinamic-ramp-generator.yaml to adi,trinamic-common.yaml as they are unrelated to ramp generation and used by the TMC2130 driver.

The driver tested using the shell as well as the drv84xx/api test suite.

#define IHOLDDELAY_MAX_VALUE 15

/* Initial register values derived from the "Getting Started" Chapter of the TMC2130 datasheet*/
#define TMC2130_CHOPCONF_INIT \
Copy link
Member

@jilaypandya jilaypandya May 27, 2025

Choose a reason for hiding this comment

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

you would probably want to offer a dedicated function for configuring CHOPCONF/PWMCONF and so on. I think this is coming from the initialization example from tmc datasheet.

Better would be to allow the end user to do it from application code by providing a function.

tmc2130_controller_read/write(dev,reg,value) , you could put all the register definitions in a file and then the user could set this up

For tmc50xx i know that settting up the ramp is bare minimum to get the steppers running. Hence in the default driver instantiation, i only introduced the bare minimum stuff to run the stepper.

Suggested change
#define TMC2130_CHOPCONF_INIT \
#define TMC2130_CHOPCONF_INIT 0x000100C3

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought about about also offering the the register function to the user, through we might want to think about making it a general function for all tmc drivers, sort of like tmc_spi_write_register is already. But that specific idea would be beyond the scope of this PR.

That said, I dont see an issue with the default value. I need to set something, as zeroing the register with exception of ms-resolution, will disable the driver. So I choose values that are recommended by ADI as reasonable starting values. Everybody who needs finer control, for this or other registers would need to use their own spi calls at the moment - the same is true with the tmc50xx and tmc51xx drivers as well.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah in terms of tmc50xx driver the bare minimum required to run the driver in ramp_gen register which i added to the dt-bindings.

In the example in dt-bindings you can document the default init values as mentioned in the datasheet. But hardcoding a INIT value over here is not a good practice. An end user has no control as of now on these parameters.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Did some tests and managed to strip each of the two down to one parameter each, which is the minimum to safely run the driver.

struct tmc2130_spi_sd_data *data = dev->data;
int ret;

K_SPINLOCK((struct k_spinlock *)&data->common.lock) {
Copy link
Member

Choose a reason for hiding this comment

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

you can also enable/disable via spi, or? Not a must for this PR, though, would be nice if you could give a look into it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I am aware of it, but there is a little bit more to it and it is not a priority for me during this PR.

K_SPINLOCK_BREAK;
}
if (!config->common.dual_edge) {
ret = gpio_pin_set_dt(&config->common.step_pin, 0);
Copy link
Member

Choose a reason for hiding this comment

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

is this step required? Disabling the driver should turn of all the coils, or?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From what I remember this came up in other driver discussions. At the moment this is not needed, but as soon as the step signal has 50% duty cycle, the driver might stop with step signal high, this is to fix that, through there is an argument to be made, that the step-dir implementation should ensure that it does not stop on a step signal high with dual_edge disabled.

struct tmc2130_spi_sd_data *data = dev->data;

if (!data->enabled) {
LOG_ERR("Failed to move by delta, device is not enabled");
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
LOG_ERR("Failed to move by delta, device is not enabled");
LOG_ERR("Failed to move by %d, device is not enabled", steps);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

drv84xx and a4979 also use this message + the step amount is irrelevant as it is an enable/disable question. Also see the message for move_to.

@jbehrensnx jbehrensnx force-pushed the tmc2130-integration branch from 736deba to 64e0536 Compare June 3, 2025 14:18
@jbehrensnx
Copy link
Contributor Author

That should be the majority of things outside of large scale renaming and splitting the reg.h file. That + plus some other changes to file locations will require a rebase if I am not mistaken.

@jbehrensnx jbehrensnx force-pushed the tmc2130-integration branch from 64e0536 to 5be76a7 Compare June 16, 2025 09:08

/* Check availability of the enable pin, as it might be hardwired. */
if (config->en_pin.port == NULL) {
LOG_ERR("%s: Failed to disable device, enable pin is not "
Copy link
Member

Choose a reason for hiding this comment

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

could you shorten this message?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would prefer not to, as everything in the message is relevant. Also, drv84xx uses the same message.

Copy link
Member

Choose a reason for hiding this comment

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

These messages get stored in Flash and probably get used only during development, you can either shorten them or change this LOG_DBG.

Reference:
#86620 (comment)

Copy link
Member

@jilaypandya jilaypandya Jun 18, 2025

Choose a reason for hiding this comment

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

Hey, I see that you have changed to LOG_WRN_ONCE, but the reasoning behind using LOG_DBG or shorter messages has been correctly pointed out in the comment that i have referenced earlier. If you want to keep the entire log message, then switch is LOG_DBG and then release builds atleast in my experience, set the loggging level to LOG_INFO and hence this message won't take up flash anymore

{
struct tmc2130_data *data = dev->data;

if (!data->enabled) {
Copy link
Member

@dipakgmx dipakgmx Jun 16, 2025

Choose a reason for hiding this comment

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

You use a spinlock when setting the enabled flag, whereas when reading, theres no lock. Any reason to not use a spinlock in this case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That would, I think, require putting the entire function in a spinlock, which is definitely an option, as otherwise you could change the enable status after the check but while the initial movement function is still running (I hope that made sense).
That raises the interesting question, what the intended behavior of of the api is in that instance.

uint32_t reg_combined[][2] = {
{TMC2130_CHOPCONF, TMC2130_CHOPCONF_INIT + (ustep_res_reg_value << 24)},
{TMC2130_IHOLD_IRUN,
0x00000000 + (config->iholddelay << 16) + (config->irun << 8) + config->ihold},
Copy link
Member

Choose a reason for hiding this comment

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

Why use magic numbers being used for the shifts here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using defined constants is definitely an option. The downside is that these lines become even longer and thus potentially less readable.

Copy link
Member

@jilaypandya jilaypandya Jun 18, 2025

Choose a reason for hiding this comment

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

here you go, we are already doing it in tmc5xxx drivers, probably you can copy paste the same pattern in tmc2130_reg.h

#define TMC5XXX_IHOLDDELAY(n) (((n) << TMC5XXX_IHOLDDELAY_SHIFT) & TMC5XXX_IHOLDDELAY_MASK)

#define TMC5XXX_IHOLD_MASK  GENMASK(4, 0)
#define TMC5XXX_IHOLD_SHIFT 0
#define TMC5XXX_IHOLD(n)    (((n) << TMC5XXX_IHOLD_SHIFT) & TMC5XXX_IHOLD_MASK)

#define TMC5XXX_IRUN_MASK  GENMASK(12, 8)
#define TMC5XXX_IRUN_SHIFT 8
#define TMC5XXX_IRUN(n)    (((n) << TMC5XXX_IRUN_SHIFT) & TMC5XXX_IRUN_MASK)

#define TMC5XXX_IHOLDDELAY_MASK  GENMASK(19, 16)
#define TMC5XXX_IHOLDDELAY_SHIFT 16
#define TMC5XXX_IHOLDDELAY(n)    (((n) << TMC5XXX_IHOLDDELAY_SHIFT) & TMC5XXX_IHOLDDELAY_MASK)

.iholdrun = (TMC5XXX_IRUN(DT_PROP(node, irun)) | \

Copy link
Member

Choose a reason for hiding this comment

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

The downside is that these lines become even longer and thus potentially less readable.

{TMC2130_CHOPCONF, TMC2130_CHOPCONF_INIT + (ustep_res_reg_value << 24)}, {TMC2130_IHOLD_IRUN, 0x00000000 + (config->iholddelay << 16) + (config->irun << 8) + config->ihold}

How is this readable

@jbehrensnx jbehrensnx force-pushed the tmc2130-integration branch from 5be76a7 to d9b2575 Compare June 18, 2025 11:06
@jbehrensnx
Copy link
Contributor Author

I tried to rebase onto latest main locally to fix the compliance issues, but that didn't help. As they all occur in files I didn't touch, I will wait on this.

uint32_t reg_combined[][2] = {
{TMC2130_CHOPCONF, TMC2130_CHOPCONF_INIT + (ustep_res_reg_value << 24)},
{TMC2130_IHOLD_IRUN,
0x00000000 + (config->iholddelay << 16) + (config->irun << 8) + config->ihold},
Copy link
Member

@jilaypandya jilaypandya Jun 18, 2025

Choose a reason for hiding this comment

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

here you go, we are already doing it in tmc5xxx drivers, probably you can copy paste the same pattern in tmc2130_reg.h

#define TMC5XXX_IHOLDDELAY(n) (((n) << TMC5XXX_IHOLDDELAY_SHIFT) & TMC5XXX_IHOLDDELAY_MASK)

#define TMC5XXX_IHOLD_MASK  GENMASK(4, 0)
#define TMC5XXX_IHOLD_SHIFT 0
#define TMC5XXX_IHOLD(n)    (((n) << TMC5XXX_IHOLD_SHIFT) & TMC5XXX_IHOLD_MASK)

#define TMC5XXX_IRUN_MASK  GENMASK(12, 8)
#define TMC5XXX_IRUN_SHIFT 8
#define TMC5XXX_IRUN(n)    (((n) << TMC5XXX_IRUN_SHIFT) & TMC5XXX_IRUN_MASK)

#define TMC5XXX_IHOLDDELAY_MASK  GENMASK(19, 16)
#define TMC5XXX_IHOLDDELAY_SHIFT 16
#define TMC5XXX_IHOLDDELAY(n)    (((n) << TMC5XXX_IHOLDDELAY_SHIFT) & TMC5XXX_IHOLDDELAY_MASK)

.iholdrun = (TMC5XXX_IRUN(DT_PROP(node, irun)) | \

#define IHOLDDELAY_MAX_VALUE 15

/* Initial register values derived from the "Getting Started" Chapter of the TMC2130 datasheet*/
#define TMC2130_CHOPCONF_INIT \
Copy link
Member

Choose a reason for hiding this comment

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

Yeah in terms of tmc50xx driver the bare minimum required to run the driver in ramp_gen register which i added to the dt-bindings.

In the example in dt-bindings you can document the default init values as mentioned in the datasheet. But hardcoding a INIT value over here is not a good practice. An end user has no control as of now on these parameters.


/* Check availability of the enable pin, as it might be hardwired. */
if (config->en_pin.port == NULL) {
LOG_ERR("%s: Failed to disable device, enable pin is not "
Copy link
Member

@jilaypandya jilaypandya Jun 18, 2025

Choose a reason for hiding this comment

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

Hey, I see that you have changed to LOG_WRN_ONCE, but the reasoning behind using LOG_DBG or shorter messages has been correctly pointed out in the comment that i have referenced earlier. If you want to keep the entire log message, then switch is LOG_DBG and then release builds atleast in my experience, set the loggging level to LOG_INFO and hence this message won't take up flash anymore

@jbehrensnx jbehrensnx force-pushed the tmc2130-integration branch 2 times, most recently from 1808170 to c130d61 Compare June 20, 2025 08:35
@jbehrensnx jbehrensnx requested a review from dipakgmx June 20, 2025 08:36
Copy link
Member

@jilaypandya jilaypandya left a comment

Choose a reason for hiding this comment

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

Hey, overall I think it's going In a good direction.

K_SPINLOCK_BREAK;
}

data->ustep_res = micro_step_res;
Copy link
Member

Choose a reason for hiding this comment

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

3 questions:

  1. Why are you saving this when you are saving this value in the IC register
  2. The whole spinlock is necessary to lock your step-dir data and you seem to use the same lock over the spi transactions
  3. If you still intend to use this information, why aren't you using it when querying the microsteps?

Could you please follow the patterns being followed in the tmc5xxx series.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will switch, in the next update, to the 5xxx approach of storing that in config as the default ustep_res and no longer update it in set_microstep_resolution.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed data->ustep_res

Copy link
Member

Choose a reason for hiding this comment

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

Once again, could you please follow the pattern being followed in the tmc5xxx series. Why are using a spinlock on IO operations? You are writing 2 SPI registers with the spinlock held on. Are you certain you want to disable all interrupts until your SPI transactions are complete?

Copy link
Member

Choose a reason for hiding this comment

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

Am un-resolving this comment here, since this does not follow the tmc5xxx pattern here.
Please do not resolve comments which are not resolved.

Moved some dt properties for tmc drivers from ramp-generator to common
as they are also used by non-ramp drivers.

Signed-off-by: Jan Behrens <jan.behrens@navimatix.de>
@jbehrensnx jbehrensnx force-pushed the tmc2130-integration branch from c130d61 to a7a0b7d Compare June 30, 2025 13:51
Added a driver for the tmc2130 stepper driver. The driver is only for the
tmc2130s step-dir interface in combination with spi-based configuration.
The other control interfaces of the tmc2130 are not supported.

Signed-off-by: Jan Behrens <jan.behrens@navimatix.de>
Adds the tmc2130 stepper driver to the build_all test.

Signed-off-by: Jan Behrens <jan.behrens@navimatix.de>
The Silent Step 2 Click shield offers a ADI TMC3130 stepper driver.
Some pins are accessed via a NXP PCA9538A gpio expander.

Signed-off-by: Jan Behrens <jan.behrens@navimatix.de>
@jbehrensnx jbehrensnx force-pushed the tmc2130-integration branch from 5db2f2c to c41b3de Compare July 7, 2025 06:56
Copy link

sonarqubecloud bot commented Jul 7, 2025

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.

}

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.

Copy link
Member

@jilaypandya jilaypandya left a comment

Choose a reason for hiding this comment

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

Dropping some comments about how the Inital Register Values are getting configured.

#define TMC2130_CHOPCONF_MRES_SHIFT 24
#define TMC2130_CHOPCONF_DOUBLE_EDGE_SHIFT 29

#define TMC2130_TPWMTHRS_MAX_VALUE ((1 << 20) - 1)
Copy link
Member

Choose a reason for hiding this comment

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

https://docs.zephyrproject.org/latest/doxygen/html/group__sys-util.html#ga3c12c6d36ad0aa481a3436923d21f4f8

Suggested change
#define TMC2130_TPWMTHRS_MAX_VALUE ((1 << 20) - 1)
#define TMC2130_TPWMTHRS_MAX_VALUE BITMASK(20)

/* Initial register values */
#define TMC2130_GCONF_INIT(stealth_chop) \
(0x00000000 + (stealth_chop << TMC2130_GCONF_STEALTH_CHOP_SHIFT))
#define TMC2130_IHOLD_IRUN_INIT(iholddelay, irun, ihold) \
Copy link
Member

@jilaypandya jilaypandya Jul 12, 2025

Choose a reason for hiding this comment

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

You can pass the node in the macro and compose the entire register from DT_PROPs as we are doing in tmc5xxx drivers. I think down the line we could even generalize some of the these macros for all trinamic drivers, reducing code duplication :)

Suggested change
#define TMC2130_IHOLD_IRUN_INIT(iholddelay, irun, ihold) \
#define TMC2130_IHOLD_IRUN(node) \

#define TMC_RAMP_DT_SPEC_GET_COMMON(node) \

.iholdrun = (TMC5XXX_IRUN(DT_PROP(node, irun)) | \

#define TMC2130_IHOLD_IRUN_INIT(iholddelay, irun, ihold) \
(0x00000000 + (iholddelay << TMC2130_IHOLDDELAY_SHIFT) + \
((irun << TMC2130_IRUN_SHIFT) & TMC2130_IRUN_MASK) + (ihold & TMC2130_IHOLD_MASK))
#define TMC2130_TPOWERDOWN_INIT(tpowerdown) (0x00000000 + tpowerdown)
Copy link
Member

Choose a reason for hiding this comment

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

Is this macro really required?

Ain't (0x00000000 + tpowerdown) effectively the same tpowerdown?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

tpowerdown on its own should be enough, but I used a macro to keep the array in the init function consistent.
At least, that was the idea I think, but looking at the code again, some of the other registers also don't use a macro, so I will drop the macro and just use the value, especially, as this register doesn't store multiple properties.

((irun << TMC2130_IRUN_SHIFT) & TMC2130_IRUN_MASK) + (ihold & TMC2130_IHOLD_MASK))
#define TMC2130_TPOWERDOWN_INIT(tpowerdown) (0x00000000 + tpowerdown)
#define TMC2130_TPWMTHRS_INIT(tpwmthrs) (0x00000000 + tpwmthrs)
#define TMC2130_CHOPCONF_INIT(ustep_res, double_edge) \
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
#define TMC2130_CHOPCONF_INIT(ustep_res, double_edge) \
#define TMC_CHOPCONF_DT_SPEC_GET_TMC2130(node) \

#define TMC_RAMP_DT_SPEC_GET_TMC51XX(node) \

#define TMC2130_CHOPCONF_INIT(ustep_res, double_edge) \
(0x00000002 + (double_edge << TMC2130_CHOPCONF_DOUBLE_EDGE_SHIFT) + \
((ustep_res << TMC2130_CHOPCONF_MRES_SHIFT) & TMC2130_CHOPCONF_MRES_MASK)) /* TOFF=3 */
#define TMC2130_PWMCONF_INIT 0x00040000 /* pwm_autoscale=1 */
Copy link
Member

Choose a reason for hiding this comment

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

We should not hardcode registers like this.

What i would do is basically compose this register using appropriaate properties as mentioned in the datasheet. We follow this pattern #define TMC_RAMP_DT_SPEC_GET_TMC50XX(node) for configuring ramps. You could do something similiar here as well, like #define TMC_PWMCONF_DT_SPEC_GET_TMC2130(node) or even TMC2130_PWMCONF_DT_SPEC_GET(node), so that we have a coherent way of configuring these trinamic register :)

For reference:
https://github.com/analogdevicesinc/TMC-API/blob/master/tmc/ic/TMC2130/TMC2130_HW_Abstraction.h

#define TMC2130_TPOWERDOWN_INIT(tpowerdown) (0x00000000 + tpowerdown)
#define TMC2130_TPWMTHRS_INIT(tpwmthrs) (0x00000000 + tpwmthrs)
#define TMC2130_CHOPCONF_INIT(ustep_res, double_edge) \
(0x00000002 + (double_edge << TMC2130_CHOPCONF_DOUBLE_EDGE_SHIFT) + \
Copy link
Member

Choose a reason for hiding this comment

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

What is 0x00000002 for? Are you configuring toff1?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

TOFF=2 as that is the smallest valid toff value for running the driver. The comment for toff=3 at the line end is outdated.

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.

Copy link
Member

@dipakgmx dipakgmx left a comment

Choose a reason for hiding this comment

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

Please follow the tmc5xxx bus wrapper logic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: Shields Shields (add-on boards) area: Stepper platform: ADI Analog Devices, Inc.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants