Lucky Resistor
Menu
  • Home
  • Learn
    • Learn C++
    • Product Photography for Electronics
      • Required Equipment and Software
    • Soldering for Show
  • Projects
  • Libraries
  • Applications
  • Shop
  • About
    • About Me
    • Contact
    • Stay Informed
  •  
Menu

Make your Code Safe and Readable with Flags

Posted on 2018-05-062022-09-04 by Lucky Resistor

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.

/**
 * Copyright (c) 2015 Atmel Corporation. All rights reserved.
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
 * 3. The name of Atmel may not be used to endorse or promote products derived from this software without specific prior written permission.
 * 4. This software may only be redistributed and used in connection with an Atmel microcontroller product.
 * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/* -------- PM_AHBMASK : (PM Offset: 0x14) (R/W 32) AHB Mask -------- */
#if !(defined(__ASSEMBLY__) || defined(__IAR_SYSTEMS_ASM__))
typedef union {
  struct {
    uint32_t HPB0_:1;          /*!< bit:      0  HPB0 AHB Clock Mask                */
    uint32_t HPB1_:1;          /*!< bit:      1  HPB1 AHB Clock Mask                */
    uint32_t HPB2_:1;          /*!< bit:      2  HPB2 AHB Clock Mask                */
    uint32_t DSU_:1;           /*!< bit:      3  DSU AHB Clock Mask                 */
    uint32_t NVMCTRL_:1;       /*!< bit:      4  NVMCTRL AHB Clock Mask             */
    uint32_t DMAC_:1;          /*!< bit:      5  DMAC AHB Clock Mask                */
    uint32_t USB_:1;           /*!< bit:      6  USB AHB Clock Mask                 */
    uint32_t :25;              /*!< bit:  7..31  Reserved                           */
  } bit;                       /*!< Structure used for bit  access                  */
  uint32_t reg;                /*!< Type      used for register access              */
} PM_AHBMASK_Type;
#endif /* !(defined(__ASSEMBLY__) || defined(__IAR_SYSTEMS_ASM__)) */

#define PM_AHBMASK_OFFSET           0x14         /**< \brief (PM_AHBMASK offset) AHB Mask */
#define PM_AHBMASK_RESETVALUE       0x0000007Ful /**< \brief (PM_AHBMASK reset_value) AHB Mask */

#define PM_AHBMASK_HPB0_Pos         0            /**< \brief (PM_AHBMASK) HPB0 AHB Clock Mask */
#define PM_AHBMASK_HPB0             (0x1ul << PM_AHBMASK_HPB0_Pos)
#define PM_AHBMASK_HPB1_Pos         1            /**< \brief (PM_AHBMASK) HPB1 AHB Clock Mask */
#define PM_AHBMASK_HPB1             (0x1ul << PM_AHBMASK_HPB1_Pos)
#define PM_AHBMASK_HPB2_Pos         2            /**< \brief (PM_AHBMASK) HPB2 AHB Clock Mask */
#define PM_AHBMASK_HPB2             (0x1ul << PM_AHBMASK_HPB2_Pos)
#define PM_AHBMASK_DSU_Pos          3            /**< \brief (PM_AHBMASK) DSU AHB Clock Mask */
#define PM_AHBMASK_DSU              (0x1ul << PM_AHBMASK_DSU_Pos)
#define PM_AHBMASK_NVMCTRL_Pos      4            /**< \brief (PM_AHBMASK) NVMCTRL AHB Clock Mask */
#define PM_AHBMASK_NVMCTRL          (0x1ul << PM_AHBMASK_NVMCTRL_Pos)
#define PM_AHBMASK_DMAC_Pos         5            /**< \brief (PM_AHBMASK) DMAC AHB Clock Mask */
#define PM_AHBMASK_DMAC             (0x1ul << PM_AHBMASK_DMAC_Pos)
#define PM_AHBMASK_USB_Pos          6            /**< \brief (PM_AHBMASK) USB AHB Clock Mask */
#define PM_AHBMASK_USB              (0x1ul << PM_AHBMASK_USB_Pos)
#define PM_AHBMASK_MASK             0x0000007Ful /**< \brief (PM_AHBMASK) MASK Register */
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:

#pragma once
//
// Flags
// —————————————————————————
// (c)2018 by Lucky Resistor. See LICENSE for details.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#include <cstdint>
#include <type_traits>
#include <initializer_list>
namespace lr {
/// A flag type template.
///
/// This class supports a wide range of flag operations in combination
/// with the used enum for the bit masks.
///
/// @tparam Enum The enum type to use for the flags.
///
template<class Enum>
class EnumFlags
{
public:
/// The enum type for this flags class.
///
typedef Enum EnumType;
/// The type used to store the mask for the flags.
///
typedef typename std::underlying_type<Enum>::type FlagsType;
public:
static_assert(std::is_enum<Enum>::value, "The Enum parameter needs to be a enum defining the bit masks.");
static_assert(std::is_integral<FlagsType>::value, "The enum type needs to be a uint8_t, uint16_t, uint32_t or uint64_t.");
static_assert(std::is_unsigned<FlagsType>::value, "The enum type needs to be an unsigned integer.");
public:
/// Create a flags value with no flags set.
///
constexpr inline EnumFlags() noexcept
: _flags(0)
{
}
/// Create a flags value with the given flag set.
///
/// @param flag The flag to convert into flags.
///
constexpr inline EnumFlags(Enum flag) noexcept
: _flags(static_cast<FlagsType>(flag))
{
}
/// Create a flags value from an initializer list.
///
/// @param flags An initializer list with flags.
///
constexpr inline EnumFlags(std::initializer_list<Enum> flags) noexcept
: _flags(initializer_list_helper(flags.begin(), flags.end()))
{
}
/// Create a flags value from a bitmask.
///
constexpr inline explicit EnumFlags(const FlagsType mask) noexcept
: _flags(mask)
{
}
public: // Operators
/// Combine this flags value with another value.
///
inline EnumFlags& operator|=(EnumFlags other) noexcept { _flags |= other._flags; return *this; }
/// Combine this flags value with another value.
///
inline EnumFlags& operator|=(Enum other) noexcept { _flags |= static_cast<FlagsType>(other); return *this; }
/// Combine this flags value with another value.
///
inline EnumFlags& operator&=(EnumFlags other) noexcept { _flags &= other._flags; return *this; }
/// Combine this flags value with another value.
///
inline EnumFlags& operator&=(Enum other) noexcept { _flags &= static_cast<FlagsType>(other); return *this; }
/// Combine this flags value with another value.
///
inline EnumFlags& operator^=(EnumFlags other) noexcept { _flags ^= other._flags; return *this; }
/// Combine this flags value with another value.
///
inline EnumFlags& operator^=(Enum other) noexcept { _flags ^= static_cast<FlagsType>(other); return *this; }
/// Combine this flags value with another value.
///
constexpr inline EnumFlags operator|(EnumFlags other) const noexcept { return EnumFlags(_flags|other._flags); }
/// Combine this flags value with another value.
///
constexpr inline EnumFlags operator|(Enum other) const noexcept { return EnumFlags(_flags|static_cast<FlagsType>(other)); }
/// Combine this flags value with another value.
///
constexpr inline EnumFlags operator&(EnumFlags other) const noexcept { return EnumFlags(_flags&other._flags); }
/// Combine this flags value with another value.
///
constexpr inline EnumFlags operator&(Enum other) const noexcept { return EnumFlags(_flags&static_cast<FlagsType>(other)); }
/// Combine this flags value with another value.
///
constexpr inline EnumFlags operator^(EnumFlags other) const noexcept { return EnumFlags(_flags^other._flags); }
/// Combine this flags value with another value.
///
constexpr inline EnumFlags operator^(Enum other) const noexcept { return EnumFlags(_flags^static_cast<FlagsType>(other)); }
/// Nagate this flags value.
///
constexpr inline EnumFlags operator~() const noexcept { return EnumFlags(~_flags); }
/// Convert this flags value into a bit mask.
///
constexpr inline operator FlagsType() const noexcept { return _flags; }
public: // Helper methods.
/// Check if a flag is set.
///
/// @param flag The flag to test.
/// @return `true` if the flag is set, `false` if the flag is not set.
///
constexpr inline bool isSet(Enum flag) const noexcept {
return (_flags&static_cast<FlagsType>(flag))==static_cast<FlagsType>(flag)&&(static_cast<FlagsType>(flag)!=0||_flags==static_cast<FlagsType>(flag));
}
/// Set a flag.
///
/// @param flag The flag to set.
///
inline void setFlag(Enum flag) noexcept {
_flags |= static_cast<FlagsType>(flag);
}
/// Clear a flag.
///
/// @param flag The flag to set.
///
inline void clearFlag(Enum flag) noexcept {
_flags &= (~static_cast<FlagsType>(flag));
}
/// Create flags from a bit mask.
///
/// Helper method to make code converting bit masks into
/// a EnumFlags object more readable.
///
/// @param mask The bit mask.
/// @return The flags object for the mask.
///
inline constexpr static EnumFlags fromMask(const FlagsType mask) noexcept {
return EnumFlags(mask);
}
private:
/// Helper to convert an initializer list with flags into a bitmask.
///
/// The idea of this recursive helper function is to let the compiler solve
/// the bit mask at compile time.
///
constexpr static inline FlagsType initializer_list_helper(
typename std::initializer_list<Enum>::const_iterator it,
typename std::initializer_list<Enum>::const_iterator end) noexcept
{
return (it == end ? static_cast<FlagsType>(0) : (static_cast<FlagsType>(*it)|initializer_list_helper(it+1, end)));
}
private:
FlagsType _flags; ///< The flags.
};
/// Declare a new flags type.
///
/// This macro needs to be places just after the enum declaration.
/// The enum has to be a typed one and define each value as bit mask.
///
/// @param flagEnum The name of the enum to use. E.g `Channel`.
/// @param flagsName The name of the resulting flags type. E.g. `Channels`.
///
#define LR_DECLARE_FLAGS(FlagEnum, FlagsName) \
typedef ::lr::EnumFlags<FlagEnum> FlagsName;
/// Declare operators for the flags.
///
/// This declares additional operatos for the flags to be combined.
///
#define LR_DECLARE_OPERATORS_FOR_FLAGS(FlagsName) \
constexpr inline ::lr::EnumFlags<FlagsName::EnumType> operator|(FlagsName::EnumType a, FlagsName::EnumType b) noexcept { \
return ::lr::EnumFlags<FlagsName::EnumType>(a)|b; }
}
view raw Flags.hpp hosted with ❤ by GitHub

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.

#pragma once
#include "Flags.hpp"
#include <cstdint>
namespace lr {
class TCA9548A
{
public:
enum Channel : uint8_t {
Channel0 = (1<<0),
Channel1 = (1<<1),
Channel2 = (1<<2),
Channel3 = (1<<3),
Channel4 = (1<<4),
Channel5 = (1<<5),
Channel6 = (1<<6),
Channel7 = (1<<7),
};
LR_DECLARE_FLAGS(Channel, Channels);
enum class Status : uint8_t {
Success,
Error,
};
public:
TCA9548A(/*…*/);
public:
void initialize();
Status test();
void reset();
Status setChannel(Channel channel);
Status setChannels(Channels channels);
Status clearAllChannels();
private:
// …
Channels _enabledChannels;
};
LR_DECLARE_OPERATORS_FOR_FLAGS(TCA9548A::Channels);
}
view raw TCA9548A_FlagsDemo.hpp hosted with ❤ by GitHub

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.

#include "TCA9548A.hpp"
namespace lr {
// …
TCA9548A::Status TCA9548A::setChannel(Channel channel)
{
if (_enabledChannels != Channels(channel)) {
_enabledChannels = Channels(channel);
return writeChannelsToChip();
}
return Status::Success;
}
TCA9548A::Status TCA9548A::setChannels(Channels channels)
{
if (_enabledChannels != channels) {
_enabledChannels = channels;
return writeChannelsToChip();
}
return Status::Success;
}
TCA9548A::Status TCA9548A::clearAllChannels()
{
if (_enabledChannels != Channels()) {
_enabledChannels = Channels();
return writeChannelsToChip();
}
return Status::Success;
}
TCA9548A::Status TCA9548A::writeChannelsToChip()
{
const uint8_t data = _enabledChannels;
if (_wireMaster->writeBytes(_address, &data, 1) != WireMaster::Status::Success) {
return Status::Error;
}
return Status::Success;
}
}
view raw TCA9548A_FlagsDemo.cpp hosted with ❤ by GitHub

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.

#include "Flags.hpp"
#include <iostream>
enum class Demo : uint16_t {
Enabled = (1<<0),
Mode5k = (0x0<<2),
Mode10k = (0x1<<2),
Mode16k = (0x2<<2),
Mode20k = (0x3<<2),
ModeMask = (0x3<<2),
Speed0 = (0x0<<9),
Speed1 = (0x1<<9),
Speed2 = (0x2<<9),
SpeedMask = (0x7<<9),
SpeedDefault = Speed1,
};
LR_DECLARE_FLAGS(Demo, Demos)
LR_DECLARE_OPERATORS_FOR_FLAGS(Demos)
int main()
{
// Initializer list.
Demos demos = {Demo::Enabled, Demo::Mode10k, Demo::SpeedDefault};
// Flag check.
if (demos.isSet(Demo::Enabled)) {
std::cout << "Enabled" << std::endl;
}
// Masking flags.
if ((demos&Demo::ModeMask) == Demos(Demo::Mode10k)) {
std::cout << "Mode 10k" << std::endl;
}
// Using a switch statement for masked flags.
switch (demos&Demo::SpeedMask) {
case Demos(Demo::Speed0):
std::cout << "Speed 0" << std::endl;
break;
case Demos(Demo::Speed1):
std::cout << "Speed 1" << std::endl;
break;
case Demos(Demo::Speed2):
std::cout << "Speed 2" << std::endl;
break;
default:
break;
}
return 0;
}
view raw FlagsDemo.cpp hosted with ❤ by GitHub

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!

Leave a Reply Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Stay Updated

Join me on Mastodon!

Top Posts & Pages

  • Storage Boxes System for 3D Print
  • Event-based Firmware (Part 1/2)
  • Build a 3D Printer Enclosure
  • Yet Another Filament Filter
  • Circle Pattern Generator
  • Circle Pattern Generator
  • Real Time Counter and Integer Overflow
  • Projects
  • Logic Gates Puzzle 11
  • Units of Measurements for Safe C++ Variables

Latest Posts

  • Better Bridging with Slicer Guides2023-02-04
  • Stronger 3D Printed Parts with Vertical Perimeter Linking2023-02-02
  • Logic Gates Puzzle 1012023-02-02
  • Candlelight Emulation – Complexity with Layering2023-02-01
  • Three Ways to Integrate LED Light Into the Modular Lantern2023-01-29
  • The 3D Printed Modular Lantern2023-01-17
  • Rail Grid Alternatives and More Interesting Updates2022-12-09
  • Large Update to the Circle Pattern Generator2022-11-10

Categories

  • 3D Printing
  • Build
  • Common
  • Fail
  • Fun
  • Learn
  • Projects
  • Puzzle
  • Recommendations
  • Request for Comments
  • Review
  • Software
Copyright (c)2022 by Lucky Resistor. All rights reserved.