扫码加入

  • 正文
  • 相关推荐
申请入驻 产业图谱

【防扯皮指南】嵌入式软件,如何进行版本号管理?

01/27 10:23
523
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

我是老温,一名热爱学习的嵌入式工程师,关注我,一起变得更加优秀!

嵌入式软件开发里面,经常会涉及到软件和固件的版本号管理,版本号并不只是一个简单的编号,而是把控迭代节奏、快速查找问题根源、稳住产品质量的重要手段。

嵌入式软件的升级,有时候会牵扯到硬件底层驱动、硬件适配等几个方面,如果版本管理乱成了一锅粥,就很容易会出现适配冲突,会容易找不到问题产生的源头。
所以,搞一套容易理解并且又能科学管理软件版本号的规则,对于嵌入式软件开发甚至产品运维来说,都显得非常重要!

在软件行业里面,最常用的是四级语义化版本结构,也就是“主版本号 . 次版本号 . 修订号 . 构建号”。

主版本号(Major):主要用来标记互不兼容的大版本改动,一般从1开始计算。比如,某款工业网关设备软件版本是V1.2.3.007,后来因为重新适配了CPU和设计了驱动程序,这种大变动之后,版本号要改为V2.0.0.0。

主版本号如果发生改变,就要跟很多部门同步这个更新信息,也要跟用户描述清楚新旧版本的兼容情况。

如果是在研发阶段而非量产阶段,主版本号就设置为0,比如V0.1.2.345,这样很容易直观看出该版本还没有量产发布。

次版本号(Minor):对应新增功能,并且这些功能与同一个主版本号的新旧版本兼容,次版本号一般从0开始,每更新一次就代表增加了新的功能或做了重要的优化。

比如,某款传感器的固件V2.1.1.123已经支持了蓝牙通信,后来要增加Wi-Fi通信(不改动硬件),在固件开发完成之后,版本号就改为V2.2.0.1了。

需要注意的是,这次的改版只增加功能,但不能动核心模块的接口,这样用户才能进行增量迭代升级,而不用折腾硬件配置。

修订号(Patch):主要用来修复bug,不增加新的功能,并且还需要兼容新旧的主版本号和次版本号,修订号也是从0开始增加的,最常见的用途就是修复bug、优化性能、微调稳定性参数,等等。

比如,用户反馈产品的蓝牙通信容易断开连接,工程师找到问题所在并修复了bug,没有改动任何的功能模块,这样就可以在修订号上进行升级,例如从V3.2.1.309升级到V3.2.2.315。

注意,更改修订号的时候,需要在版本日志里面注明修改了哪个bug,影响范围有多大,测试结果怎样,方便后续继续追溯问题。

构建号(Build):这是在嵌入式场景里面额外增加的版本号字段,是用来标记同一个版本的不同编译次数的,一般由脚本工具自动生成,不需要工程师手动更改。

它的主要作用是方便研发团队内部更新测试固件,比如,某款模组第一版测试时版本号是V0.1.1.102,然后下一次构建就变成了V0.1.1.103.

构建号不影响版本的兼容性,并且只在研发团队内部使用,在正式量产发布的时候,需要把构建号进行固定,不能出现一个正式版本对应多个构建版本的情况。

还有一点就是,对于预发布版本,可以加一个后缀来进行状态区分,比如某款路由器固件,可以先出一个内测版本V1.1.0.123-beta给内部使用,内测完成之后再出一个V1.1.0.135-rc1候选版,最终没有问题就发布V1.1.0.135。

在嵌入式软件里面,通常在头文件里面确定版本号的宏定义,这样既能方便业务逻辑调用和日志打印,还能用来校验固件升级。

以下是直接可用的C语言代码,头文件 version.h

// version.h 全局版本定义头文件#ifndef VERSION_H#define VERSION_H
// 版本号宏定义,便于编译控制和逻辑判断#define MAJOR_VERSION    2       // 主版本号#define MINOR_VERSION    2       // 次版本号#define PATCH_VERSION    1       // 修订号#define BUILD_VERSION    218     // 构建号,可由编译脚本自动递增
// 构建日期时间:标准化格式(YYYYMMDD_HHMMSS),由Makefile/IDE脚本自动注入// 脚本示例(Makefile):-DBUILD_DATETIME="$(shell date +%Y%m%d_%H%M%S)"#ifndef BUILD_DATETIME#define BUILD_DATETIME   "20260126_143005"  // 默认值,避免编译报错#endif
// 版本号字符串拼接,格式:主.次.修订.构建#define VERSION_STR      STR(MAJOR_VERSION)"."STR(MINOR_VERSION)"."                        STR(PATCH_VERSION)"."STR(BUILD_VERSION)
// 字符串转换辅助宏#define STR(x)           #x
// 版本号数值化,用于升级校验(如比较当前版本是否低于目标版本)#define VERSION_NUM      ((MAJOR_VERSION << 24) | (MINOR_VERSION << 16) |                         (PATCH_VERSION << 8) | BUILD_VERSION)#endif // VERSION_H

具体应用案例,源文件 main.c

// main.c 版本号应用示例#include "version.h"#include <stdio.h>
// 版本日志打印,启动时输出当前版本及构建日期(辅助追溯)void print_version_info(void) {    printf("Embedded Firmware Version: %sn", VERSION_STR);    printf("Build Datetime: %sn", BUILD_DATETIME);  // 新增构建日期打印    printf("Version Code: 0x%Xn", VERSION_NUM);}
// 版本升级校验,判断目标版本是否可升级int check_upgrade(uint32_t target_version) {    if (target_version > VERSION_NUM) {        // 目标版本更新,允许升级        return 1;    } else if (target_version == VERSION_NUM) {        // 版本一致,无需升级        return 0;    } else {        // 目标版本更低,禁止降级(嵌入式场景通常限制降级)        return -1;    }}
int main(void) {    print_version_info();    // 模拟升级校验,目标版本V2.2.2.315(数值化后为0x0202021F)    uint32_t target_ver = 0x0202021F;    int ret = check_upgrade(target_ver);    if (ret == 1) {        printf("Start upgrading to target version...n");    } else if (ret == 0) {        printf("Current version is the latest.n");    } else {        printf("Downgrade is not allowed.n");    }    return 0;}

上述的示例代码,版本号是用宏定义的方式进行编写的,方便编译脚本自动更新构建号和构建日期,构建日期采用的是YYMMDD_HHMMSS这种标准格式,可以用Makefile或者IDE脚本自动获取固件的编译时间。

版本号贯穿嵌入式软件开发的每一个环节,开发分支要对应明确的版本范围,测试时要记录清楚每一个测试用例对应的版本,量产时要把版本号和生产批次进行绑定。

上面介绍的四级版本号结构还可以进行灵活调整,可以简化为三个级别的版本号结构,但核心要点还是要整个软件流程保持一致,不能乱定义版本号的层级。

总的来说,嵌入式软件需要有一套规范的版本号管理规则,然后再加上代码的编程规范,这样能让嵌入式软件迭代得更有条理,出现问题的时候也能快速进行回溯。

你平时是如何管理嵌入式软件版本的呢?

相关推荐