xyf
xyf

Reputation: 714

How to structure an embedded application while decoupling application and driver code for a peripheral (UART)?

So I'm writing drivers for UART on STM32 and even though I kind of have an idea on laying out the structure, I'd still want to clarify prior to implementing, and also considering how essential it is to keep the code clean and organized.

So I have main.c, sensor.c (application file that uses UART layer), hal_usart.c (which is the driver file).

I have heard different things regarding how application code should have no clue whatsoever about the driver APIs, and read up an article the other day that you could decouple the sensor code from HAL driver code using function pointers but not sure how I could do that here, and if that will decouple it considering I'm still passing a reference to USART_Handle struct that contains info like baudRate, parityControl, wordLength, reference to USART_TypeDef etc.

Below is a snippet of my idea:

// main.c
static USART_Handle pUSART;

int main(void) {
   // initialize clocks/HAL 

   // ...initialize USART struct

   // Get data via UART (calling application API)
   GetData(&pUSART);   
}

// sensor.c (application)
void GetData(USART_Handle *pUSART) {
     HAL_USART_TX(); 
     HAL_USART_RX();   // assuming data is stored in one of the struct members
}

// hal_usart.c  (Driver file)
void HAL_USART_TX() {} 
void HAL_USART_RX() {}



Upvotes: 1

Views: 751

Answers (2)

The utility of function pointers is maximum when you want to change the pointer while the program is running; or, in other words, when you have at least two different functions and, based on some condition, you want to use one or another.

For example, let's say you have a routine which reads a sensor using a serial port, and the user can switch from a sensor to another (two serial ports or even two different methods). In that case, the sensor routine could call a function, to read, using a function pointer, and manage the back read data. The main program, by changing the function pointer, can instruct the sensor routine to use one or the other sensor.

If you don't need to change things at runtime, you can simply write your sensor routine making it call a reading function (external) defined in some other file. The documentation of the sensor routine simply has to state that somewhere must be defined a routine called "int sensor_get_data()".

This involves to design your own "internal protocol" based on what data goes and comes from the "detached drivers". For example, the sensor routine, which copes with a precise model of a sensor, can have the need to send a command and receive a response. You can write your sensor routine which construct the command and decodes the answer, and take away the low level details wrapping all them in the single function "int sensor_get_data(int command)". You then link or include the sensor code, and implement the function sensor_get_data() in the main.

The main() does not know the details of the sensor, it only knows that the sensor code, when needed, will call the function sensor_get_data(). But this function can use an UART, or an i2c or a spi, without the sensor routine even noticing that; moreover, this routine can use any of the three ports, perhaps basing on parameters the user can modify at runtime.

All this can be named "callback mechanism", and implements a sort of separation between declaration and implementation, as mentioned in the other answer. What I depicted is not different from that, but it is static - the program is compiled with a fixed callback, instead of passing a function pointer in every call.

Upvotes: 1

dustin2022
dustin2022

Reputation: 294

Since you would like to separate Application code and Driver code in an embedded system, I suggest you study how to implement callback functions in C also embedded systems.

Here is the reference.

Upvotes: 1

Related Questions