Why not use bit fields for device registers?

A topic that I have touched upon before [here and here] is bit fields in C. They are facility in the C language that should probably be retired. They were designed to perform a single function, which is almost obsolete and irrelevant to modern software. However, they are also mistakenly used for an alternative application that can lead to less maintainable code and I strongly wish to encourage the discontinuation of this practice …

The idea of bit fields is quite simple: you can define a number of integer variables, signed or unsigned, of arbitrary bit size within a structure. For example:

struct bf
{
   unsigned a : 2;
   unsigned b : 2;
   unsigned c : 4;
} z;

This creates three unsigned integers with widths 2, 2 and 4 bits. You can now write obvious code like:

z.b = 3;

Quite straightforward to do, but begs the question: why? The answer is very simple: it saves memory. These three variables might be packed into a single [8-bit] byte. The code to insert and extract the data is created automatically – no masking and shifting necessary. The saving in memory across an application might be significant.

However, nowadays, memory is cheap and, whilst not enormous in many embedded systems, is really not in short supply. So the saving of memory is not that useful, particularly bearing in mind the increased access time. Hence, I would contend that bit fields are mostly obsolete.

Many embedded developers see bitfields as an ideal opportunity to make programming external devices more straightforward. This is because device registers often use groups of bits in a very similar fashion to bitfields. It, therefore, seems logical to create a bitfield structure that matches the bits in the device register and map the structure to the device’s address. Accessing the device register then becomes very straightforward. Except that this is not a satisfactory or safe way to program for several reasons.

Firstly, the order that the bitfields are placed in a word/byte is compiler dependent. In fact, all aspects of how the bits are stored is not under the programmer’s control. For example, the bitfield structure above might look like this:

Or it could look like this:

Only one of these is correct for a device register and the compiler decides which to use. [It could even use some other layout quite validly, but that is less likely.]

Another problem this approach is that, to set a value in a bitfield, the code must read the whole byte/word, set/clear the appropriate bits and write it back. Some device registers do not facilitate read access, so this mechanism will not work. Furthermore, some devices respond oddly when they are read from and written to at unexpected times.

The best approach to managing a device register is to keep a “shadow” copy in memory and manage its content by appropriate AND and OR operations, writing the whole word/byte to the device when necessary.

Leave a Reply