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

convert.c

源文件路径:common/convert.c 所属模块:common(通用工具库) 代码行数:69

1. 文件职责

本文件实现两个最基础的 ASCII 十六进制转换函数:ascii_to_hex(单字符→4 位半字节)和 hex_ascii_to_bin(ASCII 十六进制串→二进制字节流)。它处在固件升级与云端数据链路的「文本解码」环节:服务器通过 JSON 下发的固件数据是十六进制文本,必须先转成二进制才能写进 SPI Flash/SDRAM;逆变器升级前的 MD5 一致性比对也需要先把 32 字符的十六进制摘要还原成 16 字节。文件无状态、无全局变量、不访问硬件,是典型的纯计算工具,被调用它的升级任务(midware/inverter/alphaess/alphaess_upgrade.c)与网络任务(net/server.c)同步调用,不拥有自己的任务或定时器。

2. 关键数据结构与宏

名称类型说明
ascii_to_hex全局函数u8 ascii_to_hex(char c)(convert.c:27)
hex_ascii_to_bin全局函数int hex_ascii_to_bin(u8 *in_buf, u32 len, u8 *ou_buf)(convert.c:54)
c形参 char待转换字符,比较区间为 '0'~'9' / 'a'~'f' / 'A'~'F'
len形参 u32输入 ASCII 串长度(字符数),必须为偶数
i局部变量 int循环下标
-2魔法数字hex_ascii_to_bin 的错误返回值,语义是「长度为奇数」
<< 4 / ``位运算高半字节左移 4 位后与低半字节或运算,拼接成一个字节

本文件不定义结构体、枚举、联合体、位域,也没有 #define 宏常量,全部逻辑靠两个函数完成。

3. 函数清单

函数名作用域说明
ascii_to_hex全局把单个十六进制字符转成 0~15,非法字符返回 0
hex_ascii_to_bin全局每两个字符拼成一个字节写入 ou_buf,奇数长度返回 -2,成功返回 len/2

共 2 个函数,全部为全局作用域(都不是 static),convert.h:16~:17 有对应原型。

4. 核心逻辑解析

4.1 ascii_to_hex(convert.c:27~:45)

if (c >= '0' && c <= '9') return (c - '0');
else if (c >= 'a' && c <= 'f') return ((c - 'a') + 10);
else if (c >= 'A' && c <= 'F') return ((c - 'A') + 10);
else return 0;

纯 if-else 链实现的表驱动替代品,三种合法区间覆盖数字、小写、大写,大小写不敏感。非法字符(空格、x、换行、G、乱码等)统一返回 0,不置错误标志、不打印、不返回特殊值——这是本函数最关键的行为特征:它把「非法输入」和「合法输入 0/'0'」混为一谈。作为一个被跨模块复用的公共函数,这个返回值设计会在解码错误时静默制造错误数据(详见备注 1)。

4.2 hex_ascii_to_bin(convert.c:54~:68)

int i = 0;
if (len % 2 != 0) return -2;
for (i = 0; i < len/2; i++)
    ou_buf[i] = (ascii_to_hex(in_buf[i*2]) << 4) | ascii_to_hex(in_buf[i*2+1]);
return (len/2);

算法:以两个输入字符为一个字节,前一字符作高半字节(左移 4 位),后一字符作低半字节,或运算拼合后写入 ou_buf。

4.3 调用方数据流(三处,共 6 次调用)

调用点输入输出用途
midware/inverter/alphaess/alphaess_upgrade.c:105get_curr_md5_code_fw_file(UPDT_INV1)(32 字符)l_u8_inv1_upgrade_md5[16]本地 INV1 固件的 MD5 摘要
midware/inverter/alphaess/alphaess_upgrade.c:107同上(UPDT_INV2)l_u8_inv2_upgrade_md5[16]本地 INV2 固件的 MD5 摘要
midware/inverter/alphaess/alphaess_upgrade.c:109g_stEMSInfo.stUpt[UPDT_INV1].as8MD5Codel_u8_inv1_upgrade_md5_from_server[16]服务器下发 INV1 摘要
midware/inverter/alphaess/alphaess_upgrade.c:111g_stEMSInfo.stUpt[UPDT_INV2].as8MD5Codel_u8_inv2_upgrade_md5_from_server[16]服务器下发 INV2 摘要
net/server.c:3743g_au8FWBuf(JSON fileData 字段的十六进制正文,缓冲 2048 字节)g_au8FWBin[0..lenPck-1](缓冲 1024 字节)固件报文还原,随后按掩码写入 SPI Flash(UPDT_EMS/UPDT_INV1/UPDT_INV2/UPDT_INV3/UPDT_BMS/UPDT_Meter/UPDT_EVC)

α-ESS 那 4 处是紧邻的配对调用:先把本地文件摘要与服务器摘要都还原成 16 字节,再在后续流程里 memcmp 比对,决定是否需要重新下载/烧写固件。也就是说本文件的正确性直接影响「是否需要升级」的判定。

4.4 通信协议帧格式 / 超时重试 / uCOS-II 配合

4.5 关键全局变量的读写

本文件不读写任何全局变量:输入来自形参,输出写入调用方给的缓冲。这一点使它天然可重入、线程安全(在 uCOS-II 下即使被多个任务并发调用也不会互相干扰,前提是调用方各自使用独立的输出缓冲)。

5. 对外接口

导出(供外部使用):

名称使用方
ascii_to_hex仅被本文件的 hex_ascii_to_bin(convert.c:64)调用,工程内无其它调用点;作为全局符号导出,convert.h:16 有原型
hex_ascii_to_binmidware/inverter/alphaess/alphaess_upgrade.c:105、:107、:109、:111;net/server.c:3743(共 5 处,返回值均未使用)

本文件不导出任何全局变量,不定义回调,不注册到任何调度器。

被调用(本文件引用的外部符号):

名称来源
includes.hUCOSII/uCOS-CONFIG/includes.h(convert.c:13),提供 u8 / u32 / NULL 等基础定义
convert.hcommon/convert.h(convert.c:14),自身接口声明
无其它外部函数两个函数只使用 C 语言内建运算符,未调用 memset/memcpy/strlen

6. 依赖关系

依赖:

被依赖:

7. 备注与风险点

  1. ascii_to_hex 用 0 表示非法字符,会造成静默的数据错误:'0' 的合法返回值也是 0,调用方无法区分「字符是 '0'」和「字符是垃圾」。例如服务器下发的十六进制串里混入一个空格或 'G',转换结果是该半字节变成 0,还原出的固件二进制被悄悄改坏;后续 MD5 比对只会表现为「摘要不一致 → 重新下载」,排查时很难定位到是解码环节丢的字节。更健壮的做法是返回 -1 或用 s32 返回并让 hex_ascii_to_bin 中止。
  2. -2 是裸魔法数字,且与正常返回值混淆:错误返回值没有任何宏(如 #define HEX_ASCII_ODD_LEN (-2)),接口注释里也没写。而且所有调用方都没有检查返回值:
    • alphaess_upgrade.c:105~:111 四处传入的常量是 32(偶数),行为正常;
    • net/server.c:3743 传入 2 * lenPck。lenPck 来自服务器下发的 length 字段(server.c:3719),只做了 <= LEN_REQ_FILE(1024)的上界检查,没有做奇偶检查:若服务器给的长度为奇数,函数返回 -2 且一个字节都没写入,而 g_au8FWBin 刚在 server.c:3742 被 memset 清 0,随后 save_fw_to_spi_flash(g_au8FWBin, lenPck, ...)(server.c:3746 起,按固件掩码分派到 UPDT_EMS/UPDT_INV1/UPDT_INV2/UPDT_INV3/UPDT_BMS/UPDT_Meter/UPDT_EVC)会把一整块全 0 数据当作固件写进 SPI Flash。这是本文件最需要关注的完整性问题(是否可触发取决于服务器行为,本文件内没有任何防守)。同一条路径上若 fileData 字段长度短于 length 声明值,多出来的那一部分同样是被 memset 清 0 的内存,经 ascii_to_hex 会把 '\0' 当非法字符返回 0,结果是静默地在固件里填 0 而不报错。
  3. 无任何参数防护:hex_ascii_to_bin 不检查 in_buf/ou_buf 是否为 NULL,也不检查输出缓冲容量。若调用方把 len 算大了(例如用了含 '\0' 的 sizeof 而非 strlen),会直接越界写内存。
  4. len 与返回值的符号性不匹配:len 是 u32,hex_ascii_to_bin 返回 int,错误码 -2 在有符号域里可表达,但调用方若把返回值赋给 u8/u32(工程内不存在这种写法,但很容易发生),-2 会变成 254/4294967294,再用 != 0 判断就会误判成功。建议返回值改用 s32 并在注释里固定错误码。
  5. ascii_to_hex 未收敛为 static:工程内没有外部调用者(common/json.c 的 @Name :u8 ascii_to_hex(char c) 只是复制粘贴的注释残留,并未定义该函数),却因为非 static 而暴露成全局符号,存在与其它模块同名符号链接冲突的潜在风险,也污染了全局命名空间。
  6. int i = 0; 后又 for (i = 0; ...):初始化重复,属无害的风格冗余。
  7. 头文件包含方式不一致:convert.h:13 用 #include <includes.h>(尖括号),而同目录的 crc.h、hex_file_parse.h 用 "chip.h"/"includes.h",s19.h 用 "chip.h"。尖括号形式要求编译器把 UCOSII/uCOS-CONFIG 目录加入 include 搜索路径,工程配置变更时会比引号形式更脆弱。