In real world applications, transferring a single number or character from one device to another, has very little use cases. A fairly sophisticated application will use structures to group data. Consider an IMU measuring orientation of an Unmanned Aerial Vehicle (UAV), communicating to the UAV’s main controller which stabilizes the aircraft. At a defined frequency, IMU would be transmitting the roll, pitch and yaw of the UAV. Instead of sending these values separately, struct enables the developers to group them into a single structure and transmit it. To demonstrate this with an example, let us look at the Simple Integrated System[1], that we discussed in my earlier posts[2][3][4][5]. Struct would make it easy for us to control multiple devices connected to the STM32 microcontroller by just writing the appropriate commands to the device file, /dev/stm32, in raspberry pi.
Our system has two peripherals such as an LED and a servo motor, attached to the STM32 microcontroller. A raspberry pi is connected to the STM32 controller through an I2C bus. The expected functionalities are, depending on the input data,
change the frequency of the LED blink
set the position of the servo motor
So, the two factors pertinent here are the device_type and the data. Let us call the struct, Payload. So, the struct can be defined as follows.
typedef struct{
char device_type; // can either be l or s for LED and Servo respectively
uint8_t data;
} Payload;
No matter what kind of data you are transferring through whatever medium or protocol in a computing system, they are transferred byte by byte. The videos you watch, the images that you see in the web; All are being transferred as bytes. Serialization is the process of converting a struct or an object into byte(s) that can be transferred. So, our struct has to be serialized before it can be sent from the raspberry pi to STM32. One way to do it shown below.
int send_payload(Payload *payload){
char buff[2];
buff[0] = payload->device_type;
buff[1] = payload->data;
int count = i2c_master_send(arm_i2c_client, buff, 2);
return count;
}
As you can see in the previous code snippet, two consecutive bytes are sent through I2C bus from the raspberry pi to the STM32. Nowhere is it mentioned or hinted that this of type, Payload. So, STM32 does not magically know that a Payload is being sent. It is the burden of the developer to catch the bytes in order and create a struct out of it.
int i2c_data_to_payload(uint8_t* data, Payload* payload){
// convert the data into payload
payload->device_type = data[0];
payload->data = data[1];
return 0;
}
Now that we have the received data encapsulated into a struct, it can be passed as a single argument to another function which takes care of the peripheral’s unique activities. An example of such a function is given below.
int peripheral_action(Payload* payload){
// This function takes payload as argument and delegates the proper action based on its attributes
int status = 0;
switch(payload->device_type){
case 'l':
// It is an led. So, blink the LED at the given frequency
set_led_blink_frequency(payload->data);
break;
case 's':
// It is a servo motor. So rotate the motor to the set degree
rotate_servo(&servo_motor, payload->data);
break;
default:
status = 1;
}
return status;
}
With this implementation, the following commands will cause the LED to blink at a frequency of 10 Hz and set the position of the servo motor to 55 degrees, respectively.
echo l 10 > /dev/stm32
echo s 55 > /dev/stm32
If you are interested in this project, you can download the code pertinent to this article from the github repository[1]. It is tagged as demo2[6].
The medium article can be found here.
[1] https://github.com/rocheparadox/simple-integrated-system
[6]https://github.com/rocheparadox/simple-integrated-system/releases/tag/demo2