DEV Community

Cover image for Object Oriented Embedded C Programming
Olayiwola Ayinde
Olayiwola Ayinde

Posted on • Updated on

Object Oriented Embedded C Programming

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:

#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;

Enter fullscreen mode Exit fullscreen mode

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.

#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);
}

Enter fullscreen mode Exit fullscreen mode

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

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;
}
Enter fullscreen mode Exit fullscreen mode

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.


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) {       

}

Enter fullscreen mode Exit fullscreen mode

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.

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");
     }
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (1)

Collapse
 
kenechif profile image
Kenechif

Great post, quite insightful.
Thanks for the effort.