alphaess_upgrade.c
源文件路径:
midware/inverter/alphaess/alphaess_upgrade.c所属模块:midware/inverter/alphaess(AlphaESS 逆变器通信协议 — 固件升级) 代码行数:691
1. 文件职责
本文件实现 AlphaESS 逆变器(含 DSP 双核 CPU1/CPU2)的串口固件升级流程。它把已下载到 SDRAM 的固件镜像(g_u8_fw_sdram1 / g_u8_fw_sdram2)通过 USART2 按照 AlphaESS 私有升级协议分帧发送给逆变器 DSP,逐帧等待 ACK,最后发送结束帧并等待 DSP 完成 MD5 校验。它处在"服务器下载固件 → SDRAM 缓存 → 逆变器升级"数据流的最后一环,属于 alphaess_comm_usart.c 通信任务的一个子流程(由 is_inverter_firm_download_finished() 触发),不被独立任务调度。文件顶部由 g_t_alphaess_comm_info.u32_ems_inv_comm_tick 参与链路保活,向 g_t_inv_info.t_debug_log 回报错误原因与升级状态,并通过 alphaess_read_upgrade_* 系列接口向 inverter.c 暴露升级进度。
2. 关键数据结构与宏
| 名称 | 类型 | 说明 |
|---|---|---|
ALPHAESS_UPGRADE_TABLE_T | 结构体 | 升级过程上下文表,唯一实例为静态变量 s_alphaess_upgrade_tab(alphaess_upgrade.c:31)。含文件长度、总帧数、当前帧号、SDRAM 基址、启动/数据/结束三个发送缓冲区、读写偏移、升级对象和完成时刻 tick |
s_alphaess_upgrade_tab | 静态全局变量 | ALPHAESS_UPGRADE_TABLE_T 唯一实例,alphaess_upgrade.c:31,全文件的状态载体 |
ALPHAESS_UPGRADE_OBJECT_E | 枚举 | ALPHAESS_NONE / ALPHAESS_MASTER / ALPHAESS_SLAVE,标记当前正在升级 CPU1 还是 CPU2(alphaess_upgrade.h:30) |
ALPHAESS_UPGRADE_SATE_E | 枚举 | 升级状态机状态:ALPHA_UPGRADE_START(启动帧 0x41) / ALPHA_SEND_UPGRADE_FILE(数据帧 0x42) / ALPHA_UPGRADE_END(结束帧 0x43) / ALPHA_UPGRADE_FAULT(失败)(alphaess_upgrade.h:37) |
UPGRADE_DATA_MAX_LEN | 宏 | 224,单帧最大有效载荷字节数(alphaess_upgrade.h:24),决定 232 字节数据帧缓冲的布局 |
ALPHAESS_UPGRADE_DEBUG | 宏 | 0,置 1 时打开 printf 调试输出(alphaess_upgrade.h:16) |
EMS_INV_SHAKE_HANDS | 宏 | 0x301,声明于本头文件但本文件未使用(仅 kelong 的 CAN 升级在用,见第 7 节) |
EMS_INV_SEND_UPGRADE_FILE | 宏 | 0x302,同上,本文件未使用 |
EMS_INV_CHECK_UPGRADE_FILE | 宏 | 0x303,同上,本文件未使用 |
EMS_INV_CHECK_UPGRADE_SATTE | 宏 | 0x304,同上,本文件未使用 |
INV_EMS_ACK | 宏 | 0x401,同上,本文件未使用 |
g_t_alphaess_comm_info | 外部结构体变量 | alphaess_comm_usart.h:715,本文件只写其 u32_ems_inv_comm_tick 做链路保活 |
g_t_inv_info.t_debug_log | 外部结构体成员 | u32_err_reason(失败原因,取值见 inverter.h:383 起,如 SENDBOOTERR/RECVMASTERERR)、u8_upgrade_status、u8_err_tick |
g_t_inv_info.t_run_info.u8_run_mode | 外部结构体成员 | 升级中置 cUpdateMaster / cUpdateSlave,供 HMI/逻辑层显示运行模式 |
3. 函数清单
| 函数名 | 作用域 | 说明 |
|---|---|---|
alphaess_inv_com_reply | static | 在 timeout 个 tick 内轮询 alphaess_recv_data(),判断逆变器回帧是否为升级 ACK(0x41/0x43 回帧看 buf[2]=buf[3]=0x06,0x42 回帧看 buf[5]=0x06) |
send_ems_send_start_cmd | static | 组装并发送 0x41 启动帧(含双核文件长度/MD5/总帧数/版本号),发送前先做 MD5 一致性校验 |
send_ems_send_upgrade_file_cmd | static | 组装并发送 0x42 数据帧(CPU 号 + 帧号 + 本帧字节数 + 载荷 + CRC),超时 500 tick |
send_ems_send_end_cmd | static | 组装并发送 0x43 结束帧(载荷为 ASCII "End"),超时由调用方传入 |
alphaess_upgrade_handle | 全局 | 升级主状态机,每次被通信任务调用推进一个步骤 |
alphaess_read_upgrade_flag_master | 全局 | 返回当前升级对象是否为 CPU1(MASTER) |
alphaess_read_upgrade_percent_master | 全局 | 返回 CPU1 升级进度百分比 = cpu1_file_offset*100 / cpu1_file_len |
alphaess_read_upgrade_flag_slave | 全局 | 返回当前升级对象是否为 CPU2(SLAVE) |
alphaess_read_upgrade_percent_slave | 全局 | 返回 CPU2 升级进度百分比 = cpu2_file_offset*100 / cpu2_file_len |
alphaess_read_upgrade_file_offset | 全局 | 返回 CPU1+CPU2 已传输字节偏移之和 |
alphaess_get_upgrade_finish_tick | 全局 | 返回升级成功完成时刻的系统 tick |
4. 核心逻辑解析
4.1 状态机与迁移条件
状态存在函数内的 static u8 s_u8_upgrade_sate(alphaess_upgrade.c:247),另有 s_u8_send_err_cnt、s_u8_rev_err_cnt、s_u8_upgrade_fault_cnt、s_u16_cpu1_file_send_cnt、s_u16_cpu2_file_send_cnt 五个静态计数器。
ALPHA_UPGRADE_START(252行起)- 调
send_ems_send_start_cmd(500);发送失败 →s_u8_send_err_cnt++,>3时置RECVBOOTERR/SENDBOOTERR并转ALPHA_UPGRADE_FAULT(300、286)。 - 发送成功 →
alphaess_inv_com_reply(600)等 ACK;成功则转ALPHA_SEND_UPGRADE_FILE,u8_upgrade_object = ALPHAESS_MASTER,初始化u32_upgrade_cpu1_file_offset = 128(跳过 128 字节文件头)、u32_upgrade_cpu2_file_offset = 0(276、277)。
- 调
ALPHA_SEND_UPGRADE_FILE(308行起)u8_upgrade_object == ALPHAESS_MASTER时发 CPU1:非末帧用固定 224 字节(323),末帧用u32_cpu1_file_len - u32_upgrade_cpu1_file_offset + 128(371,即"剩余字节数",+128 用于抵消起始偏移的头部 128 字节);每帧 ACK 成功后offset = 帧序号*224 + 128(337、338)。- CPU1 末帧 ACK 成功 →
u8_upgrade_object = ALPHAESS_SLAVE,帧号清零(379、381),下一轮进入 CPU2 分支。 - CPU2 末帧 ACK 成功 →
u8_upgrade_object = ALPHAESS_MASTER并转ALPHA_UPGRADE_END(482、483)。 - 特殊分支(
410):当对象已切到 SLAVE 且is_bwinv_firm_download_finished() && get_board_type() != BOARD_EMS3_6_4_E时,直接跳过 CPU2 转到ALPHA_UPGRADE_END。
ALPHA_UPGRADE_END(516行起)- 发 0x43 结束帧;发送失败立即
SENDENDERR→ FAULT。 - 发送成功 →
alphaess_inv_com_reply(30000),超时长 30 秒,因为 DSP 端要做整体 MD5 校验。 - ACK 成功 → 调
alphaess_comm_init()/alphaess_comm_init_can()复位通信,memset清空升级表,清除UPDT_INV1/UPDT_INV2的服务器已更新标志与强制更新标志,所有计数器清零,记录u32_upgrade_finish_tick = OSTimeGet(),t_debug_log.u8_upgrade_status = 2,状态回ALPHA_UPGRADE_START(533–547)。 - ACK 失败 → 不重试,直接
RECVENDERR→ FAULT(555、557)。
- 发 0x43 结束帧;发送失败立即
ALPHA_UPGRADE_FAULT(571行起)- 每次进入
s_u8_upgrade_fault_cnt++;>2(即第 3 次)时执行与 END 成功相同的清理动作并回ALPHA_UPGRADE_START;否则仅清计数器回ALPHA_UPGRADE_START(577–603)。 - 每轮都执行
u8_err_tick++、u8_upgrade_status = TRUE(604、605),供日志/上报使用。
- 每次进入
状态迁移图(简化):START --ACK--> SEND_FILE(cpu1) --ACK--> SEND_FILE(cpu2) --ACK--> END --ACK--> START;任意环节发送或接收连续超限 → FAULT --> START。
4.2 MD5 一致性校验(启动前置条件)
send_ems_send_start_cmd(93 行)在组帧前做四路 MD5 比对(105–120):
get_curr_md5_code_fw_file(UPDT_INV1/UPDT_INV2)—— 由net/upd_download.c对 SDRAM 中已落盘固件实时算出的 MD5,ASCII 十六进制串(32 字符);g_stEMSInfo.stUpt[UPDT_INV1/UPDT_INV2].as8MD5Code—— 服务器下发的期望 MD5。
两者均经 hex_ascii_to_bin() 转成 16 字节二进制后逐字节比较,只有当 16 字节全部相同(l_u8_md5_check_cnt == 16)才允许发启动帧,否则直接 return FALSE,状态机会把它当作"发送失败"累计 s_u8_send_err_cnt,3 次后进入 FAULT。注意 l_u8_md5_check_cnt 是 u8,上限 16 无溢出风险。
4.3 通信协议帧格式
三帧均以从机地址 ALPHAESS_SLAVR_ADDRESS = 0x03 起始,CRC 为 Modbus CRC16(GetCrcCode_Modbus),低字节在前。
启动帧 0x41(发送 112 字节,缓冲区 u8_upgrade_start_buf[112] 刚好用满)
| 偏移 | 长度 | 内容 |
|---|---|---|
| 0 | 1 | 0x03 从机地址 |
| 1 | 1 | 0x41 启动命令 |
| 2–5 | 4 | CPU1 文件长度(大端,源自 g_u8_fw_sdram1[0..3]) |
| 6–21 | 16 | CPU1 文件 MD5(源自 g_u8_fw_sdram1[4..19]) |
| 22–23 | 2 | CPU1 总帧数(大端,源自 g_u8_fw_sdram1[20..21]) |
| 24–55 | 32 | CPU1 软件版本号(g_u8_fw_sdram1[22..53]) |
| 56–59 | 4 | CPU2 文件长度(g_u8_fw_sdram1[54..57]) |
| 60–75 | 16 | CPU2 MD5(g_u8_fw_sdram1[58..73]) |
| 76–77 | 2 | CPU2 总帧数(g_u8_fw_sdram1[74..75]) |
| 78–109 | 32 | CPU2 软件版本号(g_u8_fw_sdram1[76..107]) |
| 110–111 | 2 | CRC16(低字节在前) |
即 SDRAM 中固件前面有 128 字节描述头(双核长度/MD5/帧数/版本),故数据区从偏移 128 开始。
数据帧 0x42(最长 232 字节,缓冲区 u8_upgrade_data_buf[232])
| 偏移 | 长度 | 内容 |
|---|---|---|
| 0 | 1 | 0x03 |
| 1 | 1 | 0x42 数据命令 |
| 2 | 1 | CPU 号:0x01=CPU1,0x02=CPU2 |
| 3–4 | 2 | 帧序号(大端,u16_file_send_cnt) |
| 5 | 1 | 本帧有效载荷字节数 byte_num |
| 6…5+n | n | 载荷 addr[offset + i],非末帧 n=224 |
| 尾 2 字节 | 2 | CRC16(低字节在前) |
总长 = 6 + n + 2,n=224 时为 232,正好等于缓冲区大小。
结束帧 0x43(7 字节,缓冲区 u8_upgrade_end_buf[7]):03 43 45 4E 44 CRCL CRCH,其中 45 4E 44 为 ASCII "End"。
响应/超时机制:所有发送走 alphaess_write_data(buf, len, timeout)(内部 usart2_send_data);接收走 alphaess_inv_com_reply(timeout) → alphaess_recv_data()。判断 ACK 的依据(64、68):
buf[2] == 0x06 && buf[3] == 0x06:0x41/0x43 回帧的状态字段为0x06;buf[1] == 0x42 && buf[5] == 0x06:0x42 回帧的buf[5]状态字段为0x06。
alphaess_recv_data 内部对 buf[1]==0x42 按 CRC over 前 6 字节、对 0x41/0x43 按 CRC over 前 4 字节校验,校验失败置 *len = 0,因此升级流程里只有 CRC 正确的回帧才会被 alphaess_inv_com_reply 看到。
各环节超时值:启动帧发送 500、启动 ACK 600、数据帧发送 500、数据帧 ACK 600、结束帧发送 500、结束 ACK 30000(等 DSP MD5)。
4.4 定时/调度时序与任务配合
本文件没有自己的任务、信号量或消息邮箱,全部是同步阻塞式顺序代码。调用链为:
alphaess_comm_usart.c:5441(通信主循环) → alphaess_upgrade_handle()。
前置条件在 alphaess_comm_usart.c:5435:if (is_inverter_firm_download_finished()),即固件已完整下载到 SDRAM。调用前后:
- 调用前
g_t_alphaess_comm_info.u32_ems_inv_comm_tick = OSTimeGet()(5438),并在alphaess_inv_com_reply每次收到数据后再刷新(62),用于抑制"通信丢失"判定; - 调用后
alphaess_comm_usart.c:5450用alphaess_get_upgrade_finish_tick()判断升级完成后 120 秒内把丢连阈值放宽 90 秒,避免升级期间误报逆变器通信中断。
等待 tick 用的是 OSTimeGet() 与忙循环 + abs() 差值比较,没有 OSTimeDly(OSTimeDly 只出现在被调用的 alphaess_recv_data 里),因此单次 alphaess_upgrade_handle 最多可阻塞数十秒(结束帧 30 秒)。
4.5 关键全局变量读写
| 变量 | 读/写 | 触发条件 |
|---|---|---|
g_t_alphaess_comm_info.u32_ems_inv_comm_tick | 写 | 每次收到回帧(alphaess_upgrade.c:62) |
g_t_inv_info.t_debug_log.u32_err_reason | 写 | 各类发送/接收连续失败(288/302/351/364/391/404/454/467/495/508/557/566) |
g_t_inv_info.t_debug_log.u8_upgrade_status | 写 | 成功置 2(547),失败置 TRUE(605);alphaess_comm_usart.c:5470 据此调 set_info_status(INV_UPGRADE_LOG) |
g_t_inv_info.t_debug_log.u8_err_tick | 写 | FAULT 分支每轮自增(604) |
g_t_inv_info.t_run_info.u8_run_mode | 写 | CPU1/CPU2 数据帧 ACK 成功后置 cUpdateMaster / cUpdateSlave(341、444) |
g_stEMSInfo.stUpt[].as8MD5Code | 读 | 启动帧组帧前的 MD5 比对(109、111) |
g_u8_fw_sdram1 / g_u8_fw_sdram2 | 读 | 描述头解析(124–169)与数据帧载荷(203) |
5. 对外接口
本文件导出(供外部调用) —— 声明见 alphaess_upgrade.h:66-72:
| 导出符号 | 调用方 |
|---|---|
alphaess_upgrade_handle() | midware/inverter/alphaess/alphaess_comm_usart.c:5441 |
alphaess_read_upgrade_flag_master() | midware/inverter/inverter.c:3820 |
alphaess_read_upgrade_percent_master() | midware/inverter/inverter.c:3855 |
alphaess_read_upgrade_flag_slave() | midware/inverter/inverter.c:3890 |
alphaess_read_upgrade_percent_slave() | midware/inverter/inverter.c:3925 |
alphaess_read_upgrade_file_offset() | midware/inverter/inverter.c:3947 |
alphaess_get_upgrade_finish_tick() | midware/inverter/alphaess/alphaess_comm_usart.c:5450 |
未导出全局变量(仅 static):s_alphaess_upgrade_tab。
本文件调用的外部函数/变量:
| 外部符号 | 来源模块 |
|---|---|
alphaess_write_data / alphaess_recv_data / alphaess_comm_init | midware/inverter/alphaess/alphaess_comm_usart.c(.h:262/285/718) |
alphaess_comm_init_can | midware/inverter/alphaess/alphaess_comm_can.h:208 |
get_curr_md5_code_fw_file / get_length_file_updating / set_flag_force_update / Set_Status_Is_Updated_From_Server | net/upd_download.h:38/25/39 |
is_bwinv_firm_download_finished / is_inverter_firm_download_finished | net/inv_download.h:35(后者在 usart 侧判断) |
g_u8_fw_sdram1 / g_u8_fw_sdram2 | net/server.h:258-259,实际落在 hardware/sdram/sdram.h 定义的 BANK5 区(各 3MB) |
hex_ascii_to_bin | common/convert(convert.h) |
GetCrcCode_Modbus | common/crc(crc.h) |
OSTimeGet | uCOS-II |
g_t_alphaess_comm_info | midware/inverter/alphaess/alphaess_comm_usart.h:715 |
g_t_inv_info / cUpdateMaster / cUpdateSlave / SENDBOOTERR 等错误码 | midware/inverter/inverter.h(枚举自 inverter.h:383) |
g_stEMSInfo.stUpt[] / UPDT_INV1 / UPDT_INV2 | net/upd_download.h、net/ems.h |
get_board_type / BOARD_EMS3_6_4_E | system/board_init/board_init.h:22/30 |
set_info_status | 日志/上报模块(ems 侧) |
alphaess_upgrade.c:13-26 的 include 列表还包括 hex_file_parse.h、sdram.h、usart2.h、inverter.h、board_init.h 等。
6. 依赖关系
- 依赖:
alphaess_comm_usart.h(外部函数与g_t_alphaess_comm_info)、alphaess_comm_can.h、alphaess_upgrade.h、upd_download.h、inv_download.h、hex_file_parse.h、server.h、sdram.h、crc.h、usart2.h、convert.h、ems.h、inverter.h、board_init.h。 - 被依赖:
midware/inverter/alphaess/alphaess_comm_usart.c(驱动状态机 + 用完成时刻做丢连阈值补偿)midware/inverter/inverter.c(读取主/从升级标志与百分比、已传偏移,供 HMI/云端上报)
7. 备注与风险点
alphaess_inv_com_reply缓冲区过小(alphaess_upgrade.c:47):u8 buf[16]只有 16 字节,而alphaess_recv_data会把 UART 收到的整帧(usart2_get_data_len()长度,普通 Modbus 读帧可达 200+ 字节)拷贝进来(alphaess_comm_usart.c:315的循环无长度上限保护),存在栈溢出风险。- 收到非预期帧立即返回 FALSE(
73–80):噪声帧或上一帧的残留回包会让本轮判定直接失败并计入错误次数,容错性差。 abs()用于 tick 差值(54、alphaess_comm_usart.c:5450):OSTimeGet()是 32 位 tick,回绕后abs()结果不具单调性,理论上会误判超时;同时l_u32_recv_start_tick由 u32 减去 u32 再取abs(参数为int),符号扩展下有边界问题。byte_num为u8会截断:末帧长度u32_cpu2_file_len - u32_upgrade_cpu2_file_offset(474)直接作为u8形参传入,若剩余字节数 >255 则被截断,导致最后一帧少发数据;u32_cpu1_file_len - ... + 128(371)同理。- CPU2 描述头从
u8_cpu1_upgrade_addr读取(151–169):CPU2 的长度/MD5/帧数/版本全部从g_u8_fw_sdram1的描述头继续读取,而u8_cpu2_upgrade_addr(=g_u8_fw_sdram2)只用于数据帧载荷。这依赖"128 字节描述头同时描述双核"这一隐含约定,若服务器端文件布局变化会静默错发。 u8_upgrade_start_buf[112]与组帧长度 112 完全相等:一旦协议追加任一字节字段就会越界,无任何余量或长度断言。- 错误计数阈值不一致:CPU1 末帧
s_u8_rev_err_cnt > 3(389),CPU2 末帧却是> 10(493),其余均为> 3,疑为历史调试遗留。 - 分支条件可疑(
410):is_bwinv_firm_download_finished() && get_board_type() != BOARD_EMS3_6_4_E决定跳过 CPU2。该条件把"下载完成"与"板型"耦合,is_bwinv_firm_download_finished()在升级途中通常恒为真,效果等价于"除 EMS3_6_4_E 外都不升级 CPU2",与注释"升级从机"语义不符,建议确认。 - 错误原因只在计数超限时才落盘:
u32_err_reason仅在s_u8_send_err_cnt/s_u8_rev_err_cnt > 3时写入(288、302等),前三次失败完全不留痕,现场只能看到最后一次原因,且不同阶段的错误码会互相覆盖同一字段。 ALPHA_UPGRADE_FAULT是自旋退出而非真正中止 DSP 侧升级:该分支不发送任何取消帧,只清理本地状态,此时 DSP 可能仍停在等待数据的中间态,需要依赖下一轮ALPHA_UPGRADE_START重新握手恢复。- 头文件中的
EMS_INV_*/INV_EMS_ACK宏与本文件无关:这些是 kelong 的 CAN 升级命令字(midware/inverter/kelong/kelong_upgrade.c:133起),拷贝到本头文件后从未使用,属版本遗留。 u8_upgrade_object用u8存枚举:ALPHAESS_UPGRADE_OBJECT_E值 0/1/2,赋值与比较均依赖隐式转换,可读性差但无功能问题。- 未使用的 include:
hex_file_parse.h、ems.h、sdram.h(地址宏经server.h间接引入)在本文件中未见直接符号引用,属复制粘贴遗留。