ArticlesEmbedded C & MCU

Embedded C: Struct and Union (Part 2)

Usage Cautions and Real-life Applications

The first part of this article has introduced the concepts behind Structs and Unions, and how to define and use each either independently, or even interleaved .i.e. a Union inside a Struct. In this part, we’re going to see useful applications of Struct and Union in Embedded C/C++, like how to use them to map registers to access its fields and we are going to discuss some pros and cons of bit fields.

Bit Fields in A Nutshell

Bit fields are designed specifically to reduce the needed memory amount to a minimum, where the same memory location can be divided to “bit fields” instead of having a dedicated location for every bit field. To declare a bit field inside a Struct, use the “:” operator followed by the number of bits as an integer value.

What bit fields do, is basically masking bitwise operations to access the value of its fields. They are actually memory addresses with specific lengths (foo occupies 1 Byte). To examine this, we will disassemble an (.elf) file after compiling a c++ code for an AVR MCU (Arduino Sketch). The assembly code shows how to access a value inside “foo”:

So, using bit fields saves memory, that’s right, but add more instructions to access the bit fields variables. One more line of code couldn’t be a problem, but this is not the best case. There are complicated Structs or even simple ones but with special cases i.e. when using bit fields belongs to 2 bytes in the same time. Let’s tweak “foo” a little to explain how:

“b” field has 4 bits belong to the first byte (0x8001db) and 2 bits belong to the next byte (0x8001dc). So the compiler has to add much more lines to deal with “foo.b” and that’s clear in the assembly code.

Let’s see what it will look like if foo.a was used with the old foo’s definition

Less lines of code!

So it’s important to keep an eye on how fields are divided. For instance, the last “foo” definition can be modified to be like this:

So a pad was added as needed to avoid unwanted behavior/performance.

Don’t Trust The Code, Listen to the Compiler

Dealing with bit field needs open eyes as we have seen in the last example, where the Struct division has a great impact on performance and size of code. Now, two examples can say why doesn’t the compiler understand the struct definition as we expect. The following examples are adapted from questions appeared on Stackoverflow website.

Example #1

These two Structs should be the same, right?  Saying “unsigned char b:8;” and “unsigned char b;” seems the same for us. But actually, if we print the size of foo and FOO, we will find that foo’s size is 2 and size of FOO is 3. But Why? The compiler understands the first one as the following:

  • First Byte: 0..3 bits for “a” and 4..7 for first 4 bits for “b”.
  • Second Byte: 0..3 bits for the rest of “b” and 4..7 for “c”.

In the second Struct:

  • First Byte: 0..3 bits for “a” and 4..7 padded (unused).
  • Second Byte: for b.
  • Second Byte: 0..3 bits for “c” and 4..7 unused.

Example #2

In this two Structs, most of us will expect to have the same size (both 8 Bytes), but the fact is the first one will have the following Bytes:

  • Bytes 0..3: for unsigned long a:1.
  • Bytes 4..7: for unsigned long b:32.
  • Bytes 8..11: for unsigned long c:1.

While the second one will have:

  • Bytes 0..3: for unsigned long a:1 and unsigned long b:31.
  • Bytes 4..7: for unsigned long c:1.

Should I Use Bit Field Or Not?

 

As any other solution in engineering world, it’s not always a win-win situation. Bit-fields save place in data memory, and they also provide a simplified way to set and get values (that aren’t byte-aligned) rather than using bitwise operations. On the other hand, this means that whenever you use a bit-field variable, the processor/compiler will perform READ-MODIFY-WRITE operations. As the memory location is shared with others, then the compiler will read the entire variable to store it in a temporary place, mask other fields, change the value and then restore the value of unchanged fields (this is what is called READ-MODIFY-WRITE operation).

So it’s true that we saved some space in the SRAM, but we will need more instructions for each reading/writing operation in the Flash memory. Accessing a bit-field variable is another concern when using bit-field. It is not an atomic operation, which could lead to faults in some critical code sections. Especially for shared bit-field variables between interrupts and processes.

Many developers advocate to avoid using bit fields because it makes the code less portable as changing the compiler/version means changing how it looks and do with bit field. They also advocate to use bit-banding (the hardware version of bit fields) when it’s available. Actually, the past line was written in an article I wrote about bit-field variables.

Have a look for this great discussion via Hacker News board about this article.

Real Life Applications of Structs and Unions in Embedded Systems

Application #1: Access to Bits, Nibbles and Bytes of Variables

Note: Order in struct is important. It starts from LSB to MSB.

 

Application #2: Implementing Protocols

Any non-ASCII protocol has fields with special meaning inside each byte (or any other size of data). Let’s say that we have a protocol that starts with one byte called “command”, and has the first bit to indicate direction, if it’s (get or response), and 2 bits as an address and rest is the command id. Using bitwise operation may be annoying. As Struct already do the necessary bitwise operations, then it’s useful to implement the protocol packets using a Union.

Thus, when sending or receiving a cmd packet can be done using that Union. A demo pseudo-code:

Application #3: Access to MCU Registers

Representing hardware registers as Bitfields is a very handy trick and a useful way for the ease of MCU register access. This techniques is used widely in SDKs i.e. ARM cortex M3/M4 SiliconLabs’ Gecko SDK. Using this technique, each register can be accessed using a bit field struct with its name. Later this struct will point to the peripheral/register base-address.

For EFM32 SDK, a struct was defined for GPIO:

Later in another header file, there is a “define” for a pointer with “GPIO_TypeDef” type casting to the real address:

Now when any register need from GPIO, simply can be access using GPIO pointer. A Demo:

This method of mapping hardware registers was stated in an article via ARM information Center and discussed in detail in “Representing and Manipulating Hardware in Standard C and C++” by Dan Saks besides other ways.

Yahya Tawil

Embedded Hardware Engineer interested in open hardware and was born in the same year as Linux. Yahya is the editor-in-chief of Atadiat and believes in the importance of sharing free, practical, spam-free and high quality written content with others. His experience with Embedded Systems includes developing firmware with bare-metal C and Arduino, designing PCB&schematic and content creation.

Related Articles

One Comment

  1. It might be good to mention, that in standard C and C++ it is undefined behavior to write to a union using one field and read using another (see for example http://en.cppreference.com/w/cpp/language/union). It works on many compilers (and optimizations) and platforms, but you need to be sure of it before using this feature.

    Also, packing and ordering in bitfields is implementation-defined (http://en.cppreference.com/w/c/language/bit_field), so the wire-format between different platforms and compilers might not be compatible when serializing using bitfields.

Leave a Reply

Your email address will not be published. Required fields are marked *