Flags play an important role in embedded software development. Microcontrollers and chips are using registers where single bits or combinations of bits play a big role in the configuration. All the bits and their role are described in the specification, but writing the bits directly in the code would be very confusing and hard to read:

AHBMASK.reg = 0x14 // Huh!?

For this reason it makes sense to write an interface to access the registers of a chip. This interface will define identifiers, in the form of constant values, to build bit combinations to write into the registers.

The Outdated and Bad Approach

Chip manufacturers are well known for their extremely bad and outdated interfaces to access chip registers. Let us have a look at the CMSIS implementation from Atmel. This is part of the interface to access registers in one of the microcontrollers. Please ignore the copyright block, it is just added for legal reasons.

To be fair, CMSIS is a hardware abstraction layer standard defined by ARM. It shall standardise the software interfaces across all Cortex-M products. Therefore Atmel had no choice as to follow this standard.

Do you feel the dust on this code? There are a vast amount of problems caused by this interface.

First the values are defined as macros instead of constants. All this identifiers will clutter the global namespace of your code and make naming conflicts very likely. Way better would be using simple constants like this:

const uint8_t PM_AHBMASK_DSU_Pos = 3;
const uint32_t PM_AHBMASK_DSU = (0x1ul << PM_AHBMASK_DSU_Pos);

This would give the compiler the correct hint about the used data type for the registers. All this constants could be put into a own namespace to prevent any name collisions with the user code.  It would still not prevent incorrect assignments.

I can write and compile the following code, which absolutely makes no sense:

CPUSEL.reg = PM_AHBMASK_DSU; // nonsense!

There will be not even a compiler warning about any problems and this is exactly what makes debugging embedded code very difficult.

Use the C++ Language!

I personally think, many software developers writing C++ code for hardware do not make fully use of the language. One reason could be the lack of support of many language features at the beginning or just the lack of experience.

In the the last 10 years, there was a huge progress in the development of the C++ language. Ignoring this progress would be silly in my opinion. Especially the C++11 standard added lots of useful features to the language.

A good support for the C++11, C++14 or even better for C++17 is very important, especially for embedded software development. Many of the introduced features will improve your code, make it simpler and more readable, without adding any additional byte to your firmware.

The features I describe in this article will not increase the size of the final firmware, they will just affect safety, readability and simplicity of your code. At the end, the optimiser in the compiler will resolve all the code and generate small and compact binaries.

Working with Flags

Let me start do demonstrate how you should work with flags in you code. First, flags shall be easily combined using the or | operator:

Chip::setMode(Mode::Enabled|Mode::Speed100kHz|Mode::Alternative);

In your implementation, you should be able to test for flags and convert combined flags into a bit mask.

void setMode(Modes modes)
{
    if (modes.isSet(Mode::Enabled)) {
        // ...
    }
    MODE_REG = modes;
}

The last requirement is to make everything type safe. This means, a flag e.g. from the enumeration A can not be used for the flags in B. This will prevent many typos and errors in your code.

Chip::setMode(Pin::Enabled); // ERROR!

Implementation

The implementation of this behaviour is very simple. First we need a simple template class to create a new type for the flags:

Used Language Features

This code is using many language features from C++11. Therefore you have to make sure the compiler in your build chain supports this language version. This is already the case for the Arduino IDE any many other GCC based environments. Sometimes you have to add the -std=C++11 or even better -std=C++17 command line option to enable the latest language features.

Basic Required Features

  • The  header: This header from the standard library defines the types uint8_t or int32_t to declare variables of a defined size. The header should exist in every C++ environment. If your development environment does not support this header, you should switch to another one.
  • Template class: Template classes working like regular classes, but certain parameters are defined at the point of use. Usually this parameters define types which are used for the class. The compiler will automatically generate a new class definition using this parameter at the point a template class is first used in the code. Template classes are an important feature of the C++ language and should be present in every development environment.
  • The inline specifier: This specifier is a simple hint to the compiler to use the code of a function directly, replacing the call. This is just a hint.
  • Namespaces: Especially for embedded code, namespaces are a really great tool to keep your own types and variables in a own scope. They are also a nice tool to create logical modules without using singleton classes.

Features from C++11

  • Type Traits: The  header defines a number of template classes which can be used in combination with static_assert, to check the type of parameters. It can also be used in template to conclude a type from a parameter.
  • The noexcept specifier: It declares a function will never throw an exception. Exception are usually not used in embedded code, but this class can be universally used in desktop and embedded code. Marking functions not throwing exceptions will allow the compiler to generate better optimisations.
  • The constexpr specifier: It declares a function with a given input will always generate the same output. This will allow the compiler to evaluate a function at compile time. Marking the right methods with the specifier will not only reduce the size of the generated binary, it will also allow to use flag combinations as constants.
  • The initialiser list feature: Using the  header allows creating a constructor using an initialiser list. This will allow to initialize flags with syntax like this: const Flags flags = {Flag::A, Flag::B, Flag::C};

Simple Usage Example

See the following code of a driver for the TCA9548A chip as a very simple example. I removed all comments and some methods to demonstrate the usage.

This simple I2C multiplexer chip allows enabling any combination of the eight supported channels. Using the flags class, the usage of this interface is simple and safe. You easily enable one or more channels.

Working with the flags in the implementation is very simple.

As you can see, you can work with the channels very similar as you would with any other bit mask. The difference is the type safety. It is not possible to accidentally mix bits from one flags type with another.

In line 38, you can also see how the flags type is implicitly converted into a bit mask. This is a one way operation. The flags can be converted into a bit mask, but an “anonymous” bit mask can not be converted automatically into a flags type.

Advanced Usage

You can not only use simple flags, where each bit is assigned to one enumeration value. Any combination of bits and masks is possible.

Conclusion

The flags template class will allow you to work with flags in a safe and comfortable way. The compiler will detect any potential problems where you use the wrong enumeration type for the flags.

If you have questions, miss some information or just have any feedback, feel free to add a comment below.

Have fun!