meter_upgrade.c
METER_UPGRADE_T 字段本文件静态全局3. 函数清单4. 核心逻辑解析4.1 主状态机(meter_upgrade_handle(),:285)4.2 通信协议帧格式4.3 正泰无线多节点升级(栈式地址管理)4.4 定时/调度与 uCOS-II 配合4.5 关键全局变量的读写5. 对外接口导出(供外部调用)调用(依赖的外部函数/变量)6. 依赖关系7. 备注与风险点源文件路径:
midware/meter/meter_upgrade.c所属模块:midware/meter(电表中间件) 代码行数:482
1. 文件职责
本文件实现外部电表(含电网侧电表、PV 侧电表、正泰 CHNT 无线电表)的固件在线升级。EMS 从服务器下载电表固件(HEX/S19 文本)落到 SDRAM 缓冲区 g_u8_fw_sdram1,本模块按厂商私有的 Modbus-兼容协议把二进制数据分块烧写到电表 MCU 的 Flash:进 Bootloader → 握手 → 擦除 → 分块写 → 退出 Bootloader。
数据流位置:net/ 层负责下载与置位 UPDT_Meter 升级标志;midware/meter/meter.c:5742/5756/5761 的 task_meter() 在标志为真时每轮调用一次 meter_upgrade_handle(),直到本模块在结束时把标志清掉。本模块是"数据流的末端执行者",也是电表总线的独占使用者(升级期间其他电表查询被跳过)。
除常规电表外,本模块还支持正泰无线模块组的多点升级:先通过 g_t_meter.g_code_stack 栈把 AP(0xE1)与其 STA 子节点(0xE2/0xE3)依次入栈,再逐个出栈升级,并在过程中切换串口波特率到 115200。
2. 关键数据结构与宏
本文件宏
| 名称 | 类型 | 说明 |
|---|---|---|
DEBUG_METER_UPDATE | 宏(.h:16) | 0,调试打印开关;关闭时 printf 相关代码被预处理剔除 |
METER_UPGRADE_MAX_DATA | 宏 | 250,单帧最大固件数据字节数(主循环与尾块长度都用它) |
METER_UPGRADE_BEGIN_ADDR | 宏 | 0x00000000,电表侧 Flash 起始烧写地址 |
RECV_SEQUENCE_FRAME | 宏 | 0x06,响应帧中"序列/数据帧"标识(判定接收有效时用 recv[7] 比对) |
METER_UPGRADE_CMD_CODE | 宏 | 0x23,本协议的命令字(请求帧 [1] 固定为该值) |
METER_BUF_LEN | 宏 | 1024;本文件与全工程均未使用,遗留常量 |
METER_UPDATE_VALID_LEN | 宏 | 500;本文件与全工程均未使用,遗留常量 |
CHNT_WIRELESS_UPDATE_POP | 宏 | 7,正泰无线升级的"子节点状态位域"常量,0b111 使 STA1/STA2 都被入栈 |
本文件用到的头文件定义
| 名称 | 类型 | 说明 |
|---|---|---|
METER_UPGRADE_STATE_E | 枚举(.h:27) | 主状态机:METER_UPGRADE_INIT(0)、METER_BOOT_ENTER(1)、METER_HAND_SHAKE(2)、METER_ERASE_FLASH(3)、METER_WRITE_FLASH(4)、METER_BOOT_EXIT(5)、METER_UPGRADE_END(6)。类型名本身未被引用,只用其枚举常量 |
METER_UPGRADE_CMD_E | 枚举(.h:38) | 命令码:METERCMD_BOOT_ENTER(0)、METERCMD_HAND_SHAKE(1)、METERCMD_ERASE_FLASH(2)、METERCMD_WRITE_FLASH(3)、METERCMD_BOOT_EXIT(4);类型名未被引用 |
METER_UPGRADE_T | 结构体(.h:48,#pragma pack(1)) | 升级上下文,见下节 |
METER_UPGRADE_T 字段
| 字段 | 类型 | 说明 |
|---|---|---|
u16_length | u32 | 本次要发送的数据字节数(命名前缀 u16_ 但实际是 u32);每帧为 250,尾块为剩余长度 |
u32_addr_flash | u32 | 电表侧目标 Flash 地址,从 METER_UPGRADE_BEGIN_ADDR 起每次 +250(实际只用于组帧) |
u32_addr_flash_offset | u32 | 源侧(g_u8_fw_sdram1)已发送的字节偏移;也是升级进度的来源 |
u8_upgrade_file_buf | u8* | 固件缓冲区基址,擦除成功后指向 g_u8_fw_sdram1 |
u16_send_file_num | u16 | 总帧数 = ceil(固件长度 / 250) |
u8_upgrade_state | u8 | 状态机当前状态(METER_UPGRADE_STATE_E 的值) |
u8_meter_addr | u8 | 本次升级目标的 Modbus 从站地址(电网表 0x01 / PV 表 0x02 或 0x0B / 无线节点 0xE1~0xE3) |
u8_err_cnt | u8 | 当前状态连续失败次数,超过各自门限即跳到 METER_UPGRADE_END |
u32_sequno | u32 | 通信序列号,每次成功发送后自增;填入请求帧 [2] |
本文件静态全局
| 名称 | 类型 | 说明 |
|---|---|---|
s_meter_upgrade_send_buf[306] | static u8[306] | 发送帧缓冲,最大帧 13 + 250 + 2 = 265 字节,留有余量 |
s_meter_upgrade_recv_buf[11] | static u8[11] | 接收帧缓冲,仅 11 字节且接收时不做长度上限检查 |
s_meter_upgrade_tab | static METER_UPGRADE_T | 升级上下文(BSS 清零,状态初值即 METER_UPGRADE_INIT) |
3. 函数清单
| 函数名 | 作用域 | 说明 |
|---|---|---|
meter_upgrade_recv_data | static | 阻塞式接收一帧:按 timeout 轮询 USART1,做 CRC16/Modbus 校验与 4 个字段比对,结果通过 *len 返回(0 表示失败) |
meter_upgrade_send_cmd | static | 清串口缓冲后把 s_meter_upgrade_send_buf 按指定长度发出(发送超时固定 500ms) |
meter_upgrade_send_code | static | 组"控制命令帧"(15 字节,命令码放在 [10])并发送 + 等应答,返回成功/失败 |
meter_upgrade_send_data | static | 组"写数据帧"(13 + length + 2 字节,含地址、长度与固件数据)并发送 + 等应答 |
chnt_wireless_add_update_code | static | 按位域参数把 AP 地址与各 STA 子节点地址压入 g_t_meter.g_code_stack |
chnt_wireless_add_pop_code | static | 从 g_t_meter.g_code_stack 弹出一个地址(LIFO),栈空返回 0 |
meter_upgrade_handle | 全局 | 升级主状态机入口,每次调用推进一个状态 |
get_percent_upgrade_meter | 全局 | 返回升级百分比 = u32_addr_flash_offset * 100 / get_length_file_updating(UPDT_Meter) |
get_upgrade_file_offset_meter | 全局 | 返回已发送的固件字节偏移 u32_addr_flash_offset |
本文件另在 :30-33 有 4 行前置声明(meter_upgrade_recv_data、meter_upgrade_send_cmd、meter_upgrade_send_code、meter_upgrade_send_data)。
4. 核心逻辑解析
4.1 主状态机(meter_upgrade_handle(),:285)
METER_UPGRADE_INIT ──确定目标地址──► METER_BOOT_ENTER
│ 成功(清零 err_cnt, sequno++)
▼
┌──────────────► METER_HAND_SHAKE
│ │ 成功
│ ▼
│ METER_ERASE_FLASH ──成功→ 记录文件指针/总帧数/起始地址
│ │
│ ▼
│ METER_WRITE_FLASH ◄──┐
│ │ 全部帧发完 │ 每帧成功: cnt++, sequno++,
│ ▼ │ offset = cnt*250, addr += 250
│ METER_BOOT_EXIT ───────┘
│ │ 成功
│ ▼
│ METER_UPGRADE_END
│ │ 无线表: 栈中还有地址
└─────────────────────┤ → 直接回 METER_BOOT_ENTER(升级下一个无线节点)
│ 否则: 恢复波特率/绑定 → 清 server 标志 → METER_UPGRADE_INIT
各状态动作与迁移条件
METER_UPGRADE_INIT(:291):先无条件把状态置为METER_BOOT_ENTER,再按get_meter_updt_object()决定目标地址:GRID_METER→GRID_ALPHA_METER_ADDR(meter.h:28,0x01)PV_METER→ 若meter_get_pv_meter_addr_change_flag() != TRUE用PV_ALPHA_METER_ADDR(0x02),否则用PV_GRIDFORMING_EN_METER_ADDR(0x0B)——即并网/构网两种模式 PV 侧电表地址不同CHNT_WIRELESS→meter_set_chnt_wireless_bound(0xE1, 6)让 AP 进入无线透传配对态,OSTimeDlyHMSM(0,0,0,30)等 30ms,usart1_init(115200)切高速波特率,然后用chnt_wireless_add_update_code(7)把 AP + STA1 + STA2 压栈,再弹出一个地址作为本次目标- 其他 → 直接置
METER_UPGRADE_END(不升级)
METER_BOOT_ENTER(:317):meter_upgrade_send_code(METERCMD_BOOT_ENTER, 5000)(命令码 0,超时 5s)。成功 →METER_HAND_SHAKE,err_cnt = 0,sequno++;失败 →err_cnt++,超过 5 次才跳METER_UPGRADE_END(本状态门限比其它状态宽,因为进入 Boot 需要时间)。METER_HAND_SHAKE(:334):命令码 1,超时 5s。成功 →METER_ERASE_FLASH;失败err_cnt > 3→ END。METER_ERASE_FLASH(:351):命令码 2,超时 5s。成功时初始化写 Flash 所需的全部上下文:u8_upgrade_file_buf = g_u8_fw_sdram1(固件源指针)u16_send_file_num = ceil(get_length_file_updating(UPDT_Meter) / 250)(用"整除若有余则 +1"的三目表达式实现,:356-358)u32_addr_flash = METER_UPGRADE_BEGIN_ADDR(0)err_cnt = 0,sequno++
失败 err_cnt > 3 → END。
METER_WRITE_FLASH(:374):每次调用只发一帧(不做内循环),是升级耗时最长的状态。- 若
s_u16_file_send_cnt < u16_send_file_num - 1:u16_length = 250,meter_upgrade_send_data(u32_addr_flash_offset, 2000)(超时 2s)。成功 →err_cnt = 0,s_u16_file_send_cnt++,sequno++,u32_addr_flash_offset = cnt * 250,u32_addr_flash += 250。失败 →err_cnt++,>3 则 END。 - 否则(最后一帧):
u16_length = get_length_file_updating(UPDT_Meter) - u32_addr_flash_offset(尾块长度 1..250)。成功 →u32_addr_flash_offset = 固件总长,状态转METER_BOOT_EXIT,err_cnt = 0,sequno++。失败err_cnt > 3→ END。 - 由于
u16_send_file_num = ceil(len/250),前send_file_num-1帧各 250 字节,最后一帧为余数;当长度是 250 的整数倍时余数恰为 250 而不是 0,逻辑自洽。
- 若
METER_BOOT_EXIT(:417):命令码 4,超时 5s。成功或失败err_cnt > 3都到 END(成功时顺手清err_cnt,但该值随后被memset覆盖,属冗余)。METER_UPGRADE_END(:433):s_u16_file_send_cnt = 0,memset(&s_meter_upgrade_tab, 0, sizeof(...))整体复位上下文(随即把状态也清成 0 =METER_UPGRADE_INIT)。- 若是
CHNT_WIRELESS且chnt_wireless_add_pop_code()还能弹出地址 → 把u8_meter_addr设为该地址、状态置METER_BOOT_ENTER——直接开始升级下一个无线节点(多节点串行升级)。 - 否则若是
CHNT_WIRELESS:延时 2s、meter_set_chnt_wireless_bound(0xE1, 3)恢复绑定、延时 30ms、usart1_init(9600)恢复总线波特率。 Set_Status_Is_Updated_From_Server(UPDT_Meter, FALSE)清除升级标志(这是让task_meter()跳出升级分支的唯一出口),状态置METER_UPGRADE_INIT。
default(:454):非法状态一律跳 END。
错误计数门限一览:METER_BOOT_ENTER 为 5,METER_HAND_SHAKE / METER_ERASE_FLASH / METER_WRITE_FLASH / METER_BOOT_EXIT 均为 3。没有全局超时看门狗(与 bb_update.c 的 60 秒总超时不同),但每一次收发本身有超时(控制帧 5s、数据帧 2s),因此单次调用最长阻塞约 5 秒;若某状态持续失败,最多 4~6 次即终止。
4.2 通信协议帧格式
控制命令帧(meter_upgrade_send_code(),共 15 字节)
| 偏移 | 值 | 含义 |
|---|---|---|
| 0 | u8_meter_addr | 目标从站地址 |
| 1 | 0x23(METER_UPGRADE_CMD_CODE) | 命令字 |
| 2 | u32_sequno | 通信序列号(低 8 位) |
| 3 | 0x08 | 长度域低位 |
| 4 | 0x00 | 长度域高位(小端,值 = 8) |
| 5–9 | 0x00 ×5 | 保留/填充 |
| 10 | 命令码(0=进 Boot,1=握手,2=擦除,4=退 Boot) | 操作码 |
| 11–12 | 0x00,0x00 | 保留 |
| 13–14 | CRC16/Modbus | 低字节在前 |
写数据帧(meter_upgrade_send_data(),共 13 + length + 2 字节)
| 偏移 | 值 | 含义 |
|---|---|---|
| 0 | u8_meter_addr | 从站地址 |
| 1 | 0x23 | 命令字 |
| 2 | u32_sequno | 序列号 |
| 3–4 | u16_length*2 + 8 | 长度域,小端(:204-205);疑似"数据字节数×2 + 8" |
| 5 | 0x00 | 保留 |
| 6–9 | u32_addr_flash | 目标 Flash 地址,编码为 高 16 位字在前、每个 16bit 字小端:[6]=>>16, [7]=>>24, [8]=>>0, [9]=>>8 |
| 10 | METERCMD_WRITE_FLASH = 3 | 操作码 |
| 11–12 | u16_length | 数据长度,小端 |
| 13 … 13+length-1 | 固件数据 | 从 u8_upgrade_file_buf[offset + i] 逐字节拷贝(:216-219) |
| 末 2 字节 | CRC16/Modbus | 低字节在前 |
响应帧校验(meter_upgrade_recv_data(),:86-101)
一旦 CRC 校验通过,还要求全部满足:
| 判定 | 说明 |
|---|---|
recv[0] == u8_meter_addr | 从站地址回显正确 |
recv[3] == 0x03 | 固定值(协议约定的响应类型/长度标识) |
recv[6] == send_buf[10] | 回显请求帧的操作码(即"这条响应属于哪条命令") |
recv[7] == RECV_SEQUENCE_FRAME(0x06) | 序列/数据帧标识 |
四项全满足才把 *len 置为实际长度(成功);否则置 0(失败)。
说明:源码中没有给出完整响应帧的字段布局文档,代码只检查上述 4 个位置;
recv[1]、recv[2]、recv[4]、recv[5]、recv[8..10]的含义未在代码中体现。
超时与重试机制
- 接收:
meter_upgrade_recv_data()用OSTimeGet()做忙轮询,每次循环检查(OSTimeGet() - recv_tick) > timeout则返回失败;节拍回绕时(OSTimeGet() < recv_tick)重置起点。 - 发送:
meter_upgrade_send_cmd()固定usart1_send_data(..., 500)(500ms 发送超时)。 - 重试:不做帧内自动重发;重试由上层的
err_cnt计数 + 状态机重入同一状态实现(每次进入该状态重发一次)。因此"重试次数"等于错误计数门限(3 或 5)。 - 接收缓冲在每次发送前由
meter_upgrade_send_cmd()调用usart1_clear_buffer()清空,避免残留帧干扰。
4.3 正泰无线多节点升级(栈式地址管理)
入栈(chnt_wireless_add_update_code(),:250)
g_t_meter.g_code_stack.u8_top = 0;
memset(g_t_meter.g_code_stack.u8_codes, 0, sizeof(...));
g_t_meter.g_code_stack.u8_codes[top++] = CHNT_WIRELESS_AP_ADDR; // 0xE1,AP
for (u8 i = 1; i < MAX_CODES; i++) // MAX_CODES = 3 → i = 1,2
if (((state >> i) & 0x01) && (top < (MAX_CODES + 1)))
g_t_meter.g_code_stack.u8_codes[top++] = 0xE0 | (i + 1); // 0xE2, 0xE3
g_code_stack(meter.h:313)定义 u8_codes[MAX_CODES+1] = 4 个字节、u8_top 为栈顶。以实参 CHNT_WIRELESS_UPDATE_POP = 7(0b111)调用时,位 1、位 2 命中,栈内容为 [0xE1, 0xE2, 0xE3]、top = 3。
出栈(chnt_wireless_add_pop_code(),:268)
LIFO:栈空返回 0(表示升级序列结束);否则 top--,取出 u8_codes[top] 并把该槽位清 0。
实际升级顺序:METER_UPGRADE_INIT 弹出的是 0xE3(STA2),METER_UPGRADE_END 再弹 0xE2(STA1),最后弹 0xE1(AP)——即先子节点、后 AP。
波特率切换:进入无线升级前 usart1_init(115200),全部完成后 usart1_init(9600) 恢复。恢复前后各有 2s / 30ms 延时用于等待无线模块重新绑定(meter_set_chnt_wireless_bound(0xE1, 3))。
4.4 定时/调度与 uCOS-II 配合
- 本文件不使用信号量、互斥量、消息邮箱,全部同步依赖
OSTimeGet()忙轮询 +OSTimeDlyHMSM()阻塞延时。 - 延时使用点:
:305OSTimeDlyHMSM(0,0,0,30)(30ms,无线绑定后)、:444OSTimeDlyHMSM(0,0,2,0)(2s)、:446OSTimeDlyHMSM(0,0,0,30)(30ms)。 - 任务上下文:调用方
task_meter()(meter.c:5710)。升级期间该任务不再执行meter_config_handle()/meter_handle()等常规电表轮询(升级分支直接continue,见meter.c:5768-5770),且每次升级帧之后才IWDG_Feed()。因此升级过程会独占电表总线,最长单次阻塞约 5 秒(控制帧超时)。 - 回归周期:
task_meter()在升级分支末尾OSTimeDlyHMSM(0,0,0,50),即每 50ms 推进一个升级状态;写 Flash 阶段的实速受串口速率与电表响应速度限制(每帧约 265 字节 + 等待应答)。
4.5 关键全局变量的读写
| 变量 | 写方 | 读方 | 触发条件 |
|---|---|---|---|
s_meter_upgrade_tab.u8_upgrade_state | 本文件 | 本文件 | BSS 初值 0 = METER_UPGRADE_INIT |
s_meter_upgrade_tab.u32_addr_flash_offset | METER_WRITE_FLASH、METER_ERASE_FLASH | get_percent_upgrade_meter() / get_upgrade_file_offset_meter() | 每帧成功后按 cnt*250 重算 |
s_meter_upgrade_tab.u8_meter_addr | METER_UPGRADE_INIT、METER_UPGRADE_END | meter_upgrade_send_code/data、接收校验 | 每次确定目标设备时设置 |
g_t_meter.g_code_stack | chnt_wireless_add_update_code / chnt_wireless_add_pop_code | 同上 | 仅 CHNT_WIRELESS 流程 |
g_u8_fw_sdram1 | net/ 层下载模块 | meter_upgrade_send_data | 只读,METER_ERASE_FLASH 成功后把指针绑到 u8_upgrade_file_buf |
g_t_meter.t_config_data.e_grid_meter_type_old / e_pv_meter_type_old | 配置模块 | meter.c:5733-5757(判断走 BB 升级还是本模块) | 由调用方决定,不在本文件 |
UPDT_Meter 升级标志 | 本文件(METER_UPGRADE_END 清除) | task_meter() 循环条件 | 由 net/ 层置位 |
OSTimeGet() 节拍 | uCOS-II | 本文件所有超时判定 | — |
5. 对外接口
导出(供外部调用)
| 名称 | 声明位置 | 调用方 |
|---|---|---|
void meter_upgrade_handle(void) | meter_upgrade.h:66 | midware/meter/meter.c:5742、:5756、:5761(task_meter()) |
u8 get_percent_upgrade_meter(void) | meter_upgrade.h:67 | midware/meter/meter.c:6848、:6861、:6866(GetPercentUpgradeMeter()) |
u32 get_upgrade_file_offset_meter(void) | meter_upgrade.h:68 | net/ems.c:6711(上报 g_t_upgrade_run_info.u32_upgrade_file_offset) |
本文件的三个静态缓冲(s_meter_upgrade_send_buf / s_meter_upgrade_recv_buf / s_meter_upgrade_tab)均为 static,无外部链接。
调用(依赖的外部函数/变量)
| 名称 | 来源 |
|---|---|
usart1_init() / usart1_clear_buffer() / usart1_send_data() / usart1_is_recv_async_data() / usart1_get_data_len() / usart1_read_data_async() | hardware/usart1/usart1.h:8-13 |
GetCrcCode_Modbus() | common/crc.h:17 |
get_meter_updt_object() | net/meter_download.h:18(返回 GRID_METER/PV_METER/CHNT_WIRELESS/MID_METER) |
get_length_file_updating() / Set_Status_Is_Updated_From_Server() / UPDT_Meter | net/upd_download.h:37/39 |
g_u8_fw_sdram1 | net/server.h:258(net/sdram.h) |
g_t_meter、METER_UPGRADE_T 相关地址宏 | midware/meter/meter.h:GRID_ALPHA_METER_ADDR(0x01)、PV_ALPHA_METER_ADDR(0x02)、PV_GRIDFORMING_EN_METER_ADDR(0x0B)、CHNT_WIRELESS_AP_ADDR(0xE1)、MAX_CODES(3)、CHNT_WIRELESS_STACK |
meter_get_pv_meter_addr_change_flag() | midware/meter/meter.h:482 |
meter_set_chnt_wireless_bound() | midware/meter/meter.h:522 |
OSTimeGet() / OSTimeDlyHMSM()、memcpy / memset / size_t | uCOS-II、标准 C 库(经 includes.h) |
TRUE / FALSE | includes.h |
6. 依赖关系
- 依赖:
meter_upgrade.h、includes.h、hardware/usart1/usart1.h、net/upd_download.h、net/meter_download.h、midware/meter/meter.h、net/server.h、hardware/sdram.h、common/crc.h。 - 被依赖:
midware/meter/meter.c(:30includemeter_upgrade.h;:5742/5756/5761调meter_upgrade_handle();:6848/6861/6866调get_percent_upgrade_meter())。net/ems.c:6711(调get_upgrade_file_offset_meter()上报升级进度)。midware/afci/afci_upgrade.c:484的注释里提到同名函数名get_percent_upgrade_meter,但那是该文件自身的注释标题,不是本文件的调用者——本文件与afci_upgrade.c无代码依赖。task_meter()的分支判断还依赖meter.c中e_grid_meter_type_old/e_pv_meter_type_old是否为BACKUPBOX_*来决定调用本文件还是bb_update.c。
7. 备注与风险点
- 接收缓冲无长度上限检查(高优先级,栈/全局溢出)——
meter_upgrade_recv_data()的:72-79:for(size_t i = 0; i < l_u16_len; i++) *(buf + i) = usart1_read_data_async();
l_u16_len 直接取自 usart1_get_data_len(),没有像 meter_recv_data()(meter.c:78)那样先按缓冲尺寸截断。而唯一调用方传入的是 11 字节的 s_meter_upgrade_recv_buf。只要总线上出现超过 11 字节的一帧(错误帧、其他设备应答、噪声、或厂商响应变长),就会越界写 11 字节之外的静态存储区。这是本文件最严重的隐患。
- 校验用的是全局缓冲而不是参数缓冲——
:88-91判定条件读的是s_meter_upgrade_recv_buf[...],而数据是写进形参buf的。当前唯一调用点传的正好就是同一个数组,因此"能用",但函数签名与实际语义不一致,一旦被复用即出错。应改为判定*(buf + n)。 - 完整响应帧布局未文档化——只校验
recv[0]、recv[3]、recv[6]、recv[7]四个位置(:88-91),其中recv[3]==0x03与recv[7]==0x06为魔数,recv[6]被解释为"操作码回显"。这种"四点抽样校验 + CRC"的方式在协议变更时很难定位问题,且缺乏注释。 CHNT_WIRELESS_UPDATE_POP被当作常量位域传入——:307写死chnt_wireless_add_update_code(CHNT_WIRELESS_UPDATE_POP),形参名却叫u8_module_child_nodes_state,暗示它本该是"实际在线的子节点掩码"。当前实现等于无条件同时升级 STA1 与 STA2,若现场只装了其中一个节点,升级其一会失败并消耗 3 次重试后跳到 END;虽然不会导致死循环,但每次升级都会白跑一轮。u16_send_file_num为 0 时尾块逻辑会异常——:375判断s_u16_file_send_cnt < u16_send_file_num - 1,两者都是u16;若固件长度为 0(send_file_num = 0),0 - 1回绕成 65535,条件成立,于是会以u16_length = 250发送 250 字节来自g_u8_fw_sdram1的未定义内容(实为缓冲区残留)。虽然正常下载流程不会给出 0 长度文件,但缺少防御。- 两个除法/取长度接口缺少除零保护——
get_percent_upgrade_meter()(:469)直接用get_length_file_updating(UPDT_Meter)作除数;文件长度未知时返回 0 会触发除零异常(Cortex-M4 上表现为 HardFault 或返回 0,取决于配置)。METER_ERASE_FLASH里也多次调用同一接口(:356-358),若长度在升级中途变化会导致帧数错乱。 METER_BUF_LEN、METER_UPDATE_VALID_LEN是死宏——.h:22-23定义了 1024 与 500,全工程检索无任何引用,属从早期大缓冲设计遗留下来的常量,容易误导(让人以为接收缓冲有 1024 字节,实际是 11)。u16_length字段类型与命名不一致——METER_UPGRADE_T.u16_length声明为u32,u8_upgrade_state用u8存枚举,风格与bb_update.h中同样的s32_前缀误用问题一致,属工程通病。METER_UPGRADE_CMD_E/METER_UPGRADE_STATE_E的类型名从未被引用——只用到了枚举常量。这样枚举实际上退化为"常量集合",失去了类型检查意义(任何整型都能赋给u8_upgrade_state)。usart1_init(115200)会改变共享总线波特率——无线升级期间把电表总线波特率从 9600 切到 115200,若该串口上同时挂了电网表/PV 表(实际接线可能是分时的),切换期间其它设备无法通讯。代码通过"升级期间task_meter()不跑常规查询"来回避冲突,但这依赖调用方严格遵循控制流,属隐式契约。METER_BOOT_EXIT成功分支里的err_cnt = 0是无效赋值——:421之后METER_UPGRADE_END会memset整个表,该赋值随即被覆盖。meter_upgrade_recv_data()在l_u16_len < 2时忙等——:67-70用continue跳过,但数据未被读出,若接收缓冲长期只有 1 字节,会以极小间隔空转直到超时(最多 5 秒)。虽不会死锁,但属于忙等待,未用OSTimeDlyHMSM()让出 CPU(对比bb_update.c:260有 30ms 让出)。RECV_SEQUENCE_FRAME宏在bb_update.h与meter_upgrade.h中重复定义(都是0x06,含义不同:前者是 BB 应答特征,后者是电表响应帧类型)。若某 .c 同时 include 两个头文件会产生宏重定义告警(当前meter.c两者都 include,定义值相同故编译器只报 warning 或静默)。- 无线多节点升级失败会静默放弃后续节点吗?——不会:
METER_UPGRADE_END只要栈里还有地址就继续弹,因此即使某个节点升级失败(err_cnt超限跳到 END),也会继续升级下一个节点,整体以"清UPDT_Meter标志"收尾,失败信息不向云端反馈(没有错误码字段),现场只能从电表版本号判断。