One topic that can be tricky for embedded systems engineers is how to version their embedded system. Every embedded system has two primary systems that need to be versioned, the hardware and the software. While versioning the system sounds simple, it can be confusing on the best way to version the system. In this post, we will examine five tips for versioning your embedded system.
Tips #1 – Use GPIO to Hard Code PCB Versions
Putting the hardware version number on the PCB silkscreen is a common way to version hardware for an embedded system, but the system software can’t read the silkscreen. In some applications, the hardware can change considerably between versions and it can be critical to ensure that a certain software version runs on specific hardware. To accomplish this, the software needs to be able to read the hardware version number to make sure that it is running on compatible hardware.
There are two ways that developers can version their hardware so that software can read the hardware version. First, if the system has spare GPIO, two or three GPIO lines can be dedicated to indicating the hardware version. Each GPIO line can be either pulled to VCC (represents a 1) or ground (represents a 0). If a binary representation is used, two GPIO would provide up to four versions while three GPIO would provide eight versions as shown below:
|Version||GPIO 0||GPIO 1||GPIO 2|
Not all systems will have two or three free GPIO lines available. Instead, developers could also leverage a free analog to digital (ADC) channel and use an analog voltage to represent the version number. In this case, a single ADC channel could be connected to the mid-point of a resistor bridge with one resistor connected to VCC and the other to ground. In this situation, the resistor values would be adjusted to provide a specific voltage that is associated with a version number. For low power applications, developers would want to choose resistance values that are sufficiently large enough to prevent any large parasitic current draw through the circuit.
Tip #2 – Use the Major.Minor.Patch Semantic
There are quite a few different methods that developers can use to version their embedded software. The system that should be adopted though, and fits with what most software development teams use is the MAJOR.MINOR.PATCH semantics. You’ve undoubtedly seen software with these version numbers such as 1.0.0, 1.1.2, 2.4.2, and so on.
The general versioning scheme, and understanding the changes impacts, can be easily seen at a glance because of the semantics definitions. For example, the MAJOR number only increments when the software is released with incompatible API changes. This means that we only increment MAJOR if there are changes to the code that are not backwards compatible. The MINOR number is only incremented when improvements are made that are backwards compatible with the existing API’s. PATCH is only incremented when bug fixes are applied to the software.
Using this semantic pattern for versioning will make it so that any software developer can understand the versioning.
Tip #3 – Create a version.h module
I’ve generally found that creating a version.h module that contains the Major.Minor.Patch semantics along with a version log to be the most effective software versioning method. The version number can be easily set for the software using macro definitions such as:
#define VERSION_MAJOR (1)
#define VERSION_MINOR (0)
#define VERSION_PATCH (0)
A developer could even set the minimum hardware version allowed to run the software:
#define HARDWARE_VERSION_MIN (4)
An important piece of the version.h module is not just to version the software but to also include the version log. This can be done using comments and should include information such as:
- Software version
- Module changed
- Changes to module
In my own version logs, I always append the changes to the top of the log so that I don’t have to scroll all the way to the bottom to see what changes were made in a previous version.
Tip #4 – Avoid module and function versioning if possible
The way that an embedded application is versioned is completely up to the development team, but one recommendation I have is to try to avoid module and function level versioning if possible. What I mean by this is that we don’t want to be assigning version numbers to individual C modules or the functions within them. The reason for this is that it starts to add an unnecessary overhead to versioning that also improves the likelihood that the versioning information will be wrong.
For example, if I have a Dio_Write function that I’ve made changes to, I don’t want to increment the version number for this function. I want to update the version number for the component that Dio_Write is in. This could be Dio.c or it could be a driver component that is a collection of the drivers for a specific microcontroller. If I version the function, I also have to version the module, and the component and the software. I’m going to forget to update it somewhere and then those changes are going to get lost. It’s best to just avoid versioning at such a low-level.
Tip #5 – Integrate VCS with your IDE
An important aspect to versioning software is how that software is integrated into a version control system (VCS). The most popular VCS these days is git, but svn and mecurial are still used by some development teams. When versioning software, I’ve found that it’s critical to make sure that you integrate your VCS functionality into your development environment in order to making committing changes simple and easy. For example, many VCS plug-ins will allow a developer to click a single button and they will add any new changes to the push. They’ll also pop up a dialog that gives a developer a chance to comment on the changes they’ve made. This is the perfect opportunity to copy the changes that were logged in version.h and paste them into the VCS log. That way all the version information between the software and the VCS match!
Versioning an embedded system doesn’t have to be complicated. In fact, the simpler you make versioning your system, the smaller the chances will be that there will be confusion or issues from using the wrong version of hardware or software. As we have seen, there are several tricks developers can leverage to simplify versioning such as using an integrated version log and hardware-based versioning. No matter what system is adopted, the only way it will be successful is if the developers involved adopt a disciplined approach to versioning and don’t cut corners when they commit their software.