The MAX22200 driver 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 controlling solenoids/motors, 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
4. Type Safety
Virtual functions: Runtime errors if method not implemented
CRTP: Compile-time errors if method not implemented
Impact: Catch bugs at compile time, not in the field
// Base template class (from max22200_spi_interface.hpp)template<typenameDerived>classSpiInterface{public:boolTransfer(constuint8_t*tx_data,uint8_t*rx_data,size_tlength){// Cast 'this' to Derived* and call the derived implementationreturnstatic_cast<Derived*>(this)->Transfer(tx_data,rx_data,length);}};// Your implementationclassMySPI:publicmax22200::SpiInterface<MySPI>{public:// This method is called directly (no virtual overhead)boolTransfer(constuint8_t*tx_data,uint8_t*rx_data,size_tlength){// Your platform-specific SPI code}};```cpp## Interface Definition
TheMAX22200driverrequiresyoutoimplementthefollowinginterface:```cpptemplate<typenameDerived>classSpiInterface{public:// Required methods (implement all of these)boolInitialize();boolTransfer(constuint8_t*tx_data,uint8_t*rx_data,size_tlength);voidSetChipSelect(boolstate);boolConfigure(uint32_tspeed_hz,uint8_tmode,boolmsb_first=true);boolIsReady()const;};```cpp## Implementation Steps
### Step 1: Create Your Implementation Class
```cpp#include"max22200_spi_interface.hpp"classMyPlatformSPI:publicmax22200::SpiInterface<MyPlatformSPI>{private:// Your platform-specific membersspi_device_handle_tspi_device_;// Example for ESP32public:// ConstructorMyPlatformSPI(spi_device_handle_tdevice):spi_device_(device){}// Implement required methodsboolInitialize(){// Your initialization codereturntrue;}boolTransfer(constuint8_t*tx_data,uint8_t*rx_data,size_tlength){// Your transfer codereturntrue;}voidSetChipSelect(boolstate){// Your CS control code}boolConfigure(uint32_tspeed_hz,uint8_tmode,boolmsb_first){// Your configuration codereturntrue;}boolIsReady()const{// Check if SPI is readyreturntrue;}};
#include"driver/spi_master.h"
#include"max22200_spi_interface.hpp"classEsp32SPIBus:publicmax22200::SpiInterface<Esp32SPIBus>{private:spi_device_handle_tspi_device_;gpio_num_tcs_pin_;public:Esp32SPIBus(spi_host_device_thost,constspi_device_interface_config_t&config,gpio_num_tcs){spi_bus_add_device(host,&config,&spi_device_);cs_pin_=cs;}boolInitialize(){returnspi_device_!=nullptr;}boolTransfer(constuint8_t*tx_data,uint8_t*rx_data,size_tlength){spi_transaction_ttrans={};trans.length=length*8;trans.tx_buffer=tx_data;trans.rx_buffer=rx_data;esp_err_tret=spi_device_transmit(spi_device_,&trans);returnret==ESP_OK;}voidSetChipSelect(boolstate){gpio_set_level(cs_pin_,state?0:1);// Active low}boolConfigure(uint32_tspeed_hz,uint8_tmode,boolmsb_first){// ESP-IDF handles this via device configreturntrue;}boolIsReady()const{returnspi_device_!=nullptr;}};
#include"stm32f4xx_hal.h"
#include"max22200_spi_interface.hpp"externSPI_HandleTypeDefhspi1;classSTM32SPIBus:publicmax22200::SpiInterface<STM32SPIBus>{private:GPIO_TypeDef*cs_port_;uint16_tcs_pin_;public:STM32SPIBus(GPIO_TypeDef*cs_port,uint16_tcs_pin):cs_port_(cs_port),cs_pin_(cs_pin){}boolInitialize(){returnHAL_SPI_Init(&hspi1)==HAL_OK;}boolTransfer(constuint8_t*tx_data,uint8_t*rx_data,size_tlength){SetChipSelect(true);// Assert CSHAL_StatusTypeDefstatus=HAL_SPI_TransmitReceive(&hspi1,(uint8_t*)tx_data,rx_data,length,HAL_MAX_DELAY);SetChipSelect(false);// Deassert CSreturnstatus==HAL_OK;}voidSetChipSelect(boolstate){HAL_GPIO_WritePin(cs_port_,cs_pin_,state?GPIO_PIN_RESET:GPIO_PIN_SET);}boolConfigure(uint32_tspeed_hz,uint8_tmode,boolmsb_first){// STM32 HAL handles this via SPI initreturntrue;}boolIsReady()const{returnhspi1.State==HAL_SPI_STATE_READY;}};
#include<SPI.h>
#include"max22200_spi_interface.hpp"classArduinoSPIBus:publicmax22200::SpiInterface<ArduinoSPIBus>{private:uint8_tcs_pin_;public:ArduinoSPIBus(uint8_tcs_pin):cs_pin_(cs_pin){pinMode(cs_pin_,OUTPUT);digitalWrite(cs_pin_,HIGH);}boolInitialize(){SPI.begin();returntrue;}boolTransfer(constuint8_t*tx_data,uint8_t*rx_data,size_tlength){SPI.beginTransaction(SPISettings(10000000,MSBFIRST,SPI_MODE0));SetChipSelect(true);for(size_ti=0;i<length;i++){uint8_tbyte=SPI.transfer(tx_data?tx_data[i]:0);if(rx_data){rx_data[i]=byte;}}SetChipSelect(false);SPI.endTransaction();returntrue;}voidSetChipSelect(boolstate){digitalWrite(cs_pin_,state?LOW:HIGH);// Active low}boolConfigure(uint32_tspeed_hz,uint8_tmode,boolmsb_first){// Arduino SPI handles this via beginTransactionreturntrue;}boolIsReady()const{returntrue;}};
Common Pitfalls
β Donβt Use Virtual Functions
1
2
3
4
5
6
7
// WRONG - defeats the purpose of CRTPclassMyBus:publicmax22200::SpiInterface<MyBus>{public:virtualboolTransfer(...)override{// β Virtual keyword not needed// ...}};
β Correct CRTP Implementation
1
2
3
4
5
6
7
// CORRECT - no virtual keywordclassMyBus:publicmax22200::SpiInterface<MyBus>{public:boolTransfer(...){// β Direct implementation// ...}};
// CORRECT - pass your class as template parameterclassMyBus:publicmax22200::SpiInterface<MyBus>{// β // ...};
Testing Your Implementation
After implementing the interface, test it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
MyPlatformSPIspi(/* your config */);max22200::MAX22200driver(spi);if(driver.Initialize()==max22200::DriverStatus::OK){max22200::ChannelConfigconfig;config.drive_mode=max22200::DriveMode::CDR;config.side_mode=max22200::SideMode::LOW_SIDE;config.hit_setpoint=500.0f;config.hold_setpoint=200.0f;config.hit_time_ms=10.0f;// IFS from SetBoardConfig (not set on ChannelConfig)config.chop_freq=max22200::ChopFreq::FMAIN_DIV2;driver.ConfigureChannel(0,config);driver.EnableChannel(0);}