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

afci_upgrade.c

源文件路径:midware/afci/afci_upgrade.c 所属模块:midware / afci(电弧故障断路器 AFCI OTA 升级) 代码行数:519

1. 文件职责

本文件实现 EMS 把已下载到 SDRAM 的 AFCI 固件透传给 AFCI 板完成 OTA 的完整流程:先写寄存器让 AFCI 进入升级模式,再用自定义帧协议做握手 → 逐包传 128 字节固件 → 结束传输,每一步都要等 AFCI 应答。它消费的是 net/server.c 从云端下载并 MD5 校验通过后放在 g_u8_fw_sdram1 里的固件镜像,因此是升级链路的最后一段(EMS → AFCI 板)。

驱动方是逆变器通信任务:midware/inverter/alphaess/alphaess_comm_usart.c:5444 判定 is_afci_firm_download_finished() 为真后每轮调用 afci_upgrade_handle()。

该函数每被调用一次只推进一步状态机,靠 5 个状态自持推进;中途失败由 3 次重试 + 5 秒软重启 + 最多 3 轮的方式兜底,最终放弃时清掉服务器侧的升级标志以停止调用。进度与已完成偏移通过 get_percent_upgrade_afci() / get_upgrade_file_offset_afci() 暴露给 HMI 与云端。

2. 关键数据结构与宏

名称类型说明
AFCI_UPGRADE_ADDRESS宏afci_upgrade.h:16,值 0xF8。OTA 专用从站地址,与常规地址 AFCI_SLAVR_ADDRESS(0x01) 区分
EMS_AFCI_SHAKE_HANDS宏afci_upgrade.h:18,值 0x01。握手帧命令字
EMS_AFCI_SEND_UPGRADE_TRANSMIT宏afci_upgrade.h:19,值 0x02。数据帧命令字
EMS_AFCI_UPGRADE_FINISH宏afci_upgrade.h:20,值 0x03。结束帧命令字
EMS_AFCI_EXIT_OTA宏afci_upgrade.h:21,值 0x04。退出 OTA(本文件未使用,无组帧无调用)
EMS_AFCI_RESTART_DEVICE宏afci_upgrade.h:22,值 0x05。重启设备(本文件未使用;失败路径改用常规寄存器软重启 set_afci_soft_reboot())
AFCI_UPGRADE_DATA_MAX_LEN宏afci_upgrade.h:24,值 128。单包固件数据字节数,同时作为握手/结束帧里上报的“包大小”
AFCI_ADDR_REG_VERSION_UPDATA_ENABLE / AFCI_NUM_VERSION_UPDATA_ENABLE宏afci_upgrade.h:26-27。寄存器 0x0023,长度 1,写 1 进入升级模式
TIME_ELAPSED(start, ms)宏afci_upgrade.h:29。((OSTimeGet() - (start)) > (ms)),无符号差值比较,对 OSTimeGet() 49.7 天回绕安全(与同文件另处 abs() 写法不同)
AFCI_UPGRADE_SATE_E枚举afci_upgrade.h:33-40(枚举名拼写 SATE 为 STATE 笔误)。5 态:AFCI_UPGRADE_START(0) / AFCI_UPGRADE_HANDSHAKE(1) / AFCI_SEND_UPGRADE_FILE(2) / AFCI_UPGRADE_END(3) / AFCI_UPGRADE_FAULT(4)
AFCI_UPGRADE_TABLE_T结构体afci_upgrade.h:44-51,#pragma pack(1) 紧凑打包。字段:u8* u8_upgrade_file_buf(固件源指针)、u16 u16_send_file_num(总包数)、u8 u8_upgrade_state(状态机当前态)、u32 u32_upgrade_file_len(文件总长)、u32 u32_addr_flash_offset(当前包在文件中的偏移)
s_afci_upgrade_tab全局变量afci_upgrade.c:26。AFCI_UPGRADE_TABLE_T 的唯一实例。非 static 且未在 afci_upgrade.h 中 extern,是"半隐藏"的全局符号
s_afci_upgrade_recv_buf[11]静态数组:28。接收首次 0x06 写寄存器应答的缓冲,仅 11 字节
s_afci_upgrade_send_buf[150]静态数组:29。发送缓冲,最大帧(数据帧)= 6 + 128 + 2 = 136 字节,容量足够
ALPHAESS_UPGRADE_DEBUG宏定义于 alphaess_upgrade.h:16(值 0),因本文件 :14 include 了 alphaess_comm_usart.h(其 :14 又 include alphaess_upgrade.h)而可见;当前全链路调试打印关闭

3. 函数清单

函数名作用域说明
send_ems_send_afci_start_cmdstatic:47。向寄存器 0x0023 写 1 让 AFCI 进入 OTA 模式,返回是否收到正确应答
send_afci_upgrade_handshakestatic:85。组并发送握手帧 [F8][01][包数][0x0080][CRC]
alphaess_afci_com_replystatic:113。在 timeout 毫秒内轮询 alphaess_recv_data(),判定 AFCI 升级应答是否有效
send_ems_send_afci_upgrade_datastatic:173。组并发送数据帧 [F8][02][序号][长度][数据][CRC],返回底层写是否成功
send_ems_send_afci_upgrade_endstatic:214。组并发送结束帧 [F8][03][包数][0x0080][CRC]
afci_upgrade_handle全局:241。OTA 主状态机(5 态),单次调用推进一步
get_percent_upgrade_afci全局:490。返回 已传偏移 * 100 / 文件总长,供 HMI 显示进度
get_upgrade_file_offset_afci全局:503。返回当前已传偏移,供云端升级进度上报
is_upgrade_afci全局:515。返回服务器侧 UPDT_AFCI 已完成标志,即“是否正在向 AFCI 传固件”

共 9 个函数:4 个全局 + 5 个 static。无函数指针、无回调。文件对 5 个 static 函数在 :34-38 做了前向声明。

4. 核心逻辑解析

4.1 通信协议:AFCI OTA 自定义帧(从站地址 0xF8)

全部帧走 USART2,通过 alphaess_write_data()(发送)/ alphaess_recv_data()(接收)与逆变器、AFCI 常规通信共用总线。帧结构统一为「地址 + 命令字 + 参数 + CRC16-Modbus(低字节在前)」:

握手帧 / 结束帧(4 个参数,共 8 字节)
[0xF8][cmd(0x01或0x03)][总包数_H][总包数_L][0x00][0x80][CRC_L][CRC_H]
                                        └── AFCI_UPGRADE_DATA_MAX_LEN = 128

数据帧(共 6+n+2 字节,n ≤ 128)
[0xF8][0x02][序号_H][序号_L][本包字节数_H][本包字节数_L][数据 n 字节][CRC_L][CRC_H]
              ↑ 从 1 开始计数              ↑ 最后一包为余数,其余为 128

升级模式使能帧(标准 Modbus 0x06,共 8 字节)
[0x01][0x06][0x00][0x23][0x00][0x01][CRC_L][CRC_H]

应答(由 alphaess_recv_data 判定的有效条件)
[0xF8][cmd ∈ {0x01,0x02,0x03}][b2][b3][b4][b5][CRC_L][CRC_H]
                              └── 4 字节参数不得全为 0

关键点:

4.2 应答判定与超时(alphaess_afci_com_reply,:113)

l_u32_recv_start_tick = OSTimeGet();
while(1) {
    if (abs(l_u32_recv_start_tick - OSTimeGet()) > timeout) return FALSE;  // ① 超时先判
    alphaess_recv_data(buf, &l_u16_len);                                   // ② 阻塞收一帧
    if (l_u16_len) {
        g_t_alphaess_comm_info.u32_ems_inv_comm_tick = OSTimeGet();        // 喂逆变器通信心跳
        if (buf[0] == AFCI_UPGRADE_ADDRESS) {
            if (buf[1] ∈ {0x01,0x02,0x03} && buf[2..5] 不全为 0) return TRUE;
            else return FALSE;                                             // ③ 地址对但命令字错 → 立即失败
        } else return FALSE;                                               // ④ 地址错 → 立即失败
    }
}

4.3 主状态机(afci_upgrade_handle,:241)

状态存放在 s_afci_upgrade_tab.u8_upgrade_state,跨调用保持;其余进度存在函数内 static:s_u8_rev_err_cnt(连续应答失败计数)、s_u16_afci_file_send_cnt(已发送包数)、s_u8_upgrade_fault_cnt(整轮失败轮数)、s_u32_fault_tick(故障等待起点)。

AFCI_UPGRADE_START  ── 写寄存器 0x0023 = 1,进 OTA 模式
   ├ 成功 → u8_upgrade_file_buf = g_u8_fw_sdram1
   │        u32_upgrade_file_len = get_length_file_updating(UPDT_AFCI)
   │        u16_send_file_num = ceil(len / 128)
   │        → AFCI_UPGRADE_HANDSHAKE
   └ 失败 → s_u8_rev_err_cnt++;>3 → err_reason = SENDBOOTERR;→ AFCI_UPGRADE_FAULT

AFCI_UPGRADE_HANDSHAKE ── 发握手帧,等 5000 ms
   ├ 收到 → AFCI_SEND_UPGRADE_FILE,s_u8_rev_err_cnt = 0
   └ 失败 → cnt++;>3 → err_reason = RECVBOOTERR;→ AFCI_UPGRADE_FAULT

AFCI_SEND_UPGRADE_FILE ── 分两种包
   ├ 非最后一包 (send_cnt < total-1):
   │    发 (send_cnt+1, offset, 128) → 等 500 ms
   │    ├ 成功 → send_cnt++;offset = send_cnt * 128;cnt = 0       // 留在本态
   │    └ 失败 → cnt++;>3 → err_reason = SENDMASTERERR;→ FAULT
   └ 最后一包 (send_cnt == total-1):
        发 (send_cnt+1, offset, file_len - offset) → 等 500 ms
        ├ 成功 → send_cnt++;offset = send_cnt * 128;→ AFCI_UPGRADE_END;cnt = 0
        └ 失败 → cnt++;>3 → err_reason = RECVMASTERLASTERR;→ FAULT

AFCI_UPGRADE_END ── 发结束帧,等 500 ms
   ├ 成功 → memset(s_afci_upgrade_tab);state = AFCI_UPGRADE_START
   │        send_cnt = 0;cnt = 0
   │        Set_Status_Is_Updated_From_Server(UPDT_AFCI, FALSE)   // 停止本轮
   │        set_flag_force_update(UPDT_AFCI, FALSE)
   │        u8_upgrate_flag = TRUE;u32_afci_upgrade_tick = OSTimeGet()
   │        t_debug_log.u8_upgrade_status = 2
   └ 失败 → cnt++;>3 → err_reason = RECVENDERR;→ FAULT

AFCI_UPGRADE_FAULT ── 恢复流程
   首次进入 (s_u32_fault_tick == 0):set_afci_soft_reboot();fault_tick = OSTimeGet()
   等 5 s (TIME_ELAPSED(fault_tick, 5000)):
     s_u8_upgrade_fault_cnt++
     ├ > 2 → 放弃:memset(tab);清 UPDT_AFCI 的 server/force 标志
     │        send_cnt = 0;cnt = 0;fault_cnt = 0;fault_tick = 0
     └ 否则 → 重来:send_cnt = 0;cnt = 0;fault_tick = 0;state = AFCI_UPGRADE_START
     两种情况都执行:t_debug_log.u8_err_tick++;t_debug_log.u8_upgrade_status = TRUE

偏移量与包长的推导(以 L = 文件总长、N = ceil(L/128) 计):

事件s_u16_afci_file_send_cntu32_addr_flash_offset
进入 SEND 态00
第 1 包发送成功1128
第 k 包发送成功kk × 128
第 N 包(末包)发送成功NN × 128

因此第 k 包发送时用的 offset = (k-1) × 128,与“先发后加”的顺序自洽;末包 byte_num = L - (N-1)×128 = L mod 128(整除时为 128),正是余数包长。

停机语义:升级流程只在 is_afci_firm_download_finished()(= Get_Status_Is_Updated_From_Server(UPDT_AFCI),net/afci_download.c:59)为 TRUE 时被调用(alphaess_comm_usart.c:5444)。无论成功(END 分支)还是最终放弃(FAULT 分支 > 2),都会把这个标志清掉,从而让调用方停止再进入本函数,这是本模块唯一的"退出"机制。成功时额外置 u8_upgrate_flag,触发 alphaess_comm_usart.c:5074-5080 在 50 s 后重新读一次 AFCI 软版本,确认新固件生效。

4.4 错误码与重试层次

三层重试,逐层放大:

层次计数器阈值效果
帧级s_u8_rev_err_cnt> 3退出当前态进入 AFCI_UPGRADE_FAULT,并写 g_t_inv_info.t_debug_log.u32_err_reason
轮级s_u8_upgrade_fault_cnt> 2整轮放弃,清服务器标志,不再重试
时间级s_u32_fault_tick + TIME_ELAPSED(..., 5000)5 s每轮失败后软重启 AFCI 并等 5 s 再重来

失败原因写入 g_t_inv_info.t_debug_log.u32_err_reason,取值来自 INVERTER_UPGRADE_ERR_E(midware/inverter/inverter.h:380-401):SENDBOOTERR(进 OTA 失败)、RECVBOOTERR(握手无应答)、SENDMASTERERR(中间包失败)、RECVMASTERLASTERR(末包失败)、RECVENDERR(结束帧失败)。注意这些枚举是储能机(master)升级复用的错误码,AFCI 只是借用。

u8_upgrade_status 被置 TRUE(1) 或 2(成功时),供 alphaess_comm_usart.c:5465 判定后调 set_info_status(INV_UPGRADE_LOG) 让 HMI 显示升级日志。

4.5 与 uCOS-II 的配合

5. 对外接口

本文件导出(供谁调用)

导出名调用方
afci_upgrade_handlemidware/inverter/alphaess/alphaess_comm_usart.c:5447(is_afci_firm_download_finished() 为真时每轮调用一次)
is_upgrade_afcihmi/ui/ui_main.c:907(决定主界面是否显示 AFCI + Upgrade: 进度行)
get_percent_upgrade_afcihmi/ui/ui_main.c:913(Lcd_Draw_Number(3, 11, ...) 显示 3 位进度)
get_upgrade_file_offset_afcinet/ems.c:6725(UPDT_AFCI 分支刷新 g_t_upgrade_run_info.u32_upgrade_file_offset,上报云端升级进度)
s_afci_upgrade_tab名义上是全局符号,但头文件未 extern,无外部引用点

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

名称来源
ems_send_ctrl_data_to_afci、alphaess_afci_recv_data、set_afci_soft_rebootmidware/afci/afci_comm.c(:77、:119、:623)
alphaess_write_data / alphaess_recv_data / ALPHAESS_DATA_RCV_TIMEOUT(500) / ALPHAESS_FUNC_WRITE_SIGNAL_HOLDING_REGISTER(0x06)midware/inverter/alphaess/alphaess_comm_usart.{c,h}
g_t_alphaess_comm_info.u32_ems_inv_comm_tickalphaess_comm_usart.h:715
g_t_inv_info.t_afci_info / t_debug_logmidware/inverter/inverter.h:1914 / :1912
g_u8_fw_sdram1net/server.c:69(定义,定址 SDRAM_FIRM1_START_ADDR,3 MB);声明于 server.h:258
get_length_file_updating / Set_Status_Is_Updated_From_Server / Get_Status_Is_Updated_From_Server / set_flag_force_updatenet/upd_download.c(声明 upd_download.h:37/39/40/25)
UPDT_AFCInet/server.h:195(ENUpdateType 枚举项,也是 UPDT_MAX 前最后一项)
GetCrcCode_Modbuscommon/crc.h
OSTimeGet / OSTimeDlyHMSM(经 alphaess_recv_data)uCOS-II
memset / abs / printfC 标准库

6. 依赖关系

依赖(本文件 include,:13-21):inverter.h、alphaess_comm_usart.h、afci_comm.h、afci_upgrade.h、sdram.h、server.h、usart2.h、upd_download.h、crc.h。其中 usart2.h 在本文件内无直接符号引用(收发已封装在 alphaess_* 里),属冗余 include。

被依赖(include afci_upgrade.h 的文件):

7. 备注与风险点

  1. s_afci_upgrade_recv_buf 只有 11 字节,接收侧无长度上限检查(越界风险)::28 定义为 u8[11],:56 交给 alphaess_afci_recv_data(),而该函数按 usart2_get_data_len() 的实际收到长度全量拷贝(afci_comm.c:143-150)。正常应答是 8 字节(0x06 回显),但一旦 AFCI 返回异常长帧或总线噪声凑成长帧,就会直接溢出紧随其后的 s_afci_upgrade_send_buf(.bss 相邻变量),破坏发送缓冲。这是本文件最值得优先修的问题。
  2. s_u8_upgrade_fault_cnt 在成功路径上不复位(跨升级轮次泄漏):该计数只在「整轮放弃」分支清零(:456)。若某次升级中发生过 1~2 轮失败但最终成功(AFCI_UPGRADE_END 成功分支 :396-410 只复位 s_u16_afci_file_send_cnt 与 s_u8_rev_err_cnt),计数器会残留;下一次升级(可能是数月后由云端再次触发)只要再失败一次就可能提前触发「>2 放弃」,把原本还有重试机会的升级直接判死。同理 s_u32_fault_tick 也只在特定分支复位。
  3. get_percent_upgrade_afci() 的进度可能显示 > 100%:分子 u32_addr_flash_offset 在每包成功后按 send_cnt * 128 计算(:337、:366),而末包实际长度是余数。因此当 N × 128 > L(L 不是 128 整数倍时必然成立),最后一包后返回值为 N×128×100/L,例如 L = 100(N = 1)时返回 128。ui_main.c:913 用 3 位数字绘制,会显示 128。虽然此刻状态已切到 AFCI_UPGRADE_END、is_upgrade_afci() 很快转 FALSE,但显示上确实存在越界数值。
  4. get_percent_upgrade_afci() 存在除零面:除数是 get_length_file_updating(UPDT_AFCI)(:492)。调用方 ui_main.c:907 先判 is_upgrade_afci() 再算百分比,而该标志为 TRUE 意味着固件已下载并 MD5 校验通过,长度通常非 0,所以现实风险低于同类 afci_download.c 的版本;但函数自身无守卫,属"依赖调用顺序"的隐式契约。
  5. alphaess_afci_com_reply() 用 abs() 比较无符号差值::123 abs(l_u32_recv_start_tick - OSTimeGet()) > timeout。参数是 u32,abs 形参为 int,差值超过 INT_MAX(约 24.8 天)时会得到错误结果。本文件已提供更正确的 TIME_ELAPSED() 宏(:438 就在用,其无符号差值写法对回绕安全),两处风格不一致。
  6. send_ems_send_afci_upgrade_data() 的返回值被丢弃:该函数 :204 特意 return alphaess_write_data(...)(返回 1/0 表示串口是否发完),但调用方 :332、:361 完全忽略返回值,只依据 alphaess_afci_com_reply() 判定成功。串口发送超时(对方不收)会被静默忽略,直到应答等待超时才暴露,浪费一轮 500 ms。
  7. buf[16] 与 alphaess_recv_data() 的契约不匹配:alphaess_afci_com_reply 的 :116 u8 buf[16],而 alphaess_recv_data() 会写入实际收到长度的字节数(alphaess_comm_usart.c:309-316)。升级应答正常为 8 字节,安全;但与风险点 1 同源——接收函数不带上限,安全性完全依赖对端行为。
  8. s_afci_upgrade_tab 是"半隐藏全局"::26 定义时没有 static,却也没在 afci_upgrade.h 中 extern。既污染全局命名空间,又无法被外部正常引用;建议改为 static。
  9. 枚举名与拼写问题:AFCI_UPGRADE_SATE_E(应为 STATE)拼写错误;afci_upgrade.h:26 的宏 AFCI_ADDR_REG_VERSION_UPDATA_ENABLE 中 UPDATA 应为 UPDATE(与 afci_comm.h 的 AFCI_LOG_UPDATA_STATE_E 一致地沿用了错拼,说明是团队习惯性笔误);afci_upgrade.c:47 的函数头注释写 send_ems_send_start_cmd,与实际名 send_ems_send_afci_start_cmd 不符。
  10. 死代码:注释掉的旧实现与未使用的宏::262-270 保留了三段注释掉的 u32_upgrade_file_len / u16_send_file_num 计算(含一个未使用的 l_u32_file_len,:244 也被注释),是历史重构残留;EMS_AFCI_EXIT_OTA(0x04) 与 EMS_AFCI_RESTART_DEVICE(0x05) 两个命令字在本文件及全工程均无使用者,却仍在头文件里占据协议号。
  11. AFCI_UPGRADE_END 成功分支不复位 s_u32_fault_tick:若只在 END 分支清理而不进 FAULT,s_u32_fault_tick 通常为 0(FAULT 的重来分支会置 0),因此暂未观察到问题;但与风险点 2 属同一类清理不完整。
  12. (u32)(offset * 100) 的溢出面::492 (u32_addr_flash_offset * 100) / file_len,u32 乘法。3 MB 固件 × 100 = 3.1e8,未溢出;SDRAM 上限 3 MB(SDRAM_3M)也保证了这一点。若未来放宽到更大镜像需注意。
  13. 失败时调用的是常规寄存器软重启而非协议里的 EMS_AFCI_RESTART_DEVICE::432 用的是 set_afci_soft_reboot()(走 afci_comm.c:623 的 0x06 写寄存器 0x0025),而 afci_upgrade.h 定义的 EMS_AFCI_RESTART_DEVICE(0x05) 在 OTA 通道上未被使用。两者是否等价取决于 AFCI 固件实现,代码层面无法确认。