Platform Integration Guide
This guide explains how to implement the hardware abstraction interface for the TMC51x0 driver (TMC5130 & TMC5160) on your platform.
Understanding CRTP (Curiously Recurring Template Pattern)
The TMC51x0 driver (TMC5130 & TMC5160) uses CRTP (Curiously Recurring Template Pattern) for hardware abstraction. This design choice provides several critical benefits for embedded systems:
Why CRTP Instead of Virtual Functions?
1. Zero Runtime Overhead
- Virtual functions: Require a vtable lookup (indirect call) = ~5-10 CPU cycles overhead per call
- CRTP: Direct function calls = 0 overhead, compiler can inline
- Impact: In time-critical embedded code, this matters significantly
2. Compile-Time Polymorphism
- Virtual functions: Runtime dispatch - the compiler cannot optimize across the abstraction boundary
- CRTP: Compile-time dispatch - full optimization, dead code elimination, constant propagation
- Impact: Smaller code size, faster execution
3. Memory Efficiency
- Virtual functions: Each object needs a vtable pointer (4-8 bytes)
- CRTP: No vtable pointer needed
- Impact: Critical in memory-constrained systems
SPI Interface Implementation
ESP32 (ESP-IDF) Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "inc/tmc51x0.hpp"
class Esp32SPI : public tmc51x0::SpiCommInterface<Esp32SPI> {
private:
spi_device_handle_t spi_device_;
gpio_num_t cs_pin_;
gpio_num_t en_pin_;
gpio_num_t dir_pin_;
gpio_num_t step_pin_;
public:
Esp32SPI(spi_host_device_t host, gpio_num_t cs_pin, gpio_num_t en_pin,
gpio_num_t dir_pin, gpio_num_t step_pin)
: SpiCommInterface(true, true, true), // EN, DIR, STEP active high
cs_pin_(cs_pin), en_pin_(en_pin), dir_pin_(dir_pin), step_pin_(step_pin) {
spi_device_interface_config_t dev_config = {};
dev_config.clock_speed_hz = 4 * 1000 * 1000; // 4 MHz
dev_config.mode = 3; // SPI Mode 3
dev_config.spics_io_num = cs_pin_;
dev_config.queue_size = 1;
spi_bus_add_device(host, &dev_config, &spi_device_);
// Configure GPIO pins
gpio_set_direction(en_pin_, GPIO_MODE_OUTPUT);
gpio_set_direction(dir_pin_, GPIO_MODE_OUTPUT);
gpio_set_direction(step_pin_, GPIO_MODE_OUTPUT);
}
CommMode GetMode() const noexcept {
return CommMode::SPI;
}
bool SpiTransfer(const uint8_t* tx, uint8_t* rx, size_t length) noexcept {
spi_transaction_t trans = {};
trans.length = length * 8;
trans.tx_buffer = tx;
trans.rx_buffer = rx;
esp_err_t ret = spi_device_transmit(spi_device_, &trans);
return ret == ESP_OK;
}
bool GpioSet(TMC51x0CtrlPin pin, GpioSignal signal) noexcept {
gpio_num_t gpio_pin;
switch (pin) {
case TMC51x0CtrlPin::EN:
gpio_pin = en_pin_;
break;
case TMC51x0CtrlPin::DIR:
gpio_pin = dir_pin_;
break;
case TMC51x0CtrlPin::STEP:
gpio_pin = step_pin_;
break;
default:
return false;
}
bool level = SignalToGpioLevel(pin, signal);
gpio_set_level(gpio_pin, level ? 1 : 0);
return true;
}
bool GpioRead(TMC51x0CtrlPin pin, GpioSignal& signal) noexcept {
// TMC5160 doesn't have input pins for reading, but implement for completeness
return false;
}
void DebugLog(int level, const char* tag, const char* format, va_list args) noexcept {
esp_log_level_t esp_level;
switch (level) {
case 0: esp_level = ESP_LOG_ERROR; break;
case 1: esp_level = ESP_LOG_WARN; break;
case 2: esp_level = ESP_LOG_INFO; break;
case 3: esp_level = ESP_LOG_DEBUG; break;
default: esp_level = ESP_LOG_VERBOSE; break;
}
esp_log_writev(esp_level, tag, format, args);
}
void DelayMs(uint32_t ms) noexcept {
vTaskDelay(pdMS_TO_TICKS(ms));
}
void DelayUs(uint32_t us) noexcept {
esp_rom_delay_us(us);
}
};
STM32 (HAL) Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include "stm32f4xx_hal.h"
#include "inc/tmc51x0.hpp"
class STM32SPI : public tmc51x0::SpiCommInterface<STM32SPI> {
private:
SPI_HandleTypeDef* hspi_;
GPIO_TypeDef* cs_port_;
uint16_t cs_pin_;
GPIO_TypeDef* en_port_;
uint16_t en_pin_;
public:
STM32SPI(SPI_HandleTypeDef* hspi, GPIO_TypeDef* cs_port, uint16_t cs_pin,
GPIO_TypeDef* en_port, uint16_t en_pin)
: SpiCommInterface(true, true, true),
hspi_(hspi), cs_port_(cs_port), cs_pin_(cs_pin),
en_port_(en_port), en_pin_(en_pin) {}
CommMode GetMode() const noexcept {
return CommMode::SPI;
}
bool SpiTransfer(const uint8_t* tx, uint8_t* rx, size_t length) noexcept {
HAL_GPIO_WritePin(cs_port_, cs_pin_, GPIO_PIN_RESET);
HAL_StatusTypeDef status = HAL_SPI_TransmitReceive(hspi_,
const_cast<uint8_t*>(tx), const_cast<uint8_t*>(rx), length, HAL_MAX_DELAY);
HAL_GPIO_WritePin(cs_port_, cs_pin_, GPIO_PIN_SET);
return status == HAL_OK;
}
bool GpioSet(TMC51x0CtrlPin pin, GpioSignal signal) noexcept {
GPIO_TypeDef* port;
uint16_t gpio_pin;
switch (pin) {
case TMC51x0CtrlPin::EN:
port = en_port_;
gpio_pin = en_pin_;
break;
default:
return false;
}
bool level = SignalToGpioLevel(pin, signal);
HAL_GPIO_WritePin(port, gpio_pin, level ? GPIO_PIN_SET : GPIO_PIN_RESET);
return true;
}
bool GpioRead(TMC51x0CtrlPin pin, GpioSignal& signal) noexcept {
return false;
}
void DebugLog(int level, const char* tag, const char* format, va_list args) noexcept {
// Implement your logging
}
void DelayMs(uint32_t ms) noexcept {
HAL_Delay(ms);
}
void DelayUs(uint32_t us) noexcept {
// STM32 HAL doesn't have microsecond delay, use DWT or timer
// For now, approximate with HAL_Delay
if (us >= 1000) {
HAL_Delay(us / 1000);
}
}
};
UART Interface Implementation
ESP32 UART Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#include "driver/uart.h"
#include "inc/tmc51x0.hpp"
class Esp32UART : public tmc51x0::UartCommInterface<Esp32UART> {
private:
uart_port_t uart_num_;
gpio_num_t tx_pin_;
gpio_num_t rx_pin_;
gpio_num_t txen_pin_;
gpio_num_t tmc_power_en_pin_{GPIO_NUM_NC}; // optional: load-switch enable for TMC VIO
public:
Esp32UART(uart_port_t uart_num, gpio_num_t tx_pin, gpio_num_t rx_pin,
gpio_num_t txen_pin, uint8_t node_address,
gpio_num_t tmc_power_en_pin = GPIO_NUM_NC)
: UartCommInterface(true, true, true, node_address),
uart_num_(uart_num), tx_pin_(tx_pin), rx_pin_(rx_pin), txen_pin_(txen_pin),
tmc_power_en_pin_(tmc_power_en_pin) {
uart_config_t uart_config = {};
uart_config.baud_rate = 500000;
uart_config.data_bits = UART_DATA_8_BITS;
uart_config.parity = UART_PARITY_DISABLE;
uart_config.stop_bits = UART_STOP_BITS_1;
uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
uart_param_config(uart_num_, &uart_config);
uart_set_pin(uart_num_, tx_pin_, rx_pin_, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
uart_driver_install(uart_num_, 1024, 1024, 0, NULL, 0);
gpio_set_direction(txen_pin_, GPIO_MODE_OUTPUT);
// Optional: power-enable control for true hard reset (power cycle)
if (tmc_power_en_pin_ != GPIO_NUM_NC) {
gpio_set_direction(tmc_power_en_pin_, GPIO_MODE_OUTPUT);
gpio_set_level(tmc_power_en_pin_, 1); // power on by default
}
}
CommMode GetMode() const noexcept {
return CommMode::UART;
}
bool UartSend(const uint8_t* data, size_t length) noexcept {
gpio_set_level(txen_pin_, 1); // Enable transmitter
int bytes_written = uart_write_bytes(uart_num_, data, length);
uart_wait_tx_done(uart_num_, portMAX_DELAY);
gpio_set_level(txen_pin_, 0); // Disable transmitter
return bytes_written == length;
}
// Optional power control hook (enables TMC51x0::HardReset() to power-cycle)
tmc51x0::Result<void> SetPowerEnabled(bool enabled) noexcept {
if (tmc_power_en_pin_ == GPIO_NUM_NC) {
return tmc51x0::Result<void>(tmc51x0::ErrorCode::UNSUPPORTED);
}
gpio_set_level(tmc_power_en_pin_, enabled ? 1 : 0);
return tmc51x0::Result<void>();
}
bool UartReceive(uint8_t* data, size_t length) noexcept {
int bytes_read = uart_read_bytes(uart_num_, data, length, pdMS_TO_TICKS(100));
return bytes_read == length;
}
bool GpioSet(TMC51x0CtrlPin pin, GpioSignal signal) noexcept {
return true; // UART mode doesn't use GPIO pins
}
bool GpioRead(TMC51x0CtrlPin pin, GpioSignal& signal) noexcept {
return false;
}
void DebugLog(int level, const char* tag, const char* format, va_list args) noexcept {
esp_log_writev(ESP_LOG_DEBUG, tag, format, args);
}
void DelayMs(uint32_t ms) noexcept {
vTaskDelay(pdMS_TO_TICKS(ms));
}
void DelayUs(uint32_t us) noexcept {
esp_rom_delay_us(us);
}
};
Required Methods Summary
SPI Interface
You must implement:
GetMode()- ReturnCommMode::SPISpiTransfer()- Perform SPI transferGpioSet()- Set GPIO pins (EN, DIR, STEP)GpioRead()- Read GPIO pins (if needed)DebugLog()- Debug logging (optional)DelayMs()- Millisecond delayDelayUs()- Microsecond delay
UART Interface
You must implement:
GetMode()- ReturnCommMode::UARTUartSend()- Send bytes via UARTUartReceive()- Receive bytes via UARTGpioSet()- Set GPIO pins (if needed)GpioRead()- Read GPIO pins (if needed)DebugLog()- Debug logging (optional)DelayMs()- Millisecond delayDelayUs()- Microsecond delay
Next Steps
- Review the Configuration guide for driver settings
- Check Examples for complete working examples
- See API Reference for all available methods
Navigation <- Hardware Setup | Next: Configuration -> | Back to Index