Object Oriented Embedded C Programming
Olayiwola Ayinde
Posted on February 3, 2022
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;
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);
}
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;
}
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) {
}
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");
}
}
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.
Posted on February 3, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.