meter_upgrade.h
METER_UPGRADE_STATE_E 枚举成员METER_UPGRADE_CMD_E 枚举成员METER_UPGRADE_T 字段(#pragma pack(1))3. 函数清单4. 核心逻辑解析4.1 状态机契约(METER_UPGRADE_STATE_E)4.2 命令码与错误门限(METER_UPGRADE_CMD_E)4.3 分帧参数(METER_UPGRADE_MAX_DATA / METER_UPGRADE_BEGIN_ADDR)4.4 收发协议(帧格式)4.5 定时/调度与 uCOS-II 配合4.6 关键全局变量5. 对外接口导出(供外部调用)使用的外部符号(本文件自身依赖)6. 依赖关系7. 备注与风险点源文件路径:
midware/meter/meter_upgrade.h所属模块:midware/meter(电表中间件) 代码行数:71
1. 文件职责
这是外部电表(电网表 / PV 表 / 正泰无线电表)固件在线升级模块的对外头文件。它声明了三类内容:升级协议常量与调度参数(命令字 0x23、单帧最大数据 250、起始 Flash 地址 0、无线升级位域常量)、升级状态机与命令码的两套枚举,以及升级上下文结构体 METER_UPGRADE_T。
在数据流中它处在 net/(固件下载、置升级标志)与 midware/meter/meter.c(task_meter() 驱动升级)之间的契约位置:meter_upgrade.c 是唯一实现者,meter.c 通过本头文件调用 meter_upgrade_handle() 并读取升级进度上报云端。
本文件不含任何实现代码。
2. 关键数据结构与宏
| 名称 | 类型 | 说明 |
|---|---|---|
__METER_UPDATE_H__ | 宏 | 头文件防重复包含保护 |
DEBUG_METER_UPDATE | 宏 | 0,调试打印开关;meter_upgrade.c 用 #if (DEBUG_METER_UPDATE) 包裹所有 printf |
METER_UPGRADE_MAX_DATA | 宏 | 250,单帧最大固件数据字节数;实现文件中用于 METER_WRITE_FLASH 的分块长度与总帧数计算 |
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:位 1、位 2 置位 → STA1、STA2 都入栈) |
METER_UPGRADE_STATE_E | 枚举 | 主状态机 7 个状态,见下节;类型名本身未被任何文件引用,仅使用其枚举常量 |
METER_UPGRADE_CMD_E | 枚举 | 5 个命令码,见下节;类型名本身未被任何文件引用 |
METER_UPGRADE_T | 结构体(#pragma pack(1)) | 升级上下文,见下节 |
meter_upgrade_handle | 函数声明 | 升级主状态机入口 |
get_percent_upgrade_meter | 函数声明 | 返回升级百分比 |
get_upgrade_file_offset_meter | 函数声明 | 返回已发送的固件字节偏移 |
METER_UPGRADE_STATE_E 枚举成员
| 成员 | 值 | 说明 |
|---|---|---|
METER_UPGRADE_INIT | 0 | 初始化/确定目标设备地址;也是 BSS 清零后的上电初值 |
METER_BOOT_ENTER | 1 | 发命令码 0,让电表进入 Bootloader |
METER_HAND_SHAKE | 2 | 发命令码 1,握手 |
METER_ERASE_FLASH | 3 | 发命令码 2,擦除电表应用区;成功后初始化文件指针/总帧数/起始地址 |
METER_WRITE_FLASH | 4 | 发命令码 3,分块写数据(主体状态,每帧 250 字节) |
METER_BOOT_EXIT | 5 | 发命令码 4,退出 Bootloader 并让电表跳转到新固件 |
METER_UPGRADE_END | 6 | 收尾:复位上下文、处理无线多节点、清除升级标志 |
METER_UPGRADE_CMD_E 枚举成员
| 成员 | 值 | 说明 |
|---|---|---|
METERCMD_BOOT_ENTER | 0 | 进 Boot(错误门限 5 次) |
METERCMD_HAND_SHAKE | 1 | 握手(门限 3 次) |
METERCMD_ERASE_FLASH | 2 | 擦除(门限 3 次) |
METERCMD_WRITE_FLASH | 3 | 写 Flash(门限 3 次);同时作为写数据帧 [10] 的操作码 |
METERCMD_BOOT_EXIT | 4 | 退 Boot(门限 3 次) |
METER_UPGRADE_T 字段(#pragma pack(1))
| 字段 | 类型 | 说明 |
|---|---|---|
u16_length | u32 | 本次发送的数据字节数(每帧 250、尾块为余数);命名前缀 u16_ 与实际类型 u32 不符 |
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(get_length_file_updating(UPDT_Meter) / 250) |
u8_upgrade_state | u8 | 状态机当前状态(取 METER_UPGRADE_STATE_E 值) |
u8_meter_addr | u8 | 目标从站地址:电网表 0x01、PV 表 0x02 或 0x0B、无线节点 0xE1~0xE3 |
u8_err_cnt | u8 | 当前状态连续失败计数,超过门限即跳 METER_UPGRADE_END |
u32_sequno | u32 | 通信序列号,每次成功发送后自增,填入请求帧 [2] |
#pragma pack(1) 使结构体无填充;该结构体是纯运行期上下文(不做串口字节流映射),因此 pack(1) 的作用主要是让 memset(&s_meter_upgrade_tab, 0, sizeof(...)) 的语义稳定。
3. 函数清单
本文件为纯头文件,不包含任何函数定义,仅在 :66-68 声明了 3 个由 meter_upgrade.c 实现的导出函数:
| 函数名 | 作用域 | 说明 |
|---|---|---|
void meter_upgrade_handle(void) | 全局(外部实现) | 升级主状态机入口,每次调用推进一个状态 |
u8 get_percent_upgrade_meter(void) | 全局(外部实现) | 返回升级百分比(0–100) |
u32 get_upgrade_file_offset_meter(void) | 全局(外部实现) | 返回已发送的固件字节偏移 |
4. 核心逻辑解析
本文件无运行逻辑。以下说明它约定的协议、状态迁移与调度参数如何被实现文件使用。
4.1 状态机契约(METER_UPGRADE_STATE_E)
实现文件 meter_upgrade.c:289 用 switch(s_meter_upgrade_tab.u8_upgrade_state) 驱动:
METER_UPGRADE_INIT → METER_BOOT_ENTER → METER_HAND_SHAKE → METER_ERASE_FLASH
│
▼
METER_UPGRADE_END ◄── METER_BOOT_EXIT ◄── METER_WRITE_FLASH
│ ▲ │
│ └─────────────────┘
│ (无线表:栈还有地址 → 直接回 BOOT_ENTER 升级下一节点)
└──► 清 UPDT_Meter 标志 → METER_UPGRADE_INIT(等待下一次升级请求)
- 枚举值 0(
METER_UPGRADE_INIT)即上电默认状态,因为s_meter_upgrade_tab是 BSS 段未初始化静态变量。 - 整个流程没有全局超时看门狗(对比
bb_update.c有 60 秒总超时);出口完全依赖各状态的错误计数门限与单次收发超时。 METER_UPGRADE_END在memset整个结构体后临时把状态置为METER_BOOT_ENTER(进入下一无线节点)或METER_UPGRADE_INIT(收尾),因此状态在 END 之后必然是 0 或 1,不会停留在 END。
4.2 命令码与错误门限(METER_UPGRADE_CMD_E)
命令码通过 meter_upgrade_send_code(code, timeout) 填入控制帧的 [10] 位置,同时在响应帧的 [6] 位置被回显校验(实现文件 :90:recv[6] == send_buf[10])。这套"请求发操作码 / 响应回显操作码"的机制用于把响应与具体命令关联起来。
各命令的调用超时与错误门限由实现文件决定,并不在同名枚举或宏中表达:
| 命令 | 调用超时 | 失败门限(超过即结束升级) |
|---|---|---|
METERCMD_BOOT_ENTER | 5000ms | 5 |
METERCMD_HAND_SHAKE | 5000ms | 3 |
METERCMD_ERASE_FLASH | 5000ms | 3 |
METERCMD_WRITE_FLASH | 2000ms | 3 |
METERCMD_BOOT_EXIT | 5000ms | 3 |
4.3 分帧参数(METER_UPGRADE_MAX_DATA / METER_UPGRADE_BEGIN_ADDR)
- 单帧数据上限 250 字节。实现文件的发送缓冲
s_meter_upgrade_send_buf[306]正是按13 + 250 + 2 = 265并留余量设计的。头文件未定义缓冲尺寸,两者靠 250 这个数字隐式关联:若把METER_UPGRADE_MAX_DATA改大到 288 以上,接收/发送缓冲会立刻溢出。 - 总帧数 =
ceil(固件长度 / 250),用三目表达式实现(meter_upgrade.c:356-358)。当长度恰为 250 的整数倍时,最后一帧长度为 250 而非 0,逻辑自洽。 METER_UPGRADE_BEGIN_ADDR恒为 0,说明电表侧的烧写地址空间与固件文件内偏移一一对应(不做基址搬移)。
4.4 收发协议(帧格式)
头文件不定义帧格式,只提供命令字 0x23 与响应帧标识 0x06。完整格式在 meter_upgrade.c 中硬编码:
- 请求帧公共结构:
[0]从站地址、[1]=0x23、[2]= 序列号、[3..4]小端长度域、[5]保留、[6..9]目标地址或填充、[10]操作码、[11..12]/ 数据、末 2 字节 CRC16/Modbus(低字节在前)。 - 控制命令帧固定 15 字节(无数据域),写数据帧为
13 + 数据长度 + 2字节。 - 响应校验:CRC 通过后再比对
recv[0](地址)、recv[3] == 0x03、recv[6] == 请求操作码、recv[7] == RECV_SEQUENCE_FRAME,四项全中才算成功。 - 超时/重试:接收用
OSTimeGet()忙轮询计时,发送固定 500ms 超时;不做帧内自动重发,重试靠状态机重入(err_cnt计数)。
4.5 定时/调度与 uCOS-II 配合
- 无信号量、互斥量、消息邮箱;同步全部依赖
OSTimeGet()时间比较与OSTimeDlyHMSM()阻塞延时。 - 调用方
task_meter()在升级分支末尾延时 50ms,因此升级状态机以约 50ms 的粒度推进;实际帧间隔由串口速率与电表响应决定。 - 升级期间
task_meter()走continue分支(meter.c:5768-5770),独占电表总线且不再执行常规电表查询,也不在升级中途IWDG_Feed()(仅每轮结束喂一次)。 - 无线升级会在流程内切换串口波特率(进 115200、收尾恢复 9600),这是本模块与其他电表通讯之间的隐式互斥条件。
4.6 关键全局变量
本文件不导出任何全局变量,只导出 3 个函数与若干宏/类型。升级上下文实例 s_meter_upgrade_tab 在 meter_upgrade.c:25 定义为 static,外部无法直接访问,只能通过两个取值函数(百分比、偏移)间接读取。这一点是刻意的封装。
正泰无线多节点升级用到的地址栈 不属于本文件,而是 meter.h:313 的 g_t_meter.g_code_stack(CHNT_WIRELESS_STACK 类型,u8_codes[MAX_CODES+1] + u8_top,MAX_CODES = 3)。头文件 CHNT_WIRELESS_UPDATE_POP 的注释"正泰无线电表升级地址按位域存储"正是指这一栈的填充方式。
5. 对外接口
导出(供外部调用)
| 名称 | 说明 |
|---|---|
void meter_upgrade_handle(void) | 由 midware/meter/meter.c:5742、:5756、:5761 调用 |
u8 get_percent_upgrade_meter(void) | 由 midware/meter/meter.c:6848、:6861、:6866 调用 |
u32 get_upgrade_file_offset_meter(void) | 由 net/ems.c:6711 调用 |
METER_UPGRADE_MAX_DATA | 宏,实现文件用于分块与缓冲尺寸设计 |
METER_UPGRADE_STATE_E、METER_UPGRADE_CMD_E、METER_UPGRADE_T | 类型定义 |
使用的外部符号(本文件自身依赖)
| 名称 | 来源 |
|---|---|
includes.h | :13 include;提供 u8/u16/u32/s32 等基础类型与工程公共声明 |
6. 依赖关系
- 依赖:仅
#include <includes.h>。 - 被依赖:
midware/meter/meter_upgrade.c(唯一实现者)midware/meter/meter.c:30(#include "meter_upgrade.h";调meter_upgrade_handle()与get_percent_upgrade_meter())net/ems.c:6711(调get_upgrade_file_offset_meter())
- 重复定义提示:
RECV_SEQUENCE_FRAME在bb_update.h:19与本文件:20各定义一次(值都是0x06)。midware/meter/meter.c同时 include 这两个头文件,属实际的宏重定义场景,只是值相同而未引发行为差异。
7. 备注与风险点
METER_BUF_LEN(1024) 与METER_UPDATE_VALID_LEN(500) 是全工程无引用的死宏——极易误导:读者看到METER_BUF_LEN = 1024会以为接收缓冲有 1024 字节,实际实现里的接收缓冲只有 11 字节(meter_upgrade.c:24)且不校验长度上限,是一个真实的溢出隐患。建议删除或在头文件中显式声明缓冲尺寸常量。- 缓冲尺寸常量未在头文件定义——
METER_UPGRADE_MAX_DATA(250) 与实现文件的s_meter_upgrade_send_buf[306]、s_meter_upgrade_recv_buf[11]之间的尺寸约束只存在于实现文件中,头文件没有提供"单帧最大缓冲长度"这样的宏来集中约束,修改 250 时无法从接口层面得到提示。 u16_length字段名与类型不符——声明为u32却叫u16_length;同一工程METER_UPGRADE_T中u8_upgrade_file_buf是指针、u32_sequno命名正常,只有这个字段前缀错位,属历史遗留。- 状态机与命令码的两套枚举都不做类型检查——
METER_UPGRADE_STATE_E与METER_UPGRADE_CMD_E的类型名在工程中从未被引用(u8_upgrade_state是u8、meter_upgrade_send_code的形参是u8 code),枚举退化为常量集合,编译器无法阻止越界状态值;实现文件只能靠default分支兜底。 CHNT_WIRELESS_UPDATE_POP语义可疑——宏名带POP(出栈)但实现是把它当"子节点在位掩码"传给入栈函数chnt_wireless_add_update_code();形参名u8_module_child_nodes_state与宏名不匹配,注释只说"按位域存储",没有解释为什么是常量 7(而不是运行期探测到的实际在位节点)。当前行为是无条件升级 AP + STA1 + STA2 三个节点。- 错误门限未在头文件表达——5 次(进 Boot)/ 3 次(其余)的门限是"多久放弃"这一关键行为,却完全硬编码在实现文件里;从接口使用者的角度看,
meter_upgrade_handle()可能最长阻塞(5+3+3+3×N+3)次收发时间,没有可查的契约。 - 无错误码输出接口——本文件只导出"进度百分比"与"已发偏移"两个观测点,没有任何"升级失败原因/失败设备地址"的查询接口。升级失败时只把
UPDT_Meter标志清零,云端与 HMI 无从得知具体环节(是进 Boot 失败、擦除失败还是写 Flash 失败),现场排障只能依靠串口日志(且DEBUG_METER_UPDATE默认为 0)。 u8_err_cnt不加注释的语义——该字段在实现中对不同状态有不同含义的门限(>5 或 >3),头文件没有标注。#pragma pack(1)与#pragma pack()无参数成对使用——Keil ARMCC 支持,跨编译器行为不同;本工程固定 Keil MDK 编译,可接受,但同工程其它头文件(如diesel_generator.h)完全不使用 pack,风格不统一。METER_UPGRADE_T内混用"目标侧"与"源侧"偏移——u32_addr_flash是电表侧地址、u32_addr_flash_offset是源侧(SDRAM)偏移,两者名字极为相似且都带addr_flash,仅靠_offset后缀区分。头文件没有区分性注释,是实现者容易混用的点。