The BNO08x 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 IMU data processing, 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 bno08x_comm_interface.hpp)template<typenameDerived>classCommInterface{public:boolOpen(){// Cast 'this' to Derived* and call the derived implementationreturnstatic_cast<Derived*>(this)->Open();}intRead(uint8_t*data,uint32_tlength){returnstatic_cast<Derived*>(this)->Read(data,length);}// ... other methods};// Your implementationclassMyComm:publicbno08x::CommInterface<MyComm>{public:// This method is called directly (no virtual overhead)boolOpen(){// Your platform-specific initialization codereturntrue;}intRead(uint8_t*data,uint32_tlength){// Your platform-specific read codereturnlength;}};
Interface Definition
The BNO08x driver requires you to implement the following interface:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template<typenameDerived>classCommInterface{public:// Required methods (implement all of these)boolOpen();voidClose();intWrite(constuint8_t*data,uint32_tlength);intRead(uint8_t*data,uint32_tlength);boolDataAvailable();voidDelay(uint32_tms);uint32_tGetTimeUs();// Optional methods (implement if pins are wired)voidSetReset(boolstate);voidSetBoot(boolstate);voidSetWake(boolstate);voidSetPS0(boolstate);voidSetPS1(boolstate);};
#include"bno08x_comm_interface.hpp"classMyPlatformComm:publicbno08x::CommInterface<MyPlatformComm>{private:// Your platform-specific membersi2c_handle_ti2c_handle_;// Example for I²Cpublic:// ConstructorMyPlatformComm(i2c_handle_thandle):i2c_handle_(handle){}// Implement required methodsboolOpen(){// Your initialization codereturntrue;}voidClose(){// Your cleanup code}intWrite(constuint8_t*data,uint32_tlength){// Your write codereturnlength;}intRead(uint8_t*data,uint32_tlength){// Your read codereturnlength;}boolDataAvailable(){// Check if data is available (e.g., via interrupt pin)returntrue;}voidDelay(uint32_tms){// Your delay implementation}uint32_tGetTimeUs(){// Return current time in microsecondsreturn0;}};
#include"stm32f4xx_hal.h"
#include"bno08x_comm_interface.hpp"externI2C_HandleTypeDefhi2c1;classSTM32I2CComm:publicbno08x::CommInterface<STM32I2CComm>{private:uint16_tdevice_addr_;public:STM32I2CComm(uint16_taddr):device_addr_(addr<<1){}boolOpen(){returnHAL_I2C_IsDeviceReady(&hi2c1,device_addr_,3,100)==HAL_OK;}voidClose(){// Nothing to do}intWrite(constuint8_t*data,uint32_tlength){HAL_StatusTypeDefstatus=HAL_I2C_Master_Transmit(&hi2c1,device_addr_,const_cast<uint8_t*>(data),length,100);return(status==HAL_OK)?length:-1;}intRead(uint8_t*data,uint32_tlength){HAL_StatusTypeDefstatus=HAL_I2C_Master_Receive(&hi2c1,device_addr_,data,length,100);return(status==HAL_OK)?length:-1;}boolDataAvailable(){returntrue;}voidDelay(uint32_tms){HAL_Delay(ms);}uint32_tGetTimeUs(){returnHAL_GetTick()*1000;}};
#include<Wire.h>
#include"bno08x_comm_interface.hpp"classArduinoComm:publicbno08x::CommInterface<ArduinoComm>{private:uint8_tdevice_addr_;public:ArduinoComm(uint8_taddr):device_addr_(addr){}boolOpen(){Wire.begin();Wire.setClock(400000);delay(50);returntrue;}voidClose(){// Nothing to do}intWrite(constuint8_t*data,uint32_tlength){Wire.beginTransmission(device_addr_);Wire.write(data,length);return(Wire.endTransmission()==0)?length:-1;}intRead(uint8_t*data,uint32_tlength){Wire.requestFrom(device_addr_,length);size_tcount=0;while(Wire.available()&&count<length){data[count++]=Wire.read();}returncount;}boolDataAvailable(){returntrue;}voidDelay(uint32_tms){delay(ms);}uint32_tGetTimeUs(){returnmicros();}};
Optional Pin Control Methods
If your hardware has these pins wired, implement the optional methods:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
voidSetReset(boolstate){// Control RSTN pin (active low)gpio_set_level(rst_pin_,state?0:1);}voidSetBoot(boolstate){// Control BOOTN pin (for DFU mode)gpio_set_level(boot_pin_,state?0:1);}voidSetWake(boolstate){// Control WAKE pin (SPI mode only)gpio_set_level(wake_pin_,state?0:1);}