Skip to content

Add UART support for TMC51xx series #90687

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

Merged
merged 6 commits into from
May 30, 2025
Merged
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: 3 additions & 2 deletions drivers/stepper/adi_tmc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
zephyr_library()
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 tmc22xx.c)
zephyr_library_sources_ifdef(CONFIG_STEPPER_ADI_TMC50XX tmc50xx.c)
zephyr_library_sources_ifdef(CONFIG_STEPPER_ADI_TMC51XX tmc51xx.c)
add_subdirectory_ifdef(CONFIG_STEPPER_ADI_TMC51XX tmc51xx)

add_subdirectory_ifdef(CONFIG_STEPPER_ADI_TMC bus)
7 changes: 7 additions & 0 deletions drivers/stepper/adi_tmc/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ config STEPPER_ADI_TMC_SPI
help
A Trinamic Stepper Controller with SPI is enabled

config STEPPER_ADI_TMC_UART
bool "Use Trinamic Stepper Controller with single wire UART"
depends on STEPPER_ADI_TMC
select UART
help
A Trinamic Stepper Controller with single wire UART is enabled

comment "Trinamic Stepper Drivers"

rsource "Kconfig.tmc22xx"
Expand Down
3 changes: 2 additions & 1 deletion drivers/stepper/adi_tmc/Kconfig.tmc51xx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
config STEPPER_ADI_TMC51XX
bool "Activate trinamic tmc51xx stepper driver"
depends on DT_HAS_ADI_TMC51XX_ENABLED && STEPPER_ADI_TMC
select STEPPER_ADI_TMC_SPI
select STEPPER_ADI_TMC_UART if $(dt_compat_on_bus,$(DT_COMPAT_ADI_TMC51XX),uart)
select STEPPER_ADI_TMC_SPI if $(dt_compat_on_bus,$(DT_COMPAT_ADI_TMC51XX),spi)
default y

module = TMC51XX
Expand Down
10 changes: 8 additions & 2 deletions drivers/stepper/adi_tmc/adi_tmc_reg.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* @file drivers/stepper/adi/tmc_reg.h
* @file drivers/stepper/adi_tmc/adi_tmc_reg.h
*
* @brief TMC Registers
*
Expand Down Expand Up @@ -56,13 +56,19 @@ extern "C" {
#define TMC5XXX_CHOPCONF_MRES_MASK GENMASK(27, 24)
#define TMC5XXX_CHOPCONF_MRES_SHIFT 24

#define TMC5XXX_RAMPSTAT_INT_MASK GENMASK(7, 4)
#define TMC5XXX_RAMPSTAT_INT_MASK GENMASK(9, 4)
#define TMC5XXX_RAMPSTAT_INT_SHIFT 4

#define TMC5XXX_RAMPSTAT_POS_REACHED_MASK BIT(9)
#define TMC5XXX_POS_REACHED \
(TMC5XXX_RAMPSTAT_POS_REACHED_MASK >> TMC5XXX_RAMPSTAT_INT_SHIFT)

#define TMC5XXX_RAMPSTAT_POS_REACHED_EVENT_MASK BIT(7)
#define TMC5XXX_POS_REACHED_EVENT \
(TMC5XXX_RAMPSTAT_POS_REACHED_EVENT_MASK >> TMC5XXX_RAMPSTAT_INT_SHIFT)

#define TMC5XXX_POS_REACHED_AND_EVENT (TMC5XXX_POS_REACHED | TMC5XXX_POS_REACHED_EVENT)

#define TMC5XXX_RAMPSTAT_STOP_SG_EVENT_MASK BIT(6)
#define TMC5XXX_STOP_SG_EVENT \
(TMC5XXX_RAMPSTAT_STOP_SG_EVENT_MASK >> TMC5XXX_RAMPSTAT_INT_SHIFT)
Expand Down
7 changes: 7 additions & 0 deletions drivers/stepper/adi_tmc/bus/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 Dipak Shetty
# SPDX-License-Identifier: Apache-2.0

zephyr_library_include_directories(include)

zephyr_library_sources_ifdef(CONFIG_STEPPER_ADI_TMC_SPI adi_tmc_spi.c)
zephyr_library_sources_ifdef(CONFIG_STEPPER_ADI_TMC_UART adi_tmc_uart.c)
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

#include <zephyr/sys/util.h>
#include "adi_tmc_spi.h"
#include <adi_tmc_spi.h>

#define BUFFER_SIZE 5U

Expand Down
163 changes: 163 additions & 0 deletions drivers/stepper/adi_tmc/bus/adi_tmc_uart.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2025 Dipak Shetty
*
* SPDX-License-Identifier: Apache-2.0
*/

#include <zephyr/sys/util.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/byteorder.h>
#include <adi_tmc_uart.h>

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

/* TMC UART standard datagram size */
#define ADI_TMC_UART_DATAGRAM_SIZE 8
/* TMC UART read request datagram size */
#define ADI_TMC_UART_READ_REQ_DATAGRAM_SIZE 4
/* TMC UART protocol constants */
#define ADI_TMC_UART_SYNC_BYTE 0x05
#define ADI_TMC_UART_WRITE_BIT 0x80

uint8_t tmc_uart_calc_crc(const uint8_t *datagram, uint8_t len)
{
uint8_t crc = 0;

for (uint8_t i = 0; i < len; i++) {
uint8_t current_byte = datagram[i];

for (uint8_t j = 0; j < 8; j++) {
if ((crc >> 7) ^ (current_byte & 0x01)) {
crc = (crc << 1) ^ 0x07;
} else {
crc = (crc << 1) & 0xFF;
}
current_byte = current_byte >> 1;
}
}

return crc;
}

static int tmc_uart_send_byte_with_echo(const struct device *uart, uint8_t byte)
{
uint8_t echo_byte;
int err;

uart_poll_out(uart, byte);

/* Wait for echo with timeout */
k_timepoint_t end = sys_timepoint_calc(K_MSEC(5)); /* 5ms timeout for echo */

do {
err = uart_poll_in(uart, &echo_byte);
if (err >= 0 && echo_byte == byte) {
/* Received matching echo */
return 0;
}
} while (err == -1 && !sys_timepoint_expired(end));

LOG_ERR("Echo mismatch or timeout: sent 0x%02X", byte);
return -EIO;
}

int tmc_uart_write_register(const struct device *uart, uint8_t device_addr,
uint8_t register_address, uint32_t data)
{
uint8_t buffer[ADI_TMC_UART_DATAGRAM_SIZE];
int err = 0;

/* Format the UART TMC datagram */
buffer[0] = ADI_TMC_UART_SYNC_BYTE; /* Sync byte */
buffer[1] = device_addr; /* Device address */
buffer[2] = register_address | ADI_TMC_UART_WRITE_BIT; /* Register address with write bit */
sys_put_be32(data, &buffer[3]); /* Write data */
buffer[7] = tmc_uart_calc_crc(buffer, ADI_TMC_UART_DATAGRAM_SIZE - 1U); /* CRC */

/* Send datagram byte by byte using polling */
for (size_t i = 0; i < ADI_TMC_UART_DATAGRAM_SIZE; i++) {
err = tmc_uart_send_byte_with_echo(uart, buffer[i]);
if (err) {
LOG_ERR("Failed to send byte %d: 0x%02X", i, buffer[i]);
return err;
}
}

return 0;
}

int tmc_uart_read_register(const struct device *uart, uint8_t device_addr, uint8_t register_address,
uint32_t *data)
{
uint8_t write_buffer[ADI_TMC_UART_READ_REQ_DATAGRAM_SIZE];
uint8_t read_buffer[ADI_TMC_UART_DATAGRAM_SIZE];
struct uart_config uart_cfg;

int err = 0;

/* Get current UART configuration */
err = uart_config_get(uart, &uart_cfg);
if (err) {
LOG_ERR("Failed to get UART configuration: %d", err);
return -EIO;
}

/* Calculate delay based on UART baudrate (SENDDELAY: default=8 bit times))*/
uint32_t delay_us = (8 * 1000000) / uart_cfg.baudrate;

/* Format the UART TMC datagram for read */
write_buffer[0] = ADI_TMC_UART_SYNC_BYTE; /* Sync byte */
write_buffer[1] = device_addr; /* Device address */
write_buffer[2] = register_address; /* Register address */
write_buffer[3] = tmc_uart_calc_crc(write_buffer, 3);

/* Send read request byte by byte and wait for each echo */
for (size_t i = 0; i < ADI_TMC_UART_READ_REQ_DATAGRAM_SIZE; i++) {
err = tmc_uart_send_byte_with_echo(uart, write_buffer[i]);
if (err) {
LOG_ERR("Failed to send byte %d: 0x%02X", i, write_buffer[i]);
return -EIO;
}
}

/* Small delay to allow device to prepare response */
k_busy_wait(delay_us);

/* Receive response with timeout */
for (size_t i = 0; i < sizeof(read_buffer) && (err == 0); i++) {
k_timepoint_t end = sys_timepoint_calc(K_MSEC(1000));

do {
err = uart_poll_in(uart, &read_buffer[i]);
} while (err == -1 && !sys_timepoint_expired(end));

if (err == -1) {
LOG_ERR("Timeout waiting for byte %d for register 0x%x", i,
register_address);
return -EAGAIN;
}
if (err < 0) {
LOG_ERR("Error %d receiving byte %d for register 0x%x", err, i,
register_address);
return -EIO;
}
}

/* Print the received bytes */
LOG_HEXDUMP_DBG(read_buffer, ADI_TMC_UART_DATAGRAM_SIZE, "Received bytes:");

/* Validate CRC */
uint8_t crc = tmc_uart_calc_crc(read_buffer, ADI_TMC_UART_DATAGRAM_SIZE - 1U);

if (crc != read_buffer[7]) {
LOG_ERR("CRC mismatch for register 0x%x: got 0x%x, expected 0x%x", register_address,
read_buffer[7], crc);
return -EIO;
}

/* Construct a 32-bit register value from received bytes */
*data = sys_get_be32(&read_buffer[3]);

return 0;
}
59 changes: 59 additions & 0 deletions drivers/stepper/adi_tmc/bus/include/adi_tmc_bus.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2025 Dipak Shetty
*
* SPDX-License-Identifier: Apache-2.0
*/

#ifndef ZEPHYR_DRIVERS_STEPPER_ADI_TMC_BUS_H_
#define ZEPHYR_DRIVERS_STEPPER_ADI_TMC_BUS_H_

#include <zephyr/types.h>
#include <zephyr/device.h>
#include <zephyr/drivers/spi.h>

/* Communication interface types */
#define TMC_COMM_SPI 0
#define TMC_COMM_UART 1

union tmc_bus {
struct spi_dt_spec spi;
const struct device *uart;
};

/**
* @brief Function pointer type for bus check function.
* @param bus Pointer to the bus structure.
* @param comm_type Communication type (SPI or UART).
* @return 0 if bus is ready, negative error code otherwise.
*/
typedef int (*tmc_bus_check_fn)(const union tmc_bus *bus, uint8_t comm_type);

/**
* @brief Function pointer type for register read function.
* @param dev Pointer to the device structure.
* @param reg Register address to read.
* @param val Pointer to store the read value.
* @return 0 on success, negative error code otherwise.
*/
typedef int (*tmc_reg_read_fn)(const struct device *dev, uint8_t reg, uint32_t *val);

/**
* @brief Function pointer type for register write function.
* @param dev Pointer to the device structure.
* @param reg Register address to write.
* @param val Value to write to the register.
* @return 0 on success, negative error code otherwise.
*/
typedef int (*tmc_reg_write_fn)(const struct device *dev, uint8_t reg, uint32_t val);

/**
* @brief Structure for bus I/O operations.
* Contains function pointers for bus check, register read, and register write.
*/
struct tmc_bus_io {
tmc_bus_check_fn check; /* Function to check if the bus is ready */
tmc_reg_read_fn read; /* Function to read a register */
tmc_reg_write_fn write; /* Function to write to a register */
};

#endif /* ZEPHYR_DRIVERS_STEPPER_ADI_TMC_BUS_H_ */
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* @file drivers/stepper/adi/stepper.h
* @file drivers/stepper/adi_tmc/bus/include/adi_tmc_spi.h
*
* @brief Private API for Trinamic SPI bus
*
Expand All @@ -10,8 +10,8 @@
* SPDX-License-Identifier: Apache-2.0
*/

#ifndef ZEPHYR_DRIVERS_STEPPER_ADI_TMC_SPI_H_
#define ZEPHYR_DRIVERS_STEPPER_ADI_TMC_SPI_H_
#ifndef ZEPHYR_DRIVERS_STEPPER_ADI_TMC_BUS_SPI_H_
#define ZEPHYR_DRIVERS_STEPPER_ADI_TMC_BUS_SPI_H_

#ifdef __cplusplus
extern "C" {
Expand Down Expand Up @@ -60,4 +60,4 @@ int tmc_spi_write_register(const struct spi_dt_spec *bus, const uint8_t write_bi
}
#endif

#endif /* ZEPHYR_DRIVERS_STEPPER_ADI_TMC_SPI_H_ */
#endif /* ZEPHYR_DRIVERS_STEPPER_ADI_TMC_BUS_SPI_H_ */
45 changes: 45 additions & 0 deletions drivers/stepper/adi_tmc/bus/include/adi_tmc_uart.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2025 Dipak Shetty
*
* SPDX-License-Identifier: Apache-2.0
*/

#ifndef ZEPHYR_DRIVERS_STEPPER_ADI_TMC_BUS_UART_H_
#define ZEPHYR_DRIVERS_STEPPER_ADI_TMC_BUS_UART_H_

#include <zephyr/drivers/uart.h>

/**
* @brief Calculate CRC for TMC UART datagram
*
* @param datagram Pointer to datagram buffer
* @param len Length of datagram
* @return uint8_t CRC value
*/
uint8_t tmc_uart_calc_crc(const uint8_t *datagram, uint8_t len);

/**
* @brief Read register via UART single-wire interface
*
* @param uart Pointer to UART device specification
* @param device_addr Secondary address of TMC device
* @param register_address Register address to read
* @param data Pointer to store read data
* @return int 0 on success, negative errno on failure
*/
int tmc_uart_read_register(const struct device *uart, uint8_t device_addr, uint8_t register_address,
uint32_t *data);

/**
* @brief Write register via UART single-wire interface
*
* @param uart Pointer to UART device specification
* @param device_addr Secondary address of the TMC device
* @param register_address Register address to write
* @param data Data to write to register
* @return int 0 on success, negative errno on failure
*/
int tmc_uart_write_register(const struct device *uart, uint8_t device_addr,
uint8_t register_address, uint32_t data);

#endif /* ZEPHYR_DRIVERS_STEPPER_ADI_TMC_BUS_UART_H_ */
4 changes: 3 additions & 1 deletion drivers/stepper/adi_tmc/tmc50xx.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#include <zephyr/drivers/stepper.h>
#include <zephyr/drivers/stepper/stepper_trinamic.h>

#include "adi_tmc_spi.h"
#include <adi_tmc_spi.h>
#include "adi_tmc5xxx_common.h"

#include <zephyr/logging/log.h>
Expand Down Expand Up @@ -276,6 +276,8 @@ static void rampstat_work_handler(struct k_work *work)
break;

case TMC5XXX_POS_REACHED_EVENT:
case TMC5XXX_POS_REACHED:
case TMC5XXX_POS_REACHED_AND_EVENT:
LOG_DBG("RAMPSTAT %s:Position reached", stepper_data->stepper->name);
execute_callback(stepper_data->stepper, STEPPER_EVENT_STEPS_COMPLETED);
break;
Expand Down
4 changes: 4 additions & 0 deletions drivers/stepper/adi_tmc/tmc51xx/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# SPDX-FileCopyrightText: Copyright (c) 2025 Dipak Shetty
# SPDX-License-Identifier: Apache-2.0

zephyr_library_sources_ifdef(CONFIG_STEPPER_ADI_TMC51XX tmc51xx.c tmc51xx_spi.c tmc51xx_uart.c)
Loading