4tlas.io

Articles, stories and news

Discover a wealth of insightful materials meticulously crafted to provide you with a comprehensive understanding of the latest trends.

blog hero section
4TL Icon Color 1 1

Categories

Tags

Photo by Chris Ried on Unsplash

A Modern DevOps Solution for the Age-Old Embedded Build, Test, and Release Process

If you have been a developer or leader in the embedded space for any longer than 5 minutes, here are some stories you know very well:

The “It Won’t Build for Me” Scenario

Jack and Jill are feverishly working on developing modules with each’s respective Windows PC and local development systems. Each has been using their locally installed cross-compile toolchains all day without issue. Jill commits her code to the branch when they are ready to test together, and Jack pulls to integrate.

Ugh! Jill’s code won’t build on Jack’s machine.

The “It Doesn’t Build for Production” Scenario

Jack is working on a delivery for today with his Linux machine and development system on his desk. Finally, at 7 pm, he’s got it working and commits his code to the repository. The build system picks up the changes and starts the build.

Ugh! The production build system, a Windows machine, won’t compile the code.

The “Disgruntled Windows User” Scenario

New hire Jack requests a machine with Ubuntu for his development environment. Sorry, our IT department only supports Windows. Here’s your Dell, and here’s Visual Studio.

Ugh! But wait, isn’t our target environment Linux?

For a software developer, their PC is like a chef’s set of knives — it’s a very personal tool. Most developers are intimately familiar with their machines. Partially because we have to be, but more so because we want to be. We configure them just the way we like it. We are comfortable and fast when using our favorite editor and development environment.

We want the IT team to leave us alone, thank you very much.

The Best of the Old Way

No alt text provided for this image

Good organizations do their best to minimize these issues by using a combination of organizational and personal discipline along with good configuration management. The purpose is always to keep the developers’ local build environment close to the production and each other’s build environment.

Toolchain and Build System/Environment

Embedded developers always need a cross-compile toolchain. Sometimes free (ie, ARM GCC), sometimes licensed (ie, ARM Keil). Managing this toolchain, its version, configuration, and environment is one of the most critical symphonies across the development team because it is a source of many “it doesn’t build for me issues.”

There is also the matter of the build system or build scripts. Whether it’s some flavor of make, autotools, yocto, an IDE project, or custom scripts in you-name-the-shell, this set of black magic can singlehandedly derail any release on any day.

The best organizations figure out how to get both the toolchain and build system under configuration management. One such method is putting the entire toolchain and build utilities into a repository.

Even though the golden master build system exists, most developers’ local build environment deviates from it. There are many reasons in the day-to-day operations where developers modify or update these elements.

We try stuff. It’s what we do.

Installation Script

Good organizations create a utility that installs and configures the golden-master build environment. This utility provides a common process and known state for getting someone up and running.

But the usefulness is limited over time since installation. Developers modify their environment. The build scripts can be easily updated locally via pulls from the repository, but the toolchain may not update in as streamlined a fashion.

And again, we try stuff.

Virtual Machines

There is also the not-so-little matter of the user’s host environment. In some cases, we’re all are using different OS’s. We’re all using the same OS in others, but the inevitable update and configuration nuances create subtly different build environments between each development and the production build machine.

Another reasonable practice is to use an officially supported Virtual Machine that includes the supported toolchain and build environment. That helps solve the host environment differences and even liberates the user from a particular host OS. But it opens up a new set of challenges because the VM is only a copy. As soon as the user copies it to their local system, now they’ve forked it, and it can start to deviate. The VM goes on its own path the first time the developer starts whacking at the build environment to debug issues. It’s essentially just like giving a developer yet another machine that they have to manage.

The DevOps Solution — Common Build Environment

No alt text provided for this image

Regardless of how good the organization’s configuration management is and how disciplined a developer is, a developer’s local environment can only ever get similar to the production environment with the practices described above.

However, we’ve solved the problem in our organization for both build configuration management and local build versus production build. We did so by applying the modern DevOps concept of containers.

We create production build containers with the golden master toolchain and access to the necessary repositories and build scripts. All developers, testers, and managers have access to these containers and use them locally to build. We generically name these containers as a Common Build Environment (CBE).

A CBE is not similar to the production environment, it is the production environment. Therein lies the magic. Since we first deployed the CBE to our firmware team about several years ago, we’ve enjoyed 100% success between local development and the production build. We’ve had not one single build failure attributed to the build environment.

Here’s how a CBE is deployed both locally and to the production build environment.

No alt text provided for this image

Another benefit to CBE is that it can liberate the developers from a particular platform, at least for the build. Docker containers are supported on Windows, Linux, and macOS.

If your build is for an embedded target processor, there is a good chance that you can create some flavor of Linux container for that target. However, even if you require Windows (*cough*…ARM Keil…*cough*), you can still build a Windows CBE. The downside is that a Windows host system is required to execute Windows containers. However, you may work around this by using a Windows VM on your Host OS and then executing the CBE build from within that VM.

How to Create Your CBE and its Workflow

You’ll need some development and configuration management to make the CBE useful for the development team. A benefit is that everything required for a CBE can be put into a source repository and a container repository. Therefore, it is always traceable and reproducible.

What You Need:

  1. Container in a container repository with the toolchain and 3rd party utilities (Dockerfile and/or container image)
  2. User build scripts/project and utilities (i.e., make, autotools, custom, etc.)
  3. Docker installation on the host machine

The CBE Container

Create your container, and install the toolchain and any 3rd party, non-custom, build-related utilities (note that these are NOT the “build” scripts/utilities themselves). The build-related utilities needed in a CBE are applications such as lint, statistics gatherers, linker and image tools, 3rd party static analysis, etc.

We found that in some cases, it was easier to start from a prototype container image (rather than work from the Dockerfile) and use it interactively to install the toolchain and utilities. That provided an easy and quick method for testing the install. We created the Dockerfile after we were satisfied with the container we built interactively.

You can use a single CBE that contains all the toolchains for the various targets you support or separate CBE’s with single toolchains. The choice is yours, with benefits and drawbacks to each. We have chosen to use many CBE’s, each with a particular target toolchain. This allows us to easily keep all the CBE’s very stable and, once created, rarely require updates. That helps with traceability and recreation of previous releases.

This diagram shows you the basics of how we configuration manage the CBE’s themselves.

No alt text provided for this image

We create a CBE per target processor toolchain and then version that CBE with the tag.

You must be aware about placing IP or internal repository access credentials into your containers. If you do, you should use an internal container repository. If you don’t have any IP or credentials in your CBE, then you can use the public hub.docker.io. We don’t have any IP in our containers, but we do have SSH keys for accessing our internal repositories. Therefore, we host our containers inside our network.

Build Project and Scripts

These are the Makefiles, autotools, Bazel, yocto, etc, or custom build scripts that are required to compile, link, and package your embedded image files.

Use good software design principles that include encapsulation and interface consideration. Encapsulation is essential since you will export the source workspace to the CBE to execute the build.

We have a relatively easy build and use a combination of Makefiles with some bash scripts on top.

User Level Scripts and Utilities: Working with the Container

Here is where you’ll find most of the new work required to effectively use a CBE in your build workflow.

You will need to create a setup of utilities/scripts that allow your developers to use the CBE easily. Ideally, a developer doesn’t even know that the build occurs in a CBE container. It appears, whether by CLI or IDE that the build is native to the host system.

Your requirements are as follows:

  1. The speed of the build must be on par with a local host OS build.
  2. All build options (i.e., CLI options, targets, flags, etc.) must be accessible as if the build was local.
  3. All build stdout, stderr, and log files must be presented just as if the build was local.
  4. All build artifacts, including image files, debug symbols, linker maps, etc, must be deposited into the same location as if the build was local.

Here is the basic user level script workflow for using a CBE:

No alt text provided for this image

This workflow assumes the following:

  • The user either has internet connectivity or already has the CBE locally. However, one of the benefits is that the internet is not required once the CBE is available locally.
  • Docker is present and executing on the developer’s system.

When we initially rolled out the CBE, we did it for a single particular target — an ARM using the GCC. The build was make with some bash on top for usability (referred to below as build.sh).

To support the developers’ use of CBE, we created another bash wrapper that implemented the workflow above. We’ll refer to it as cbe_build.sh from hereon.

Here are the main guts of that initial wrapper. The CBE is an Ubuntu 14 LTS image stripped down, and it contains the ARM GCC 4.9 and 6.3 series toolchains in it. The developer specifies which toolchain version as a command-line argument to this script.

Overall Structure of cbe_build.sh

######################################################################

# Main script

main()

{

cmdline $ARGS

get_container

 

# Grab the PID of the most-recently-launched container

DOCKER_PID=`docker ps -q -n 1`

clean

copy_source_code

build

get_artifacts

 

log “=== Destroying container…”

docker rm -f $DOCKER_PID

}

Get the Container

Since we host our containers internally, we use a service account to access and are not very strict with the credentials.

#########################################################################

# get_container()

#

# Pulls latest version of CBE container

get_container()

{

log “=== Pulling latest version of container…”

# No IP, can be loose with credentials

docker login -u $UNAME -p $PWORD

docker pull $CBE

# Launch the container, give it a no-op command to run so it will stop

# quickly and wait for us.

echo “echo ”” | docker run -i $CBE

}

Copy the Source Code

Note: We copy the source into the container rather than share a mounted volume due to the unreliability of shared mount in Windows Host OS. If your Host OS is all Linux or macOS, then skip this step and share the volume.

When copying the source code, also copy in your build tools/scripts. In our case, our build scripts are part of the source tree because we use Makefiles with some bash on top.

Note that our source code base for this target is very small (< 100K lines of code), therefore, we copy the entire source code tree into CBE.

We do have to hack the line endings if the developer is on Windows. I’m sure there is a more elegant solution to that part.

#########################################################################

# copy_source_code()

#

# Copies the source code from Host OS into the container

# ensures proper line endings

#

copy_source_code()

{

# Copy user’s sandbox into container FS

log “=== Copying source files…”

docker cp ./ $DOCKER_PID:/build

# if windows, change the EOL’s in the container

log “=== OS = ${OS}”

if [[ ${OS} =~ .*MINGW.* ]] || [[ ${OS} =~ .*CYGWIN.* ]]

then

log “=== Changing EOL’s IX style”

container_run $DOCKER_PID “find . -type f -exec dos2unix -q {} {} ‘;'”

else

log “=== EOL change NOT required”

fi

}

Build

I’ve left out some $ARGS preprocessing for brevity. That preprocessing sets the toolchain path as well as massages the $ARGS variable to ensure the build options (ie, target, etc) are correct. As mentioned previously, a bash script build.sh sits on top of make to perform the build inside CBE.

#########################################################################

# container_run (DOCKER_PID) (command)

#

# Runs (command) in the stopped foreground container with pid (DOCKER_PID)

# by piping it into stdin of “docker start -i (DOCKER_PID)”

container_run()

{

if [ -z “$2” ]

then

log_error “container_run(): missing parameter”

log_error “Usage: container_run (DOCKER_PID) (command string)”

return 1

fi

echo $2 | docker start -i $1

}

##########################################################################

# build()

#

# Runs the build command and captures time information

#

build()

{

# Run script in container

log “=== Building with CBE…”

bdstart=$(date +%s)

log ” – build ARGS: $ARGS”

# Note that toolchain path is set by preprocessing

container_run $DOCKER_PID “export PATH=$toolchain:\”$PATH\” && cd /build/tools && ./build.sh $ARGS”

bdstop=$(date +%s)

BDCOUNT=$((bdstop-bdstart))

}

#

Get the Artifacts

This step requires precision to keep the build time with CBE on par with a local build. Copy ONLY what is absolutely necessary from the CBE back to the host OS. We use some logic to determine if the build was a success or failure and modify the behavior accordingly.

#########################################################################

# container_run (DOCKER_PID) (command)

#

# Runs (command) in the stopped foreground container with pid (DOCKER_PID)

# by piping it into stdin of “docker start -i (DOCKER_PID)”

container_run()

{

if [ -z “$2” ]

then

log_error “container_run(): missing parameter”

log_error “Usage: container_run (DOCKER_PID) (command string)”

return 1

fi

echo $2 | docker start -i $1

}

##########################################################################

# build()

#

# Runs the build command and captures time information

#

build()

{

# Run script in container

log “=== Building with CBE…”

bdstart=$(date +%s)

log ” – build ARGS: $ARGS”

# Note that toolchain path is set by preprocessing

container_run $DOCKER_PID “export PATH=$toolchain:\”$PATH\” && cd /build/tools && ./build.sh $ARGS”

bdstop=$(date +%s)

BDCOUNT=$((bdstop-bdstart))

}

#

Integration with an IDE

We integrated the CBE build with a few IDE’s, but it requires another custom-developed utility. Unfortunately, each IDE is a different animal, and therefore, we haven’t found a common method for all. But here are some concepts to understand:

  1. Calling the build and how the targets and options are specified
  2. Debug symbols and files/info required for the debugger

Benefits and Challenges

Here are what we’ve experienced as both benefits and challenges to the CBE approach.

Benefits

  • All users are building in the production environment.
  • Configuration management is on a single item, the CBE, rather than spread across the user base.
  • All users get any updates to the build environment automatically
  • Internet on or off (assuming prereq’s are met)
  • Host OS independent

Challenges

  1. Docker on all the developer machines. Not much of an issue once it’s installed.
  2. Enabling developers to monkey around with the tools and scripts. This is a legitimate challenge because as embedded developers, we all need to (or like to) monkey with the tools and options sometimes. We solve this by showing a developer how to work in a CBE interactively, or the developer installs the build environment locally and works locally until happy.
  3. Windows containers have portability limitations. Windows containers must execute on Windows Host OS. Therefore, the developer must use Windows natively or a Windows VM to execute the build.
  4. Licensed toolchains require floating licenses. You are roped into a floating license scenario with a licensed toolchain, which is typically more expensive. This is how many organizations work, so it may not be a problem.

Summary

The Common Build Environment has eliminated the “doesn’t build for me” and “doesn’t build in production” problems for embedded target builds in our organization. The CBE uses a Docker container with the golden-standard embedded toolchain and build environment. Since all developers and the production build server use the same docker container, the developers are always building in the production environment.

 

Photo by Malachi Brooks on Unsplash

In modern web and mobile application development, the concepts, toolsets, and services of DevOps have been employed to speed up product development while simultaneously increasing quality. DevOps has been a major win for the software industry for the last ten years.

What about the software and firmware in embedded systems? What makes them any different? Surely they’re benefitting as well, right?

Embedded systems are often the hidden, but critical piece to products in various industries, from automotive to medical to defense to consumer electronics. Unfortunately, their development and testing present unique challenges that traditional DevOps toolsets and services don’t address. These challenges arise from two very distinct differences between web and embedded software.

Discontinuous Delivery

Traditional DevOps workflows for web and mobile applications employ Continuous Delivery (CD). The build, automated test, and deployment pipelines live and scale entirely in the cloud and typically pump out new releases daily or many times per day as the HEAD of the codebase moves forward relentlessly. Anytime a mistake makes it into the field, the CD backend quickly and effortlessly pushes an update or rolls back to a previous version. Users are none-the-wiser.

Not so with embedded systems. The embedded firmware image is usually a line item on the assembly parts list that gets programmed into the units at manufacture time. Almost like a resistor. Even with IoT and other field-upgradeable embedded systems, pushing firmware updates is complex, discontinuous, and usually requires some user intervention.

The firmware that makes it out the door has to work. You might not get a second shot at it.

Hardware-in-the-Loop

At the end of the day, the firmware requires the specific product hardware for which it was built, including testing the system prior to deployment. Hardware, especially for testing, is inherently expensive, finite, and messy. Plus, it lives in a physical, geographical location on a lab bench or in some rack connected to a host or network switch.

Which version of the product hardware supports which version of the firmware? How do I know what version of the firmware is on that field unit? How do I perform continuous integration and automated test with hardware? How do I scale across my test farm? How do I get this build into that system in the lab?

HIL makes everything slower and more difficult.

The Five Unique Challenges of Embedded Systems

These two differences lead to several unique challenges with embedded systems firmware development, test, and deployment.

Complex Toolchains

Cross-compilation and the use of different build host environments add layers of complexity. Embedded developers typically work locally with the build toolchain installed on their laptop and a development system connected to their machine. They code, build, and test locally on their laptop and connected test system. When it’s time to push and integrate, they often face the “works on my machine” issue, where the build or testing fails for their colleague during integration or in the production build system due to differences in toolchains and build environment setups.

Complex Version Management

Embedded systems often run multiple firmware versions across different product lines and customer deployments that originate from the same code base. You’ve got different versions of the hardware out in the field. Likely different versions of firmware also. Knowing who has what, and who’s supposed to get what is non-trivial. Managing these versions, ensuring compatibility, and maintaining clear traceability of changes is a significant challenge.

Required Point-Fixes

The certification test house just kicked it back to you with a message that says, “Fix this bug only without changing anything else. If you change anything else, you will reset the certification testing process.”

Crap, you can’t give them your HEAD of the codebase, which is unfortunate, because you fixed that bug months ago. So now you have to recreate the source and build environment from that release, which happened four months ago. Does the release ticket contain all the commitIDs? What about the versions of the library dependencies? How about the specific version of the toolchain and build scripts?

Then, once you’ve fixed the bug, you’ve gotta prove that your new firmware contains only that fix.

Proof of Version and Forensic Analysis

Your customer is asking that you prove that the units that roll out of manufacturing actually has the firmware that was certified. Or, maybe, your support team has received a bunch of field returns. How do you know definitively what firmware they have, and how you get back to the source and build environment that created it?

Hardware-in-the-Loop

Software for embedded systems must be thoroughly tested on the actual hardware platforms it will run on. Discontinuous delivery puts more pressure on testing embedded systems prior to release. This dependency introduces complexity in testing workflows, requiring a heterogeneous test environment that can handle various hardware configurations and the specific test content that matches. Your test farm has finite and limited resources, but you need to parallelize across it as best you can. You want to test PRs, master merges, releases, and then conduct nightly and weekly soak tests. How do you manage all of this?

Addressing the Challenges

Although the challenges are formidable, we’ve had great success at using the concepts of DevOps with the addition of purpose-specific tools. Here are some ways we’ve addressed, overcome, and thrived through these challenges.

Common Build Environment

We create a common build environment (CBE) by using the once-but-no-longer-magic-in-the-embedded-world technology of containers. A CBE is a container that contains the correct version of the toolchain, plus access to the codebase and utilities repositories. Now, whether a developer builds locally or the pipeline is building in the cloud for integration, the exact production build machine is used. We’ve eliminated 100% of build issues due to mismatched versions or build environment differences.

Fuze Build, Package, Release, and Delivery Tool

We developed a tool that automates and integrates universal configuration management from build all the way through delivery. The key to solving complex versioning across a heterogeneous product family, recreating previous builds no matter how far in the past, and forensically proving what you have in your hand is what you said it would be, is to build configuration management into the process as a fundamental pillar of the automation. Much like Jira does with communications and git does with source code, making configuration management a fundamental pillar of the tool eliminates not only human time and process, but also human error.

Fuze starts with the build. As a generic build executive, it allows you to use the tools that you already have. It simply wraps your current build procedure, utilizes a CBE, and stores every piece of metadata associated with that build and the CBE so that you, the QA team, the product manager, and everybody else knows exactly what’s in there and how to recreate it. If you build the FuzeID into the image itself (based on your requirements of security and obfuscation), now you have the perfect forensic tool to prove and know everything about this firmware image.

What goes into the package for the system delivery to manufacture? Images for multiple processors? Static config files? Documentation? No problem. Fuze also builds the release packages for you with 100% configuration management at its core.

When it’s time to release and deliver, if even just a demo build through an FAE, Fuze is again the answer. Find the FuzeID of your intended release package, release it, and then deliver it, all without leaving the tool.

We create a single source of truth for configuration management — the FuzeID.

With a FuzeID you know the following:

  • The build – who, when, what CBE/tools, build command(s)
  • Source commitIDs
  • Dependencies and versions
  • Build package contents
  • Test results
  • Release status – stage, by who, when
  • Delivery status – to who, by who, when

Cloud2Lab Automated Test Framework

HIL testing gets little attention in the open-source and generic automated test framework world, so we built our own — C2L.

C2L, like Fuze, takes the perspective of the person-in-the-lab. We believe and have seen the benefits of using the person as the focal point for automation. We ask, “what does a person do?” And then, we build the automation tool to allow that.

C2L uses the tools, test commands, and sequences that you already use if you’re testing it yourself manually. No new language, API’s, or scripting environment required. It gives you a straight-forward and easily understood environment for fully automating what you already do.

C2L also bridges the cloud to the lab. The build pipeline ran, or you built it locally, and now you have to push that image into a particular device or farm of devices on a bench or in a rack in some lab. We built C2L so it can do that, all through configuration. Plus, it handles the different versions and products across which you must test. Do you have 3 of these and 5 of those? C2L can parallelize according to how you configure it.

Debuggers, scopes, robots, and other control and acquisition required in your test setup? No problem. C2L handles it all and orchestrates your heterogeneous test content across the entire farm, then retrieves and organizes the test results.

You Can Do It (We Can Help)

Although embedded systems present some unique challenges, DevOps is still the way to help your team go faster and pump out products with higher quality. You just gotta know how to make it work for you.

 

Embedded firmware development isn’t the same animal as web and mobile application development. We can’t always treat it the same. Discontinuous delivery places an emphasis on the need for proper configuration management (CM) for firmware that starts with the build and continues all the way through delivery.

CM serves as the anchor that prevents chaos from derailing the firmware development, test, and delivery workflow. Embedded firmware must be meticulously managed from conception to deployment in order to know who has what, who gets what, what version supports which hardware, and to recreate previous releases for bug fixing and forking.

Without robust CM practices, the complexity of tracking code changes, build configurations, and deployment statuses can quickly spiral out of control, leading to costly mistakes and system failures.

Unfortunately, it’s easy to miss some crucial information.

This article will guide you through the essentials of configuration management in embedded firmware development. We’ll explore what needs to be tracked, how to implement these practices effectively, and the critical role that automation tools like Fuze play in maintaining order.

The Essentials of Configuration Management for Embedded Firmware

Configuration management in embedded firmware is the disciplined practice of ensuring that every aspect of the firmware build process—from source code, build environments, and dependencies to build artifacts and release statuses—is meticulously tracked and documented. The goal is to create a repeatable, fully traceable, and auditable process that ensures consistency and quality across all stages of firmware development and delivery.

Key Elements to Track for Each Firmware Build

CM should set you up to fully reproduce a build – bit-exact (except for any purposeful dynamic elements).

Let’s start with what information needs to be tracked to ensure proper CM.

Tracking these details ensures that the build process is fully reproducible. If a bug is discovered in the firmware, knowing precisely who built it, when, and with what tools can be crucial for debugging and fixing the issue.

The Build

  • Who: Identify the person or automated system that initiated the build.
  • When: Document the exact date and time the build occurred.

Pro Level:

  • Build Command(s): Capture the exact commands and parameters used to initiate the build. Arguments, options, and input files all affect the build output.

Source

  • Source CommitIDs and Branch Names: Document the specific commitIDs from all source repositories. This includes any build tools and config files (memory map, etc) that are tightly coupled to your source code. If branch names are important in your vernacular, then record them as well.
  • Tags: Tag your repo(s) and record those values.

Pro Level:

  • Diff/Patchset: CommitIDs can disappear. Now what? Branches get deleted, and the associated commitID’s disappear with them. You can/should create a diff/patchset with each build against a tag regularly timed tag.

Tools and Environment

  • Toolchain: Record the versions of compilers, linkers, and any other tools used during the build process.
  • Build Environment: Machine and HostOS details.
  • Script/ConfigCommitIDs: Document the commitIDs of any build scripts or configuration files loosely coupled to the source repositories. For example, you may have a “utilities” repository that isn’t part of the source code repo but contains build utilities.

Pro Level:

  • Dockerfiles and Containers: Use containers for the production build toolchain and then put its dockerfile or the container itself under CM.

Dependencies:

  • Standard Libraries: Record version/config info for all third-party and standard libraries.
  • Linked Package Libraries: Record the version information for all custom and self-built libraries linked in the firmware image.

Pro Level:

  • Source for Linked Package Libraries: Extend your traceability back through the source of the linked binary library. Now you can build a dependency traceability graph all the way back to the source.

Build Package:

  • Package Contents: In addition to a particular firmware image, create a traceable package of all images and supporting files (configuration, documentation, etc) that comprise a particular release.
  • Package Structure: Document the package structure so that any deployment/programming tools know where to find the important files.

Pro Level:

  • Test Results: Include a reference to the test results or the test results themselves of the build package.

Release Status:

  • State: Document the state of the release.
  • Who: Identify the person who changed the release state
  • When: Document when it happened.

Pro Level:

  • Delivery Status and Traceability: To whom, by who, when, and what method.

Commonly Overlooked Aspects of Configuration Management

Even with the best intentions, certain aspects of CM are often overlooked:

  • Complete Environment Documentation: Teams often fail to fully document the build environment, including specific versions of the operating system, installed tools, and environmental variables. This can lead to inconsistencies and bugs that are difficult to trace back to their source.
  • Missing CommitIDs: CommitIDs can be deleted.
  • Comprehensive Artifact Management: It’s easy to focus solely on the final firmware binary, but all intermediate artifacts and logs should also be tracked and stored. These can be invaluable for debugging and future maintenance.
  • Single Source of Truth: CM data can become fragmented across different configuration management methods and teams, leading to inconsistencies and gaps. Establishing a centralized location where all CM data is stored and accessible is crucial for maintaining integrity.

Creating a Single Source of Truth

A Single Source of Truth (SSOT) is vital for ensuring that all team members have access to consistent and up-to-date configuration data. This can be achieved through centralized systems that integrate all aspects of CM, from source code and dependencies to build artifacts and test results.

Methods to Implement SSOT:

  1. Manifest Files: These files list all components, their versions, and configurations used in the build. Manifest files serve as a quick reference and can be stored in the same repository as the source code.
  2. Jira Tickets: Using issue tracking systems like Jira helps log every change and decision made throughout the development process. By linking Jira tickets to specific commits, builds, and releases, you can maintain a traceable history that spans the entire project.
  3. Databases: A dedicated CM database can store all configuration items, their versions, and relationships. This structured approach ensures that all relevant data is stored in a consistent, searchable format.

Pro Level:

Use a tool that automates the entire configuration management process from build through test and delivery and then allows easy, yet secure access to all of the CM information for each build.

Automating Configuration Management with Fuze

Automation plays a critical role in maintaining effective CM, especially as projects scale. Fuze is an automation tool designed specifically for embedded firmware development, helping teams to manage and streamline the CM process.

Key Features of Fuze for CM Automation:

  • FuzeID: Every build managed by Fuze is tagged with a unique identifier, the FuzeID. This ID encapsulates all relevant data about the build, including the source commit ID, toolchain versions, build commands, dependencies, test results, and more. The FuzeID provides a complete traceable history for each firmware version, ensuring that it can be reproduced and analyzed at any time.
  • Centralized Management with Distributed Operation: Fuze is a distributed build executive that can execute locally or in cloud infrastructure, but it centralizes all elements of the firmware build and configuration management process. This helps in maintaining a single source of truth and ensures that all team members have access to consistent data.
  • Environment Replication: Fuze supports the creation of reproducible build environments using containerization. This ensures that the same environment can be used across different systems, reducing the risk of environment-related bugs.
  • Automated Configuration Management: Fuze generates and tracks all of the information described above for each build. These reports are invaluable for maintaining accountability and ensuring that the build process meets all required standards.

Conclusion

Effective configuration management (CM) is crucial for the success of embedded firmware development. In an industry where the stakes are high and the environments are complex, CM serves as the backbone that keeps every aspect of the development process organized and traceable. By ensuring that each firmware build is meticulously documented—from who initiated the build to the exact tools and environments used—you safeguard against the chaos that can arise in embedded systems.

CM is not just about tracking versions; it’s about creating a system where every build is fully reproducible, where every issue can be traced back to its source, and where every deployment is precise and error-free. In this increasingly complex landscape, automation tools like Fuze elevate CM practices by centralizing and streamlining these processes, ensuring that your firmware is not only reliable but also fully auditable.

As embedded systems continue to evolve, robust configuration management is no longer optional—it’s essential. Whether you’re managing a small project or a large-scale deployment, the ability to control and replicate every aspect of your firmware builds is key to maintaining quality, reliability, and innovation.

 

Automated testing is the backbone of Continuous Integration, the centerpiece of modern software development and DevOps. In the embedded systems world, discontinuous delivery forces a rigorous emphasis on pre-release testing.Even more so than in the Continuous Delivery world of web applications.

What’s the cost of a mistake? You get one shot to make the release. It better be right.

The good news is that if you apply the right workflow and toolset, you can do everything in your power to test the heck out of your release with automation, leaving the development and test teams to focus on what they get paid to do – product development and test development.

The Two Levels of Testing

Whether you develop for the web or for embedded systems, you’re familiar with the two levels of testing: unit testing and End-to-End testing.

Unit Testing

Unit testing is common between web/mobile applications and embedded applications. You employ unit test, right?

The frameworks may be different, but the purpose is the same. Test the API’s and functions underneath to ensure that they don’t fall over at the basic level. Unit testing is more associated with the integrity of the build itself, rather than the system’s functionality.

In embedded systems, you can use frameworks such CppUTest or Google Test.

End-to-End / Integration / System Testing

True functional testing of the system and all its components and interfaces.

This is where the embedded and web/mobile worlds diverge in toolsets and industry support. End-to-end testing for web and mobile applications often revolves around UI interactions, employing tools like Selenium to automate user actions and elastic cloud instances to scale test volume and speed.

In embedded systems, this level of testing is far more complex due to the hardware-software interplay, requiring custom test rigs, specialized equipment, and custom software tools. But this level of testing is also absolutely critical to the success of your product. You have to know that when you release it, it’s going to work.

The Challenges in Automating Embedded End-to-End Testing

True end-to-end testing of embedded systems requires Hardware-in-the-Loop (HIL), which presents a number ofchallenges for automated testing.

Geography

Your devices under test (DUTs) live in the physical world. They exist on a desk, bench, or rack in some physical location in an office or lab. No matter where you build your firmware image(s), whether locally, on a centralized physical server, or in the cloud, you gotta get that image pushed into a device that has ontological status in the physical world. How do you bridge from where your build took place to the physical geography of the DUTs?

Finite Resources

Hardware is limited. You can’t elasticize your DUT farm. You may only have one or a few devices because of cost or other factors that affect availability. Therefore, how do you best utilize these resources to avoid slowing down the development team or holding up a release?

Heterogeneous DUT Farm

You probably have a bunch of different products, or several versions of the same product, or both. Each of these may or may not get the same build or firmware image. You’ll need to manage both the matching of build to DUT type and the matching of test content to DUT type. How do you do this in automation?

Command, Control, and Acquisition

Your test system might have debuggers, USB switches, power supplies, card readers, scopes, logic analyzers, robotic mechanisms, rPi controllers, and a plethora of other physical equipment or host applications required to exercise your system and acquire the necessary data and results. How do you utilize all of this in a 100% hands-off environment?

Reproducibility

Ensuring that test results are consistent and repeatable across multiple test runs and devices is essential for reliable validation. But end-to-end testing with HIL is messy. DUT states, test system states, and environmental factors all work against reproducible results. How will you control the environment to ensure that subsequent runs produce the same results?

Infrastructure Reliability

Automating a plethora of external equipment and applications introduces risk of test failures outside of the DUT and its test content. How will you keep the team from having to triage each and every failure to determine if its a “real” failure or an infrastructure failure?

How to Fully Automate Your End-to-End Testing with Embedded Systems

Luckily, there are some things you can do to fully automate your end-to-end testing with embedded systems.

A Framework – The Person in the Lab

Start with the premise: what does the person do?

How do they interact with a device under test? What applications and tools do they use? What commands do they type? How do they interpret the results? Where do they store the results? How do they know which test content to execute on which DUT?

The person likely interacts with the DUT like this:

Make your automated test framework do that same thing. A similar concept to shell scripting.

Your QA or system tester already has tools, workflows, and methods. Your development team has already built tools to help them develop and test. White box and black box. Don’t reinvent the wheel. This stuff works. Use it.

Not only does that approach reduce further development and eliminate interfaces and APIs prone to errors, but it also allows you to integrate all of the additional equipment needed by embedded systems testers, such as robot mechanisms, control devices, debuggers, and acquisition devices.

When you architect and build your automation system to stand-in for the person in the lab, you minimize the extra work of developing and debugging non-essential systems, and maximize the reuse of the knowledge, workflow, and tools that already exist for manual testing.

Ideally, your automated test framework is a stand-in for the person in the person in the lab.

Tiered CI Test Workflow

Design a tiered automated test workflow for CI. CI testing aims to quickly find and squash bugs that might make it into the main (or release) branch. The quicker you spot an issue, the easier for development to pinpoint the bug.

But that doesn’t mean you should run 100% of the tests in all scenarios. If every PR requires 24 hours of testing before you can merge, your development team will have a lot of time to scroll through TikTok. You can be smart about where you take on risk without slowing down the development team.

Make a technical assessment and ask yourself the following types of questions:

  • What set of tests can we run in less than 10 minutes to give us at least an 80% confidence that this PR is good?
  • What set of tests can we run in less than 30 minutes when we merge to the main branch to give us at least 95% confidence that the merge didn’t break the main branch?
  • What set of tests can we run overnight in a few hours that gives us 100% confidence in the integrity of the main branch?
  • What set of tests should we run over the weekend for 24 hours or more that gives us confidence in the reliability of our main branch?
  • What set of tests must be run on each release?

DevOps

Although the universe of open-source and commercial toolsets for implementing DevOps is limited for some of the specific challenges of embedded systems development, you can and should still use the concepts.

Source code control for everything, including test content, control scripting, and results analysis. Keep the test content colocated with the firmware source code. Update test content along with firmware updates because these are tightly coupled. Separate the control scripting and results analysis from the firmware source code because these are loosely coupled.

Use pipelines to initiate automated test runs and gather and analyze results. Use the pipeline reporting mechanisms to indicate pass or fail and tie this status into the overall PR, merge build, or release status. Give the test results the power they deserve in the workflow.

Build self-monitoring into your physical infrastructure and hook it into notification channels such as Slack/Teams or even text messages. Use time-based and event-based mechanisms to perform hardware resets and reboots.

EmbedScale with Cloud2Lab Automated Test Framework

At 4TLAS, we’ve taken our years of experience in embedded systems development, automation, testing, and DevOps skills and built a system that helps you automate your end-to-end testing by addressing the areas described above. The EmbedScale DevOps pipeline offers a solution for automating end-to-end testing in embedded systems through the Cloud2Lab automated test framework. Cloud2Lab extends the capabilities of the EmbedScale pipeline by integrating an enterprise-level framework for automating testing activities across a heterogeneous farm of virtual and physical devices. This includes managing and deploying test content, coordinating and parallelizing test case execution, infrastructure monitoring, and publishing results to ensure products meet and exceed quality standards.

By bridging the gap between cloud-based development and lab-based testing, Cloud2Lab facilitates a fully automated CI pipeline. Cloud2Lab allows for scalable and tiered test automation, managing a diverse range of devices and versions with ease, and acts as the Person-in-the-Lab. This approach minimizes the need for custom code, instead relying on configuration, thus enabling a seamless transition to automated testing for embedded systems.

Conclusion

Automated testing in embedded systems presents unique challenges due to hardware dependencies, real-time constraints, and specialized environments. But that doesn’t mean you can’t move towards fully automated end-to-end testing. By following a few key architectural and workflow guidelines, you can move more and more HIL testing from manual to automated. More testing means less risk. To achieve more testing, put the machines to work and keep the people focused on what they do best and where they add the most value.

With the introduction of solutions like 4TLAS’s EmbedScale pipeline with Cloud2Lab, you can quickly and effortlessly overcome the hurdles of rolling your own. The future of automated testing with embedded systems lies in adopting these advanced solutions, ensuring efficiency, scalability, and unparalleled quality.

 

Embedded software development is hard. The cost of mistakes is high, the skillset is specialized, the software industrial complex of open-source and commercial toolsets largely ignores it, yet it’s at the core of an entire industry that’s growing.

Although most of the DevOps world is aimed at the web and mobile industries, you can and should be integrating best practices from this world, where it makes sense. DevOps, and specifically Continuous Integration (CI) is one of those concepts that greatly improves quality, speed, and lowers the overall effort and cost of your development cycle.

The Importance of CI in Embedded Systems

DevOps engineers typically talk about CI/CD, where CD is “Continuous Delivery” or “Continuous Deployment.” Well, in embedded systems, we don’t typically enjoy the CD piece. In fact, we usually must manage Discontinuous Delivery.

But we can and should use the CI piece.

What is CI?

CI emphasizes integrating new source code often and continuously in small chunks. Each integration, ie, PR, merge to main, merge to release branch, etc, is then built, tested, and analyzed to ensure early detection of integration bugs. To be effective, this process must be 100% automated from start to finish, and the analysis of test results should have voting rights into the PR or merge mechanism.

word image 969 1

The Value of CI

CI can dramatically improve embedded software development practices. Some of the key benefits CI bring to embedded developers include:

  • Fix bugs quicker: The sooner you can pinpoint the commit that caused the bug, the sooner you can fix it.
  • Faster time to market: You know the state of the codebase at all times. Can you take your current base and release it? Does it work for demo purposes? Can we send it to the certification test house?
  • Improved quality: More build and testing, all the time.
  • Enhanced collaboration: Better alignment across teams through more frequent integration.

How to Build an Automated Embedded CI Pipeline

A CI pipeline is a process that moves code from development, through test, into production-ready. An embedded CI pipeline automates the trigger for building, testing, and analyzing that commit. The developers use it. So does QA, system test, and product management. When done right, it executes fully automated and acts as another member of the team.

Core Operations of a CI Pipeline for Embedded Systems

  • Trigger: What triggers the execution?
  • Pull: The source code has to go somewhere to build it.
  • Build: Build the firmware image(s).
  • Analyze/Unit Test: [Optional]: Static analysis and unit/build test.
  • Package: Grab the built image(s) and combine it with config files, other images, scripts, and anything else that’s required to be pushed to a fully functional device system.
  • Automated Device Test: Push the image package to the devices in the test farm, initialize it all, and let it fly. No hands required.
  • Analyze and Report Device Testing Results: Grab the device test results and analyze them for failures. Push the results back to pipeline for voting rights.

word image 969 2

Core Components of a CI Pipeline for Embedded Systems

The core components for an embedded CI pipelines support the operations described above.

  • Code Repository(s): GitHub, GitLab, BitBucket, etc
  • Pipeline Execution Engine/Runner: Jenkins, or the pipeline/runner from your git provider
  • Build Machine/Resource: Container, VM, or physical system(s) that contains the cross-compile toolchain and performs the build
  • Static Analysis Tool: Clang, Coverity, etc
  • Unit Test Tool: Unity, gtest, CppUnite, etc
  • Configuration Management for Build and Packaging: Artifact repository plus an executive around it to manage the versioning of firmware packages. Likely homegrown.
  • Automated Test Framework: A framework to perform and manage the automated testing across the DUT farm. Likely homegrown.
  • Devices Under Test (DUT) Systems: Your devices, plus all of the supporting equipment necessary to program, control, execute, and acquire.
  • Results Analysis and Storage Tools: Scripts and other applications for extracting data, saving results, analyzing, and determining pass/fail. Likely homegrown.

word image 969 3

Building and Configuring the Embedded CI Pipeline

When implementing the embedded CI pipeline, you’ll need to make several decisions.

  1. Trigger: What events should trigger your pipeline? Typical events are PR creation and update, merge to the/a main branch, release branch creation and update, and time-based triggers such as nightly and weekly. Triggering decisions are usually related to and driven by testing decisions due to time and resource constraints. How much testing should we execute for a PR? Ditto for a merge to main, etc.
  2. Pipeline: You’ll want to either use the built-in pipeline runners associated with your git provider, or you can stand up an executive like Jenkins in your own infrastructure. Jenkins provides the ultimate in flexibility and customization over the built-in pipeline runners, but it also comes with the management and maintenance overhead.
  3. Build: Do you build in the cloud or on a physical golden master server? Do you use a VM or a container? How do you configuration manage the build environment and toolchain? Do you maintain a set of build scripts colocated with the source code or independent?
  4. Static Analysis and Unit Test: This step, if you use it, should be associated with the build, rather than the test stage. Does it provide value? If so, how do you interpret the output and configure the rules? What constitutes a violation? What tools do you use?
  5. Configuration Management of the Artifacts: When you create the build and packaged artifacts, you’ll want to ensure you have complete traceability to them, and to the constituent parts that comprise them. The pipeline instance itself is ephemeral, therefore, you don’t want to rely on it for the storage of the artifacts. You can use artifact repository tools such as Artifactory to hold the binaries and scripting inside the pipeline to manage it.
  6. Automated Test Framework: The most important aspect of your automated test framework is that it is truly 100% automated. No human required. This is a hard problem to solve because no open-source frameworks exist to serve the needs of hardware testing environments, so you’ll almost certainly need to develop it yourself. The automated test framework must command, control, and execute the testing, as well as acquire the results.
  7. DUT Systems: The DUT systems are finite resources. They cost money, they take up space, and they’re messy. But they’re also the most important enabler of automated device testing, which is a critical piece to the CI pipeline. What other elements besides your product are necessary or valuable for automated test? How do you program your image into the device?
  8. Results Analysis: Your automated testing will likely fall into one or both of two categories: regression testing and device characterization testing. The purpose of results analysis is to determine pass/fail and whether your release falls into the ranges given by the requirements. Python and other scripting languages are your friend here.
  9. Merge or Release Decisions: The stages of your pipeline feed the merge and release decision. You must configure the pipeline such that results along the way either block or allow the execution to continue.

Implementing Embedded CI with 4TLAS EmbedScale

4TLAS’s EmbedScale pipeline is a done-for-you solution for incorporating CI into your embedded development workflow.

Why Choose EmbedScale for Embedded CI?

  • It solves the build and package configuration management problem: The Fuze build executive inserts configuration management into the workflow right at the beginning. It manages the build environment and toolchain as well as all dependencies and source commitIDs. It keeps a record of everything related to build and allows you to easily and effortless know exactly what you have, what’s in it, and how to recreate it.
  • We manage the pipeline. You focus on your code: Why distract your team with developing and managing all the bits of the pipeline? We’ll do that for you and you don’t have to worry or spend cycles on it.
  • It solves the automated test framework problem: No open-source automated test frameworks exist for automating your device testing, but EmbedScale has one. The Cloud2Lab Automated Test Framework is a Configuration-as-Code solution that you can plug into your workflow and will 100% automate your device testing.
  • It scales with you: As your needs change, EmbedScale can dynamically meet those needs.

Conclusion

CI can significantly enhance the efficiency and effectiveness of your embedded software development. CI practices can help embedded teams deliver high-quality software faster and more reliably. 4TLAS’s EmbedScale pipeline, you can implement the embedded CI model efficiently and quickly.

 

word image 960 1

Hardtech embedded systems pose a challenge that modern web and mobile apps don’t experience — the matrix of firmware versions across the product versions and families.

Discontinuous delivery makes managing multiple firmware versions across diverse product lines, hardware revisions, and customer deployments a persistent challenge in the embedded systems industry. This delicate version matrix can quickly spiral into a complex web, making it difficult to track, reproduce, and maintain firmware builds internally, through the test and cert process, and in the field.

word image 960 3

Lifecycle Version Management

One pain point arises when managing firmware deployments in the field. With numerous products and hardware revisions, each potentially requiring slightly different firmware versions, keeping track of who has what and who gets what becomes a logistical nightmare.

Bug Fix Point Releases

Another challenge is the need to recreate a specific firmware version from months or years ago, often required for point releases or bug fixes. The certification house or customers may request an update to address a particular issue, but they cannot adopt the HEAD of the codebase due to certification and testing requirements. Recreating the exact build environment, toolchain, and dependencies from that specific point in time can be a daunting task, leading to wasted effort and potential inconsistencies.

Multiple Processors and Firmware Images in the System

The problem is further compounded when dealing with systems that have multiple processors, each requiring its own firmware image, multiplying the complexity of the version matrix exponentially.

Taming the Version Matrix

Historically, successful hardtech teams and companies use configuration management procedures and tools to handle this version matrix. Build and product managers use tools like Jira to create a manifest and traceability throughout the matrix, rely on the source code and artifact repositories for commit IDs and retrieval, and use golden build servers (or VMs) for production builds.

When done right, this system works, but requires people, procedures, time, and attention to detail. It’s fraught with opportunities for error and holes. If you’ve been in the embedded industry for a while, you probably have some of your own horror stories about version matrix hell.

The good news is that we can borrow some ideas from the modern DevOps practices of the web and mobile application worlds and then reimagine and reapply them to deal with the version matrix problem of hardtech.

Traceability Tools

The no-brainer. Ticketing tools such as Jira and source code control tools like git provide traceability in your process and source code.

Automation

Automate the build itself and attach it to the development workflow. Source code management tools like GitHub, GitLab, and BitBucket allow you to build pipelines around triggers such as PR’s, branch commits, and merges to the main branch. Automating the builds allows you to easily build the images for the entire system, including multiple processor systems. It provides a snapshot, possibly a release snapshot, of build artifacts across the system. The set of commit ID’s associated with that build are important source material for the version matrix.

Suppose libraries or prebuilt packages need to be part of the link or release packaging. In that case, you can use automation to pull the correct versions of these from the proper locations and make them available to your build workflow.

With the traceability tools described above, you can build automation into your workflow to auto-generate and populate as much of the build manifest as possible into a Jira ticket or configuration file.

More automation equals less chance for error and less time to complete.

Containers

Web and mobile application teams use containers for just about everything in their workflow, including the test and operations sides. For hardtech, we can’t use containers for those purposes because we need Harware-in-the-Loop, but we can reimagine and reapply containers for the production build toolchain.

This provides the dual benefit of making the production build machine available to the developers for local builds and allowing build managers to put the container or its Dockerfile under configuration management. Then the build machine and its toolchain can be part of the version matrix helping to ensure you can reproduce old versions in the future.

Delivery Tracking

The web application world uses continuous delivery to an elastic sea of servers and containers. It’s constantly pushing, controlling the scale, and managing rollback and regional updates. It’s in charge.

The discontinuous delivery of hardtech embedded firmware requires a different approach to delivery tracking. First, delivery is a pull. Even if the product development team initiates the new delivery, the manufacturer, test house, or end-user must accept it. This further complicates the version matrix by creating a situation where the field deployed hardware may currently be using a variety of versions.

It’s critical to know what versions of firmware are compatible with what versions of hardware and what has been delivered to whom.

The Single Source of Truth

At the end of the day, you need a single source of truth. Whether you call it a manifest, a release ticket, or a version document, you need to be able to point to a document, file, or database entry that contains everything that describes all of the version and dependency information for a build, it’s package, and any associated release information.

The single source of truth ensures that for any build or release package, whether its for test, certification, or already out in the field, you know exactly what went into it and how to recreate it.

How We’ve Solved the Complex Version Matrix Problem

word image 960 4

Our solution, which we call Fuze, combines the DevOps concepts from above with a purpose-built application to create a universal automated build and configuration management executive that streamlines the entire firmware development, build, packaging, release, and delivery process. We build configuration management into the system at the very beginning — the build.

For each build (whether local or in the cloud), we generate the single source of truth, a unique ID value called the FuzeID, that now lives forever and contains all of the metadata required to reproduce it. If that build moves forward through test, release, and delivery, Fuze attaches all of that metadata as well.

word image 960 5

With a FuzeID, you know the following:

  • The build – who, when, what tools, build command(s)
  • Source commitIDs for all repositories
  • Pre-built and library dependencies
  • Build package contents
  • Test results
  • Release status – stage, by who, when
  • Delivery status – to who, by who, when

For systems with multiple processors and firmware images, Fuze’s hierarchical packaging capabilities can handle the intricate matrix of builds and dependencies with the same automated configuration management, ensuring that you know everything about the release package.

word image 960 6

Conclusion

By embracing some modern DevOps concepts, and understanding where current DevOps tools leave gaps, embedded systems teams can effectively tame the complex version matrix problem. They can scale operations to meet growing demands, enhance quality and speed through automated CI/CD pipelines, and redirect their focus towards innovation and complex problem-solving, leaving the repetitive tasks of build, packaging, releasing, and delivering to the automated pipeline.

In the ever-evolving landscape of embedded systems, where complexity is the norm, embracing modern DevOps practices requires some reimagining and reapplication of the current toolset as well as adding your own secret sauce. Solutions like we’ve built with Fuze pave the way for embedded teams to conquer the version matrix challenge, ensuring reliable and market-ready products while fostering a culture of innovation and efficiency.

 

Scroll to Top