Object Oriented Embedded C Programming
Tags: software, hardware, programming, embedded
Published: 2022-02-03 13:25:50

I haven't written any technical article since the last two years, as a contributor to the Opensource community, writing is one of the ways to help grow the community.

In bare metal embedded programming, **C** is still mostly used because of its closeness to hardware low levels _(assembly language and machine language)_. Billions of devices OS kernels are written in **C** and it doesn't seem to have expiration date because the world is running on powered C devices.

Meanwhile, the main reason for this article is to show programmers how to write embedded C codes in a more structured way for readability, reliability, maintainability and portability.

Object oriented programming languages like C++, Java and Python are well known for writing a structured code but only few programmers knows how to write object oriented codes in **C**, because it's a subset of **C++**, I'm gonna show you how to achieve this using `typedef struct`.

> Please note, I have used **STM32 HAL** library for my example but you can use either self library or any other third party library for your application, the main focus of  this tutorial is to write embedded C codes in an object  oriented way.

In embedded C, it's highly recommended to always `typedef` your `struct` and `enum`. So let's create **EEPROM** and **UART** struct type:

```C
#define MAX_EEPROM_BUFFER_SIZE 256
#define MAX_UART_BUFFER_SIZE   512
#define EEPROM_WRITE_TIME      5
#define TIMEOUT                500




typedef enum {
     EEPROM_OK      = 0,
     EEPROM_ERROR   = 1,
     EEPROM_BUSY    = 2,
     EEPROM_TIMEOUT = 3,
}EEPROM_STATUS;

typedef struct eeprom_t {
        uint8_t   pData [MAX_EEPROM_BUFFER_SIZE];  /* Pointer to data buffer */
        uint8_t   size;                     /* Amount of data to be sent or read */
        uint16_t  devAddress;               /* Target device address: The device 7 bits address value
                                                in datasheet must be shifted to the left before calling the interface */
        uint16_t  memAddress;               /* Internal memory address */
        I2C_HandleTypeDef* eepromI2C;       /* Pointer to a I2C_HandleTypeDef structure that contains
                                                the configuration information for the specified I2C. */
        EEPROM_STATUS status;
        EEPROM_STATUS (*write) (I2C_HandleTypeDef* hi2c, uint16_t devAddress, uint16_t memAddress, uint8_t  *pData); 
        EEPROM_STATUS (*read)  (I2C_HandleTypeDef* hi2c, uint16_t devAddress, uint16_t memAddress, uint8_t  *pData, uint16_t size);
        void (*init)  (void);
}eeprom_t;


typedef struct uart_t {
        uint8_t           bufferData[MAX_UART_BUFFER_SIZE];
       UART_HandleTypeDef *uart1, *uart2;
       void (*init)       (void);
       void (*print)      (UART_HandleTypeDef *huart, const char* format, ...);
       HAL_StatusTypeDef  (*write) (UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
       HAL_StatusTypeDef  (*read)  (UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
       HAL_StatusTypeDef  (*write_interrupt) (UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
       HAL_StatusTypeDef  (*read_interrupt) (UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
       HAL_StatusTypeDef  (*write_dma) (UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
       HAL_StatusTypeDef  (*read_dma)  (UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
       HAL_StatusTypeDef  (*pause_dma) (UART_HandleTypeDef *huart);
       HAL_StatusTypeDef  (*resume_dma)(UART_HandleTypeDef *huart);
       HAL_StatusTypeDef  (*stop_dma)  (UART_HandleTypeDef *huart);
}uart_t;

```

The next step is to create the functions that will be needed in the code, so here I have created a `va_arg` function type for my serial print application. In the code below, I have included `string.h` for `memset`, `strlen` and `stdarg.h` for `va_list`, `va_start`, `vsnprintf` and `va_end`.

```C
#include <stdio.h>
#include <stdarg.h>
#include <string.h>

void Serial_Print(UART_HandleTypeDef *huart, const char* format, ...) {
    memset(serial.bufferData, 0x00, sizeof(serial.bufferData));
    va_list args;
    va_start(args, format);
    vsnprintf((char*)serial.bufferData, sizeof(serial.bufferData), format, args);
    serial.write(huart, serial.bufferData, strlen((char*) serial.bufferData), TIMEOUT);
    va_end(args);
}

```

Now, let's create EEPROM functions to be used:

```C
EEPROM_STATUS EEPROM_Write (I2C_HandleTypeDef* hi2c,  
                            uint16_t devAddress, 
                            uint16_t memAddress,
                            uint8_t  *pData) 
{
   EEPROM_STATUS eepromStatus = HAL_I2C_Mem_Write(hi2c, devAddress, memAddress, I2C_MEMADD_SIZE_8BIT, pData, strlen((char*)pData), TIMEOUT);
   HAL_Delay(EEPROM_WRITE_TIME);
   return eepromStatus;
}


EEPROM_STATUS EEPROM_Read (I2C_HandleTypeDef* hi2c,  
                            uint16_t devAddress, 
                            uint16_t memAddress,
                            uint8_t  *pData,
                            uint16_t size) 
{
   EEPROM_STATUS eepromStatus = HAL_I2C_Mem_Read(hi2c, devAddress, memAddress, I2C_MEMADD_SIZE_8BIT, (uint8_t*) &pData, size, TIMEOUT);
   return eepromStatus;
}
```

It's time to create a variable in `main.c` or any other source file within your project. In the code below, I have created variable `e24lc02` which is the `Object` with type `eeprom_t` as its `Class` for EEPROM chip **24LC02** by **Microchip Technology**. In the variable declaration, I have assigned a function pointer to the functions that we created earlier.

```C

eeprom_t e24lc02 = {
         .size       = 0,
         .eepromI2C  = &hi2c2,
         .devAddress = 0xA0,
         .memAddress = 0x00,
         .init       = MX_I2C2_Init,   
         .read       = EEPROM_Read,
         .write      = EEPROM_Write, 
};

uart_t serial = {
         .uart1      = &huart1,
         .init       = MX_USART1_UART_Init,
         .write      = HAL_UART_Transmit,
         .read       = HAL_UART_Receive,
         .print      = Serial_Print,
         .bufferData = {0},
};

void main(void) {       

}

```

Finally, let's write the code in object oriented format, I always use **Visual Studio Code** environment for my application because of its rich editor features.

```C
void main(void) {
     /* Initialize all configured peripherals --- */
     serial.init();
     e24lc02.init();

     /* Print serial buffer size */
     serial.print(serial.uart1, "Buffer Size: %d", MAX_UART_BUFFER_SIZE);
          
     for(;;) {
         /* EEPROM Data write ----------------------- */
         e24lc02.status = e24lc02.write(e24lc02.eepromI2C, e24lc02.devAddress, e24lc02.memAddress, (uint8_t*)"00000000");
         if(e24lc02.status != EEPROM_OK) 
               serial.print(serial.uart1, "Failed to write value to EEPROM");

        /* EEPROM Data read  -------------------------- */
        e24lc02.size   = 8;
        e24lc02.status =  e24lc02.read(e24lc02.eepromI2C, e24lc02.devAddress, e24lc02.memAddress, (uint8_t*) &e24lc02.pData, e24lc02.size);
        if(e24lc02.status != EEPROM_OK) 
               serial.print(serial.uart1, "Failed to read  value in EEPROM");
     }
}
```

I really hope you find this article meaningful, please comment if you have any question and also help to share this content so that I can another article to show you how to create a `private` class in C.