写在前面
在嵌入式软件开发中,版本管理是至关重要但常被忽视的一环。你是否遇到过以下场景?
❓ 现场运行的固件到底是哪个版本? ❓ 客户反馈的问题对应哪次代码提交? ❓ 多个工程师编译的固件如何区分?
本文将介绍两种实用的软件版本管理方法:
✅ 方式一:使用 __DATE__和__TIME__预定义宏,自动记录编译时间✅ 方式二:使用 __attribute__((at()))将版本信息固化到 Flash 指定地址✅ 附录: __attribute__关键字详解
一、方式一:使用预定义宏记录编译时间
1.1 基本用法
C 编译器提供了两个标准预定义宏,用于记录编译时的日期和时间:
printf("CompileDate: %s %s\r\n", __DATE__, __TIME__);
1.2 输出效果
CompileDate: Aug 4 2026 14:35:22
1.3 预定义宏详解
__DATE__ | ||
__TIME__ |
📌 注意:每次编译时,这两个宏会自动更新为当前编译时刻的时间。因此,它们记录的是 固件构建时间,而非代码编写时间。
1.4 组合使用示例
voidPrintVersionInfo(void)
{
printf("========== Firmware Info ==========\r\n");
printf("Version : v1.2.3\r\n");
printf("Compile Date : %s\r\n", __DATE__);
printf("Compile Time : %s\r\n", __TIME__);
printf("===================================\r\n");
}
输出效果:
========== Firmware Info ==========
Version : v1.2.3
Compile Date : Aug 4 2026
Compile Time : 14:35:22
===================================
二、方式二:在 Flash 指定地址固化版本信息
2.1 应用场景
将版本信息固化在 Flash 的固定地址,有以下好处:
| BootLoader 读取 | |
| 远程升级 | |
| 生产追溯 | |
| 现场诊断 |
2.2 代码实现
#define VERINFO_ADDR_BASE (0x0800FF00) // Flash 存放版本信息的起始地址
// 软件版本号,存放在 VERINFO_ADDR_BASE + 0x00
constchar Software_Ver[] __attribute__((at(VERINFO_ADDR_BASE + 0x00))) = "Software: 1.0.0";
// 编译日期,存放在 VERINFO_ADDR_BASE + 0x40
constchar Compiler_Date[] __attribute__((at(VERINFO_ADDR_BASE + 0x40))) = "Date: " __DATE__;
// 编译时间,存放在 VERINFO_ADDR_BASE + 0x60
constchar Compiler_Time[] __attribute__((at(VERINFO_ADDR_BASE + 0x60))) = "Time: " __TIME__;
2.3 存储器布局
Flash 地址空间
┌─────────────────────────────────────────────────────────┐
│ 0x08000000 │
│ ┌─────────────────────────────────────────────┤
│ │ 程序代码(.text) │
│ │ │
│ ├─────────────────────────────────────────────┤
│ 0x0800FF00 │ Software: 1.0.0\0 ← 版本号字符串 │
│ 0x0800FF40 │ Date: Aug 4 2026\0 ← 日期字符串 │
│ 0x0800FF60 │ Time: 14:35:22\0 ← 时间字符串 │
│ ├─────────────────────────────────────────────┤
│ 0x08010000 │ 保留 / 其他数据 │
└─────────────────────────────────────────────────────────┘
2.4 地址偏移设计说明
Software_Ver | |||
Compiler_Date | |||
Compiler_Time |
⚠️ 注意:每个字符串预留足够空间(如 64 字节),避免长度溢出覆盖后续数据。
三、__attribute__ 关键字详解
3.1 基本概念
__attribute__ 是 GCC 编译器 提供的一个关键字,用于向编译器传递属性信息,实现特殊的功能控制。
语法格式:
__attribute__((attribute-list))
3.2 三大应用方向
| 函数属性 | __attribute__((noreturn)) | |
| 变量属性 | __attribute__((at(addr))) | |
| 类型属性 | __attribute__((packed)) |
四、读取 Flash 中的版本信息
4.1 直接读取
// 声明外部变量(链接器会从 Flash 中获取)
externconstchar Software_Ver[];
externconstchar Compiler_Date[];
externconstchar Compiler_Time[];
voidReadVersionFromFlash(void)
{
printf("Version : %s\r\n", Software_Ver);
printf("Date : %s\r\n", Compiler_Date);
printf("Time : %s\r\n", Compiler_Time);
}
4.2 通过地址读取
#include<stdint.h>
voidReadVersionByAddress(void)
{
constchar* pVer = (constchar*)0x0800FF00;
constchar* pDate = (constchar*)0x0800FF40;
constchar* pTime = (constchar*)0x0800FF60;
printf("Version : %s\r\n", pVer);
printf("Date : %s\r\n", pDate);
printf("Time : %s\r\n", pTime);
}
4.3 上位机读取方法
| J-Link | mem 0x0800FF00, 64 | |
| ST-Link | ||
| 串口 |
五、工程实践建议
5.1 版本号命名规范
推荐使用 语义化版本号(SemVer) 规范:
主版本号.次版本号.修订号(如 2.1.3)
● 主版本号:不兼容的API修改(重大变更)
● 次版本号:向下兼容的功能性新增
● 修订号:向下兼容的Bug修复
constchar Software_Ver[] __attribute__((at(VERINFO_ADDR_BASE + 0x00)))
= "Software: v2.1.3";
5.2 增加更多版本信息
#define VERINFO_ADDR_BASE (0x0800FF00)
constchar Software_Ver[] __attribute__((at(VERINFO_ADDR_BASE + 0x00))) = "SW: v1.0.0";
constchar Compiler_Date[] __attribute__((at(VERINFO_ADDR_BASE + 0x20))) = "Date: " __DATE__;
constchar Compiler_Time[] __attribute__((at(VERINFO_ADDR_BASE + 0x40))) = "Time: " __TIME__;
constchar Git_Hash[] __attribute__((at(VERINFO_ADDR_BASE + 0x60))) = "Git: a1b2c3d";
constchar Build_User[] __attribute__((at(VERINFO_ADDR_BASE + 0x80))) = "User: " __USER__;
constchar MCU_Type[] __attribute__((at(VERINFO_ADDR_BASE + 0xA0))) = "MCU: STM32F407";
5.3 使用条件编译控制
#ifdef VERSION_DEBUG
#define VERINFO_ADDR_BASE (0x0800FF00)
// ... 版本信息定义
#else
// 正式发布版本,不占用Flash
#endif
六、常见问题排查
| 编译报错 | at 属性 | __attribute__((section())) 替代 |
| 地址被覆盖 | .map 文件,确认地址未使用 | |
| 读取乱码 | \0 终止符 | |
| 字符串长度溢出 | ||
| IAR 编译器报错 | at | @ 符号:const char ver[] @ 0x0800FF00 |
6.1 IAR 编译器写法
// IAR 中指定地址的方法(与 Keil/GCC 不同)
constchar Software_Ver[] @ 0x0800FF00 = "Software: 1.0.0";
constchar Compiler_Date[] @ 0x0800FF40 = "Date: " __DATE__;
6.2 GCC 通用写法(推荐)
// 使用 section 属性 + 链接脚本控制
#define VERINFO_SECTION __attribute__((section(".version_info")))
constchar Software_Ver[] VERINFO_SECTION = "Software: 1.0.0";
七、总结
__DATE__/__TIME__ | ||
__attribute__((at())) |
推荐组合方案:
// 1. 在 Flash 固定地址存储版本信息
constchar Software_Ver[] __attribute__((at(0x0800FF00))) = "SW: v1.0.0";
constchar Compiler_Date[] __attribute__((at(0x0800FF40))) = "Date: " __DATE__;
constchar Compiler_Time[] __attribute__((at(0x0800FF60))) = "Time: " __TIME__;
// 2. 在串口打印时一并输出
printf("Firmware: %s, Built on %s at %s\r\n",
Software_Ver, Compiler_Date, Compiler_Time);
📌 一句话总结:__DATE__ 和 __TIME__ 自动记录编译时间,__attribute__((at())) 将版本信息固化到 Flash 指定地址,两者结合实现固件身份的统一管理。
夜雨聆风