hex_file_parse.h
PARSE_ADDR / PARSE_END 的语义(调用方自定义)4.4 通信协议 / 定时 / uCOS-II 配合5. 对外接口6. 依赖关系7. 备注与风险点源文件路径:
common/hex_file_parse.h所属模块:common(通用工具库) 代码行数:44
1. 文件职责
本文件是 common/hex_file_parse.c 的对外接口头文件,定义 Intel HEX(.hex)格式单行记录的解析状态枚举 PARSE_STATE_E、记录类型码枚举 HEX_CODE 与解析结果结构体 HEX_FILE_T,并声明解析函数 hex_parse()。它是 EMS 里「对下位设备(EVC 充电桩、电表、科隆逆变器等)做固件升级」这条链路的公共解析层:固件 .hex 文本存放在 SDRAM 中,升级任务逐行调用 hex_parse() 把一行文本拆成「长度/地址/记录类型/数据/校验」,再把数据按目标设备的协议重新组装后发出。文件本身不含实现与状态,只提供类型与原型。
2. 关键数据结构与宏
| 名称 | 类型 | 说明 |
|---|---|---|
__HEX_FILE_PARSE_H | 宏 | include guard |
PARSE_STATE_E | 枚举(typedef 匿名枚举) | 解析结果状态集,显式从 0 起编号,共 5 个成员 |
PARSE_OK | 枚举成员(= 0) | 解析成功且校验通过 |
PARSE_ADDR | 枚举成员(= 1) | 非数据记录(基址/线性地址/结束记录),调用方需另行按 u8_code_type 分支处理;hex_parse() 自身不会返回它,由调用方在判定记录类型后自行返回 |
PARSE_END | 枚举成员(= 2) | 文件结束(由调用方在遇到 GET_END_CODE 时返回,hex_parse() 本身不区分记录类型) |
DATA_ERR | 枚举成员(= 3) | 入参为空 / 输入缓冲中找不到 ':' |
PARSE_ERR | 枚举成员(= 4) | 格式或校验和错误(sscanf 失败、校验和不匹配) |
HEX_CODE | 枚举(typedef 匿名枚举) | Intel HEX 记录类型码,共 4 个成员,数值即文件里的 TT 字段 |
GET_DATA_CODE | 枚举成员(= 0x00) | 数据记录 |
GET_END_CODE | 枚举成员(= 0x01) | 文件结束记录(EOF) |
GET_BASE_ADDRESS_CODE | 枚举成员(= 0x04) | 扩展线性地址记录。命名易误解:Intel HEX 里 0x02 才是「扩展段地址(base address)」,0x04 是「扩展线性地址」(见备注 3) |
GET_LINEAR_ADDRESS_CODE | 枚举成员(= 0x05) | 起始线性地址记录(一般用于声明程序入口,不携带固件数据) |
HEX_FILE_T | 结构体(#pragma pack(1) 紧凑布局) | 单行 Intel HEX 的解析结果,字段见下 |
HEX_FILE_T.u8_data_len | u8 | 记录里的 LL 字段:本行数据字节数(不是字符数) |
HEX_FILE_T.u16_addr | u16 | 记录里的 AAAA 字段:16 位装载地址(对 0x04/0x05 记录而言是地址值本身) |
HEX_FILE_T.u8_code_type | u8 | 记录里的 TT 字段:记录类型,取值见 HEX_CODE |
HEX_FILE_T.u16_data_buf[32] | u16[32] | 数据域;每两个 ASCII 字符打包成一个 u16(大端:高字节在前),容量 32 个字 = 64 个数据字节 |
HEX_FILE_T.u8_sum_check | u8 | 记录里的 CC 字段:文件给出的校验和原值 |
HEX_FILE_T.u8_ascll_len | u8 | 解析成功时回填的「本行 ASCII 总长度」= 13 + u8_data_len*2(含 ':'、LL、AAAA、TT、数据、CC、CRLF) |
#pragma pack(1) 下 HEX_FILE_T 的编译期大小为 1+2+1+64+1+1 = 70 字节(不打包则为 72 字节)。本文件不定义宏常量(除 include guard 外)。
3. 函数清单
| 函数名 | 作用域 | 说明 |
|---|---|---|
hex_parse | 全局(原型声明) | 解析一行 Intel HEX 文本到 HEX_FILE_T,定义于 hex_file_parse.c:27 |
共 1 个原型声明,对应 hex_file_parse.c 中的唯一全局定义;本头文件无 static 函数、无函数指针、无回调。
4. 核心逻辑解析
本文件是纯声明文件,不含可执行逻辑。接口契约要点如下(实现细节见 hex_file_parse.c.md):
4.1 输入契约
hex_parse(HEX_FILE_T *hex_data, u8 *buf, u8 len):
buf必须指向以':'开头的整行 Intel HEX 文本所在的位置(实现用strstr(buf, ":")定位,因此buf之前允许有内容,但必须是同一行的数据);len是可用窗口长度,实现中只用于「是否为 0」的判断(hex_file_parse.c:34),不参与任何长度校验或边界保护——这是本接口最容易误用的一点;hex_data是输出结构体,调用方负责分配(工程内 3 个调用点都用static HEX_FILE_T s_hex_file_data = {0}或函数内局部变量)。
4.2 输出契约
成功(返回 PARSE_OK)时回填 u8_data_len / u16_addr / u8_code_type / u16_data_buf[] / u8_sum_check / u8_ascll_len 六个字段;失败时不保证任何字段有效(前 3 个字段可能已被写入)。
数据域的字节序约定:u16_data_buf[i] 里放的是第 2i、2i+1 两个数据字节,高字节在前(等价于网络序)。因此所有调用方都必须用两次移位取字节,工程内的标准写法是:
buf[k] = (u8)(s_hex_file_data.u16_data_buf[i] >> 8) & 0xFF; // 高字节
buf[k + 1] = (u8)(s_hex_file_data.u16_data_buf[i]) & 0xFF; // 低字节
三处调用点都遵循该约定:midware/evc/evc_upgrade.c:396~:397、midware/inverter/kelong/kelong_upgrade.c:484~:485、midware/meter/bb_update.c:191~:192。
4.3 PARSE_ADDR / PARSE_END 的语义(调用方自定义)
hex_parse() 只做「文本 → 字段」的转换,不解释记录类型。记录类型的分支判断完全在调用方,例如:
| 调用点 | GET_BASE_ADDRESS_CODE(0x04) | GET_DATA_CODE(0x00) | GET_LINEAR_ADDRESS_CODE(0x05) | GET_END_CODE(0x01) |
|---|---|---|---|---|
midware/evc/evc_upgrade.c | :346 取 u16_data_buf[0] 作 Flash 基址,:354 返回 PARSE_ADDR | :356 走数据搬运 | :407 返回 PARSE_ADDR | :411 返回结束 |
midware/meter/bb_update.c | :136 取 u16_data_buf[0] 作 Flash 基址,:144 返回 PARSE_ADDR | :146 走数据搬运 | 未处理(落入 :201 的 PARSE_ADDR) | :203 返回结束 |
midware/inverter/kelong/kelong_upgrade.c | 未分支(u8_code_type 被原样搬进重发缓冲,见 :480) | 同左 | 同左 | 同左 |
也就是说 PARSE_ADDR 语义是「这一行不是数据记录,但格式合法」,是否需要 PARSE_END 完全由调用方维护自己的状态机。
4.4 通信协议 / 定时 / uCOS-II 配合
- 协议:本文件描述的是 Intel HEX 文件格式,不是线缆协议;真正的空口帧由调用方(EVC/电表/科隆各自的升级模块)按目标设备协议重新封装。
- 超时重试:无。解析是同步、无阻塞的纯 CPU 操作。
- uCOS-II:不引用任何 OS API,不声明信号量/消息邮箱。
hex_parse()无静态状态,可重入。
5. 对外接口
导出(供外部使用):
| 名称 | 使用方 |
|---|---|
hex_parse() | midware/evc/evc_upgrade.c:321、midware/meter/bb_update.c:130、midware/inverter/kelong/kelong_upgrade.c:455 |
HEX_FILE_T | midware/evc/evc_upgrade.c:33(static HEX_FILE_T s_hex_file_data)、midware/meter/bb_update.c:39(同上)、midware/inverter/kelong/kelong_upgrade.c:337(函数内局部变量) |
PARSE_STATE_E 成员(PARSE_OK/PARSE_ADDR/PARSE_END/DATA_ERR/PARSE_ERR) | 同上三个文件的解析状态机 |
HEX_CODE 成员(GET_DATA_CODE/GET_END_CODE/GET_BASE_ADDRESS_CODE/GET_LINEAR_ADDRESS_CODE) | midware/evc/evc_upgrade.c:346/:356/:407/:411、midware/meter/bb_update.c:136/:146/:203;midware/inverter/kelong/kelong_upgrade.c:480 把 u8_code_type 原样转发,未引用枚举名 |
被调用(本文件引用的外部符号):
| 名称 | 来源 |
|---|---|
includes.h | UCOSII/uCOS-CONFIG/includes.h(第 12 行 include),提供 u8 / u16 等基础类型 |
本文件不导出任何全局变量、不定义函数指针类型。
6. 依赖关系
依赖:
UCOSII/uCOS-CONFIG/includes.h(hex_file_parse.h:12),进而引入chip.h、gd32f4xx.h、ucos_ii.h。
被依赖(include 本头文件的文件):
common/hex_file_parse.c:12midware/evc/evc_upgrade.c:18(真实使用)midware/meter/bb_update.c(真实使用)midware/inverter/kelong/kelong_upgrade.c(真实使用)midware/inverter/alphaess/alphaess_upgrade.c:18(include 了但未使用,文件内无HEX_FILE_T/hex_parse/PARSE_*/GET_*引用)midware/inverter/ginlong/ginlong_upgrade.c:16(include 了但未使用)midware/inverter/set/set_upgrade_s5.c:17(include 了但未使用)
7. 备注与风险点
- 头文件的
@file注释是别的文件名:hex_file_parse.h:2写的是@file :stolar_inv_upgrade.h(@date :2019/11/04),说明这个解析器最初是给 Solar/Stolar 逆变器升级写的,后来被抽到common/复用。头注释未同步,属遗留问题。 - 枚举成员名与
common/s19.h冲突(高危):PARSE_STATE_E与s19.h的S19_FILE_PASRE_STATUS_E同时定义了PARSE_OK、PARSE_END、DATA_ERR三个同名成员,但数值完全不同:
| 名称 | hex_file_parse.h | s19.h |
|---|---|---|
PARSE_OK | 0 | 7 |
PARSE_END | 2 | 8 |
DATA_ERR | 3 | 4 |
任何同时 include 两个头文件的编译单元都会因「枚举常量重定义」报错(C99 起为错误而非警告)。当前工程内恰好没有这样的文件(hex_file_parse.h 的使用者与 s19.h 的唯一使用者 midware/bms/alpha/battery_upgrade.c 不重叠),因此问题被掩盖;一旦将来在同一个文件里同时处理两种升级文件格式,会立刻编译失败,若编译器宽容则会得到静默错误的数值比较。
GET_BASE_ADDRESS_CODE = 0x04命名与 Intel HEX 规范不对应:规范里0x02是扩展段地址(Extended Segment Address,即真正的「base address」),0x04是扩展线性地址(Extended Linear Address)。本头文件把0x04叫GET_BASE_ADDRESS_CODE、把0x05叫GET_LINEAR_ADDRESS_CODE,而0x02/0x03完全没有定义。语义上0x04记录的数据只是「线性地址的高 16 位」,必须与数据记录的u16_addr拼接(左移 16 位后相或)才是完整地址;调用方确实做了这个拼接(midware/meter/bb_update.c:173、:181、midware/evc/evc_upgrade.c:384、:387的(base<<16) | offset),但拼接逻辑分散在两个调用方里各写一遍,本文件未提供任何封装,命名也在持续误导。u8_ascll_len是拼写错误:应为ascii_len(ascll→ascii),该拼写已扩散到 4 个调用点(evc_upgrade.c:599、:606、kelong_upgrade.c:476、bb_update.c:389),修改需同步。- 结构体字段没有一个是「原始文本」:
HEX_FILE_T不保存行原文,调用方如需重发原始文本只能自己从buf取(kelong_upgrade.c就是按字段重新拼一帧,见:478~:487)。因此u8_ascll_len这个「便于定位下一行」的字段格外重要,一旦与真实行长不一致就会导致后续解析错位。 PARSE_ADDR是「调用方返回」而非「本函数返回」:从接口视角看,parse_xxx()返回PARSE_ADDR的语义只存在于调用方,文档/注释里没有说明,容易被误读为「解析器会返回地址记录状态」。u16_data_buf[32]的容量没有在接口层声明:头文件没有注释说明「每行数据最多 64 字节」,而hex_parse()也不做长度上限校验(见hex_file_parse.c.md备注 2),容量约束只存在于数组维度里。