Embedded software configuration management can be a tricky endeavor. Systems today are often designed to work in multiple products, for various customers, over long-time frames. These systems require the ability to be easily configured so that the code does not become a nightmare and minimize the chances of improperly configuring them. In this post, we will explore three tips for embedded software configuration management that I have found to simplify configuration and minimize technical debt over a product’s lifetime.
Tip #1 – Use Separate Repo’s
When working on an embedded product, it can be tempting to place all the code for a product into a single version control repository. After all, it’s one product, and shouldn’t everything for the product be contained in one place? I would argue, NO! Breaking the application into multiple repositories can dramatically improve codebase configuration management.
In many cases, a products code will be used across multiple product SKUs and, for a decade or more, may find different configurations to meet customer requirements. Therefore, teams can carefully examine their product’s design and place all configuration code into a separate repository. At a minimum, this creates the core product code and several configuration repos that manage how the core application will behave. The advantage to this is that if configuration changes are needed for a SKU or a specific customer, it doesn’t force changes to the application that could accidentally propagate to other systems.
Configuration is not the only component that can benefit from being placed in a separate repo. There can also be advantages to placing each layer of the application into different repositories as well. For example, separating drivers, middleware, and the application into separate repos can force developers to think more about the code’s interfaces and abstractions, which will help minimize coupling between layers. For example, during the chip shortage, many teams have struggled to switch microcontrollers because they tightly couple their application to their hardware.
Tip #2 – Leverage YAML, JSON, and XML files
In many embedded products, developers will use conditional compilation statements to manage the configuration of their systems. For example, if I have two different systems and one has two relays that can be controlled, and the other has four, developers will write C code like:
#if SKU == PRODUCT_1
// Custom configuration for two relays.
#elif SKU == PRODUCT_2
// Custom configuration for four relays.
#error “The Product SKU has not been defined!”
If the product is simple, then having a few conditional compilations scattered throughout the code is not a big deal; however, today’s systems are pretty complex and can have thousands of these statements scattered throughout. As one can imagine, this becomes a maintenance and configuration nightmare.
Instead of using conditional compilation, developers can leverage configuration files. There are two ways configuration files can be leveraged. First, developers can just create C module configurations that hold the product configuration information. These configuration values are passed into the various application, middleware, and driver initialization functions. Second, C-based configuration files are easy to set up and maintain.
A more modern approach is to leverage configuration files in YAML, JSON, and/or XML format. For example, looking back at the conditional compilation example, we could write a YAML file that looks something like the following for a two-relay configuration:
The four-relay configuration YAML file might look something like this:
Using YAML, JSON, and XML files has an interesting twist; how does one use these files to configure the C/C++ application? There are several ways, but the method that I’ve used the most is to write a script that reads in the YAML file and then generates the equivalent C code. For example, the process can look something like the following:
I know for some that using a configuration file outside C and relying on a script to build the configuration feels complex. However, I’ve found that if done correctly, this type of technique can become a valuable and beneficial way to manage system configuration.
Tip #3 – Use a C/C++ Package Manager
Another option developers can leverage to help manage their product configuration is using a package manager. A package manager allows developers to manage their software dependencies. In addition, the package manager can be integrated into a build system that enables the developer to manage their build, including their configuration management, easily.
There are several package managers available for developers working in C/C++. For example, the Microsoft C/C++ team maintains a freely available tool called vcpkg. The vcpkg tool can be used to integrate third-party libraries, frameworks, and open-source code or add private ones. Alternatively, conan is an open-source package manager that also has been widely used. Each tool has its advantages and disadvantages, but no matter which one you choose, they can be highly effective in helping to manage software configuration.
Embedded software, long ago, has graduated from being simple little control applications into complex systems that often require complex configuration management. Old techniques like conditional compilation can be helpful in small projects, but as the complexity and size grow, developers need to look to more modern tools. For example, package managers and autogenerated configuration files can dramatically help improve the configuration management of a system. But, of course, successful use of these tools relies on teams being disciplined. After all, who wants to chase bugs in scripts that generate your configuration?