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

goodwe_upgrade.c

源文件路径:midware/inverter/goodwe/goodwe_upgrade.c 所属模块:midware/inverter/goodwe(古瑞瓦特 GoodWe 逆变器固件升级) 代码行数:725

1. 文件职责

本文件实现 EMS 把云端下载好的 GoodWe 逆变器固件分包烧录到逆变器内部 FLASH 的完整流程,包含三套互相独立的升级协议:主机 DSP 升级(UPDT_INV1,先发 FlashDSP 命令让 DSP 进入 bootloader)、从机/模块升级(UPDT_INV2,64 字节小包 + 单字节应答)、外部 ARC/AFCI 模块升级(UPDT_INV3,512 字节包 + 0xAA 0x55 头 + 索引 + 累加和)。它由 goodwe_comm.c 的 goodwe_inverter_comm()(goodwe_comm.c:2460-2479)在检测到云端更新标志后每轮调用,是 EMS 数据流的末端环节——把 SDRAM 里的固件镜像逐包推给逆变器,并把进度百分比回传给上层显示。整个模块与主通信共用 UART4,升级期间主通信的常规数据查询全部暂停。

2. 关键数据结构与宏

名称类型说明
GOODWE_UPGRADE_STATE_E枚举升级状态机状态(goodwe_upgrade.h:34):GW_UPGRADE_NONE=0 空闲、GW_UPGRADE_MIDDLE=1 从机完成待转主机、GW_UPGRADE_MASTER=2 主机 DSP 升级中、GW_UPGRADE_SLAVE=3 从机升级中、GW_UPGRADE_EXTERNAL=4 外部 ARC 升级中、GW_UPGRADE_INFO_INIT=5 收尾(清云端标志)
s_u8_upgrade_statestatic 变量GOODWE_UPGRADE_STATE_E 唯一实例,goodwe_upgrade.c:21,整个模块的状态中枢
s_u32_slave_file_offsetstatic 变量从机升级已发送字节偏移,goodwe_upgrade.c:22
s_u32_master_file_offsetstatic 变量主机 DSP 升级已发送字节偏移,goodwe_upgrade.c:23
s_u32_external_file_offsetstatic 变量外部 ARC 升级已发送字节偏移,goodwe_upgrade.c:24
GOODWE_INV_UPGRADE_DEBUG宏(0),升级调试开关(goodwe_upgrade.h:14)
RESTART_FLASH_MODE宏0x15,逆变器应答「正在重启进入 FLASH 模式」
UPGRADE_REPLY_OK宏0x06,逆变器应答「收到/成功」
UPGRADE_WRITE_SECTION_FAULT宏0x01,逆变器应答「本扇区写入失败」,需回退一个扇区重传
UPGRADE_DSP_ENTER_BOOT宏字符串 "FlashDSP",让 DSP 进入 bootloader 的魔数命令(无帧头、无校验、裸 ASCII)
UPGRADE_SECTION_LEN宏4096,FLASH 扇区大小,用于写失败时的回退粒度
UPGRADE_PACK_LEN宏64,从机/主机升级的每包字节数
UPGRADE_PACK_LEN_NEW宏512,外部 ARC 新协议的每包字节数(在头文件中被重复定义两次,见 goodwe_upgrade.h:23 与 :30)
UPGRADE_HEADER_LEN宏32,固件镜像头部长度(第一包固定发 32 字节头)
UPGRADE_RECV_TIMEOUT宏1500(ms),旧协议单包应答超时
UPGRADE_TYPE_DSP宏0xFF01,新协议里的主机类型(未使用)
UPGRADE_TYPE_ARM宏0xFF02,新协议里的 ARM 类型(未使用)
UPGRADE_TYPE_ARC宏0xFF03,新协议里的 ARC 模块类型,实际唯一使用的类型字
UPGRADE_HEADER_LEN_NEW宏32,新协议头部长度(未使用)
g_u8_fw_sdram1 / g_u8_fw_sdram2外部数组由 net/server.h:258-259 定义、链接到 SDRAM 固定地址 SDRAM_FIRM1_START_ADDR / SDRAM_FIRM2_START_ADDR,长度 SDRAM_3M;本文件直接从这两个数组取固件镜像,不关心下载过程

3. 函数清单

函数名作用域说明
check_sum全局(仅本文件调用)逐字节累加的 16 位校验和,新旧两套协议共用
inv_com_send全局(仅本文件调用)旧协议发送:清 UART4 收缓冲后裸发,无节流、无重试
inv_com_reply全局(仅本文件调用)旧协议收包:轮询等 1 个字节,超时返回 FALSE,成功返回该字节值
inv_com_send_new全局(仅本文件调用)新协议组帧并发送:6 字节头 + type + idx + len + 数据 + 2 字节累加和
inv_com_reply_new全局(仅本文件调用)新协议收包:校验头、校验和、类型字,返回应答帧第 11 字节
goodwe_read_upgrade_flag_master全局(导出)主机升级中标志:状态为 MIDDLE 或 MASTER
goodwe_read_upgrade_percent_master全局(导出)主机升级进度百分比 = master_file_offset×100 / UPDT_INV1 文件长度
goodwe_read_upgrade_flag_slave全局(导出)从机升级中标志:状态为 SLAVE/EXTERNAL/INFO_INIT
goodwe_read_upgrade_percent_slave全局(导出)从机升级进度百分比 = slave_file_offset×100 / UPDT_INV2 文件长度
goodwe_read_upgrade_flag_external全局(导出)外部升级中标志:状态 ≤ EXTERNAL 且非 NONE
goodwe_read_upgrade_percent_external全局(导出)外部升级进度百分比 = external_file_offset×100 / UPDT_INV3 文件长度
upgrade_slave_handle全局(仅本文件调用)从机(UPDT_INV2)分包发送状态机:首包 32 字节头,之后 64 字节/包
upgrade_master_handle全局(仅本文件调用)主机 DSP(UPDT_INV1)升级状态机:先 FlashDSP 进 boot,再首包 32 字节,之后 64 字节/包
upgrade_external_handle全局(仅本文件调用)外部 ARC(UPDT_INV3)新协议分包状态机:32 字节头 + 512 字节/包 + 递增索引
goodwe_upgrade_external_handle全局(导出)外部升级顶层状态机:NONE → EXTERNAL → INFO_INIT → NONE
goodwe_upgrade_handle全局(导出)内部升级顶层状态机:NONE → SLAVE → MIDDLE → MASTER → INFO_INIT → NONE

4. 核心逻辑解析

4.1 两套帧格式

旧协议(主机 DSP + 从机)——裸数据流,无帧结构

这是 GoodWe bootloader 的最小协议:

主机 DSP 的 bootloader 入口不是协议命令,而是一段纯 ASCII 字符串 "FlashDSP"(UPGRADE_DSP_ENTER_BOOT),sizeof(...) - 1 去掉结尾 \0 后发送,等 0x06 应答表示已进入 boot。

新协议(外部 ARC,UPDT_INV3)——带帧头的流式协议

inv_com_send_new()(goodwe_upgrade.c:118)组装的帧结构:

[0..5]  固定头 0xAA 0x55 0x80 0x7F 0x02 0x29
[6..7]  类型 type(大端),本模块只用 0xFF03 (UPGRADE_TYPE_ARC)
[8..9]  包索引 idx(大端),从 0 起每包 +1
[10..11] 数据长度 len(大端)
[12..]  数据(len 字节)
[末尾2]  累加和(大端:高字节在前)

校验和由 check_sum()(goodwe_upgrade.c:32)计算:对帧头 + 类型 + 索引 + 长度 + 数据(即 CRC 之前的全部字节)做 16 位逐字节累加,结果按大端放在帧尾。这与 goodwe_comm.c 里 Modbus CRC 的小端排布不同,是两套独立协议。

响应帧头与请求帧头不同:inv_com_reply_new()(goodwe_upgrade.c:155)期望 0xAA 0x55 0x7F 0x80 0x02 0xA9——第 4、5 字节互换,第 6 字节 0x29 变 0xA9,即方向位翻转。校验与匹配流程为:长度 ≥ 2 → 读全部字节 → 取末两字节作为期望校验和(buf[len-2]<<8 | buf[len-1])→ 对前 len-2 字节重算 check_sum → 相等则比对 6 字节帧头 → 再比对 [6..7] 的类型字与调用方传入的 type → 全部通过才返回应答帧第 11 字节(buf[10]),否则返回 0。

4.2 顶层状态机 goodwe_upgrade_handle(内部升级 INV1+INV2)

goodwe_upgrade_handle()(goodwe_upgrade.c:674)由 goodwe_inverter_comm() 在 is_inverter_firm_download_finished()(= UPDT_INV1 && UPDT_INV2)为真时每轮调用:

当前状态迁移条件动作下一状态
GW_UPGRADE_NONEUPDT_INV1 && UPDT_INV2 均为真且 set_inv_wait_mode() == TRUEset_inv_feedin(ON, 0) 关闭馈电GW_UPGRADE_SLAVE
GW_UPGRADE_NONE上述条件不满足无保持 GW_UPGRADE_NONE
GW_UPGRADE_SLAVE由 upgrade_slave_handle() 内部决定执行从机分包发送见 4.3
GW_UPGRADE_MIDDLEset_inv_wait_mode() == TRUEset_inv_feedin(ON, 0)GW_UPGRADE_MASTER
GW_UPGRADE_MASTER由 upgrade_master_handle() 内部决定执行主机分包发送见 4.4
GW_UPGRADE_INFO_INIT无条件Set_Status_Is_Updated_From_Server(UPDT_INV1, FALSE) + (UPDT_INV2, FALSE)GW_UPGRADE_NONE

关键点:从机先升级、主机后升级。状态命名与 goodwe_comm.c 中的版本字段存在语义交叉——goodwe_comm.c:557 把 ARC 模块版本写进 u8_slave_ver,即本模块的「slave」在版本语义上对应 ARC 模块;而 UPGRADE_TYPE_ARC 只在新协议的 external 分支使用。因此可理解为:INV1 = 主机 DSP,INV2 = 从机(旧协议),INV3 = 外部 ARC 模块(新协议),三者在 s_u8_upgrade_state 中通过 SLAVE / MASTER / EXTERNAL 三个状态区分。

4.3 upgrade_slave_handle 的包粒度状态机

upgrade_slave_handle()(goodwe_upgrade.c:292)用 s_u32_slave_file_offset == 0 区分首包与后续包,数据源固定为 g_u8_fw_sdram2,总长度取自 get_length_file_updating(UPDT_INV2):

首包(offset == 0):发 UPGRADE_HEADER_LEN(32) 字节头,300ms 发送超时,等 1500ms 应答。

应答 0x06 (OK)         → file_send_cnt=0;offset += 32
应答 0x15 (RESTART)    → state 自赋值为 GW_UPGRADE_SLAVE;len=0;cnt=0;offset=0(重来一遍)
其它/超时              → file_send_cnt++;若 >30 → state = GW_UPGRADE_MASTER;len=0;cnt=0;offset=0

后续包(offset > 0):

若 UPDT_INV2 长度 ≤ 0 或 offset >= 总长度
    → state = GW_UPGRADE_MIDDLE;清零;OSTimeDlyHMSM(0,2,0,0) 延时 2 分钟等 ARM 重启(直接 return)
包长 = min(64, 总长度 - offset)
发送 g_u8_fw_sdram2 + offset,len 字节
应答 0x06 (OK)         → cnt=0;offset += 本包长
应答 0x01 (扇区写失败) → 若 offset > 4096+32:cnt=0;offset = offset + 本包长 - 4096(回退一整个扇区重传)
                        否则:cnt=0;len=0;offset=0(从头开始)
应答 0x15 (RESTART)    → state 自赋值为 GW_UPGRADE_SLAVE;清零偏移重来
其它/超时              → cnt++;若 >30 → state = GW_UPGRADE_MASTER;清零

「30 次连续失败后跳到主机升级」是一个降级容错设计:从机升级反复不成功时不再死等,直接推进到主机 DSP 流程,避免整个升级流程卡死。但这也意味着从机可能被留在未升级状态而流程仍算走完。

4.4 upgrade_master_handle 的两段式状态机

upgrade_master_handle()(goodwe_upgrade.c:399)比从机多一个前置阶段,由文件内 static s_u8_boot_code_flag / s_u8_boot_code_cnt 控制:

阶段一:进入 bootloader(boot_code_flag == FALSE)

inv_com_send("FlashDSP", 8, 300) → inv_com_reply(1500)
应答 0x06 → boot_code_cnt=0;boot_code_flag=TRUE(进入阶段二)
其它      → boot_code_flag 保持 FALSE;boot_code_cnt++
            若 ≥30 → boot_code_cnt=0;state = GW_UPGRADE_INFO_INIT(放弃升级、收尾清标志)

阶段二:分包发送(boot_code_flag == TRUE),数据源 g_u8_fw_sdram1,总长度 get_length_file_updating(UPDT_INV1),逻辑与从机几乎一致,差异有三处:

  1. 首包固定 32 字节(字面量 32,未用 UPGRADE_HEADER_LEN 宏,goodwe_upgrade.c:441);
  2. 首包失败只做 cnt++,超过 30 才回 GW_UPGRADE_INFO_INIT 并彻底复位(含 boot_code_flag = FALSE、boot_code_cnt = 0);
  3. 完成判定分支(goodwe_upgrade.c:465)同样 OSTimeDlyHMSM(0,2,0,0) 延时 2 分钟等 ARM 重启;
  4. 收到 RESTART_FLASH_MODE 时状态设回 GW_UPGRADE_MASTER 自身(goodwe_upgrade.c:509)——这是一个空迁移,实际效果是把偏移与计数清零后原地重来,等价于「重新发 FlashDSP」需要 boot_code_flag 仍为 TRUE,所以实际是从头重传固件而不再发 boot 命令。

4.5 upgrade_external_handle 的新协议状态机

upgrade_external_handle()(goodwe_upgrade.c:540)数据源 g_u8_fw_sdram1,长度 get_length_file_updating(UPDT_INV3),引入 s_u16_index 作为包序号:

首包(offset == 0):

inv_com_send_new(g_u8_fw_sdram1, UPGRADE_TYPE_ARC, 32, index=0, 1000ms)
OSTimeDlyHMSM(0, 0, 3, 0)      ← 阻塞 3 秒,注释说"等待 ARM 响应升级"
inv_com_reply_new(UPGRADE_TYPE_ARC, 400)
应答 0x06 → cnt=0;index++;offset += 32
其它      → state = GW_UPGRADE_EXTERNAL(自赋值,无实际迁移);cnt++
若 cnt > 3 → state = GW_UPGRADE_INFO_INIT;index=0;len=0;cnt=0;offset=0

后续包:包长 = min(512, 总长度 - offset),发送后同样等 400ms 应答;0x06 则 index++、offset += 包长;其它则 cnt++,cnt > 3 时整体复位到 GW_UPGRADE_INFO_INIT。完成判定时额外调用 set_inv_wait_mode() 并延时 2 分钟「等 ARM 传输至 AFCI 模块」。

与旧协议的关键差异:外部升级的重试阈值是 3(而非 30),应答窗口是 400ms(而非 1500ms),包长 512(而非 64),并维护递增包索引。3 次失败即整体放弃,容错明显更紧。

4.6 顶层状态机 goodwe_upgrade_external_handle

goodwe_upgrade_external_handle()(goodwe_upgrade.c:633)由 goodwe_inverter_comm() 在 Get_Status_Is_Updated_From_Server(UPDT_INV3) 为真时每轮调用:

当前状态迁移条件动作下一状态
GW_UPGRADE_NONEUPDT_INV3 为真且 set_inv_wait_mode() == TRUEset_inv_feedin(ON, 0)GW_UPGRADE_EXTERNAL
GW_UPGRADE_NONE不满足无保持
GW_UPGRADE_EXTERNAL由 upgrade_external_handle() 决定分包发送见 4.5
GW_UPGRADE_INFO_INIT无条件Set_Status_Is_Updated_From_Server(UPDT_INV3, FALSE)GW_UPGRADE_NONE

4.7 与 uCOS-II 任务和主通信的配合

4.8 状态标志函数的位区间设计

三个 flag 函数用区间判断代替精确枚举比较:

函数表达式真值状态集合
goodwe_read_upgrade_flag_masterstate != NONE && state <= MASTER(2){MIDDLE(1), MASTER(2)}
goodwe_read_upgrade_flag_slavestate != NONE && state >= SLAVE(3){SLAVE(3), EXTERNAL(4), INFO_INIT(5)}
goodwe_read_upgrade_flag_externalstate != NONE && state <= EXTERNAL(4){MIDDLE(1), MASTER(2), SLAVE(3), EXTERNAL(4)}

区间设计使 master 与 slave 恰好互补且互斥(goodwe_comm.c:2460-2463 的 if/else if 才能正确工作),但 external 的判据包含了 master/slave 状态——inverter.c:3980 用它给服务器报「外部升级中」,在主机/从机升级期间会被误报为外部升级。

5. 对外接口

本文件导出(goodwe_upgrade.h 声明)

符号说明调用者
goodwe_upgrade_handle内部升级(INV1+INV2)顶层状态机goodwe_comm.c:2465
goodwe_upgrade_external_handle外部升级(INV3)顶层状态机goodwe_comm.c:2479
goodwe_read_upgrade_percent_master主机进度百分比inverter.c:3859
goodwe_read_upgrade_flag_master主机升级中标志inverter.c:3824、goodwe_comm.c:2460
goodwe_read_upgrade_flag_slave从机升级中标志inverter.c:3894、goodwe_comm.c:2462
goodwe_read_upgrade_percent_slave从机进度百分比inverter.c:3929
goodwe_read_upgrade_flag_external外部升级中标志inverter.c:3980
goodwe_read_upgrade_percent_external外部进度百分比inverter.c:4007

本文件内部符号(未加 static 但工程内无其它引用)

本文件调用的外部函数/变量

6. 依赖关系

依赖(include 列表见 goodwe_upgrade.c:13-19):goodwe_upgrade.h、upd_download.h、inv_download.h、server.h、sdram.h、usart4.h、goodwe_comm.h;其中 server.h 提供 SDRAM 固件镜像数组与 ENUpdateType 枚举,upd_download.h 提供更新标志与文件长度查询。

被依赖:

7. 备注与风险点

  1. inv_com_send_new 存在固定栈缓冲越界(goodwe_upgrade.c:120):u8 l_u8_buf[520],但最大包长 UPGRADE_PACK_LEN_NEW = 512,写入 12 + 512 + 2 = 526 字节,越界 6 字节。当前 len 参数始终 ≤ 512,因此实际会写 l_u8_buf[512] 与 [513](校验和),刚好越界 6 字节但仍在函数栈帧附近的填充区内,不会立刻崩溃——属于「侥幸可用」的缺陷,一旦编译器改变栈布局或包长被上调即会踩坏返回地址。
  2. inv_com_reply_new 固定 20 字节收缓冲(goodwe_upgrade.c:162):u8 l_u8_recv_buf[20],但搬入长度是 uart4_get_data_len() 的任意值,未做上界判断。应答帧若超过 20 字节(例如逆变器把请求回显)会直接越界写栈。这是本文件最高风险的缺陷。
  3. inv_com_reply_new 的首次调用不做延时(goodwe_upgrade.c:164-170):static u32 recv_start_tick = 0,条件是 abs(recv_start_tick - OSTimeGet()) < timeout。上电初期 OSTimeGet() 通常已远大于 400,首次调用不会进入 400ms 延时分支。这段延时的本意是「等上一包的大响应被收完」,首次调用恰好跳过反而是正确的,但代码意图与实现不一致。
  4. inv_com_reply_new 中无条件 printf(goodwe_upgrade.c:204):printf("inv return %d \r\n", ...) 没有被 GOODWE_INV_UPGRADE_DEBUG 包住,即使调试宏为 0 也会在每包应答时输出串口日志,在高波特率升级中会拖慢时序。
  5. UPGRADE_PACK_LEN_NEW 在头文件中重复定义(goodwe_upgrade.h:23 与 :30):两处内容完全相同((512)),属冗余;若将来只改一处会造成「哪个生效取决于预处理顺序」的隐患。
  6. UPGRADE_TYPE_DSP、UPGRADE_TYPE_ARM、UPGRADE_HEADER_LEN_NEW 定义了但从未使用(已用全仓 grep 确认):新协议的类型字机制只为 ARC 实施,DSP/ARM 走的是旧协议,两者并存说明这是协议过渡期的遗留。
  7. GW_UPGRADE_INFO_INIT 的语义是「清云端标志」而非「信息初始化」:命名(INFO_INIT)与实际动作(Set_Status_Is_Updated_From_Server(..., FALSE))不符,容易误读为状态机准备阶段。
  8. RESTART_FLASH_MODE 分支存在自赋值空迁移:upgrade_slave_handle(goodwe_upgrade.c:371)、upgrade_master_handle(:509)、upgrade_external_handle(:562、:608)都在收到 0x15 时把状态设回自身再清偏移,形成「原地重传」。这段逻辑反复出现且没有注释说明意图,是拷贝粘贴的痕迹。且对 upgrade_master_handle 而言,boot_code_flag 仍为 TRUE,重传时不会再发 FlashDSP,若逆变器重启后已退出 bootloader,后续包会全部失败。
  9. 「连续失败 30 次即跳到下一阶段」会掩盖失败(goodwe_upgrade.c:318-324、:379-385、:452-460、:517-525):从机失败 30 次后直接进入主机升级,主机失败 30 次后直接进入 INFO_INIT 收尾。两个阶段都可能完全没写成功,但云端标志被清除、流程算作结束,服务器侧看不到失败。缺少「上报失败」的通道。
  10. upgrade_external_handle 首包失败分支的逻辑矛盾(goodwe_upgrade.c:561-573):else 分支先 state = GW_UPGRADE_EXTERNAL(自赋值),再 cnt++;随后判 cnt > 3。由于首次失败不会重置 offset(仍为 0),下一次仍走首包分支,逻辑上勉强自洽,但状态自赋值是明显的笔误(原意可能是 GW_UPGRADE_NONE 之类)。
  11. s_u16_index 溢出:UPGRADE_TYPE_ARC 帧的包索引是 16 位。以 512 字节/包、SDRAM 固件区 SDRAM_3M 计,最大约 6144 包,远小于 65536,暂不会溢出;但若固件区被扩展需重新评估。
  12. 完成后的 2 分钟阻塞(goodwe_upgrade.c:335、:473、:585):OSTimeDlyHMSM(0, 2, 0, 0) 期间 task_inverter 不响应任何逻辑,包括服务器下发的停机/复位命令。这是把「等 ARM 重启」用最粗暴的阻塞实现,若逆变器实际恢复更快会白白损失 2 分钟可用性,反之若更慢则后续查询会拿到错误数据。
  13. get_length_file_updating() 被反复调用且未判 0:三个 percent_* 函数(goodwe_upgrade.c:239、:261、:283)直接做 offset * 100 / get_length_file_updating(...),若文件长度尚未就绪返回 0,会发生除零。调用者 inverter.c:3859 等没有做保护。
  14. check_sum 的累加和很弱:16 位逐字节累加,无法检出字节换位或成对的互补错误。作为 bootloader 协议属可接受,但若 CRC 可用建议替换;同时注意新旧协议中校验和的字节序不同(旧协议靠单字节应答无校验、新协议大端),跨协议复用代码时极易搞反。
  15. 无 static 声明导致符号外泄:check_sum、inv_com_send、inv_com_reply、inv_com_send_new、inv_com_reply_new 都是外部链接且名字非常通用,与工程里其它厂商升级模块(如 midware/afci/afci_upgrade.c、midware/bms/alpha/battery_upgrade.c、midware/evc/evc_upgrade.c)的同类函数存在潜在重名风险,目前靠实际命名差异侥幸不冲突。
  16. 注释中的函数名与实际不符:goodwe_upgrade.c:27、:47、:113、:150 的 @Name 都写成 inv_com_send,与实际函数名(check_sum、inv_com_send、inv_com_send_new、inv_com_reply_new)不一致;:394 写 goodwe_master_handle 而函数名是 upgrade_master_handle;:535 写 upgrade_external_handle 与函数名一致但紧邻的 :628 注释写 goodwe_upgrade_external_handle。文档与代码脱节。