01Union
In the previous article “Combining Enumerations and Structures”, it was mentioned that a structure is like a packaging encapsulation, encapsulating some variables with common characteristics inside. A structure is a constructed type or complex type that can contain multiple members of different types. In the C language, there is another syntax very similar to structures, called a union (Union), which is also referred to as a shared type in some places.
Here is an example of a union:
union data{ char n; char ch; char f;};union data a, b, c;
The difference between structures and unions is that: the members of a structure occupy different memory spaces and do not affect each other; while all members of a union occupy the same memory space, modifying one member will affect all other members.
A structure occupies memory greater than or equal to the total memory occupied by all its members (there are byte alignment issues, which will not be discussed in depth here).
A union occupies memory equal to the size of its largest member. If a new value is assigned to a member of the union, it will overwrite the value of the original member.
// The union defined abovechar data;a.n = 0x0A;// Although only the member variable n is modified, the variables ch and f will also be changeddata = a.f;// Therefore, the value of data will be modified to 0x0A
02Applications of Unions
Unions are rarely used in general programming applications, but are more common in microcontroller programming. In the previous article “Combining Enumerations and Structures”, the combination of enumerations and structures was discussed. The following example briefly illustrates the application of unions within structures (in my daily development, unions are generally used together with structures).
When working on display applications, a concept is involved: a pixel on a display (full color) is composed of Red, Green, and Blue colors. In 888 mode, each pixel is made up of 8 bits. At this point, to represent a single pixel, a structure is needed, which corresponds to the previous article where a structure is like a packaging encapsulation, encapsulating some variables with common characteristics inside.
typedef struct{ uint8_t Red; uint8_t Green; uint8_t Blue; uint32_t Pix_Value;}LCD_Pixvalue_S;
This way of writing is very clear and easy to access; you can access the entire pixel or access a specific color of the pixel. One issue is that when I operate on the red pixel, I need to reassign the Pix_Value member, as shown below:
LCD_Pixvalue_S LCD_Pixvalue;LCD_Pixvalue.Red = 0x12;LCD_Pixvalue.Pix_Value = LCD_Pixvalue.Red<<16 | LCD_Pixvalue.Red<<8 |LCD_Pixvalue.Blue;
This requires an extra line of code, and the memory usage is also large. Of course, using the following writing method will not occupy extra memory, but access is inconvenient.
typedef struct{ uint32_t Pix_Value;}LCD_Pixvalue_S;
At this point, using a combination of unions and structures can save memory while also being easy to access.
typedef union{ struct{ uint8_t Red; uint8_t Green; uint8_t Blue; }Pix; uint32_t Pix_Value;}LCD_Pixvalue_S;
Then you can operate as follows:
LCD_Pixvalue_S LCD_Pixvalue;uint32_t data;LCD_Pixvalue.Pix_Value = 0x0012FF00;LCD_Pixvalue.Pix.Red = 0x25;// Modify red individuallydata = LCD_Pixvalue.Pix_Value;// The value of data will be 0x0025FF00
Of course, you can also define the structure outside, and it can be used elsewhere, as shown below:
typedef struct{ uint8_t Red; uint8_t Green; uint8_t Blue; }Pix_s;typedef union{ Pix_s Pix; uint32_t Pix_Value;}LCD_Pixvalue_S;
Regarding memory usage, the above definition method defines a variable of type LCD_Pixvalue_S occupying 4 bytes. The example image is as follows:
03Application of Unions in Serial Port Development
The above example illustrates the application of unions in LCD applications, which is due to the fact that a pixel in an LCD is composed of red, green, and blue colors, making unions very convenient. In microcontroller project development, serial protocol parsing can also utilize unions, which is very convenient.
In private custom protocols, defining the protocol reasonably and utilizing unions in the code is very convenient. The following example does not discuss the frame header and footer parts, illustrating the application of unions in protocol parsing, flexibly applied.
Here is an example of a serial protocol:
Function |
Byte Length |
Command Length |
4 |
Command Word |
1 |
Command Content |
7 |
CRC16 |
2 |
The code can be written as follows:
typedef union{ struct{ uint32_t cmdlen; uint8_t cmd; uint8_t cmdbuf[7]; uint16_t crc16; }unit; uint8_t buffer[14];}uart_buffer_s;uart_buffer_s uart_buffer;int main(void){ uint8_t len; len = 0; uart_buffer.buffer[len++] = 0x12; uart_buffer.buffer[len++] = 0x34; uart_buffer.buffer[len++] = 0x56; uart_buffer.buffer[len++] = 0x78; uart_buffer.buffer[len++] = 0xAA; for(int i=0;i<7;i++) { uart_buffer.buffer[len++] = i; } uart_buffer.buffer[len++] = 0x11; uart_buffer.buffer[len++] = 0x22; while (1);}
The result of running is as follows:
As you can see, we simply filled the uart_buffer.buffer with data, simulating serial port data reception. After receiving, it automatically parses our custom cmdlen, cmd, cmdbuf, and crc16. It should be noted that the 16-bit and 32-bit data types are in little-endian mode; for more on little-endian mode, please refer to the previous article “C Language Memory Allocation in STM32“. This is still very convenient.
!!! However!!! It is important to note the byte alignment issue. For example, if the above cmdbuf is modified to 8 bytes, it will cause problems, as shown below:
Then crc16 will have issues, which is the byte alignment problem. Students who do not understand this should search for it online; it will not be explained in detail here.
In addition to the above custom protocol parsing, unions can also solve the reading problem of floating-point types. A float occupies 4 bytes; how can we convert the 4 bytes received from the serial port into a float? Unions can solve this problem.
In the following example code, we know that the hexadecimal representation of the floating-point number 231.5 is 0x43678000.
typedef union{ float data; uint8_t buffer[4];}uart_buffer_s;uart_buffer_s uart_buffer;int main(void){ uint8_t len; len = 0; uart_buffer.buffer[len++] = 0x00; uart_buffer.buffer[len++] = 0x80; uart_buffer.buffer[len++] = 0x67; uart_buffer.buffer[len++] = 0x43; while (1);}
The result is as follows:
As we can see, we simulated receiving 4 bytes from the serial port, and using a union, we can automatically convert it to a float type without writing extra code. Of course, this conversion is also in little-endian mode; for a detailed explanation of little-endian mode, please refer to the article “C Language Memory Allocation in STM32“.