71. Decode ADC Result Using Union Bitfields

Many embedded microcontrollers store ADC results in a packed format where a 12-bit ADC value and channel number are stored together in a 16-bit register.

You are given a 16-bit ADC register where:

  • Bits 0–11 represent the ADC result (0–4095)
  • Bits 12–15 represent the ADC channel (0–15)

Your task is to:

  1. Define a union that overlays:
    • A raw 16-bit uint16_t adc_reg
    • A struct with:
      • adc_value (12 bits)
      • channel (4 bits)
  2. Read adc_reg from input
  3. Extract and print:
    • Channel number
    • ADC result (0–4095)
       

Example-1

Input: 0xC3F5
Output:
Channel: 12  
ADC Value: 1013

 

Example-2

Input: 0x10FF
Output:
Channel: 1  
ADC Value: 255


 

Need Help? Refer to the Quick Guide below

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