Common Rules for Firmware Versioning in Microcontrollers

Follow + star our public account to not miss exciting content


Source | Embedded Intelligence Bureau

Today, I would like to share a firmware versioning method for microcontrollers that I frequently use. I believe some friends, when starting at small companies, typically encounter version definitions like V1, V2, etc. However, as projects iterate and software expands, such simplistic version definitions can no longer meet the needs and lead to a host of issues, such as:

• Device bricking: A factory mistakenly flashed firmware for <span>hardware V3</span> onto <span>V2</span> devices, resulting in a complete batch being scrapped.

• Difficulty in problem tracing: When customers report device anomalies, engineers spend 3 days locating a <span>v1.2.3</span> PWM driver bug.

• Production chaos: The production line simultaneously has <span>test</span>/<span>mass production</span> firmware, with a misflash rate as high as 15%.

• Inefficient collaboration: After hardware engineers change sensors, the software team fails to update the version, leading to communication failures.

In summary, these issues primarily lead to four types of risks:

  1. Incompatibility of versions causing device functionality anomalies.
  2. Inability to quickly confirm the version of on-site devices.
  3. Inability to find the corresponding code version when reproducing issues.
  4. Inability to meet version traceability requirements for medical/industrial control devices.

I. Comprehensive Version Number Design Scheme

1.1 Version Number Definition Template

<span>Major.Minor.Patch-hwHardwareVersion+Date.gitHash.crcCheck</span> Example:<span>2.3.5-hw4+20250504.8a3f2c1.78A2B9C4</span>

1.2 Field Explanation Table

Field Rule Description Technical Implementation
Major Version Incremented on architecture-level incompatible updates Manually set, reset minor/patch
Minor Version Incremented when new backward-compatible features are added Manually set, reset patch
Patch Incremented on bug fixes/optimizations Automatically generated based on Git commit count
hwHardware PCB version number (V4.2→hw4) Read from hardware configuration file
Date Firmware compilation date (YYYYMMDD) Automatically fetch system time
gitHash First 7 characters of commit ID <span>git rev-parse --short=7</span>
crcCheck Firmware integrity check code Calculate the CRC32 value of the entire binary file

II. How to Implement?

2.1 Firmware Code Implementation

Currently, microcontrollers primarily use C language, focusing on efficiency. Below is an explanation using a structure:

2.1.1 Version Information Structure

// version.h
#pragma once
#include <stdint.h>

// Stored at Flash address 0x0800F000
typedef struct __attribute__((packed)) {
    uint8_t major;          // Major version
    uint8_t minor;          // Minor version
    uint16_t patch;         // Patch number (automatically generated)
    uint8_t hw_version;     // Hardware major version
    uint32_t build_date;    // Build date
    char git_sha[8];        // Git commit hash (7 characters + null terminator)
    uint32_t file_crc;      // Firmware file CRC32 check code
} FirmwareVersion;

// Access version information via pointer
#define FW_VERSION ((FirmwareVersion*)0x0800F000)

2.1.2 CRC Check Integration

// Reserve CRC storage area in the linker script
LR_ROM 0x08000000 0x100000 {
    ER_CRC 0x0800FFF0 EMPTY 0x00000004 { }
}

// Automatically inject CRC value after compilation
$ arm-none-eabi-objcopy --update-section .CRC=checksum.bin firmware.hex

It’s quite simple; the basic method is to reserve a section in a fixed area of flash for subsequent automated tools to fill in. Of course, you can fill it in yourself, but it can be a bit cumbersome and prone to errors.

2.2 Creating Automation Tools

Tools that you create yourself are definitely more useful. For each design, I mainly consider the following four functional aspects, which I believe are very necessary.

• 1. Ability to automatically package firmware files with version numbers.

• 2. Read hardware version from the PCB configuration file.

• 3. Integrity protection, automatically calculate and inject CRC check code.

• 4. Traceability support, embed Git commit information and build time.

2.2.3 Core Code Snippet

For the development of automation tools, I won’t go into too much detail here, as there are many ways in Windows. Here, I will only provide some rough pseudocode for reference. The idea is simple: combine the bin or hex files generated by the IDE to obtain version information and fill it into the bin and hex files.

# Automatically generate version information
def generate_version():
    # Get Git information
    git_sha = subprocess.check_output(
        ['git', 'rev-parse', '--short=7', 'HEAD']
    ).decode().strip()
    
    # Read hardware version
    with open('hw_config.json') as f:
        hw_ver = json.load(f)['main_version']
    
    # Calculate CRC32
    with open('firmware.bin', 'rb') as f:
        crc_val = zlib.crc32(f.read()) & 0xFFFFFFFF
    
    return {
        "version": f"{major}.{minor}.{patch}",
        "hw_version": hw_ver,
        "build_date": datetime.now().strftime("%Y%m%d"),
        "git_sha": git_sha,
        "crc": f"{crc_val:08X}"
    }

III. Utilizing the Fields

Since we have designed the version number, we should definitely utilize each field. This also needs to be combined with the actual project requirements. For example, I usually have a verification logic in the Bootloader as shown in the following code:

// Validate during firmware upgrade
int validate_firmware(FirmwareVersion *new_ver) {
    // Hardware version check
    if (new_ver->hw_version != CURRENT_HW_VERSION) {
        send_error("ERR_HW_MISMATCH");
        return -1;
    }
    
    // CRC integrity check
    uint32_t calculated_crc = calculate_crc(new_ver);
    if (calculated_crc != new_ver->file_crc) {
        send_error("ERR_CRC_FAIL");
        return -2;
    }
    
    // Prevent version rollback
    if (compare_version(new_ver, current_ver) < 0) {
        send_error("ERR_VERSION_ROLLBACK");
        return -3;
    }
    return 0;
}

In this way, during mass production, the hardware version is automatically verified, preventing misflashing; each version contains the Git hash to quickly lock down the problematic code version; and CRC checks can intercept some tampering and anomalies.

Of course, based on the above, further refinements can be made, such as integrating the packaging tool into Jenkins/GitLab CI, setting up a version dashboard on the intranet to display the status of each version in real-time, etc. This depends on how much each company values versioning.

———— END ————

Common Rules for Firmware Versioning in Microcontrollers

● Column “Embedded Tools”

● Column “Embedded Development”

● Column “Keil Tutorials”

● Selected Tutorials from the Embedded Column

Follow our public account Reply “Add Group” to join the technical exchange group according to the rules, reply “1024” to see more content.

Click “Read the original text” to see more shares.

Leave a Comment