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

hex_file_parse.h

源文件路径: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_lenu8记录里的 LL 字段:本行数据字节数(不是字符数)
HEX_FILE_T.u16_addru16记录里的 AAAA 字段:16 位装载地址(对 0x04/0x05 记录而言是地址值本身)
HEX_FILE_T.u8_code_typeu8记录里的 TT 字段:记录类型,取值见 HEX_CODE
HEX_FILE_T.u16_data_buf[32]u16[32]数据域;每两个 ASCII 字符打包成一个 u16(大端:高字节在前),容量 32 个字 = 64 个数据字节
HEX_FILE_T.u8_sum_checku8记录里的 CC 字段:文件给出的校验和原值
HEX_FILE_T.u8_ascll_lenu8解析成功时回填的「本行 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):

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 配合

5. 对外接口

导出(供外部使用):

名称使用方
hex_parse()midware/evc/evc_upgrade.c:321、midware/meter/bb_update.c:130、midware/inverter/kelong/kelong_upgrade.c:455
HEX_FILE_Tmidware/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.hUCOSII/uCOS-CONFIG/includes.h(第 12 行 include),提供 u8 / u16 等基础类型

本文件不导出任何全局变量、不定义函数指针类型。

6. 依赖关系

依赖:

被依赖(include 本头文件的文件):

7. 备注与风险点

  1. 头文件的 @file 注释是别的文件名:hex_file_parse.h:2 写的是 @file :stolar_inv_upgrade.h(@date :2019/11/04),说明这个解析器最初是给 Solar/Stolar 逆变器升级写的,后来被抽到 common/ 复用。头注释未同步,属遗留问题。
  2. 枚举成员名与 common/s19.h 冲突(高危):PARSE_STATE_E 与 s19.h 的 S19_FILE_PASRE_STATUS_E 同时定义了 PARSE_OK、PARSE_END、DATA_ERR 三个同名成员,但数值完全不同:
名称hex_file_parse.hs19.h
PARSE_OK07
PARSE_END28
DATA_ERR34

任何同时 include 两个头文件的编译单元都会因「枚举常量重定义」报错(C99 起为错误而非警告)。当前工程内恰好没有这样的文件(hex_file_parse.h 的使用者与 s19.h 的唯一使用者 midware/bms/alpha/battery_upgrade.c 不重叠),因此问题被掩盖;一旦将来在同一个文件里同时处理两种升级文件格式,会立刻编译失败,若编译器宽容则会得到静默错误的数值比较。

  1. 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),但拼接逻辑分散在两个调用方里各写一遍,本文件未提供任何封装,命名也在持续误导。
  2. u8_ascll_len 是拼写错误:应为 ascii_len(ascll → ascii),该拼写已扩散到 4 个调用点(evc_upgrade.c:599、:606、kelong_upgrade.c:476、bb_update.c:389),修改需同步。
  3. 结构体字段没有一个是「原始文本」:HEX_FILE_T 不保存行原文,调用方如需重发原始文本只能自己从 buf 取(kelong_upgrade.c 就是按字段重新拼一帧,见 :478~:487)。因此 u8_ascll_len 这个「便于定位下一行」的字段格外重要,一旦与真实行长不一致就会导致后续解析错位。
  4. PARSE_ADDR 是「调用方返回」而非「本函数返回」:从接口视角看,parse_xxx() 返回 PARSE_ADDR 的语义只存在于调用方,文档/注释里没有说明,容易被误读为「解析器会返回地址记录状态」。
  5. u16_data_buf[32] 的容量没有在接口层声明:头文件没有注释说明「每行数据最多 64 字节」,而 hex_parse() 也不做长度上限校验(见 hex_file_parse.c.md 备注 2),容量约束只存在于数组维度里。