Automate Your Firmware Versioning

Here’s a quick and easy way to make sure the version string in our firmware is never out of sync with our {% c-line %}git tag{% c-line-end %}.

We’re going to assume semantic versioning for the firmware, because it’s simple and easy to understand.

First we tag our first development release on {% c-line %}main{% c-line-end %} :

{% c-block language="bash" %}
$ git tag 0.0.1
$ git push --tags
{% c-block-end %}

We can now use the following line to generate a version string on any commit:

{% c-block language="bash" %}
$ git describe --tag --always --dirty="+"
0.0.1-4-g9913f2b8d
{% c-block-end %}

The above shows that the current branch is 4 commits ahead of the commit tagged as {% c-line %}0.0.1{% c-line-end %} and that the current branch's hash is {% c-line %}9913f2b8d{% c-line-end %}.

If, instead, we run that command on the most recently tagged commit on {% c-line %}main{% c-line-end %} , we'll get:

{% c-block language="bash" %}
$ git describe --tag --always --dirty="+"
0.0.1
{% c-block-end %}

We now have a handy tool for generating a version string that enables us to uniquely identify the firmware running on a device. Next we need to integrate that into our build system, for example {% c-line %}CMake{% c-line-end %}.

First let’s create a basic template for what our version header file will look like and name it {% c-line %}version.h.in{% c-line-end %}:

{% c-block language="c" %}
#ifndef VERSION_H
#define VERSION_H

#define GIT_VERSION "@GIT_VERSION@"

#endif //VERSION_H
{% c-block-end %}

Now, let’s add this snippet to one of our {% c-line %}CMakeLists.txt{% c-line-end %} files in the project:

{% c-block language="cmake" %}
find_package(Git)
if(GIT_FOUND)
 execute_process(
    COMMAND ${GIT_EXECUTABLE} describe --tags --always --dirty="+"
   WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
   OUTPUT_VARIABLE GIT_VERSION
   OUTPUT_STRIP_TRAILING_WHITESPACE
  )
else()
   set(GIT_VERSION "0.0.0")
endif()

configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.h.in ${CMAKE_BINARY_DIR}/version.h @ONLY)
{% c-block-end %}

Now, when we build our project, a new file called {% c-line %}version.h{% c-line-end %} will be created in our {% c-line %}build{% c-line-end %} directory. This file contains the {% c-line %}GIT_VERSION{% c-line-end %} which is a {% c-line %}#define{% c-line-end %} of our generated version string. We can now include {% c-line %}version.h{% c-line-end %} in any file we want to grab our version string.

On device bootup, we can log what firmware version is running with {% c-line %}printf("Firmware Version: %s", GIT_VERSION);{% c-line-end %} or if we're using ZephyrRTOS, {% c-line %}LOG_INF("Firmware Version: %s", GIT_VERSION);{% c-line-end %}.

Reach out to learn more about Lager's hardware test automation platform.

Try One Month Free

hello@lagerdata.com