Structs (struct) and Enums (enum) are essential tools in Embedded C programming that help organize data and improve code readability and maintainability.
In AVR microcontrollers, they are particularly useful for managing sensor data, handling configuration settings, and working with registers.
This tutorial will cover:
- Understanding Structs (struct)
- Working with Structs in Embedded C
- Using Pointers with Structs
- Understanding Enums (enum)
- Using Enums for Readability
- Using Structs and Enums Together
- Practical Examples with AVR Microcontrollers
- Best Practices for Structs and Enums in Embedded C
What is a Struct (struct)?
A struct (structure) is a user-defined data type that groups multiple related variables of different data types under one name.
Why Use Structs in Embedded Systems?
- Organize related data (e.g., storing multiple sensor readings).
- Improve readability by grouping relevant variables together.
- Reduce memory fragmentation in RAM-limited microcontrollers.
Defining and Using Structs in Embedded C
Basic Struct Example
#include <avr/io.h> typedef struct { uint16_t temperature; uint16_t humidity; uint16_t pressure; } SensorData; void main() { SensorData sensor; // Declare a struct variable sensor.temperature = 25; sensor.humidity = 50; sensor.pressure = 1000; while(1); }
Explanation:
- typedef struct { … } SensorData; creates a custom data type.
- The variable sensor stores temperature, humidity, and pressure values.
- Structs help organize sensor data in a clean and efficient way.
Using Pointers with Structs
Using pointers to structs allows efficient memory access and modification.
Example: Passing Structs to a Function Using Pointers
#include <avr/io.h> typedef struct { uint16_t temperature; uint16_t humidity; } SensorData; void updateSensor(SensorData *data) { data->temperature = 30; data->humidity = 55; } void main() { SensorData sensor; updateSensor(&sensor); // Pass struct by reference while(1); }
Key Points:
- The function updateSensor(SensorData *data) receives a pointer to a struct.
- data->temperature = 30; modifies the struct directly.
- Using pointers instead of copying structs saves memory and execution time.
What is an Enum (enum)?
An enum (enumeration) is a user-defined data type that assigns meaningful names to integer constants.
Why Use Enums in Embedded C?
- Improves code readability (e.g., LED_ON instead of 1).
- Avoids “magic numbers” that make debugging difficult.
- Useful for state machines, error codes, and peripheral modes.
Using Enums in Embedded C
Example: Enum for LED States
#include <avr/io.h> typedef enum { LED_OFF = 0, LED_ON = 1 } LED_State; void main() { LED_State led = LED_OFF; while(1) { if (led == LED_OFF) { PORTB &= ~(1 << PB0); // Turn LED OFF } else { PORTB |= (1 << PB0); // Turn LED ON } } }
Explanation:
- typedef enum { LED_OFF, LED_ON } LED_State; creates named constants.
- Instead of using 0 or 1, we use LED_OFF and LED_ON for clarity.
- This makes code easier to read and maintain.
Using Enums for Peripheral Configurations
Enums are useful for defining hardware configurations.
Example: Enum for ADC Configuration
#include <avr/io.h> typedef enum { ADC_REF_AREF, ADC_REF_AVCC, ADC_REF_INTERNAL } ADC_Reference; void setupADC(ADC_Reference ref) { switch(ref) { case ADC_REF_AREF: ADMUX &= ~(1 << REFS0); break; case ADC_REF_AVCC: ADMUX |= (1 << REFS0); break; case ADC_REF_INTERNAL: ADMUX |= (1 << REFS1); break; } } void main() { setupADC(ADC_REF_AVCC); // Use AVCC as reference voltage while(1); }
Explanation:
- ADC_Reference assigns names to ADC voltage reference settings.
- The function setupADC() configures ADMUX using named values instead of numbers.
Combining Structs and Enums
Using structs and enums together improves data organization and efficiency.
Example: Struct with Enum for System Status
#include <avr/io.h> typedef enum { SYSTEM_OK, SYSTEM_ERROR, SYSTEM_OVERHEAT } SystemStatus; typedef struct { uint16_t temperature; uint16_t voltage; SystemStatus status; } DeviceStatus; void updateDeviceStatus(DeviceStatus *device) { if (device->temperature > 80) { device->status = SYSTEM_OVERHEAT; } else { device->status = SYSTEM_OK; } } void main() { DeviceStatus myDevice = {75, 5, SYSTEM_OK}; updateDeviceStatus(&myDevice); while(1); }
Explanation:
- SystemStatus stores system states as an enum.
- DeviceStatus groups temperature, voltage, and system status as a struct.
- The function updateDeviceStatus() updates status based on temperature.
Practical Example: Using Structs and Enums in a Sensor System
This example reads sensor values and indicates status using structs and enums.
#include <avr/io.h> typedef enum { NORMAL, WARNING, CRITICAL } SensorLevel; typedef struct { uint16_t temperature; uint16_t humidity; SensorLevel level; } SensorData; void checkSensor(SensorData *sensor) { if (sensor->temperature > 50) { sensor->level = CRITICAL; } else if (sensor->temperature > 30) { sensor->level = WARNING; } else { sensor->level = NORMAL; } } void main() { SensorData mySensor = {25, 60, NORMAL}; checkSensor(&mySensor); while(1); }
Why This is Useful?
- Structs group sensor readings and status level.
- Enums define clear threshold levels (NORMAL, WARNING, CRITICAL).
- Function checkSensor() updates sensor status based on temperature.
Best Practices
Use typedef for cleaner code when working with structs and enums.
Use pointers (*ptr) to pass structs to functions efficiently.
Use enums for states, configurations, and error codes instead of raw numbers.
Use bit fields inside structs for optimizing memory usage.
Keep structs small to save RAM in low-memory microcontrollers.
Use meaningful names in enums to improve readability.
Summary
Feature | Purpose | Example |
---|---|---|
Struct (struct) | Group related variables | typedef struct { int temp; } Sensor; |
Pointer to Struct | Efficient memory handling | Sensor *ptr = &sensor; |
Enums (enum) | Define readable constants | typedef enum { LED_ON, LED_OFF } LED_State; |
Enums in State Machine | Manage system states | typedef enum { IDLE, RUNNING } State; |