Object-Oriented Implementation of IIC Driver in C Language

1. Overview

This article encapsulates the IIC driver using object-oriented programming principles, packaging the properties and operations of IIC into a library. When creating an IIC device, one only needs to instantiate an IIC object. This implementation is based on STM32 and the HAL library for further encapsulation.

The underlying driver methods are not important; the encapsulation concept is crucial. After completing the encapsulation of the IIC driver, we utilize inheritance to implement the driver development for the AT24C64 memory, still using object-oriented principles to encapsulate the AT24C64 driver.

2. Object-Oriented Encapsulation of IIC Driver

The iic.h header file mainly defines the class template, as follows:

// Define IIC class
typedef struct IIC_Type
{
   // Properties
   GPIO_TypeDef  *GPIOx_SCL;  // GPIO_SCL belonging GPIO group (e.g., GPIOA)
   GPIO_TypeDef  *GPIOx_SDA;  // GPIO_SDA belonging GPIO group (e.g., GPIOA)
   uint32_t GPIO_SCL;     // IO pin for GPIO_SCL (e.g., GPIO_PIN_0)
   uint32_t GPIO_SDA;     // IO pin for GPIO_SDA (e.g., GPIO_PIN_0)
   // Operations
   void (*IIC_Init)(const struct IIC_Type*);        // IIC_Init
   void (*IIC_Start)(const struct IIC_Type*);       // IIC_Start
   void (*IIC_Stop)(const struct IIC_Type*);        // IIC_Stop
   uint8_t (*IIC_Wait_Ack)(const struct IIC_Type*);    // IIC_Wait_ack, returns wait failure or success
   void (*IIC_Ack)(const struct IIC_Type*);       // IIC_Ack, IIC sends ACK signal
   void (*IIC_NAck)(const struct IIC_Type*);       // IIC_NAck, IIC sends NACK signal
   void (*IIC_Send_Byte)(const struct IIC_Type*,uint8_t);       // IIC_Send_Byte, entry parameter is the byte to send
   uint8_t (*IIC_Read_Byte)(const struct IIC_Type*,uint8_t);     // IIC_Send_Byte, entry parameter is whether to send ACK signal
   void (*delay_us)(uint32_t);              // us delay
}IIC_TypeDef;

The iic.c source file mainly implements the specific operation functions of the class template, as follows:

// Set SDA to input mode
static void SDA_IN(const struct IIC_Type* IIC_Type_t)
{
  uint8_t io_num = 0;  // Define io Num
  switch(IIC_Type_t->GPIO_SDA)
  {
   case GPIO_PIN_0:
    io_num = 0;
   break;
   case GPIO_PIN_1:
    io_num = 1;
   break; 
   case GPIO_PIN_2:
    io_num = 2;
   break; 
   case GPIO_PIN_3:
    io_num = 3;
   break;
   case GPIO_PIN_4:
    io_num = 4;
   break; 
    case GPIO_PIN_5:
    io_num = 5;
   break; 
   case GPIO_PIN_6:
    io_num = 6;
   break; 
   case GPIO_PIN_7:
    io_num = 7;
   break;
   case GPIO_PIN_8:
    io_num = 8;
   break; 
   case GPIO_PIN_9:
    io_num = 9;
   break;
   case GPIO_PIN_10:
    io_num = 10;
   break;
   case GPIO_PIN_11:
    io_num = 11;
   break; 
   case GPIO_PIN_12:
    io_num = 12;
   break;
   case GPIO_PIN_13:
    io_num = 13;
   break;
   case GPIO_PIN_14:
    io_num = 14;
   break; 
   case GPIO_PIN_15:
    io_num = 15;
   break;
  }
  IIC_Type_t->GPIOx_SDA->MODER&amp;=~(3<<(io_num*2)); // Clear GPIOx_SDA->GPIO_SDA
  IIC_Type_t->GPIOx_SDA->MODER|=0<<(io_num*2);   // Set GPIOx_SDA->GPIO_SDA to input mode
}

// Set SDA to output mode
static void SDA_OUT(const struct IIC_Type* IIC_Type_t)
{
  uint8_t io_num = 0;  // Define io Num
  switch(IIC_Type_t->GPIO_SDA)
  {
   case GPIO_PIN_0:
    io_num = 0;
   break;
   case GPIO_PIN_1:
    io_num = 1;
   break; 
   case GPIO_PIN_2:
    io_num = 2;
   break; 
   case GPIO_PIN_3:
    io_num = 3;
   break;
   case GPIO_PIN_4:
    io_num = 4;
   break; 
    case GPIO_PIN_5:
    io_num = 5;
   break; 
   case GPIO_PIN_6:
    io_num = 6;
   break; 
   case GPIO_PIN_7:
    io_num = 7;
   break;
   case GPIO_PIN_8:
    io_num = 8;
   break; 
   case GPIO_PIN_9:
    io_num = 9;
   break;
   case GPIO_PIN_10:
    io_num = 10;
   break;
   case GPIO_PIN_11:
    io_num = 11;
   break; 
   case GPIO_PIN_12:
    io_num = 12;
   break;
   case GPIO_PIN_13:
    io_num = 13;
   break;
   case GPIO_PIN_14:
    io_num = 14;
   break; 
   case GPIO_PIN_15:
    io_num = 15;
   break;
  }
  IIC_Type_t->GPIOx_SDA->MODER&amp;=~(3<<(io_num*2)); // Clear GPIOx_SDA->GPIO_SDA
  IIC_Type_t->GPIOx_SDA->MODER|=1<<(io_num*2);   // Set GPIOx_SDA->GPIO_SDA to output mode
}
// Set SCL level
static void IIC_SCL(const struct IIC_Type* IIC_Type_t,int n)
{
  if(n == 1)
  {
    HAL_GPIO_WritePin(IIC_Type_t->GPIOx_SCL,IIC_Type_t->GPIO_SCL,GPIO_PIN_SET);     // Set SCL to high level
  }
  else{
    HAL_GPIO_WritePin(IIC_Type_t->GPIOx_SCL,IIC_Type_t->GPIO_SCL,GPIO_PIN_RESET);     // Set SCL to low level
  }
}
// Set SDA level
static void IIC_SDA(const struct IIC_Type* IIC_Type_t,int n)
{
  if(n == 1)
  {
    HAL_GPIO_WritePin(IIC_Type_t->GPIOx_SDA,IIC_Type_t->GPIO_SDA,GPIO_PIN_SET);     // Set SDA to high level
  }
  else{
    HAL_GPIO_WritePin(IIC_Type_t->GPIOx_SDA,IIC_Type_t->GPIO_SDA,GPIO_PIN_RESET);     // Set SDA to low level
  }
}
// Read SDA level
static uint8_t READ_SDA(const struct IIC_Type* IIC_Type_t)
{
  return HAL_GPIO_ReadPin(IIC_Type_t->GPIOx_SDA,IIC_Type_t->GPIO_SDA);  // Read SDA level
}
// IIC initialization
static void IIC_Init_t(const struct IIC_Type* IIC_Type_t)
{
      GPIO_InitTypeDef GPIO_Initure;
    
   // Initialize GPIO clock based on GPIO group
   if(IIC_Type_t->GPIOx_SCL == GPIOA || IIC_Type_t->GPIOx_SDA == GPIOA)
   {
     __HAL_RCC_GPIOA_CLK_ENABLE();   // Enable GPIOA clock
   }
   if(IIC_Type_t->GPIOx_SCL == GPIOB || IIC_Type_t->GPIOx_SDA == GPIOB)
   {
     __HAL_RCC_GPIOB_CLK_ENABLE();   // Enable GPIOB clock
   }
   if(IIC_Type_t->GPIOx_SCL == GPIOC || IIC_Type_t->GPIOx_SDA == GPIOC)
   {
     __HAL_RCC_GPIOC_CLK_ENABLE();   // Enable GPIOC clock
   }
   if(IIC_Type_t->GPIOx_SCL == GPIOD || IIC_Type_t->GPIOx_SDA == GPIOD)
   {
     __HAL_RCC_GPIOD_CLK_ENABLE();   // Enable GPIOD clock
   }
   if(IIC_Type_t->GPIOx_SCL == GPIOE || IIC_Type_t->GPIOx_SDA == GPIOE)
   {
     __HAL_RCC_GPIOE_CLK_ENABLE();   // Enable GPIOE clock
   } 
   if(IIC_Type_t->GPIOx_SCL == GPIOH || IIC_Type_t->GPIOx_SDA == GPIOH)
   {
     __HAL_RCC_GPIOH_CLK_ENABLE();   // Enable GPIOH clock
   }     
   
     // GPIO_SCL initialization settings
     GPIO_Initure.Pin=IIC_Type_t->GPIO_SCL;
     GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;  // Push-pull output
     GPIO_Initure.Pull=GPIO_PULLUP;          // Pull-up
     GPIO_Initure.Speed=GPIO_SPEED_FREQ_VERY_HIGH;    // Fast
     HAL_GPIO_Init(IIC_Type_t->GPIOx_SCL,&amp;GPIO_Initure);
     // GPIO_SDA initialization settings
     GPIO_Initure.Pin=IIC_Type_t->GPIO_SDA;
     GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;  // Push-pull output
     GPIO_Initure.Pull=GPIO_PULLUP;          // Pull-up
     GPIO_Initure.Speed=GPIO_SPEED_FREQ_VERY_HIGH;    // Fast
     HAL_GPIO_Init(IIC_Type_t->GPIOx_SDA,&amp;GPIO_Initure);
     
            // Both SCL and SDA are initialized to high level
      IIC_SCL(IIC_Type_t,1);
       IIC_SDA(IIC_Type_t,1);
}
// IIC Start
static void IIC_Start_t(const struct IIC_Type* IIC_Type_t)
{
  SDA_OUT(IIC_Type_t);      // SDA line output
  IIC_SDA(IIC_Type_t,1);      
  IIC_SCL(IIC_Type_t,1);
  IIC_Type_t->delay_us(4);
   IIC_SDA(IIC_Type_t,0);  // START: when CLK is high, DATA changes from high to low 
  IIC_Type_t->delay_us(4);
  IIC_SCL(IIC_Type_t,0);  // Hold the I2C bus, prepare to send or receive data 
}
// IIC Stop
static void IIC_Stop_t(const struct IIC_Type* IIC_Type_t)
{
  SDA_OUT(IIC_Type_t); // SDA line output
  IIC_SCL(IIC_Type_t,0);
  IIC_SDA(IIC_Type_t,0); // STOP: when CLK is high DATA changes from low to high
   IIC_Type_t->delay_us(4);
  IIC_SCL(IIC_Type_t,1); 
  IIC_SDA(IIC_Type_t,1); // Send I2C bus end signal
  IIC_Type_t->delay_us(4); 
}
// IIC_Wait_ack returns HAL_OK indicates wait success, returns HAL_ERROR indicates wait failure
static uint8_t IIC_Wait_Ack_t(const struct IIC_Type* IIC_Type_t)   // IIC_Wait_ack, returns wait failure or success
{
  uint8_t ucErrTime = 0;
  SDA_IN(IIC_Type_t);      // Set SDA to input  
  IIC_SDA(IIC_Type_t,1);IIC_Type_t->delay_us(1);   
  IIC_SCL(IIC_Type_t,1);IIC_Type_t->delay_us(1);
  while(READ_SDA(IIC_Type_t))
  {
    ucErrTime++;
    if(ucErrTime>250)
    {
      IIC_Type_t->IIC_Stop(IIC_Type_t);
      return HAL_ERROR;
    }
  }
  IIC_SCL(IIC_Type_t,0);// Clock output 0     
  return HAL_OK;  
}
// Generate ACK response
static void IIC_Ack_t(const struct IIC_Type* IIC_Type_t)      
{
  IIC_SCL(IIC_Type_t,0);
  SDA_OUT(IIC_Type_t);
  IIC_SDA(IIC_Type_t,0);
  IIC_Type_t->delay_us(2);  
  IIC_SCL(IIC_Type_t,1);
  IIC_Type_t->delay_us(2);  
  IIC_SCL(IIC_Type_t,0);
}
// Generate NACK response
static void IIC_NAck_t(const struct IIC_Type* IIC_Type_t)      
{
  IIC_SCL(IIC_Type_t,0);
  SDA_OUT(IIC_Type_t);
  IIC_SDA(IIC_Type_t,1);
  IIC_Type_t->delay_us(2);  
  IIC_SCL(IIC_Type_t,1);
  IIC_Type_t->delay_us(2);  
  IIC_SCL(IIC_Type_t,0);
}
// IIC_Send_Byte, entry parameter is the byte to send
static void IIC_Send_Byte_t(const struct IIC_Type* IIC_Type_t,uint8_t txd)     
{
     uint8_t t = 0;   
     SDA_OUT(IIC_Type_t);      
     IIC_SCL(IIC_Type_t,0);// Pull down clock to start data transmission
     for(t=0;t<8;t++)
     {              
          IIC_SDA(IIC_Type_t,(txd&amp;0x80)>>7);
          txd <<= 1;    
       IIC_Type_t->delay_us(2);     // For TEA5767, these three delays are necessary
       IIC_SCL(IIC_Type_t,1);
       IIC_Type_t->delay_us(2);  
       IIC_SCL(IIC_Type_t,0); 
       IIC_Type_t->delay_us(2);  
     }  
}
// IIC_Send_Byte, entry parameter is whether to send ACK signal
static uint8_t IIC_Read_Byte_t(const struct IIC_Type* IIC_Type_t,uint8_t ack)     
{
   uint8_t i,receive = 0;
   SDA_IN(IIC_Type_t);// Set SDA to input
   for(i=0;i<8;i++ )
   {
      IIC_SCL(IIC_Type_t,0); 
      IIC_Type_t->delay_us(2);
      IIC_SCL(IIC_Type_t,1);
      receive<<=1;
      if(READ_SDA(IIC_Type_t))receive++;   
      IIC_Type_t->delay_us(1);
   }      
  if (!ack)
         IIC_Type_t->IIC_NAck(IIC_Type_t);// Send nACK
  else
         IIC_Type_t->IIC_Ack(IIC_Type_t); // Send ACK   
  return receive;
}
// Instantiate an IIC1 peripheral, equivalent to a struct variable, can be used directly in other files
IIC_TypeDef IIC1 = {
  .GPIOx_SCL = GPIOA,   // GPIO group is GPIOA
  .GPIOx_SDA = GPIOA,   // GPIO group is GPIOA
  .GPIO_SCL = GPIO_PIN_5,   // GPIO is PIN5
  .GPIO_SDA = GPIO_PIN_6,  // GPIO is PIN6
  .IIC_Init = IIC_Init_t,
  .IIC_Start = IIC_Start_t,
  .IIC_Stop = IIC_Stop_t,
  .IIC_Wait_Ack = IIC_Wait_Ack_t,
  .IIC_Ack = IIC_Ack_t,
  .IIC_NAck = IIC_NAck_t,
  .IIC_Send_Byte = IIC_Send_Byte_t,
  .IIC_Read_Byte = IIC_Read_Byte_t,
  .delay_us = delay_us     // Need to implement delay_us function externally
};

The above is the encapsulation of the IIC driver. Due to the lack of application scenarios, its practicality will not be tested for now. Testing will be conducted after the ATC64 driver is completed.

3. Implementation of ATC64XX Driver Encapsulation

The at24cxx.h header file mainly defines the class template, as follows:

// The following defines the capacity of specific memory
#define AT24C01  127
#define AT24C02  255
#define AT24C04  511
#define AT24C08  1023
#define AT24C16  2047
#define AT24C32  4095
#define AT24C64   8191         // 8KBytes
#define AT24C128 16383
#define AT24C256 32767  

// Define AT24CXX class
typedef struct AT24CXX_Type
{
  // Properties
  u32 EEP_TYPE;           // Memory type (memory capacity)
  // Operations
  IIC_TypeDef IIC;       // IIC driver
  uint8_t (*AT24CXX_ReadOneByte)(const struct AT24CXX_Type*,uint16_t);  // Read one byte from specified address
  void (*AT24CXX_WriteOneByte)(const struct AT24CXX_Type*,uint16_t,uint8_t); // Write one byte to specified address
  void (*AT24CXX_WriteLenByte)(uint16_t,uint32_t,uint8_t); // Start writing specified length of data to specified address
  uint32_t (*AT24CXX_ReadLenByte)(uint16_t,uint8_t);   // Start reading specified length of data from specified address
  void (*AT24CXX_Write)(uint16_t,uint8_t *,uint16_t);  // Start writing specified length of data to specified address
  void (*AT24CXX_Read)(uint16_t,uint8_t *,uint16_t);   // Start reading specified length of data from specified address
  void (*AT24CXX_Init)(const struct AT24CXX_Type*); // Initialize IIC
  uint8_t (*AT24CXX_Check)(const struct AT24CXX_Type*);   // Check device
}AT24CXX_TypeDef;

extern AT24CXX_TypeDef AT24C_64;     // External declaration of instantiated AT24CXX object

The at24cxx.c source file mainly implements the specific operation functions of the class template, as follows:

// Read a data byte from a specified address in AT24CXX
// ReadAddr: Starting address for reading
// Return value: Data read
static uint8_t AT24CXX_ReadOneByte_t(const struct AT24CXX_Type* AT24CXX_Type_t,uint16_t ReadAddr)
{      
  uint8_t temp=0;                          
  AT24CXX_Type_t->IIC.IIC_Start(&amp;AT24CXX_Type_t->IIC);  
  // Send different addresses based on the model of AT
  if(AT24CXX_Type_t->EEP_TYPE > AT24C16)
  {
    AT24CXX_Type_t->IIC.IIC_Send_Byte(&amp;AT24CXX_Type_t->IIC,0XA0);    // Send write command
    AT24CXX_Type_t->IIC.IIC_Wait_Ack(&amp;AT24CXX_Type_t->IIC);
    AT24CXX_Type_t->IIC.IIC_Send_Byte(&amp;AT24CXX_Type_t->IIC,ReadAddr>>8);// Send high address     
  }else AT24CXX_Type_t->IIC.IIC_Send_Byte(&amp;AT24CXX_Type_t->IIC,0XA0+((ReadAddr/256)<<1));   // Send device address 0XA0, write data     
  AT24CXX_Type_t->IIC.IIC_Wait_Ack(&amp;AT24CXX_Type_t->IIC); 
  AT24CXX_Type_t->IIC.IIC_Send_Byte(&amp;AT24CXX_Type_t->IIC,ReadAddr%256);   // Send low address
  AT24CXX_Type_t->IIC.IIC_Wait_Ack(&amp;AT24CXX_Type_t->IIC);     
  AT24CXX_Type_t->IIC.IIC_Start(&amp;AT24CXX_Type_t->IIC);        
  AT24CXX_Type_t->IIC.IIC_Send_Byte(&amp;AT24CXX_Type_t->IIC,0XA1);           // Enter receive mode      
  AT24CXX_Type_t->IIC.IIC_Wait_Ack(&amp;AT24CXX_Type_t->IIC);  
  temp=AT24CXX_Type_t->IIC.IIC_Read_Byte(&amp;AT24CXX_Type_t->IIC,0);     
  AT24CXX_Type_t->IIC.IIC_Stop(&amp;AT24CXX_Type_t->IIC);// Generate a stop condition     
  return temp;
}
// Write a data byte to a specified address in AT24CXX
// WriteAddr: Destination address for writing data    
// DataToWrite: Data to be written
static void AT24CXX_WriteOneByte_t(const struct AT24CXX_Type* AT24CXX_Type_t,uint16_t WriteAddr,uint8_t DataToWrite)
{                                
   AT24CXX_Type_t->IIC.IIC_Start(&amp;AT24CXX_Type_t->IIC);   
  if(AT24CXX_Type_t->EEP_TYPE > AT24C16)
  {
    AT24CXX_Type_t->IIC.IIC_Send_Byte(&amp;AT24CXX_Type_t->IIC,0XA0);    // Send write command
    AT24CXX_Type_t->IIC.IIC_Wait_Ack(&amp;AT24CXX_Type_t->IIC);
    AT24CXX_Type_t->IIC.IIC_Send_Byte(&amp;AT24CXX_Type_t->IIC,WriteAddr>>8);// Send high address     
  }else AT24CXX_Type_t->IIC.IIC_Send_Byte(&amp;AT24CXX_Type_t->IIC,0XA0+((WriteAddr/256)<<1));   // Send device address 0XA0, write data     
  AT24CXX_Type_t->IIC.IIC_Wait_Ack(&amp;AT24CXX_Type_t->IIC); 
   AT24CXX_Type_t->IIC.IIC_Send_Byte(&amp;AT24CXX_Type_t->IIC,WriteAddr%256);   // Send low address
  AT24CXX_Type_t->IIC.IIC_Wait_Ack(&amp;AT24CXX_Type_t->IIC);               
  AT24CXX_Type_t->IIC.IIC_Send_Byte(&amp;AT24CXX_Type_t->IIC,DataToWrite);     // Send byte          
  AT24CXX_Type_t->IIC.IIC_Wait_Ack(&amp;AT24CXX_Type_t->IIC);            
   AT24CXX_Type_t->IIC.IIC_Stop(&amp;AT24CXX_Type_t->IIC);// Generate a stop condition 
 AT24CXX_Type_t->IIC.delay_us(10000);  
}
// Start writing length Len data from specified address in AT24CXX
// This function is used to write 16bit or 32bit data.
// WriteAddr: Starting address for writing  
// DataToWrite: Starting address of data array
// Len: Length of data to be written 2,4
static void AT24CXX_WriteLenByte_t(uint16_t WriteAddr,uint32_t DataToWrite,uint8_t Len)
{   
  uint8_t t;
  for(t=0;t<Len;t++)
  {
    AT24CXX_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&amp;0xff);
  }                
}
// Start reading length Len data from specified address in AT24CXX
// This function is used to read 16bit or 32bit data.
// ReadAddr: Starting address for reading 
// Return value: Data
// Len: Length of data to be read 2,4
static uint32_t AT24CXX_ReadLenByte_t(uint16_t ReadAddr,uint8_t Len)
{   
  uint8_t t;
  uint32_t temp=0;
  for(t=0;t<Len;t++)
  {
    temp<<=8;
     temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1);          
  }
  return temp;                
}
// Start writing specified number of data from specified address in AT24CXX
// WriteAddr: Starting address for writing, for 24c64 is 0~8191
// pBuffer: Starting address of data array
// NumToWrite: Number of data to be written
static void AT24CXX_Write_t(uint16_t WriteAddr,uint8_t *pBuffer,uint16_t NumToWrite)
{
  while(NumToWrite--)
  {
   AT24CXX_WriteOneByte(WriteAddr,*pBuffer);
    WriteAddr++;
    pBuffer++;
  }
}
// Start reading specified number of data from specified address in AT24CXX
// ReadAddr: Starting address for reading, for 24c64 is 0~8191
// pBuffer: Starting address of data array
// NumToRead: Number of data to be read
static void AT24CXX_Read_t(uint16_t ReadAddr,uint8_t *pBuffer,uint16_t NumToRead)
{
  while(NumToRead)
  {
    *pBuffer++=AT24CXX_ReadOneByte(ReadAddr++); 
    NumToRead--;
  }
} 
// Initialize IIC interface
static void AT24CXX_Init_t(const struct AT24CXX_Type* AT24CXX_Type_t)
{
  AT24CXX_Type_t->IIC.IIC_Init(&amp;AT24CXX_Type_t->IIC);// IIC initialization
}
// Check device, returns 0 indicates check success, returns 1 indicates check failure
static uint8_t AT24CXX_Check_t(const struct AT24CXX_Type* AT24CXX_Type_t)   
{
 uint8_t temp;
  temp = AT24CXX_Type_t->AT24CXX_ReadOneByte(AT24CXX_Type_t,AT24CXX_Type_t->EEP_TYPE);// Avoid writing AT24CXX every time on power-up      
  if(temp == 0X33)return 0;     
  else// Exclude the first initialization case
  {
      AT24CXX_Type_t->AT24CXX_WriteOneByte(AT24CXX_Type_t,AT24CXX_Type_t->EEP_TYPE,0X33);
       temp = AT24CXX_Type_t->AT24CXX_ReadOneByte(AT24CXX_Type_t,AT24CXX_Type_t->EEP_TYPE);
      if(temp==0X33)return 0;
  }
  return 1;  
}
// Instantiate AT24CXX object
AT24CXX_TypeDef AT24C_64={
 .EEP_TYPE = AT24C64,           // Memory type (memory capacity)
 // Operations
 .IIC={
  .GPIOx_SCL = GPIOA,
  .GPIOx_SDA = GPIOA,
  .GPIO_SCL = GPIO_PIN_5,
  .GPIO_SDA = GPIO_PIN_6,
  .IIC_Init = IIC_Init_t,
  .IIC_Start = IIC_Start_t,
  .IIC_Stop = IIC_Stop_t,
  .IIC_Wait_Ack = IIC_Wait_Ack_t,
  .IIC_Ack = IIC_Ack_t,
  .IIC_NAck = IIC_NAck_t,
  .IIC_Send_Byte = IIC_Send_Byte_t,
  .IIC_Read_Byte = IIC_Read_Byte_t,
  .delay_us = delay_us
 },                   // IIC driver
 .AT24CXX_ReadOneByte = AT24CXX_ReadOneByte_t,  // Read one byte from specified address
 .AT24CXX_WriteOneByte = AT24CXX_WriteOneByte_t,// Write one byte to specified address
 .AT24CXX_WriteLenByte = AT24CXX_WriteLenByte_t, // Start writing specified length of data to specified address
 .AT24CXX_ReadLenByte = AT24CXX_ReadLenByte_t,   // Start reading specified length of data from specified address
 .AT24CXX_Write = AT24CXX_Write_t,  // Start writing specified length of data to specified address
 .AT24CXX_Read = AT24CXX_Read_t,   // Start reading specified length of data from specified address
 .AT24CXX_Init = AT24CXX_Init_t, // Initialize IIC
 .AT24CXX_Check = AT24CXX_Check_t   // Check device
};

Simple analysis: It can be seen that the AT24CXX class contains the IIC class member object, which is a composition relationship. Since there is no consistency in properties, inheritance cannot be discussed.

The reason for including the IIC class object as a member of the AT24CXX class is that the implementation of AT24CXX requires calling the member methods of IIC. IIC serves as a lower-level driver for AT24CXX, thus a composition relationship is more appropriate.

Therefore, when using AT24CXX, we only need to instantiate the AT24CXX class object, as IIC is included within the AT24CXX class, eliminating the need to instantiate the IIC class object, providing a better encapsulated interface. Next, we will look at the specific calling methods.

4. Main Function Testing

In the main function, we directly use AT24C_64 to complete all operations, as shown in the following code:

#include "at24cxx.h"    // To ensure the member methods of AT24C_64 and reference operation object AT24C_64
int main(void)
{
  /************ Other initialization work omitted ****************/
  // Step 1: Call the object initialization method to initialize AT24C64
  AT24C_64.AT24CXX_Init(&amp;AT24C_64);
  // Step 2: Call the object check method to check AT24C64           
  if(AT24C_64.AT24CXX_Check(&amp;AT24C_64) == 0)
  {
    printf("AT24C64 check successful\r\n");
  }
  else{
    printf("AT24C64 check failed\r\n");
  }
  return 0;
}

It can be seen that all operations are completed through the AT24C_64 object. After initializing the AT24C_64 object, we can confidently call its member methods. The advantage of this encapsulation is that a device only provides a single object interface, which is concise and clear.

5. Conclusion

This article detailed the implementation of IIC driver encapsulation and AT24CXX memory encapsulation using object-oriented methods, ultimately providing only one operational object interface, greatly enhancing code reusability and encapsulation.

Direct source: Line 8 Engineer

ClickRead the original text to view daplink

Object-Oriented Implementation of IIC Driver in C Language

Detailed explanation of IIC bus communication protocol

Take a bite of the bitter USB protocol

Analysis of low-power power supply circuit examples for microcontrollers

Three books graduated, the journey of engaging in embedded software

Leave a Comment