EMS 知识库
首页 / knowledge/代码解析/user

version.c

源文件路径:user/version.c 所属模块:user(主程序入口 / 版本与构建信息常量) 代码行数:20

1. 文件职责

本文件是 EMS 3.6 固件的构建信息登记处:它把"固件型号 + 软件版本号 + 编译时间"三要素拼成一条固定格式的只读字符串常量 BUILD_INFO,供云端/上位机在设备重启上报时读取,同时也让发布脚本能从产出的 .bin 里直接反查"这个二进制是哪份源码、什么时候编译的"。它不参与任何业务数据流,也没有任务、状态或周期调用——唯一的写者就是编译器(__DATE__、__TIME__),唯一的读者是 net/ems.c 的 fill_send_info()(net/ems.c:2429,引用点 net/ems.c:2459)。它被 user/EMS_3_0.uvprojx 直接编译进工程(<FileName>version.c</FileName>,路径 .\version.c),是典型的一次性链接资料型源文件。

2. 关键数据结构与宏

名称类型说明
BUILD_INFO全局只读字符串常量(const char [])本文件唯一定义的对象(version.c:9 或 version.c:14,两个互斥分支)。内容为 "FW:<fw>;VER:<ver>;TS:<date time>\0",带 NUL 结尾
FW_TYPE_EMS3_6宏(引用外部定义)定义于 net/server.h:137,值 "EMS3.6"。经 version.c:1 的 #include "server.h" 引入
EMS_SOFT_VERSION宏(引用外部定义)定义于 net/server.h:138,等于 VERSION_STRING
VERSION_STRING宏(间接引用)定义于 net/server.h:113-114:当 VER_TYPE == VER_TYPE_RELEASE 时等于 VER_MAIN
VER_MAIN宏(间接引用)定义于 net/server.h:109,当前为 "V0.11.676"
VER_SEQUENCE宏(间接引用)定义于 net/server.h:110,当前为空串 ""(临时版本必填)
VER_TYPE宏(间接引用)定义于 net/server.h:111,当前为 VER_TYPE_RELEASE(= 0,server.h:102)
__DATE__ / __TIME__编译器预定义宏C 标准预定义宏。__DATE__ 展开为 "Mmm dd yyyy" 形式(单数字日期用空格补齐,如 "Oct 9 2025"),__TIME__ 展开为 "hh:mm:ss"
__ARMCC_VERSION / __GNUC__ / __clang__编译器预定义宏用于选择 BUILD_INFO 的两种定义方式(见 4.2)
__attribute__((used))GNU/Arm 扩展属性附着在 BUILD_INFO 上,提示编译器"该符号被使用",防止被优化删除

本文件不定义任何结构体、枚举、联合体或位域,只有字符串常量与预处理器分支。

2.1 当前宏展开后的实际字符串

按本版本 net/server.h 的取值(VER_MAIN = "V0.11.676"、VER_TYPE = VER_TYPE_RELEASE、VER_SEQUENCE = ""):

FW:EMS3.6;VER:V0.11.676;TS:Oct  9 2025 16:48:00

3. 函数清单

函数名作用域说明
keep_build_info全局(version.c:19)空函数,内部只把一个指向 BUILD_INFO 的 volatile const char * 取出并 (void) 丢弃,用途是"制造一个外部可见的引用,防止链接器把 BUILD_INFO 扔掉"。仅在 #else 分支中编译,当前工具链下不参与编译(见 4.2)

本文件不存在 static 函数、不存在函数指针,也没有"函数清单意义上的多个函数"——除 keep_build_info(条件编译)外全部是数据定义。

4. 核心逻辑解析

4.1 字符串格式契约(version.c:3-6 注释明确写死)

"FW:<fw>;VER:<ver>;TS:<date time>\0"
字段分隔符内容来源示例
FW:分号 ;FW_TYPE_EMS3_6(net/server.h:137)EMS3.6
VER:分号 ;EMS_SOFT_VERSION(net/server.h:138)V0.11.676
TS:无后续分隔符__DATE__ + 空格 + __TIME__Oct 9 2025 16:48:00

三个字段全部出现,顺序固定,字段名与冒号不可省略——这一点由 user/rename_bin.ps1 的正则反向锁定:rename_bin.ps1:89 用 'FW:([ -~]{1,300});VER:([ -~]{1,300});TS:([ -~]{1,300})' 在整个 .bin 的 ASCII 视图中做匹配(rename_bin.ps1:86 把二进制整体按 ASCII 解码),成功时把 TS 段经 NormalizeTimestamp 归一化为 yyyyMMdd_HHmmss,并用 FW/VER/TS 三段重命名 ../OBJ/EMS3.bin。因此本文件与发布脚本之间存在隐式的格式耦合契约:只改 version.c 的字符串格式而不改脚本,发布流程会静默退化成"拿当前时间当时间戳"(rename_bin.ps1:104、112-114)。

4.2 预处理器分支的选择逻辑(version.c:7-20)

#if defined(__GNUC__) || defined(__clang__) || defined(__ARMCC_VERSION)   // version.c:7
    const char BUILD_INFO[] __attribute__((used)) = ...;                  // version.c:9-12
#else
    const char BUILD_INFO[] = ...;                                        // version.c:14-17
    void keep_build_info(void) { ... }                                    // version.c:19
#endif                                                                    // version.c:20
分支触发条件防优化手段
#if 分支(version.c:9-12)GNU GCC、Clang、任意版本 Arm Compiler(__ARMCC_VERSION)__attribute__((used))
#else 分支(version.c:14-19)上述三个宏都未定义(理论上只剩 IAR、Tasking 等)额外定义一个全局函数 keep_build_info() 制造引用

本工程实际走 #if 分支:工程文件 user/EMS_3_0.uvprojx 中 <uAC6>0</uAC6>,即使用 Arm Compiler 5(armcc),它会定义 __ARMCC_VERSION(且默认不定义 __GNUC__),因此条件成立、__attribute__((used)) 生效。结论:keep_build_info()(version.c:19)在当前配置下是死代码,#else 分支整体不可达。

4.3 静态存储与"防止被丢弃"的真实生效路径

4.4 通信协议帧格式

本文件不涉及通信。但它产出的字符串会被嵌进云端上报的 JSON 里:net/ems.c:2450-2459 在 get_info_status() & SYSTEM_RESTART 成立时,用 snprintf 拼出 {"MsgType":"System","MsgContent":"System restart:...! Type %s bootver:%s Build info:%s",...},其中最后一个 %s 就是 BUILD_INFO(net/ems.c:2459)。所以这条字符串会出现在"设备重启"事件的 MsgContent 文本中,字段名固定为 Build info:;同一条消息里还有 Type(来自 inverter_read_inv_type(),ems.c:2457)和 bootver(来自 get_ems_boot_ver(),定义于 net/ems_boot_handle.c:196)。

4.5 定时 / uCOS-II 配合

无。本文件不创建任务、不使用信号量/消息邮箱,也不包含任何 uCOS-II 头文件——注意它只 include 了 server.h(version.c:1),而 server.h 自身会链式引入工程庞大的类型头链。BUILD_INFO 是只读常量,位于 flash 的只读段,可被任意多个任务并发读取而无竞态。

4.6 关键全局变量如何被读写

变量写者读者
BUILD_INFO无运行时写者(编译期常量)net/ems.c:127(extern 声明)、net/ems.c:2459(snprintf 的 %s 实参);离线工具 user/rename_bin.ps1:89 通过扫描 .bin 读取

5. 对外接口

本文件导出(供外部使用)

名称类型使用方
BUILD_INFO全局 const char[](外部链接)net/ems.c:127 声明、net/ems.c:2459 使用(设备重启上报的 Build info: 字段);离线发布脚本 user/rename_bin.ps1(二进制扫描)
keep_build_info全局函数无调用方;当前工具链下不编译(见 4.2)。设计意图是给 IAR 等无 __attribute__((used)) 的编译器提供"强制引用"锚点

本文件引用(外部符号)

引用来源
#include "server.h"net/server.h(提供 FW_TYPE_EMS3_6、EMS_SOFT_VERSION,并链式引入 VERSION_STRING / VER_MAIN / VER_SEQUENCE / VER_TYPE)
__DATE__、__TIME__编译器预定义
__attribute__((used))Arm/GNU 编译器扩展

本文件不调用任何函数、不引用任何外部变量实体(只引用宏)。

6. 依赖关系

依赖:

被依赖:

7. 备注与风险点

  1. keep_build_info() 在当前工具链下是死代码:version.c:7 的条件包含 __ARMCC_VERSION,而工程 <uAC6>0</uAC6> 表明用的是 Arm Compiler 5,必然定义 __ARMCC_VERSION,所以走 #if 分支,#else 分支(version.c:14-19)永远不会被编译。也就是说这段"备选方案"从未经过实测,若将来真换到 IAR,需要重新验证它是否真能阻止链接器丢弃(volatile const char *p = BUILD_INFO; 只是在函数体内取了地址,函数本身若无人调用,仍可能被链接器整体移除 —— 除非加上 __root/__used 之类的编译器专属属性)。
  2. 构建不可复现(non-reproducible build):__DATE__ / __TIME__ 会让同一份源码在不同时间编译出不同二进制。这是本文件的设计目的(要留编译时间),但对"逐字节比对两个 .bin 是否来自同一源码"的校验流程是干扰项;做差异比对时必须先把该字符串区间归一化。
  3. 隐式的格式契约没有机器可校验的保护:version.c:4-5 的格式说明只是注释,rename_bin.ps1:89 的正则才是真正的消费者。任何一方改动(例如把 ; 换成 ,、把 TS: 改名)都不会有编译期报错,只会让发布脚本静默降级为"用当前时间"(rename_bin.ps1:104),产出的文件名时间戳与实际编译时间不符。建议把格式定义抽到一个头文件里,让 C 代码与脚本都以它为准。
  4. 显式 "\0" 冗余:version.c:12 与 version.c:17 的末尾 "\0" 会在字符串字面量自动追加的终止符之外再补一个 NUL,使数组比内容多出 2 字节。功能上无害(多一个 NUL 反而让正则扫描更稳妥),但属于对 C 语义的重复表达;若将来有人按 sizeof(BUILD_INFO) 计算长度并参与协议长度字段,会多算 2 字节。
  5. BUILD_INFO 的链接可见性依赖 C 语言规则:文件作用域 const 数组在 C 中有外部链接,这是 net/ems.c:127 的 extern const char BUILD_INFO[]; 能链接成功的前提。若本文件被误改扩展名为 .cpp、或工程切到 C++ 编译,const 会变成内部链接,net/ems.c 会直接报未定义引用。当前工程是纯 C(Keil 中 .c 由 armcc 按 C 编译),安全,但这是一个语言层面的隐性约束,未在任何注释中说明。
  6. 为 2 个宏引入整份 server.h:version.c:1 的 include 会把 net/server.h 的 300 行全部拉进来(含 UPGRADE_* 枚举、FILE_MASK_* 常量、多个升级结构体)。对本文件的实际需求而言是过度包含,也造成了 user/ 层对 net/ 层的反向编译依赖——按工程的分层约定,user/ 应当是最上游、最不依赖具体业务模块的一层。同时,user/ 层对 net/ 层的依赖还使得 version.c 无法脱离网络模块单独编译。
  7. FW:EMS3.6 与目录名 platform-ems-3-6 的版本标识重复:FW_TYPE_EMS3_6 是常量 "EMS3.6"(net/server.h:137),而软件版本 V0.11.676 会随每次发布变化(server.h:109)。rename_bin.ps1:128-133 的回退路径甚至用了 #define FW_TYPE_EMS3_6 "EMS3.66" 这样的示例正则做匹配演示,与实际值 "EMS3.6" 不一致——仅注释/示例层面的不一致,不影响脚本逻辑(正则只捕获引号内内容)。
  8. 无 static 限定:BUILD_INFO 是全局符号,其名字比较通用,理论上存在与其它编译单元重名的风险(当前工程内无重名)。若改用 static const char,则会失去被 net/ems.c 与 rename_bin.ps1 引用的能力,所以这里的"不带 static"是有意的,属于正确取舍,但注释中未说明,容易被后续维护者"顺手加上 static"而破坏。