Unions

A union is a special data structure where all members share the same memory location.

It allows we to store data in one form and access it in another, without conversions.

union Example {
    uint32_t num;
    uint8_t bytes[4];
};

In this example, num and bytes occupy the same 4 bytes of memory.

Why Use Unions in Firmware?

Unions are widely used in embedded firmware to:

Use CaseExample
Access individual bytes of a registerTransmitting 32-bit values as 4 bytes
Overlay structured fields on raw buffersParse sensor or protocol frames
Serialize/deserialize data to/from byte streamsUART, SPI, I2C communication
Interpret float as 4 raw bytesSending float over network or UART

 

Key Concept: Memory Reinterpretation

we can write to one member of the union and read from another:

union Data {
    float f;
    uint8_t bytes[4];
};

union Data d;
d.f = 3.14;

// Now d.bytes contains raw byte representation of float

Important: No memory is copied. This is just a reinterpretation of the same bytes.

 

Common Union Patterns in Firmware

1. Extract Bytes from a 32-bit Value

union {
    uint32_t value;
    uint8_t bytes[4];
} reg;

reg.value = 0x12345678;
// reg.bytes[0] = 0x78 (LSB), reg.bytes[3] = 0x12 (MSB)

Useful in:

  • Sending 32-bit data over 8-bit UART
  • Writing data into memory-mapped buffers

2. Modify Specific Bytes

reg.bytes[0] = 0xAB;  // Modify LSB
reg.bytes[3] = 0xCD;  // Modify MSB

Updates the overall reg.value automatically.

3. Overlay Struct on Union (Packet Layout)

union Packet {
    struct {
        uint8_t header;
        uint8_t length;
        uint8_t payload[2];
        uint8_t checksum;
    };
    uint8_t raw[5];
};

we can fill fields via struct and transmit raw[ ], or receive raw[ ] and access fields via struct.

4. Decode Bitfields from ADC or Status Register

typedef union {
    uint16_t raw;
    struct {
        uint16_t data : 12;
        uint16_t gain : 2;
        uint16_t ready : 1;
        uint16_t error : 1;
    } bits;
} ADC_Result;

ADC_Result res;
res.raw = 0x8D4E;

We can access res.bits.gain or res.bits.data directly, like register view.

5. Transmit Float as Bytes

union {
    float f;
    uint8_t b[4];
} tx;

tx.f = 2.718;
// Now send tx.b[0..3] over UART

 

Struct vs Union — Key Difference

  • Struct: All members occupy separate memory — total size is the sum of all members (plus padding).
  • Union: All members share the same memory — total size is equal to the size of the largest member.

Example:

struct S {
    uint32_t a;   // 4 bytes
    uint8_t  b;   // 1 byte
}; // Likely size = 8 (due to padding)

union U {
    uint32_t a;   // 4 bytes
    uint8_t  b;   // shares with a
}; // Size = 4

Union Firmware Relevance

Where It HelpsHow
Low-level communicationConvert structured data to raw byte stream
Compact control register designAccess fields and bytes efficiently
Protocol simulationInterpret packets by field
Debugging / AnalysisByte-level inspection of float/int
Buffer-safe conversionNo memcpy needed

 

Common Pitfalls

MistakeFix
❌ Assuming order of bytes is same across systems (endianness)Use endian-aware handling
❌ Accessing wrong member after overwriteAlways ensure which member was last written
❌ Using union across different compilersCompiler-specific behavior in padding/bit order (document  format)
✅ Use unions for reinterpretation, not logic computation 
✅ Always check sizeof(union) if using in communication 

Concept understood? Let's apply and learn for real

Practice now