Understand these three programming tools from a practical perspective to make your embedded code more elegant and efficient!
π― Introduction: Why Learn to Use Them Together?
Imagine you are managing a smart home system:
- β’ Structs are like a storage box that neatly organizes related items together.
- β’ Unions are like a transformer that can change into different forms within the same space.
- β’ Variants are like a multi-functional room that can transform into a bedroom, study, or gym as needed.
Core Question: Why can’t we use just one structure alone?
The answer is simple: The real world is complex! Embedded systems require:
- β’ β Save memory space
- β’ β Improve code readability
- β’ β Simplify data management
- β’ β Enhance operational efficiency
π Core Concepts: The Essential Differences Among the Three Musketeers
Memory Layout Comparison
Union Concept
State 1
State 2
State 3
Union (union)
Member A: 4 bytes
Member B: 4 bytes
Member C: 4 bytes
Total Size: 4 bytes
Struct (struct)
Member A: 4 bytes
Member B: 2 bytes
Member C: 1 byte
Padding: 1 byte
Total Size: 8 bytes
Feature Comparison Table
| Feature | Struct | Union | Variant Concept |
|---|---|---|---|
| Memory Usage | Sum of all member sizes | Size of the largest member | Dynamic change |
| Data Access | Access all members simultaneously | Only one member can be used at a time | Mutual exclusion of states |
| Main Use | Data organization | Memory sharing | State management |
| Typical Scenario | Sensor data packets | Data type conversion | Device operating modes |
π§ Practical Tip 1: Sensor Data Management
Problem Scenario
You are developing an environmental monitoring device that needs to handle data from multiple sensors: temperature, humidity, pressure, light intensity, etc. The traditional approach might look like this:
// Traditional approach - scattered management
float temperature;
float humidity;
uint32_t pressure;
uint16_t light;
uint8_t sensor_status;
// Parameters need to be passed one by one during data processing
void process_data(float temp, float humi, uint32_t press, uint16_t light, uint8_t status) {
// Processing logic...
}
π‘ Struct Optimization Solution
// Struct solution - unified management
typedef struct {
float temperature; // 4 bytes
float humidity; // 4 bytes
uint32_t pressure; // 4 bytes
uint16_t light; // 2 bytes
uint8_t status; // 1 byte
uint8_t reserved; // 1 byte (alignment padding)
// Total: 16 bytes
} sensor_data_t;
// Data processing becomes simpler
void process_sensor_data(sensor_data_t *data) {
if (data->status & 0x01) { // Check temperature sensor status
printf("Temperature: %.2fΒ°C\n", data->temperature);
}
// Other processing logic...
}
π Union Further Optimization
In some scenarios, sensor data needs to be transmitted via serial or network, and this is where unions come into play:
typedef union {
sensor_data_t data; // Structured access
uint8_t bytes[16]; // Byte array access
uint32_t words[4]; // 32-bit word access
} sensor_packet_t;
// Data transmission example
void send_sensor_data(sensor_packet_t *packet) {
// Send directly by bytes without conversion
uart_send_bytes(packet->bytes, sizeof(sensor_data_t));
}
// Data checksum example
uint32_t calculate_checksum(sensor_packet_t *packet) {
uint32_t checksum = 0;
// Calculate checksum by 32-bit words for higher efficiency
for (int i = 0; i < 4; i++) {
checksum ^= packet->words[i];
}
return checksum;
}
π Performance Comparison
Traditional Method
Function Parameters: 5
Memory Access: Scattered
Transmission Efficiency: Low
Struct Method
Function Parameters: 1 pointer
Memory Access: Continuous
Transmission Efficiency: Medium
Union Optimization
Function Parameters: 1 pointer
Memory Access: Continuous
Transmission Efficiency: High
βοΈ Practical Tip 2: Device State Machine Design
Problem Scenario
A smart socket has multiple operating modes: manual control, timer control, remote control, and energy-saving mode. Each mode has different configuration parameters.
Initialization
Set Timer
Enable Remote
Enter Energy Saving
Restore Manual
Manual Control Mode
Timer Control Mode
Remote Control Mode
Energy Saving Mode
π‘ Union State Management
// Define configuration parameters for various modes
typedef struct {
uint8_t switch_state; // Switch state
uint8_t brightness; // Brightness level
} manual_config_t;
typedef struct {
uint8_t switch_state;
uint32_t on_time; // On time (seconds)
uint32_t off_time; // Off time (seconds)
uint8_t repeat_days; // Repeat days (bitmask)
} timer_config_t;
typedef struct {
uint8_t switch_state;
uint32_t server_ip; // Server IP
uint16_t port; // Port number
uint8_t auth_token[16]; // Authentication token
} remote_config_t;
typedef struct {
uint8_t switch_state;
uint8_t power_threshold; // Power threshold
uint16_t sleep_delay; // Sleep delay
} powersave_config_t;
// Key: Use union to manage configuration of different modes
typedef enum {
MODE_MANUAL,
MODE_TIMER,
MODE_REMOTE,
MODE_POWERSAVE
} device_mode_t;
typedef struct {
device_mode_t current_mode;
union {
manual_config_t manual;
timer_config_t timer;
remote_config_t remote;
powersave_config_t powersave;
} config;
// Common state
uint32_t uptime;
float power_consumption;
} smart_socket_t;
π― Mode Switching Implementation
// Unified mode switching function
int switch_device_mode(smart_socket_t *device, device_mode_t new_mode) {
// Save current state
uint8_t current_switch = 0;
switch (device->current_mode) {
case MODE_MANUAL:
current_switch = device->config.manual.switch_state;
break;
case MODE_TIMER:
current_switch = device->config.timer.switch_state;
break;
case MODE_REMOTE:
current_switch = device->config.remote.switch_state;
break;
case MODE_POWERSAVE:
current_switch = device->config.powersave.switch_state;
break;
}
// Switch to new mode
device->current_mode = new_mode;
// Initialize configuration for new mode
switch (new_mode) {
case MODE_MANUAL:
device->config.manual.switch_state = current_switch;
device->config.manual.brightness = 50; // Default brightness
break;
case MODE_TIMER:
device->config.timer.switch_state = current_switch;
device->config.timer.on_time = 8 * 3600; // 8:00
device->config.timer.off_time = 22 * 3600; // 22:00
device->config.timer.repeat_days = 0x7F; // Every day
break;
// Other modes initialization...
}
printf("Device switched to mode: %d\n", new_mode);
return 0;
}
β‘ Memory Optimization Effects
// Memory usage comparison
sizeof(manual_config_t); // 2 bytes
sizeof(timer_config_t); // 13 bytes
sizeof(remote_config_t); // 23 bytes
sizeof(powersave_config_t); // 4 bytes
// If using separate variables
// Total memory: 2 + 13 + 23 + 4 = 42 bytes
// Using union
sizeof(smart_socket_t); // Approximately 35 bytes (including common fields)
// Memory saved: About 17%
π οΈ Practical Tip 3: Configuration Parameter Management
Problem Scenario
Embedded devices often have many configuration parameters: network settings, sensor calibration values, user preferences, etc. A clear hierarchical management scheme is needed.
π‘ Nested Struct Design
// Network configuration
typedef struct {
uint32_t ip_address;
uint32_t subnet_mask;
uint32_t gateway;
uint32_t dns_server;
char ssid[32];
char password[64];
} network_config_t;
// Sensor configuration
typedef struct {
float temp_offset; // Temperature offset calibration
float temp_scale; // Temperature scaling factor
uint16_t sample_interval; // Sampling interval (milliseconds)
uint8_t filter_level; // Filter level 0-5
} sensor_config_t;
// User interface configuration
typedef struct {
uint8_t language; // 0: Chinese 1: English
uint8_t brightness; // Screen brightness 0-100
uint8_t volume; // Volume 0-100
uint32_t screen_timeout; // Screen timeout (seconds)
} ui_config_t;
// Main configuration structure - hierarchical management
typedef struct {
// Configuration version number
uint32_t version;
uint32_t magic_number; // Used to verify configuration validity
// Classified configurations
network_config_t network;
sensor_config_t sensor;
ui_config_t ui;
// Configuration checksum
uint32_t checksum;
} device_config_t;
π― Configuration Management Functions
// Default configuration initialization
void init_default_config(device_config_t *config) {
config->version = 0x00010001; // v1.0.1
config->magic_number = 0x12345678;
// Default network configuration
config->network.ip_address = 0xC0A80101; // 192.168.1.1
config->network.subnet_mask = 0xFFFFFF00; // 255.255.255.0
strcpy(config->network.ssid, "MyDevice");
strcpy(config->network.password, "");
// Default sensor configuration
config->sensor.temp_offset = 0.0f;
config->sensor.temp_scale = 1.0f;
config->sensor.sample_interval = 1000; // 1 second
config->sensor.filter_level = 2;
// Default UI configuration
config->ui.language = 0; // Chinese
config->ui.brightness = 80;
config->ui.volume = 50;
config->ui.screen_timeout = 60; // 60 seconds
// Calculate checksum
config->checksum = calculate_config_checksum(config);
}
// Configuration validation
bool validate_config(device_config_t *config) {
// Check magic number
if (config->magic_number != 0x12345678) {
printf("Configuration file corrupted: Magic number error\n");
return false;
}
// Check version compatibility
if ((config->version >> 16) > 1) {
printf("Configuration version incompatible\n");
return false;
}
// Checksum verification
uint32_t calculated = calculate_config_checksum(config);
if (calculated != config->checksum) {
printf("Configuration file corrupted: Checksum error\n");
return false;
}
return true;
}
// Save configuration to Flash
int save_config_to_flash(device_config_t *config) {
// Recalculate checksum
config->checksum = calculate_config_checksum(config);
// Write to Flash (simplified here)
return flash_write(CONFIG_FLASH_ADDR, (uint8_t*)config, sizeof(device_config_t));
}
π Configuration Management Flowchart
Yes
No
Yes
No
Yes
No
Device Startup
Read Configuration from Flash
Is Configuration Valid?
Load User Configuration
Load Default Configuration
Configuration Applied Successfully
Runtime Configuration Modification
Need to Save?
Calculate Checksum
Write to Flash
Write Successful?
Update Confirmation
Error Handling
π‘ Best Practice Summary
Usage Principles
| Scenario | Recommended Solution | Reason |
|---|---|---|
| Data Packet Transmission | Struct + Union | Need both structured access and byte-level operations |
| Multiple Operating Modes | Enum + Union | Save memory, type safety |
| Hierarchical Configuration | Nested Structs | Logical clarity, easy maintenance |
| Data Type Conversion | Union | Efficient type conversion, no extra overhead |
β οΈ Cautions
- 1. Memory Alignment Issues
// Possible alignment issue typedef struct { char a; // 1 byte int b; // 4 bytes, may start from address 4 char c; // 1 byte } bad_struct_t; // May occupy 12 bytes instead of 6 bytes // Consider alignment in design typedef struct { int b; // 4 bytes, starts from address 0 char a; // 1 byte char c; // 1 byte char padding[2]; // Explicit padding } good_struct_t; // Clearly occupies 8 bytes - 2. Union Type Safety
typedef struct { enum { TYPE_INT, TYPE_FLOAT } type; // Type identifier union { int i; float f; } value; } safe_union_t; // Check type when using void print_value(safe_union_t *data) { switch (data->type) { case TYPE_INT: printf("Integer: %d\n", data->value.i); break; case TYPE_FLOAT: printf("Float: %.2f\n", data->value.f); break; } } - 3. Byte Order Issues
typedef union { uint32_t value; struct { uint8_t byte0; // Lowest byte in little-endian systems uint8_t byte1; uint8_t byte2; uint8_t byte3; // Highest byte } bytes; } endian_test_t;
π Performance Optimization Tips
- 1. Reduce Function Parameters: Use struct pointers instead of multiple independent parameters
- 2. Memory Locality: Keep related data in the same struct
- 3. Cache Friendly: Avoid overly large structs, consider cache line size
- 4. Compiler Optimization: Use
<span>const</span>and<span>restrict</span>keywords to help the compiler optimize
π Advanced Learning Recommendations
Subsequent Learning Path
Master Basic Concepts
Practical Project Exercises
Deepen Memory Management
Learn Design Patterns
Performance Optimization
Temperature and Humidity Monitoring System
Smart Home Controller
Simple Communication Protocol
Memory Alignment Mechanism
Cache Optimization Strategies
DMA and Data Structures
State Machine Pattern
Observer Pattern
Factory Pattern
Recommended Practice Projects
- 1. Environmental Monitoring Station
- β’ Multi-sensor data collection
- β’ Data packet transmission
- β’ Configuration parameter management
- β’ Multiple operating modes
- β’ State persistence
- β’ Remote control protocol
- β’ High-speed data acquisition
- β’ Memory buffer management
- β’ Data compression algorithms
π Further Reading
- β’ “Embedded C Programming Practice”
- β’ “Deep Exploration of C++ Object Model”
- ARM Cortex-M Series Processor Programming Manual
- Real-Time System Design Principles
π Conclusion
Mastering the combination of structs, unions, and variants is like mastering the “inner skills” of embedded programming. They not only make your code more elegant but also significantly enhance system performance and maintainability.
Key Points Review:
- β’ β Structs are responsible for data organization, aggregating related information together.
- β’ β Unions are responsible for memory optimization, allowing different types to share the same space.
- β’ β State machine design makes complex logic clear and controllable.
- β’ β Hierarchical configuration keeps parameter management orderly.
π¬ Interactive Communication
What data management challenges have you encountered in embedded projects? Feel free to share your experiences in the comments, and letβs discuss better solutions together!
π Follow us for more embedded development insights!