EMS 知识库
首页 / knowledge/代码解析/midware/inverter/inovance

inovance_upgrade.c

源文件路径:midware/inverter/inovance/inovance_upgrade.c 所属模块:midware/inverter/inovance(汇川逆变器通信与固件升级) 代码行数:852

1. 文件职责

本文件实现汇川(Inovance)逆变器主机 MCU1 / 从机 MCU2 / FPGA(ARM) 三颗芯片的固件在线升级流程,是逆变器固件下发链路的最后一环。上游由云端 EMS 把固件包下载进 SDRAM(g_u8_fw_sdram1 / g_u8_fw_sdram2)并置位 UPDT_INV1/INV2/INV3 更新标记,本文件负责把这些镜像按 264 字节定长分包,通过 USART2 以「Modbus-RTU 私有扩展帧」写入逆变器 bootloader。

本文件不创建任务、不使用信号量或消息邮箱,全部逻辑是在调用者任务上下文中同步轮询执行的状态机:每次被调用推进一个状态(部分状态内含 OSTimeDlyHMSM 阻塞延时)。驱动者是 inovance_comm_usart.c:2571 的 inovance_inverter_comm(),它在 inovance_comm_usart.c:2592 调用 inovance_upgrade_handle()。

对外另外导出 6 个只读查询函数(主/从/FPGA 的「升级中标志」与「升级百分比」),由 inverter.c 与 HMI 侧读取用于显示。

2. 关键数据结构与宏

名称类型说明
INOVANCE_UPGRDADE_T结构体升级上下文。字段:u8_upgrade_addr(SDRAM 镜像基址)、u8_upgrade_obj(升级对象)、u8_upgrade_state(本文件未使用,实际状态放在函数内 static 变量)、u8_upgrade_buf[264](单包数据暂存)、u32_file_offset(已发送偏移)、u32_file_length(镜像总长)。文件内以 s_t_inovance_upgrade(inovance_upgrade.c:24)单例存在
INOVANCE_UPGRADE_RETURN_E枚举设备返回码 / 设备运行状态的混合枚举,同时容纳三组含义不同的取值(详见第 7 节风险点)
INOVANCE_UPGRADE_OBJ_E枚举升级对象:INOVANCE_NONE=0、INOVANCE_MCU1、INOVANCE_MCU2、INOVANCE_FPGA、INOVANCE_INIT、INOVANCE_MAX
INOVANCE_UPGRADE_STATE_E枚举主状态机状态:INIT / QUERY_STATUS / JUMP_BOOT / SET_DOWNLOAD_MODE / SEND_FILE / START_PROGRAM / END
INOVANCE_UPGRADE_DEBUG宏调试开关,默认 0。置 1 后每个状态进入/退出都打印大量 printf,并额外定义 state_name[]/obj_name[]/STATE_NAME/OBJ_NAME
INOVANCE_UPGRADE_ADDR_READ_DEV_STATUS宏0x04,APP_OPERATE 命令读取的设备状态寄存器地址
INOVANCE_UPGRADE_ADDR_JUMP_BOOT宏0x05,APP_OPERATE 命令跳转 boot 的寄存器地址
INOVANCE_UPGRADE_CMD_APP_OPERATE宏0x45,APP 操作命令字(查状态 / 跳 boot)
INOVANCE_UPGRADE_CMD_SET_DOWNLOAD_MODE宏0x64,进入下载模式命令字
INOVANCE_UPGRADE_CMD_SEND_FILE宏0x69,下发固件数据包命令字
INOVANCE_UPGRADE_CMD_START_PROGRAM宏0x70,启动新程序命令字
INOVANCE_UPGRADE_FILE_LENGTH宏264,单包固件数据长度,同时是 u8_upgrade_buf 的容量与总长整除校验的模数
INOVANCE_UPGRADE_DELAY宏200,QUERY_STATUS 无回复时的时间片延时(ms)
INOVANCE_SLAVE_ADDRESS宏(外部)0x01,来自 inovance_comm_usart.h:40,是全部下发帧的从机地址
INOVANCE_DAT_PCK_INTERVAL_INV宏(外部)100,来自 inovance_comm_usart.h:25,用作命令帧的发送节流窗口与发送超时(ms)
INOVANCE_OPERATE_STOP / INOVANCE_OPERATE_START枚举(外部)来自 inovance_comm_usart.h:173-176(INOVANCE_OPERATE_CODE_E),值分别为 1 / 2

3. 函数清单

函数名作用域说明
inovance_read_upgrade_flag_master全局返回「当前升级对象 == INOVANCE_MCU1」,供上层判断主机是否在升级
inovance_read_upgrade_percent_master全局主机升级百分比 = u32_file_offset * 100 / get_length_file_updating(UPDT_INV1),对象不匹配时返回 0
inovance_read_upgrade_flag_slave全局返回「当前升级对象 == INOVANCE_MCU2」
inovance_read_upgrade_percent_slave全局从机升级百分比(分母为 UPDT_INV2 文件长度)
inovance_read_upgrade_flag_fpga全局返回「当前升级对象 == INOVANCE_FPGA」
inovance_read_upgrade_percent_fpga全局FPGA 升级百分比(分母为 UPDT_INV3 文件长度)
inovance_upgrade_write_datastatic串口发包封装:做 timeout 节流、清空接收缓冲、调用 usart2_send_data,返回发送结果
inovance_upgrade_recv_datastatic串口收包与解析:搬出异步接收数据、校验 Modbus CRC16、按命令字区分提取 code/ret/len
inovance_upgrade_read_dev_statusstatic组 01 45 00 04 00 01 CRC 帧,查询逆变器当前运行状态
inovance_upgrade_set_jump_bootstatic组 01 45 00 05 00 01 CRC 帧,令 APP 跳转到 bootloader
inovance_upgrade_set_download_modestatic组 01 64 F6 0F 00 08 CRC 帧,令 boot 进入下载模式
inovance_upgrade_start_programstatic组 01 70 00 00 00 00 CRC 帧,令 boot 校验并启动新程序
inovance_upgrade_send_file_contentstatic组 01 69 <264B> CRC 帧(共 268 字节)下发一个固件数据包
inovance_upgrade_handle全局升级主状态机,每次调用推进一个状态;含 3 次失败退出、超时重发、分对象串行升级逻辑

4. 核心逻辑解析

4.1 主状态机与迁移条件

状态变量为函数内 static u8 s_u8_upgrade_state(inovance_upgrade.c:364),初值 0 即 INOVANCE_UPGRADE_INIT。另有一组跨调用保持的 static 标志(inovance_upgrade.c:365-370):

函数入口的全局急停(inovance_upgrade.c:396-405):s_u8_upgrade_fail_cnt > 3 时强制回到 INIT、清零 s_t_inovance_upgrade、把 UPDT_INV1/2/3 三个服务端更新标记全部清掉并 return(本次不再推进状态机)。

各状态迁移:

当前状态条件下一状态附带动作
INITis_inverter_firm_download_finished()(即 UPDT_INV1 && UPDT_INV2 均置位)且 MCU1 待办QUERY_STATUS装载 g_u8_fw_sdram1,obj = INOVANCE_MCU1,长度取 UPDT_INV1
INIT同上但仅 MCU2 待办QUERY_STATUS装载 g_u8_fw_sdram2,obj = INOVANCE_MCU2,长度取 UPDT_INV2
INIT!is_inverter_firm_download_finished() 且 Get_Status_Is_Updated_From_Server(UPDT_INV3) 且 FPGA 首次QUERY_STATUS装载 g_u8_fw_sdram1,obj = INOVANCE_FPGA,长度取 UPDT_INV3
INIT三个来源都不成立 / 对象已全部处理完ENDaddr=NULL、obj=INOVANCE_NONE、length=0
INITfile_length == 0 或 file_length % 264 != 0END三个待办标志全部清 FALSE
INITinovance_set_inv_start_stop(INOVANCE_OPERATE_STOP) 返回 TRUEQUERY_STATUSrecv_err_cnt = 0(APP 正常停机应答)
INIT返回值 == 2(无回复)停留 INITboot_recv_cnt++;一旦 >10 直接转 QUERY_STATUS(认定已在 boot 中,无需停机)
INIT返回值 == FALSE(l_u16_inv_recv_num == 5,APP 异常回复)停留 INITrecv_err_cnt++;>3 转 END 并清三个待办标志
QUERY_STATUS收到 code == 0x45 且 (ret >> 8) == INOVANCE_UPGRADE_SAFE(3)JUMP_BOOTrecv_err_cnt = 0(APP 处于安全状态,允许跳 boot)
QUERY_STATUS收到 code == 0x45 但状态非 SAFEEND清当前对象的待办标志(本对象放弃,不影响其它对象)
QUERY_STATUS收到 code == 0xC5(0x45+0x80 异常回复)且帧长 == 8SET_DOWNLOAD_MODE判定 APP 已被擦除、设备已在 boot 中,跳过 JUMP_BOOT
QUERY_STATUS收不到 或 帧长 != 8停留recv_err_cnt++,无回复时额外 OSTimeDlyHMSM(...,200);>3 清标志转 END
JUMP_BOOT无条件(接收数据被丢弃)SET_DOWNLOAD_MODE发跳转帧后固定延时 500 ms
SET_DOWNLOAD_MODEret == INOVANCE_UPGRADE_NO_ERROR(0) 且 code == 0x64SEND_FILErecv_err_cnt = 0
SET_DOWNLOAD_MODE其它停留;连续 3 次失败后recv_err_cnt++,>3 时清当前对象标志并转 START_PROGRAM(跳过数据下发,走收尾)
SEND_FILEu32_file_offset >= u32_file_length 或 length <= 0START_PROGRAM数据已发完
SEND_FILE收到 code == 0x69 且 ret == 0停留recv_err_cnt = 0,u32_file_offset += 264
SEND_FILE收到 code == 0xE9(0x69+0x80)且 ret == INOVANCE_UPGRADE_ID_ERROR(0x08)START_PROGRAM设备侧认为镜像已收全(ID 校验结束)
SEND_FILE其它应答停留recv_err_cnt++,延时 300 ms;>3 时 fail_cnt++、u32_file_offset = 0、回到 QUERY_STATUS 从头发起
START_PROGRAMret == 0 且 code == 0x70END清当前对象待办标志、fail_cnt = 0、固定延时 15 s
START_PROGRAM其它停留recv_err_cnt++,延时 1000 ms;>= 3 时清当前对象标志并转 END
END无条件INIT清零上下文、inovance_set_inv_start_stop(INOVANCE_OPERATE_START) 恢复逆变器运行、按待办标志清除服务端更新标记

4.2 通信协议帧格式

底层是 USART2(115200bps,见 inovance_comm_usart.c:2322 的 usart2_init(115200)),帧结构为「从机地址 + 命令字 + 数据域 + Modbus CRC16(低字节在前)」,与标准 Modbus 不同之处是命令字直接复用功能码位置。

下发命令帧(INOVANCE_SLAVE_ADDRESS = 0x01):

帧用途字节序列长度
查设备状态01 45 00 04 00 01 CRC_L CRC_H8
跳转 boot01 45 00 05 00 01 CRC_L CRC_H8
进下载模式01 64 F6 0F 00 08 CRC_L CRC_H8
启动新程序01 70 00 00 00 00 CRC_L CRC_H8
下发固件包01 69 <264 字节固件> CRC_L CRC_H268

45 帧的 00 04 / 00 05 是 16 位寄存器地址,00 01 是寄存器个数;校验使用 GetCrcCode_Modbus()(common/crc.c:144),计算结果低字节先发(见 inovance_upgrade.c:237-238 等)。

接收解析(inovance_upgrade_recv_data,inovance_upgrade.c:147-215):

  1. usart2_is_recv_async_data() 判断有无整帧到达;无则 code = ret = 0、len 保持 0 返回(调用方据此走「无回复」分支)。
  2. usart2_get_data_len() 取帧长,< 2 直接返回。
  3. 逐字节 usart2_read_data_async() 搬入调用者 buffer。
  4. CRC 取自帧尾两字节并按小端拼装:crc_temp = (buf[len-1] << 8) | buf[len-2];与 GetCrcCode_Modbus(buf, len - 2) 比对。
  5. 仅在 CRC 匹配时才解析命令字与返回码:
    • code == 0x45:ret = (buf[3] << 8) | buf[4],即 16 位寄存器值(这里 ret >> 8 是运行状态,ret & 0xFF 是低字节)。
    • code ∈ {0xC5, 0x64, 0x69, 0x70, 0xE4, 0xE9, 0xF0}:ret = buf[2],即单字节错误码(正常命令字或其 +0x80 异常形态)。
  6. 结果通过指针出参 *code / *ret / *len 回传。

超时与重试:没有协议级超时定时器,重试依赖状态机 + 计数。发送侧由 inovance_upgrade_write_data(inovance_upgrade.c:115-139)做发送节流:若距上次发送不足 timeout ms 则 OSTimeDlyHMSM 补足,然后 s_u32_send_pack_tick = OSTimeGet()、usart2_clear_buffer()、usart2_send_data(buf, len, timeout)。接收侧由各状态自己的 s_u8_recv_err_cnt(阈值 3)与 SEND_FILE 的 s_u8_upgrade_fail_cnt(阈值 3,整轮重来)构成两级重试。

4.3 固件分包下发时序(SEND_FILE)

memcpy(s_t_inovance_upgrade.u8_upgrade_buf,
       s_t_inovance_upgrade.u8_upgrade_addr + s_t_inovance_upgrade.u32_file_offset,
       INOVANCE_UPGRADE_FILE_LENGTH);              // inovance_upgrade.c:697
inovance_upgrade_send_file_content(s_t_inovance_upgrade.u8_upgrade_buf);  // :698

4.4 与 uCOS-II 任务/时序的配合

4.5 关键全局变量读写

变量读/写触发条件
s_t_inovance_upgrade读+写全流程;INIT 装载地址/对象/长度,SEND_FILE 维护 u32_file_offset,END 与失败急停时 memset 清零
s_u8_upgrade_mcu1_flag / mcu2 / fpga读+写INIT 首次置 TRUE 并按 TRUE 选择对象;任一对象成功、或该对象在任何状态被判失败时置 FALSE
g_u8_fw_sdram1 / g_u8_fw_sdram2只读固件源地址,INIT 中赋给 u8_upgrade_addr(MCU1/FPGA 用 sdram1,MCU2 用 sdram2)
Get_Status_Is_Updated_From_Server(UPDT_INV1/2/3)只读INIT 判断是否进入升级、以及走 MCU1/MCU2 还是 FPGA 分支
Set_Status_Is_Updated_From_Server(UPDT_INV1/2/3, FALSE)只写失败超限急停、END 状态收尾
get_length_file_updating(UPDT_INV1/2/3)只读INIT 取镜像总长;三个百分比查询函数取分母
g_t_inv_info.t_run_info.u8_run_mode间接写由调用者 inovance_comm_usart.c:2585-2590 依据本文件的三个 flag 查询函数设置
g_t_inovance_comm_info.u32_ems_inv_comm_tick间接写INIT/END 调用 inovance_set_inv_start_stop() 成功应答时被其内部刷新

5. 对外接口

本文件导出

符号类型调用方
inovance_upgrade_handle函数inovance_comm_usart.c:2592(inovance_inverter_comm())
inovance_read_upgrade_flag_master函数inverter.c:3828、inovance_comm_usart.c:2585
inovance_read_upgrade_percent_master函数inverter.c:3863
inovance_read_upgrade_flag_slave函数inverter.c:3898、inovance_comm_usart.c:2587
inovance_read_upgrade_percent_slave函数inverter.c:3933
inovance_read_upgrade_flag_fpga函数inverter.c:3984、inovance_comm_usart.c:2589
inovance_read_upgrade_percent_fpga函数inverter.c:4011

无导出全局变量:s_t_inovance_upgrade 是 static,所有宏/枚举/结构体定义在 inovance_upgrade.h 中向外暴露。

本文件调用

外部符号来源模块
usart2_init / usart2_clear_buffer / usart2_send_data / usart2_is_recv_async_data / usart2_get_data_len / usart2_read_data_asynchardware/usart2/usart2.h
GetCrcCode_Modbuscommon/crc.h:17
is_inverter_firm_download_finishednet/inv_download.h:34
get_length_file_updating / Set_Status_Is_Updated_From_Server / Get_Status_Is_Updated_From_Servernet/upd_download.h:37,39,40
ENUpdateType / UPDT_INV1/2/3net/server.h:190-192
g_u8_fw_sdram1 / g_u8_fw_sdram2net/server.h:258-259(__attribute__((at(...))) 定位于 SDRAM 固件区)
inovance_set_inv_start_stop / INOVANCE_SLAVE_ADDRESS / INOVANCE_DAT_PCK_INTERVAL_INV / INOVANCE_OPERATE_STOP / INOVANCE_OPERATE_STARTmidware/inverter/inovance/inovance_comm_usart.h
g_t_inovance_comm_info / g_t_inv_infomidware/inverter/inovance/inovance_comm_usart.h、midware/inverter/inverter.h
OSTimeGet / OSTimeDlyHMSMUCOSII(经 includes.h)

6. 依赖关系

依赖(本文件 include):inovance_upgrade.h、upd_download.h、inv_download.h、server.h、sdram.h、usart2.h、inovance_comm_usart.h、inovance_comm_can.h、crc.h、inverter.h(inovance_upgrade.c:13-22)。其中 inovance_comm_can.h 在本文件中实际未被直接使用(CAN 相关操作在 inovance_upgrade_init() 里完成)。

被依赖:

7. 备注与风险点

  1. 潜在栈越界(较严重):inovance_upgrade_handle 里的接收缓冲是 u8 l_u8_recv_buf[20](inovance_upgrade.c:372),而 inovance_upgrade_recv_data 按 usart2_get_data_len() 的实际长度逐字节写入 *(buf + l_u16_index)(:173),完全没有上限检查。正常协议应答不超过 8 字节,但一旦 USART2 收到异常长帧(噪声、波特率不匹配、串到其它帧),就会冲掉该任务的栈。
  2. 除零风险:inovance_read_upgrade_percent_master/slave/fpga 的分母是 get_length_file_updating(UPDT_INV*)(:52/:77/:104),只判断了 u8_upgrade_obj,未判断分母是否为 0。INIT 中检测到长度为 0 时先退出到 END、上下文要到下一次调用才 memset 清零(:827),中间窗口内被其它任务读取百分比就会整数除零(Cortex-M4 上触发 UsageFault)。
  3. 无符号恒假比较:(s_t_inovance_upgrade.u32_file_length <= 0)(:688)对 u32 永远为假,属冗余写法,编译器会给警告;真正起作用的是 u32_file_offset >= u32_file_length。
  4. 失败超限急停未清待办标志:s_u8_upgrade_fail_cnt > 3 分支(:396-405)只清了服务端更新标记和上下文,没有把 s_u8_upgrade_mcu1_flag/mcu2_flag/fpga_flag 复位。这些 static 标志会跨「整轮失败」保留为 TRUE,下一次升级请求到来时 INIT 仍会按旧标志选择对象,行为与「全新一轮」不一致。
  5. abs() 用于 u32:inovance_upgrade_write_data 中 abs(OSTimeGet() - s_u32_send_pack_tick) 与 abs(timeout - abs(...))(:120、:122)把无符号差值传给 int 版 abs,在 tick 回绕或差值超过 INT_MAX 时结果未定义;节流逻辑本身也只是「补足到 timeout」,没有减法下限保护。
  6. 状态枚举三域混用:INOVANCE_UPGRADE_RETURN_E 把「设备运行状态」(RUNNING=1/REVERSE=2/SAFE=3)、「APP 侧响应错误」(CMD_ERROR=1/ADDR_ERROR=2/LENGTH_ERROR=3/CRC_CHECK_ERROR=4/JUMPER_DISCONNECT=5)、「boot 侧响应错误」(NO_ERROR=0/DATA_CRC16_ERROR=1/.../ID_ERROR=0x08/CRC32_INCOMPATIBLE_ERROR=0x11/...)塞进同一个枚举,多组取值重叠。代码里用 (ret >> 8) == INOVANCE_UPGRADE_SAFE(:542)这种「高字节是状态、低字节是错误码」的隐式约定,可读性差且极易误用。

另注:枚举首项 INOVANCE_UPGRADE_RUNNING = 0x01 与 INOVANCE_UPGRADE_CMD_ERROR = 0x01 同值;INOVANCE_UPGRADE_NO_ERROR 出现在中间位置(=0x00),故 DATA_CRC32_ERROR 后显式写成 = 0x07 以复位计数,ID_ERROR 顺延为 0x08。修改此枚举顺序会静默改变协议语义。

  1. 魔法数字:set_download_mode 帧里的 0xF6 0x0F(:287-288)与 0x00 0x08(:289-290)没有宏/注释说明含义(推测为固定握手口令与长度),jump_boot 的 0x00 0x01、start_program 的全零数据域同理。协议侧一改就要翻代码找字面量。
  2. 硬编码长延时阻塞任务:500 ms / 300 ms / 1000 ms / 5 s / 15 s / 20 s 全部以字面量出现在 OSTimeDlyHMSM 中(:630/704/712/741/786/795),其中 20 s 的「首包后擦除等待」是任务级阻塞。虽然升级分支每次调用会刷新通信丢失 tick,但这段时间整个汇川任务无法处理其它报文(含 CAN 侧),若上层对逆变器心跳有更严的时限会出问题。
  3. 重复/错误的函数头注释:多个函数的 Doxygen 注释 @Name 都写成不存在的 inovance_send_upgrade_cmd(:110、:218、:246、:274、:302、:330),@Brief 一律是「逆变器升级串口发送函数」,与实际的收包/查询函数不符(如 inovance_upgrade_recv_data 标成了发送);inovance_upgrade_read_dev_status 等函数没有自己的 @Name 块。属历史拷贝残留,排查时容易误导。
  4. INOVANCE_UPGRDADE_T.u8_upgrade_state 死字段:结构体里定义了 u8_upgrade_state,但状态机真正用的是函数内 static u8 s_u8_upgrade_state,该字段全文件从未读写。
  5. 接收缓冲残留:inovance_upgrade_recv_data 在 len < 2 时直接 return(:160-163),既不清空 USART2 接收 FIFO 也没置位出参;此时 *code/*ret/*len 保持调用者栈上的旧值,调用方可能把上一轮的应答误当作本轮结果。
  6. JUMP_BOOT 忽略应答:JUMP_BOOT 状态发完跳转帧后只是读一次串口就丢弃(:628),随后无条件延时 500 ms 前进。若设备实际未跳转,问题要到 SET_DOWNLOAD_MODE 连续 3 次失败才暴露。