单片机固件版本号常见的规则~

strongerHuang 2025-09-07 08:00
关注+星标公众,不错过精彩内容
来源 | 嵌入式情报局


今天给大家带来一个我常用的单片机固件版本定义方式。我相信一些朋友在入职一些小公司的时候一般都是一V1、V2等等这类的版本定义,然而随着项目的不断迭代,软件的逐步铺开,这样的建议版本定义已经无法满足需求,并且产生一大堆问题,比如:

 • 设备变砖:某工厂误将适配硬件V3的固件刷入V2设备,导致整批次报废

• 问题追溯难:客户反馈设备异常,工程师耗费3天定位到是v1.2.3的PWM驱动BUG

• 生产混乱:产线同时存在测试版/量产版固件,误刷率高达15%

• 协作低效:硬件工程师更换传感器后,软件组未同步更新版本,引发通信故障

等等,以上这些总结下来主要是这四种风险 :

  1. 版本不兼容导致设备功能异常
  2. 无法快速确认现场设备版本
  3. 问题复现时找不到对应代码版本
  4. 无法满足医疗/工控设备的版本追溯要求

一、完备的版本号设计方案

1.1 版本号定义模版


主版本.次版本.补丁-hw硬件版本+日期.git哈希.crc校验
示例:2.3.5-hw4+20250504.8a3f2c1.78A2B9C4


1.2 字段详解表

字段
规则说明
技术实现
主版本
架构级不兼容更新时递增
手动设置,重置次版本/补丁
次版本
新增向下兼容功能时递增
手动设置,重置补丁
补丁
BUG修复/优化时递增
自动基于Git提交数生成
hw硬件
PCB版本号(V4.2→hw4)
从硬件配置文件读取
日期
固件编译日期(YYYYMMDD)
自动获取系统时间
git哈希
取前7位提交ID
git rev-parse --short=7
crc校验
固件完整性校验码
计算整个二进制文件的CRC32值

二、具体如何实施?

2.1 固件代码实现

现在单片机基本上都还是C语言为主导,主打还是一个高效,那么下面以结构体的方式进行说明如下:

2.1.1 版本信息结构体

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

// 存储到Flash的0x0800F000地址
typedefstruct __attribute__((packed)) {
    uint8_t major;          // 主版本
    uint8_t minor;          // 次版本
    uint16_t patch;         // 补丁号(自动生成)
    uint8_t hw_version;     // 硬件主版本
    uint32_t build_date;    // 构建日期
    char git_sha[8];        // Git提交哈希(7字符+结束符)
    uint32_t file_crc;      // 固件文件CRC32校验码
} FirmwareVersion;

// 通过指针访问版本信息
#define FW_VERSION ((FirmwareVersion*)0x0800F000)

2.1.2 CRC校验集成

// 在链接脚本中保留CRC存储区域
LR_ROM 0x08000000 0x100000 {
    ER_CRC 0x0800FFF0 EMPTY 0x00000004 { }
}

// 编译后脚本自动注入CRC值
$ arm-none-eabi-objcopy --update-section .CRC=checksum.bin firmware.hex
比较简单吧,基本上方法就是在flash的固定区域中预留一段空间出来便于用于后续自动化工具的填充,当然你也可以自己去填充,不过就是麻烦了点,而且容易搞错。

2.2 制作自动化工具

自己做的工具肯定是要好用,每次设计我主要考虑如下四个方面的功能,也是我觉得非常有必要的四个方面。

• 1、能够自动打包带版本号的固件文件

•2、读取PCB配置文件中的硬件版本

• 3、完整性保护,自动计算并注入CRC校验码

• 4、追溯支持,嵌入Git提交信息和构建时间

2.2.3 核心代码片段

对于自动化工具的开发这里不过多展示,因为windows有非常多的方式,这里仅仅给出来一些大致的思路伪代码,供大家参考,思路也很简单,结合IDE生成的bin文件或者hex文件进行版本信息获取后填充到bin和hex中。

# 自动生成版本信息
def generate_version():
    # 获取Git信息
    git_sha = subprocess.check_output(
        ['git''rev-parse''--short=7''HEAD']
    ).decode().strip()
    
    # 读取硬件版本
    with open('hw_config.json'as f:
        hw_ver = json.load(f)['main_version']
    
    # 计算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}"
    }

三、字段的利用

既然我们在版本号中进行设计,那总得把各个字段用起来吧,当然这个也需要结合大家实际项目的需求,比如我这边通常会在Bootloader有个校验逻辑如下代码所示:

// 固件升级时执行校验
int validate_firmware(FirmwareVersion *new_ver) {
    // 硬件版本检查
    if (new_ver->hw_version != CURRENT_HW_VERSION) {
        send_error("ERR_HW_MISMATCH");
        return-1;
    }
    
    // CRC完整性校验
    uint32_t calculated_crc = calculate_crc(new_ver);
    if (calculated_crc != new_ver->file_crc) {
        send_error("ERR_CRC_FAIL");
        return-2;
    }
    
    // 防版本降级
    if (compare_version(new_ver, current_ver) < 0) {
        send_error("ERR_VERSION_ROLLBACK");
        return-3;
    }
    return0;
}

这样的话,在产品量产的时候进行硬件版本的自动校验,而不会导致误刷;每个版本中都有Git哈希能够快速锁定问题代码版本;CRC校验能够拦截一部分的篡改和异常。

当然在上面的基础上还可以更加的精益求精,比如将打包工具集成到Jenkins/GitLab CI,在内网搭建版本看板,实时显示各版本状态等等,这个就看各个公司对版本的重视程度了。

------------ END ------------


资讯配图

●专栏《嵌入式工具

●专栏《嵌入式开发》

●专栏《Keil教程》

●嵌入式专栏精选教程


关注公众号回复“加群”按规则加入技术交流群,回复“1024”查看更多内容。


点击“阅读原文”查看更多分享。

声明:内容取材于网络,仅代表作者观点,如有内容违规问题,请联系处理。 
单片机
more
6个月从零掌握单片机开发!软硬件全流程实战,配套开发板+项目驱动教学!
单片机还能这样输出PWM
单片机通过WiFI进行OTA固件升级
单片机项目如何添加版本信息?
单片机软件为啥要上架构?
单片机BootLoader与APP如何合并?
单片机变量不被初始化的实现方法
单片机中断变量加volatile的好处......
同样的单片机代码,编译后的hex为啥会变?
最强Cortex-M85单片机,测评免费送开发板!
Copyright © 2025 成都区角科技有限公司
蜀ICP备2025143415号-1
  
川公网安备51015602001305号