What is meant by "packed" in Embedded C programming?

attribute packed

In embedded C programming, "packed" refers to a method of organizing data in memory where there are no empty spaces or padding between the data elements. When data is packed, it's placed right next to each other without any additional room, which can help save memory space. 

What is "Packing"?

Let's look at an example to illustrate this concept. Suppose we have a structure (struct) in C to represent a point in two-dimensional space:

struct Point {
    int x;
    int y;
};

By default, the compiler may add padding between the x and y members for memory alignment reasons. So, the memory layout could look like this:

| x (4 bytes) | padding (4 bytes) | y (4 bytes) |

In this case, there are extra 4 bytes of padding added between x and y, making the total size of the struct 12 bytes.

Now, if we want to pack this struct to save memory and ensure it's exactly 8 bytes, we can use compiler-specific directives or attributes. For example, using the GCC compiler, we can declare the struct as packed like this:

struct __attribute__((packed)) Point {
    int x;
    int y;
};

With this attribute, the memory layout becomes tightly packed:

| x (4 bytes) | y (4 bytes) |

Now, the struct consumes only 8 bytes of memory, and there's no padding in between.

Packing data is useful when you need to optimize memory usage or when you're working with hardware that expects data to be in a specific format without any extra spacing. However, it's essential to use packing judiciously, as it can lead to performance issues on some architectures due to unaligned memory access.

Is “Packing” Common in Arduino Programming?

 

The use of __attribute__((packed)) or similar directives for packing data structures is less common in 8-bit microcontroller programming like the Arduino UNO for several reasons:

  • Simplicity: 8-bit microcontrollers typically have simpler architectures and memory access patterns compared to their 32-bit or 64-bit counterparts. These microcontrollers often work with byte-sized data (8 bits) naturally, so the need for precise control over memory layout is reduced.
  • Alignment: Many 8-bit microcontrollers do not have strict alignment requirements for data types. Unlike some 32-bit or 64-bit architectures, where data types may need to be aligned to their size (e.g., 4-byte alignment for 32-bit integers), 8-bit microcontrollers often allow unaligned access to data without significant performance penalties.
  • Memory Constraints: 8-bit microcontrollers typically have limited memory resources, and packing data structures tightly may not provide substantial memory savings. In some cases, it might even introduce complexity or make the code less readable.
  • Peripheral Interfaces: When interfacing with external hardware or sensors, 8-bit microcontrollers often use simple serial communication protocols (e.g., UART or SPI), which handle data byte by byte. In such cases, precise memory layouts are less critical.

However, there are situations where using __attribute__((packed)) or similar directives in 8-bit microcontroller programming can still be beneficial:

  • Hardware Registers: When working with hardware registers or memory-mapped peripherals that expect a specific memory layout, you may need to pack data structures to match the hardware requirements. This ensures that read and write operations to the hardware are correctly formatted.
  • Custom Data Formats: If you are designing custom binary data formats for communication between devices or storage, you might use packed structures to precisely control the layout of the data.
  • Cross-Platform Compatibility: If you are developing code that needs to be portable across different microcontroller architectures (including 8-bit and 32-bit), you might use __attribute__((packed)) to ensure consistent behavior across platforms.

While __attribute__((packed)) maybe less commonly used in 8-bit microcontroller programming due to the simplicity of these systems, there are scenarios where it remains valuable. The decision to use it depends on the specific requirements of your project, especially when dealing with memory-mapped hardware, custom data formats, or cross-platform compatibility considerations.

Moreover, the __attribute__((packed)) attribute is not officially supported in the Arduino IDE for the Arduino UNO and other AVR-based boards. The Arduino IDE is primarily designed to be user-friendly and abstract many low-level details to simplify programming for beginners and hobbyists. Consequently, it doesn't provide direct support for advanced compiler attributes like __attribute__((packed)).

So "Packing" Is Only For Non-8-bit Microcontrollers?

Yes, the __attribute__((packed)) attribute is applicable in 32-bit microcontrollers such as STM32 when you are using GCC-based compilers for STM32 microcontrollers. The STM32 family of microcontrollers, which is based on ARM Cortex-M cores, is often programmed using development environments and toolchains that support GCC, such as the STM32CubeIDE, PlatformIO, or custom setups with GCC.

Here's how you can use the __attribute__((packed)) attribute in STM32 programming:

#include "stm32f4xx.h" // Include the appropriate STM32 library for your microcontroller

// Define a packed structure
struct __attribute__((packed)) PackedStruct {
  uint8_t member1;
  uint16_t member2;
  uint32_t member3;
};

int main(void) {
// Your STM32 code here

  return 0;
}
In the example above, we use the __attribute__((packed)) attribute to create a tightly packed structure called PackedStruct. This attribute instructs the GCC-based compiler to pack the structure members without adding any padding bytes for alignment. This can be useful when you need precise control over the memory layout, such as when interfacing with hardware registers or working with specific data formats.