Versioning Software

20 January 2022 by Phillip Johnston Last updated 11 February 2026Versioning software projects is important, and especially so on embedded software projects: every device is likely to have a different software version flashed on it (especially during development), and there will be a range of software versions flashed onto customer devices at any given time. Because every binary is slightly different in some way or another, in order to debug the system we need to access the proper debug symbols, addresses, and change logs for that correspond to the version we are working with. For these reasons, we need the …

To access this content, you must purchase a Membership - check out the different options here. If you're a member, log in.

Versioning PCBs

We consider hardware versioning an essential device-side capability and process for embedded systems development. Every PCB used in an embedded system is bound to have multiple revisions throughout the product’s development cycle, and it is important that we can easily identify the revision we are currently working with – especially from within the software system.

The version number for a PCB should be incremented every time a new version is released, no matter how trivial the changes may seem.

Version Silkscreen on PCB

The PCB version should be printed on the board via silkscreen so that engineers and developers can unambiguously determine the version. Silkscreen is preferred to a sticker because stickers often come off, and a board may very well be mislabeled.

Version Listed on Schematic

At a minimum, print the PCB version on the schematic’s front page. Ideally, the version will be on every page of the schematic. This makes it quite easy for anyone on the team to check whether the schematic they are looking at refers to the PCB they have in hand.

Software-Interpretable Versioning

While the PCB silkscreen and schematic versions aid developers, they do not give the software access to a method to determine what hardware revision it is running on.

Some PCB revisions will be agnostic to software and merely involve tweaking some board design parameters. However, many revisions end up being significant enough to necessitate corresponding software changes: pinouts are changed, peripheral devices are changed, features are added/removed, etc.

In the “ideal” case, all of the will hardware can be immediately scrapped and the software will only need to support newer hardware. However, these ideal case rarely plays out. Because development hardware is often in limited supply due to its high production cost, teams often need to support multiple hardware revisions concurrently. Likewise, we may already be selling our devices, and now we need to maintain support for existing devices as well as the new variant. If we have a way to read the hardware version from software, we have the ability to support multiple variants with a single revision. We can also use the revision to access the proper firmware variant in the event that we maintain different binaries for different variants.

Frustratingly, the majority of embedded systems projects we work on still do not provide a method for the software to determine the hardware revision. This feature isn’t considered until it is too late, and there are multiple variants that need to be supported with no good way to distinguish them in software. At this point, the problem becomes one of build management: we must manually ensure that existing devices are flashed with the proper software version. This is an error-prone process

Use GPIO Encode PCB Version

The simplest and most common technique for encoding PCB revision so that software can access is by encoding a version using GPIO. The software will read the three GPIO lines and interpret them as a 3-bit binary version number. The GPIO will be configured in software with internal pull-ups. The version number in hardware is then controlled by placing resistors that connect the pin to GND.

Note

The same system works if the polarity is reversed as well. The directionality of the system and the value of the resistors are usually driven by power concerns (to minimize leakage current).

Version GPIO 2 GPIO 1 GPIO 0
1 0 0 1
2 0 1 0
3 0 1 1
4 1 0 0
5 1 0 1
6 1 1 0
7 1 1 1
8 0 0 0
Note

Some teams use 0b000 to represent version 1, and 0b111 will represent version 8, and some just make 0b000 version 0. In other cases, we have seen teams reverse the ordering such that they count down instead of up.

Three GPIO lines are often sufficient, as they provide a possible set of 8 versions before we need to reset. We have worked on teams that used only two GPIO lines due to GPIO constraints, but we have always found this insufficient: almost every system we have worked on has gone through four revisions before or after product launch. Four GPIO lines gives you much more wiggle room, but we have rarely been through that many revisions. In our opinion, it is better to re-start the version numbers once production hardware is ready, scrapping support for all development hardware at that time. Version increments after that point apply to production hardware changes.

Include High-Z as a Pin State

Most GPIO have a high-Z state, and you could leverage this to get 3 bits per pin instead of 2.

High-Z is detected in the following way:

  1. Drive the line low
  2. Convert to input (high-Z) and read
  3. Drive the line high
  4. Convert to input (high-Z) and read

If the two input readings hold different values, the pin is in high-Z state.

Use an ADC to Encode Version

Another method for supporting version numbers is to use an ADC to convert a voltage to a version. A resistor network is used to control the voltage seen at the ADC (the midpoint of the resistor network is connected to the ADC input). Versions correspond to “binned” ADC readings, representing equal divisions of the full ADC range (e.g., 4095 for a 12-bit ADC).

Version ADC Range (12-bit)
1 0-511
2 512-1023
3 1024-1535
4 1536-2047
5 2048-2559
6 2560-3071
7 3072-3583
8 3584-4095

Values for the resistor network should be chosen so that the target voltage/reading falls within the midpoint of the range specified. This scheme also necessitates using high-precision components (1%) that will not de-rate over the course of the product’s lifetime such that the version falls outside of the specified range.

Resistor values need to be appropriately selected to minimize leakage current. In addition, a second GPIO can be assigned to the versioning scheme. This pin will supply VCC to the resistor network, and it can be turned on only when the software needs to read the board version.

Note

A larger range of versions can be supported by tightening the binning on each version. However, tightening bins too much may lead to de-rated resistors causing erroneous version reads later in the product’s lifetime.

You can also combine the ADC and GPIO approaches, using one GPIO pin + one ADC pin. This would double the number of values supported by the ADC approach.

Write Hardware Version in Persistent Memory

Some processors provide user-configurable non-volatile memory. These registers can be written during manufacturing, storing the associated hardware revision directly in processor memory. You can also use a dedicated configuration EEPROM or a reserved region of flash to store this information.

Ideally, these registers/regions could be locked so that the version (and any other configuration information) cannot be accidentally overwritten.

Prototyping New Revisions

With software-readable board versioning, it becomes possible for software to be updated and tested prior to having new hardware produced. Existing boards can be reworked to match the new version as closely as possible, and that board can have its resistor network changed to the next revision. Software developers can use this reworked hardware to add support for the new revision in software.

Note

Not all changes are easily supported via rework, so some changes will need to land once the final hardware is ready. Nonetheless, we have used this strategy on multiple occasions to make significant progress ahead of receiving new hardware.

“Securing” Hardware Revision

Common practice involves stuffing resistors on traces present on the PCB, which enables easier rework. However, for teams that are concerned about CM’s “cheating” by marking old PCBs with a new revision ID, you can hard-code the board revision using the GPIO method by laying it out within the PCB (i.e., wiring the traces internally to encode the 0’s and 1’s of the PVB layout. We do not recommend this during development, as the rework method above is often quite useful, but it is suitable for production.

Further Reading

Device Command and Control Interface (Shell)

20 January 2022 by Phillip Johnston • Last updated 18 July 2022An essential device-side capability to create for our devices is a command and control interface. Most commonly, this interface is implemented as a command-line shell, but it may also be implemented in other ways (such as through an IDL language). For simplicity’s sake, we will generically refer to the command and control interface as the “shell”. Shells are useful in development for testing device behaviors without having to go through full production flows. You can create fine-tuned commands that let you test individual pieces of the system in isolation. …

To access this content, you must purchase a Membership - check out the different options here. If you're a member, log in.

IDLs Suitable for Microcontrollers

20 January 2022 by Phillip Johnston • Last updated 8 February 2022We prefer to manage the majority of our software’s external communication interfaces through an interface description language (IDL). In general, these languages/systems generally work in the same way: You create an IDL file which describes functions and data types You generate code for your target languages (the languages available depend on the tool), along with library code that handles encoding and decoding the data for transmission You import the generated code into your system and fill in the placeholder functions Specifically, you need to fill in the implementation for …

To access this content, you must purchase a Membership - check out the different options here. If you're a member, log in.

Device-Side Capabilities that Aid Embedded Systems Development

20 January 2022 by Phillip Johnston • Last updated 29 March 2023Every embedded systems project is certainly different, but there are a consistent set of capabilities that are valuable for the majority of embedded software projects. We believe that these capabilities are one set of indicators of an embedded software organization’s maturity level. Whenever we are working on a project lacking any of these capabilities, we notice: it hinders our development, debugging, and quality assurance efforts. Software-readable PCB Version Software Versioning Support Device Command and Control Interface (Shell) Debug logging Assertion support On-device Crash Dumps Reset reason detection Device metrics …

To access this content, you must purchase a Membership - check out the different options here. If you're a member, log in.

Ending the Embedded Software Dark Ages: Let’s Start With Processor Fault Debugging!

There are certain skills that mark you as being a grizzled, battle-hardened embedded systems veteran. One that stands out is being able to debug a processor hard fault with ease. Even more so if you know how to build the infrastructure to support such debugging efforts. I remember the first time I encountered a processor …