The TMC9660 driver uses a hardware-agnostic communication interface pattern (CRTP - Curiously Recurring Template Pattern) to abstract platform-specific SPI or UART implementations. This allows the same driver code to work across different MCU platforms.
Understanding the Interface Pattern
The driver uses CRTP (Curiously Recurring Template Pattern) for compile-time polymorphism. This provides:
✅ Zero Runtime Overhead: No virtual function calls, all resolved at compile time
✅ Compile-Time Optimization: Inlining and dead code elimination
#include<SPI.h>
#include"inc/tmc9660_comm_interface.hpp"classArduinoSPI:publictmc9660::SpiCommInterface{private:intcs_pin_;SPISettingsspi_settings_;public:ArduinoSPI(intcs_pin,uint32_tfrequency=1000000):cs_pin_(cs_pin),spi_settings_(frequency,MSBFIRST,SPI_MODE0){pinMode(cs_pin_,OUTPUT);digitalWrite(cs_pin_,HIGH);SPI.begin();}boolspiTransfer(std::array<uint8_t,8>&tx,std::array<uint8_t,8>&rx)noexceptoverride{SPI.beginTransaction(spi_settings_);digitalWrite(cs_pin_,LOW);for(size_ti=0;i<8;i++){rx[i]=SPI.transfer(tx[i]);}digitalWrite(cs_pin_,HIGH);SPI.endTransaction();returntrue;}};// UsageArduinoSPIspi(10);// CS on pin 10tmc9660::TMC9660driver(spi);
UART Interface Implementation
Base Interface
1
2
3
4
5
6
7
8
9
10
classMyUART:publictmc9660::UartCommInterface{public:booluartTransfer(consttmc9660::TMCLFrame&tx,tmc9660::TMCLReply&reply,uint8_taddress)noexceptoverride{// Your UART transfer implementation// Send 8-byte frame, receive 9-byte replyreturntrue;// Return false on error}};
#include"driver/uart.h"
#include"inc/tmc9660_comm_interface.hpp"classESP32UART:publictmc9660::UartCommInterface{private:uart_port_tuart_num_;public:ESP32UART(uart_port_tuart_num,gpio_num_ttx,gpio_num_trx,intbaud_rate=115200):uart_num_(uart_num){uart_config_tuart_config={.baud_rate=baud_rate,.data_bits=UART_DATA_8_BITS,.parity=UART_PARITY_DISABLE,.stop_bits=UART_STOP_BITS_1,.flow_ctrl=UART_HW_FLOWCTRL_DISABLE,};uart_param_config(uart_num_,&uart_config);uart_set_pin(uart_num_,tx,rx,UART_PIN_NO_CHANGE,UART_PIN_NO_CHANGE);uart_driver_install(uart_num_,1024,1024,0,NULL,0);}booluartTransfer(consttmc9660::TMCLFrame&tx,tmc9660::TMCLReply&reply,uint8_taddress)noexceptoverride{// Serialize and send frameuint8_tframe[9];tx.serialize(frame,address);uart_write_bytes(uart_num_,frame,9);// Wait for and read replyuint8_treply_data[9];intlen=uart_read_bytes(uart_num_,reply_data,9,pdMS_TO_TICKS(100));if(len==9){returnreply.deserialize(reply_data);}returnfalse;}};// UsageESP32UARTuart(UART_NUM_1,GPIO_NUM_5,GPIO_NUM_4);tmc9660::TMC9660driver(uart);
GPIO Control Interface
The TMC9660 also requires GPIO control for RST, DRV_EN, WAKE, and FAULTN pins. Implement these methods if your interface class inherits from CommInterface:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
classMySPI:publictmc9660::SpiCommInterface{public:// ... spiTransfer implementation ...// GPIO control (optional but recommended)voidgpioSetActive(tmc9660::TMC9660CtrlPinpin)noexceptoverride{// Set pin to active state}voidgpioSetInactive(tmc9660::TMC9660CtrlPinpin)noexceptoverride{// Set pin to inactive state}boolgpioRead(tmc9660::TMC9660CtrlPinpin,bool&signal)noexceptoverride{// Read pin statereturntrue;}};
Testing Your Implementation
Basic Communication Test
1
2
3
4
5
booltestCommunication(tmc9660::TMC9660&driver){// Try to read a known registerautoresult=driver.bootloaderGetVersion();return(result.has_value());}
Best Practices
Error Handling: Always return false from transfer methods on communication errors
CS Management: Properly assert/deassert CS for SPI
Timing: Respect SPI/UART timing requirements
Thread Safety: Make implementations thread-safe if used in multi-threaded environments
No Exceptions: Use noexcept and return error codes instead of throwing