afci_upgrade.c
alphaess_afci_com_reply,:113)4.3 主状态机(afci_upgrade_handle,:241)4.4 错误码与重试层次4.5 与 uCOS-II 的配合5. 对外接口6. 依赖关系7. 备注与风险点源文件路径:
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_cmd | static | :47。向寄存器 0x0023 写 1 让 AFCI 进入 OTA 模式,返回是否收到正确应答 |
send_afci_upgrade_handshake | static | :85。组并发送握手帧 [F8][01][包数][0x0080][CRC] |
alphaess_afci_com_reply | static | :113。在 timeout 毫秒内轮询 alphaess_recv_data(),判定 AFCI 升级应答是否有效 |
send_ems_send_afci_upgrade_data | static | :173。组并发送数据帧 [F8][02][序号][长度][数据][CRC],返回底层写是否成功 |
send_ems_send_afci_upgrade_end | static | :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
关键点:
- 序号从 1 开始(
:332传的是s_u16_afci_file_send_cnt + 1),与常规日志帧“从 1 开始”的注释一致。 - 握手/结束帧把
AFCI_UPGRADE_DATA_MAX_LEN当作参数上报(:94-95、:223-224),即告诉 AFCI “包大小为 128”,而不是本包实际长度。结束帧复用同一字段,语义上略显草率。 - CRC 由
GetCrcCode_Modbus(...)(common/crc.c)计算,握手与数据帧都覆盖了l_u8_send_buf_len全部已写入字节(不含 CRC 自身),与常规 AFCI 帧一致。
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; // ④ 地址错 → 立即失败
}
}
- 接收侧
alphaess_recv_data()(alphaess_comm_usart.c:285)内部先OSSemPend(g_t_uart2_rcv_sem, 500),因此单次调用最长阻塞 500 ms;:403-426分支专门处理首字节为AFCI_SLAVR_ADDRESS(0x01)或AFCI_UPGRADE_ADDRESS(0xF8)的帧,用末两字节做 CRC 校验,校验不过把*len置 0。 - 于是实际尝试次数由
timeout决定:只有“收到非法帧”会立即返回 FALSE,而“完全没数据”(*len == 0)会继续循环。两次尝试之间的最小间隔 ≈ 500 ms,所以:- 数据包每包用
timeout = 500(:333、:362)→ 约只尝试 1 次(第一次alphaess_recv_data阻塞 500 ms,回来再判超时即成立); - 握手/结束用
timeout = 5000(:301、:395)→ 约 10 次尝试; - 最坏总耗时 =
timeout + 500 ms。
- 数据包每包用
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_cnt | u32_addr_flash_offset |
|---|---|---|
| 进入 SEND 态 | 0 | 0 |
| 第 1 包发送成功 | 1 | 128 |
| 第 k 包发送成功 | k | k × 128 |
| 第 N 包(末包)发送成功 | N | N × 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 的配合
- 本文件不创建任务/信号量/邮箱,仅通过
alphaess_recv_data()→usart2_is_recv_async_data()→OSSemPend(g_t_uart2_rcv_sem, 500)消费 USART2 接收信号量。 - 函数是阻塞式的:一次
afci_upgrade_handle()调用内部可包含 1 次发送 + 1 次最长timeout + 500 ms的等待,最坏(握手 5000 ms)约 5.5 s 霸占逆变器通信任务。期间的逆变器常规轮询被推迟,但:5440/:5446在调用前都会刷新u32_ems_inv_comm_tick,所以不会误报逆变器通信丢失。 - 每收到任一有效应答都会刷新
u32_ems_inv_comm_tick(:138),这是升级期间维持"通信正常"判定的关键。 - 调用频率:由逆变器通信任务的主循环决定(
inverter.c:1116),无固定周期;每轮只推进一步,因此整个升级耗时 ≈ 包数 ×(帧耗时 + 500 ms 等待)。以 100 KB 固件为例,N = 800包,仅等待时间就 ≥ 400 s。
5. 对外接口
本文件导出(供谁调用)
| 导出名 | 调用方 |
|---|---|
afci_upgrade_handle | midware/inverter/alphaess/alphaess_comm_usart.c:5447(is_afci_firm_download_finished() 为真时每轮调用一次) |
is_upgrade_afci | hmi/ui/ui_main.c:907(决定主界面是否显示 AFCI + Upgrade: 进度行) |
get_percent_upgrade_afci | hmi/ui/ui_main.c:913(Lcd_Draw_Number(3, 11, ...) 显示 3 位进度) |
get_upgrade_file_offset_afci | net/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_reboot | midware/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_tick | alphaess_comm_usart.h:715 |
g_t_inv_info.t_afci_info / t_debug_log | midware/inverter/inverter.h:1914 / :1912 |
g_u8_fw_sdram1 | net/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_update | net/upd_download.c(声明 upd_download.h:37/39/40/25) |
UPDT_AFCI | net/server.h:195(ENUpdateType 枚举项,也是 UPDT_MAX 前最后一项) |
GetCrcCode_Modbus | common/crc.h |
OSTimeGet / OSTimeDlyHMSM(经 alphaess_recv_data) | uCOS-II |
memset / abs / printf | C 标准库 |
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 的文件):
midware/afci/afci_upgrade.c:16midware/inverter/alphaess/alphaess_comm_usart.c:32net/ems.c:58hmi/ui/ui_main.c:33
7. 备注与风险点
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相邻变量),破坏发送缓冲。这是本文件最值得优先修的问题。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也只在特定分支复位。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,但显示上确实存在越界数值。get_percent_upgrade_afci()存在除零面:除数是get_length_file_updating(UPDT_AFCI)(:492)。调用方ui_main.c:907先判is_upgrade_afci()再算百分比,而该标志为 TRUE 意味着固件已下载并 MD5 校验通过,长度通常非 0,所以现实风险低于同类afci_download.c的版本;但函数自身无守卫,属"依赖调用顺序"的隐式契约。alphaess_afci_com_reply()用abs()比较无符号差值::123abs(l_u32_recv_start_tick - OSTimeGet()) > timeout。参数是u32,abs形参为int,差值超过INT_MAX(约 24.8 天)时会得到错误结果。本文件已提供更正确的TIME_ELAPSED()宏(:438就在用,其无符号差值写法对回绕安全),两处风格不一致。send_ems_send_afci_upgrade_data()的返回值被丢弃:该函数:204特意return alphaess_write_data(...)(返回 1/0 表示串口是否发完),但调用方:332、:361完全忽略返回值,只依据alphaess_afci_com_reply()判定成功。串口发送超时(对方不收)会被静默忽略,直到应答等待超时才暴露,浪费一轮 500 ms。buf[16]与alphaess_recv_data()的契约不匹配:alphaess_afci_com_reply的:116u8 buf[16],而alphaess_recv_data()会写入实际收到长度的字节数(alphaess_comm_usart.c:309-316)。升级应答正常为 8 字节,安全;但与风险点 1 同源——接收函数不带上限,安全性完全依赖对端行为。s_afci_upgrade_tab是"半隐藏全局"::26定义时没有static,却也没在afci_upgrade.h中extern。既污染全局命名空间,又无法被外部正常引用;建议改为static。- 枚举名与拼写问题:
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不符。 - 死代码:注释掉的旧实现与未使用的宏:
: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)两个命令字在本文件及全工程均无使用者,却仍在头文件里占据协议号。 AFCI_UPGRADE_END成功分支不复位s_u32_fault_tick:若只在 END 分支清理而不进 FAULT,s_u32_fault_tick通常为 0(FAULT 的重来分支会置 0),因此暂未观察到问题;但与风险点 2 属同一类清理不完整。(u32)(offset * 100)的溢出面::492(u32_addr_flash_offset * 100) / file_len,u32乘法。3 MB 固件 × 100 = 3.1e8,未溢出;SDRAM 上限 3 MB(SDRAM_3M)也保证了这一点。若未来放宽到更大镜像需注意。- 失败时调用的是常规寄存器软重启而非协议里的
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 固件实现,代码层面无法确认。