Ground Embedded KO Subscribe

Notes / C Language

Structs, unions and bit-fields

Describing register blocks and communication packets with structs, and why alignment and padding appear.

GROUND EMBEDDED · NOTEC Language 08Structs, unions and bit-fields 1volatile uint32_t *ODR 2 = (uint32_t*)0x40020014; 3*ODR |= (1u << 5); 4while (1) { 5 if (flag) toggle(); 6}

Structs

A struct groups related values. MCU header files declare a peripheral's registers as struct members in address order and point a pointer to that struct at the peripheral's base address. TIM2->CNT works exactly this way.

Alignment and padding

A 32-bit CPU wants a 4-byte value at an address that is a multiple of 4. So the compiler inserts empty bytes between members.

struct { uint8_t a; uint32_t b; };   // sizeof is 8, not 5

Send a struct as a packet as-is and those empty bytes go out too, and the other side misreads it. Serialise packets byte by byte, or use the packed attribute knowing the cost of unaligned access.

Unions

A union views the same memory as several types. It is often used to split a float into a 4-byte array for sending.

Bit-fields

uint32_t en : 1; divides members at the bit level. It reads well, but bit order differs between compilers, so masks and shifts are the safer way to define registers and communication formats.

A struct is bigger than you think

struct a { uint8_t flag; uint32_t value; uint8_t id; };   // 12 bytes
struct b { uint32_t value; uint8_t flag; uint8_t id; };   //  8 bytes

The compiler inserts gaps (padding) so that each member lands on an address that is a multiple of its size.

+0flag1 byte+1 ~ +3paddinggap+4 ~ +7value4 bytes+8id1 byte+9 ~ +11paddinggap
The actual layout of struct a. Placing the uint32_t on a multiple of 4 left two gaps of 3 bytes.

Declaring the largest members first reduces padding. For a struct you make hundreds of in an array, the difference is RAM.

Using a struct directly as a communication packet

typedef struct __attribute__((packed)) {
    uint8_t  header;
    uint16_t length;
    uint32_t timestamp;
} packet_t;                       // exactly 7 bytes, no padding

With packed the padding disappears and the struct can be overlaid on the receive buffer. Two things need care.

  • Unaligned access: you end up reading a uint32_t at an odd address. On cores that do not support it, such as Cortex-M0, this faults.
  • Byte order: if sender and receiver differ in endianness, the value is reversed. State the byte order in the protocol document and, to be safe, assemble values one byte at a time.
uint16_t len = (uint16_t)buf[1] | ((uint16_t)buf[2] << 8);   // assembled as little-endian

Unions: two views of the same memory

typedef union {
    uint32_t word;
    uint8_t  byte[4];
} u32_t;

u32_t v; v.word = 0x12345678;
// little-endian MCU: byte[0] = 0x78, byte[3] = 0x12

Convenient for splitting a 32-bit value into bytes to send. The result depends on endianness, though, so for data exchanged with a different kind of MCU it is safer to split it yourself with shifts.

Bit-fields: convenient but not portable

typedef struct {
    uint32_t enable : 1;
    uint32_t mode   : 2;
    uint32_t        : 5;      // reserved
    uint32_t prescaler : 8;
} ctrl_t;

The code is easier to read, but the compiler decides which end the bits are filled from. The same declaration can mean different bit positions on another compiler. The usual rule is therefore masks and shifts where bit positions are fixed, such as hardware registers and packets, and bit-fields only for groups of flags inside the program.

Passing a struct to a function

Passed by value, the whole struct is copied onto the stack. Passed by pointer, only 4 bytes travel. If the function only reads, add const to show the intent.

void motor_update(const motor_cfg_t *cfg, motor_state_t *st);

Check yourself

  • How many bytes is struct { uint8_t a; uint16_t b; uint8_t c; }? How small can it get if you reorder it?
  • Code checks the received length against sizeof(packet_t). What happens if packed is removed?
FREE PDFGet the 3 free ebooksIf you want to keep reading this topic as one book: BLDC motor control, serial communication and sensors at book length. PDFs are in Korean.Get them free →