meter.c
com_switch 轮询调度:电参量 / 总电能 / 分相电能交替4.5 各厂商解析与量纲换算4.6 电能三次取中值 与「EMS 自算总电能」4.7 清表(恢复出厂)4.8 通信丢失判决与故障上报4.9 正泰 DTS 相序自适应与单相接反4.10 正泰无线模组(AP + 双 STA)4.11 CT 与 P1 两条替代通道4.12 电表与 BackupBox 升级入口4.13 配置持久化5. 对外接口5.1 本文件导出(供外部调用)5.2 本文件调用的外部函数/变量6. 依赖关系7. 备注与风险点7.1 确定性缺陷7.2 可疑写法与魔法数字7.3 竞态与越界7.4 版本遗留与死代码源文件路径:
midware/meter/meter.c所属模块:midware(设备中间件)→ 电表通信 代码行数:7273
1. 文件职责
本文件是 EMS 电表子系统的全部实现,负责通过 USART1(GRID/PV/正泰无线,Modbus RTU)与 USART3(MID 三相表,Modbus RTU)两条 485 总线,完成电表的自动探测识别、周期性数据采集、量纲换算、电能累计、通信丢失判决、恢复出厂(清表)、相序自适应与单相接反校准、以及电表固件升级入口。它处在数据流的最前端——电网/光伏的电压电流功率电能由此进入 EMS,随后被 logic/ 的功率调度、data/ 的故障表、hmi/ 的显示和 net/ 的云端上报消费。整个文件由 user/main.c 静态创建的电表守护任务 task_meter()(meter.c:5710)单线程驱动,不需要信号量或消息邮箱做内部互斥;只有 P1 无线采集通道通过 meter_set_run_data() 从外部反向注入数据。文件用同一个 static METER_DATA_TEMP s_dataTmp 的 com_switch 计数器做轮询调度,把「电参量读」与「电能读」按固定节拍交替下发。
2. 关键数据结构与宏
2.1 本文件定义的全局对象与文件级静态量
| 名称 | 类型 | 说明 |
|---|---|---|
g_t_meter | METER_TABLE_T(全局) | 电表子系统总表(meter.c:38),类型定义见 meter.h。meter_config_handle() 中写 t_config_data,grid_meter_read_data() 等写 t_grid_info.t_data,HMI/云端读全表 |
s_u8_save_flag | static u8(文件级) | 全局「配置需要落盘」标志(meter.c:39)。任意位置检测到配置变化即置 TRUE,由 meter_config_handle() 消费为 save_job_post(METER_CONFIG_PAGE) |
g_u8_recv_buf[256] | u8[](全局) | 全模块共享接收缓冲(meter.c:40)。GRID/PV/正泰无线/MID 各有独立收发函数,但缓冲区只有一份 |
s_adjust_state | static METER_ADJUST_STATE_U(文件级) | 相序自适应就绪位域(meter.c:41),bit.u8_grid_adjust_ready / bit.u8_pv_adjust_ready |
METER_DATA_TEMP | typedef struct(meter.c:2116) | 本文件私有的中间暂存结构,用于电能「三次取中值」表决。含 24 个 u32 暂存槽(总/分相 × 输入/输出 × 3 次)与 u8 com_switch 轮询计数器。不在 meter.h 中暴露 |
2.2 本文件主要使用的外部宏
| 名称 | 类型 | 说明 |
|---|---|---|
GRID_*_METER_ADDR / PV_*_METER_ADDR / PV_GRIDFORMING_EN_METER_ADDR | 宏 | 各型号电表从站地址,用于组帧与型号判定(取值见 meter.h) |
MID_3PH_METER_ADDR | 宏 | MID 三相表地址 0x01 |
METER_READ_FUN_CODE / METER_WRITE_FUN_CODE | 宏 | 0x03 / 0x06 |
METER_RECV_BUFFER_SIZE | 宏 | 256,收发缓冲上限 |
METER_LOST_MAX_CNT | 宏 | 15,丢失阈值 |
METER_DETECT_INTERVAL_TIME | 宏 | 1000ms,重探测间隔 |
WIRELESS_METER_LOST_TIME | 宏 | 10000ms,无线电表判丢失 |
CHNT_WIRELESS_DETECT_INTERVAL_TIME | 宏 | 1000ms,无线模组轮询节拍 |
BACKUPBOX_ID | 宏 | 0x00AB,区分 BB 与 ADL3000/400 响应 |
BACKUPBOX_*(0x0025~0x0073) | 宏 | BB 诊断寄存器绝对地址,代码用 (ADDR - 0x20) * 2 转成报文偏移 |
METER_TYPE_CH_* / METER_TYPE_AC_ADL400 / Chint / Acrel | 宏 | 相序自适应用的厂商型号字符串与厂商编号 |
GRID_ADJUST_READY_MASK / PV_ADJUST_READY_MASK / BOTH_ADJUST_READY_MASK | 宏 | 0x01/0x02/0x03 就绪掩码 |
TYPE_SM60A … TYPE_NONE、VERSION_BB_HARD | 宏 | meter_get_meter_modle() 等的返回字符串 |
METER_DEBUG_INFO / MID_METER_DEBUG_INFO / BB_DEBUG_INFO | 宏 | 调试开关,默认 0;多处写成 #if (METER_DEBUG_INFO) |
CT_SAMPLING / METER_SAMPLING / P1_SAMPLING | 枚举 | 采样来源,来自 meter.h 的 METER_SAMPLING_TYPE_E |
PHASE_A/B/C/T / MAX_PHASE | 枚举 | 相位索引,来自 METER_PHASE_E |
MAX_CT_SITE | 枚举 | ct_handle() 节流数组维度,来自 ct.h:47 |
METER_CONFIG_PAGE | 宏 | 配置落盘页号,来自 system_config.h |
2.3 本文件主要引用的外部全局对象
| 名称 | 类型 | 说明 |
|---|---|---|
g_t_logic_info | LOGIC_TABLE_T(logic/logic.h:646) | 读 t_config_data.u8_system_mode 判断 DC/AC/Hybrid,决定 PV 侧电表是否需要探测 |
g_t_inv_info | INVERTER_TABLE_T(midware/inverter/inverter.h:1929) | 读 t_config_data.u8_inv_software_limit_enable / u8_inv_hardware_limit_enable 决定丢表阈值降到 3 |
g_t_ems_info | EMS_TABLE_T(data/data.h) | 写 u_ems_fault.bit.u8_wifi_P1_pairing_fault |
g_t_task_run_status | 任务运行状态表 | u32_task_meter_cnt 计数,仅调试编译时累加 |
3. 函数清单
本文件共定义 133 个函数(不含 #if METER_DEBUG_INFO == 1 屏蔽编译掉的除外,meter_data_printf 已计入并标注)。
| 函数名 | 作用域 | 行号 | 说明 |
|---|---|---|---|
meter_write_data | 全局 | 51 | 清 USART1 缓冲后发送一帧(超时 1000ms) |
meter_recv_data | 全局 | 66 | 从 USART1 读一帧并做 Modbus CRC 校验,失败置 *len = 0 |
mid_meter_write_data | 全局(无头文件声明) | 113 | 清 USART3 缓冲后发送一帧 |
mid_meter_recv_data | 全局(无头文件声明) | 130 | 从 USART3 读一帧并做 CRC 校验,逐字节加 1ms 超时 |
midvalue | 全局 | 186 | 取三值中位数(用于三次读电能表决) |
grid_meter_table_init | static | 206 | GRID 运行表初始化:置探测标志、记 u32_find_tick |
pv_meter_table_init | static | 218 | PV 运行表初始化;仅 AC/Hybrid 系统模式置探测标志 |
mid_meter_table_init | static | 236 | MID 运行表初始化,并清 u8_mid_meter_function_enable |
chnt_wireless_table_init | static | 247 | 正泰无线表初始化,只记 u32_find_tick |
meter_table_init | static | 257 | 按 site 分发到上述四个初始化函数 |
grid_meter_find | static | 293 | GRID 电表探测:7 项轮询表逐一试探、区分型号、抽取版本/SN |
grid_meter_detect | static | 605 | GRID 探测外壳:成功清计数,连续 30 次失败后判定通信丢失 |
pv_meter_find | static | 655 | PV 电表探测:8 项轮询表(含 gridforming 地址切换、ALPHA 双回路) |
pv_meter_detect | static | 958 | PV 探测外壳,丢失判定区分 gridforming 与普通模式 |
mid_meter_send_clear_data | static | 1014 | MID 表用 0x10 写 0x0002=1 清零电能,重试 3 次 |
mid_meter_clear | 全局 | 1086 | 对外包装,直接调用 mid_meter_send_clear_data() |
mid_meter_find | static | 1096 | MID 表探测:读 0x0000 版本号并格式化为 "V%u.%u" |
mid_meter_detect | static | 1157 | MID 探测外壳,失败 30 次后置丢失并清运行数据 |
set_chnt_wireless_config_info | static | 1188 | 向指定无线节点写 0xE301 频段配置 |
chnt_wireless_config_info | static | 1222 | 读 AP(0xE1) 0xE300 起 27 个寄存器,解析三个节点汇总信息,返回子节点在线位图 |
get_chnt_wireless_module_info | static | 1277 | 读单个无线节点 0xEA00 起 19 个寄存器(型号/SN/软硬件版本) |
clear_chnt_wireless_moudle_info | 全局 | 1331 | 按索引清空某个节点的型号/版本/SN 缓存 |
chnt_wireless_detect | static | 1344 | 正泰无线读状态机(state 0→3),汇总 AP/STA1/STA2 信息并判定 u8_found |
meter_detect | static | 1424 | 按 site 分发到 GRID/PV/无线/MID 四个探测函数 |
agf200_grid_meter_clear | static | 1463 | AGF200(GRID) 清表前的「写使能」握手 |
adl400n_grid_meter_clear | static | 1493 | ADL400N(GRID) 清表前的「写使能」握手 |
grid_meter_clear | static | 1523 | 按当前型号选择清表命令字并比对定长应答,成功返回 TRUE |
agf200_pv_meter_clear | static | 1686 | AGF200(PV) 清表使能握手 |
pv_meter_clear | static | 1718 | PV 侧清表,按型号选命令、重算 CRC、比对定长应答 |
meter_run_data_clear | 全局 | 1884 | 清空指定位置的运行数据(GRID 额外清 t_bb_debug) |
meter_clear | static | 1921 | 先清本地数据再下发清表命令,按 site 分发 |
meter_data_printf | static | 1956 | GRID 电参量 printf 打印,仅在 METER_DEBUG_INFO == 1 时编译 |
grid_total_pahse_grid_energy_statistics | static | 1984 | GRID 侧总电能软件累加(电表不支持总电能时使用) |
grid_total_pahse_pv_energy_statistics | static | 2053 | PV 侧总电能软件累加,逻辑同上 |
meter_read_data_negate | static | 2152 | 对传入运行表的电流/有功/无功/功率因数整体取反(含 PHASE_T) |
meter_read_data_3ct | static | 2166 | 按 CT 倍率缩放电流与功率,内部硬编码操作 g_t_meter.t_grid_info |
meter_read_data_for_ADL400_3p | static | 2175 | 从响应报文偏移解析 ADL400 三相正反向电能(6 个 u32 大端) |
bytes_to_double | static | 2191 | 8 字节大端 → double(ADL400N 电能) |
bytes_to_float_be | static | 2203 | 4 字节大端 → float(ADL400N 电参量) |
grid_meter_read_data_for_ADL400N_3p | static | 2215 | 解析 ADL400N 三相分相电能(偏移 67/75/115/123/163/171) |
grid_meter_read_data_for_ADL400N_3p_total | static | 2225 | 解析 ADL400N 总电能(偏移 11/19) |
grid_meter_read_data_for_ALPHA_3p | static | 2231 | 解析 ALPHA 三相分相电能(偏移 3~26) |
grid_meter_read_data_for_ALPHA_3p_total | static | 2247 | 解析 ALPHA 总电能(偏移 27~34) |
meter_read_data_for_SM60A | static | 2256 | SM60A 解析:单相电参量 + 电能三次取中值 |
meter_read_data_for_ADL400 | static | 2339 | ADL400 解析:总电能 / 分相电能(com_switch 130~134)/ 电参量三态 |
meter_read_data_for_ADL3000 | static | 2500 | ADL3000 解析:总电能 + 电参量两态 |
meter_read_data_for_ADL400N | static | 2604 | ADL400N 解析:浮点电参量 + 分相/总电能,支持 EMS 自算总值 |
meter_read_data_for_ACR10R | static | 2730 | ACR10R 解析:电参量 + 电能;无 CT 时强制电流/功率清零 |
meter_read_data_for_ALPHA | static | 2854 | ALPHA(正泰/其他)解析:分相电能 + 总电能 + 电参量 |
meter_read_data_for_AGF200 | static | 2982 | AGF200 解析:电参量 + 电能(÷10 换算) |
grid_meter_read_data | static | 3075 | GRID 侧核心调度与解析总入口:组帧、发收、丢失判决 |
pv_meter_read_data | static | 3680 | PV 侧核心调度与解析总入口 |
be_to_float | 全局 | 4110 | 大端 4 字节 → float(联合体实现),供 MID 与外部使用 |
mid_meter_parse_data | static | 4133 | MID 表数据解析:浮点电参量 + 电能 |
mid_meter_read_data | static | 4273 | MID 表组帧、发收、解析与丢失处理 |
meter_data_read | static | 4349 | 按 site 分发到 GRID/PV/MID 三个读数据函数 |
ct_handle | 全局 | 4381 | CT 采样通道:500ms 节流,从 ct_read_* 取数填充运行表 |
meter_config_handle | static | 4465 | 检测型号/版本/BB 身份变化并置 s_u8_save_flag,触发配置落盘 |
meter_fault_handle | static | 4539 | 处理清数据请求,并把三处 u8_comm_lost_err 映射到故障位 4/5/29 |
set_meter_triphase_sequnece_adjust_mark | 全局 | 4608 | 正泰/安科瑞相序自适应开关命令下发(含 3 次重试) |
meter_query_grid_adjust_ready | static | 4778 | 读 GRID 侧 0x0128,判断相序自适应是否完成 |
meter_query_pv_adjust_ready | static | 4821 | 读 PV 侧 0x8128,判断相序自适应是否完成 |
is_chint_meter_type | static | 4864 | 判断型号字符串是否属于 5 种正泰型号之一 |
meter_get_meter_type | 全局(无头文件声明) | 4889 | 返回厂商:Chint / Acrel / 0 |
chint_adjust_state_machine | static | 4910 | 正泰相序自适应主状态机(state 0→4) |
meter_query_triphase_sequnece_adjust_scheme | static | 5028 | 相序自适应总入口,按 u8_meter_triphase_sequnece_flag(1 开/2 关) 分派 |
set_meter_phase_sequnece_adjust_mark | 全局 | 5073 | 正泰老表 DTSU666-100/40mA 且版本 ≥V127 时的相序命令 |
grid_meter_query_dts_special_command | static | 5126 | 读 GRID 侧 0x0127,解析单相接反标志与相序调整状态字 |
pv_meter_query_dts_special_command | static | 5176 | 读 PV 侧 0x8127,解析单相接反标志与相序调整状态字 |
grid_meter_set_single_negate | static | 5226 | 写 GRID 侧 0x0127,下发单相接反配置 |
pv_meter_set_single_negate | static | 5277 | 写 PV 侧 0x8127,下发单相接反配置 |
meter_control_single_negate_handle | static | 5328 | 单相接反控制逻辑:实际值 ≠ 配置值且状态字为 1/2 时下发或重查 |
meter_handle | static | 5369 | 单块电表主循环:未找到则定时探测,找到则清表或读数据 |
wireless_meter_is_all_lost | 全局 | 5450 | GRID 与 PV 无线电表是否全部丢失 |
wireless_meter_handle_lost | 全局 | 5456 | 无线电表丢失处理:清标志、清数据、按并离网判故障 |
wireless_meter_handle | static | 5485 | 无线电表专用循环:10s 无数据判丢失 + 处理清表请求 |
chnt_wireless_moudle_handle | static | 5528 | 无线模组按 1s 节拍调用 meter_detect(CHNT_WIRELESS) |
chnt_wireless_moudle_info_check | static | 5550 | 比较配置频段与模组实际频段,不一致则返回 TRUE |
set_chnt_wireless_moudle_info | static | 5567 | 无线模组写状态机(state 0→3),逐节点下发频段 |
chnt_wireless_moudle_check | 全局(无头文件声明) | 5637 | 无线模组总入口:自动使能判定、读/写状态机调度、配置变化上报 |
task_meter | 全局 | 5710 | 电表守护任务主循环(uCOS-II 静态任务) |
meter_set_meter_or_ct | 全局 | 5898 | 设置 GRID/PV 为 CT 还是电表 |
meter_get_meter_or_ct | 全局 | 5916 | 读取 GRID/PV 为 CT 还是电表 |
meter_set_ct_rate | 全局(无头文件声明) | 5937 | 设置直接接 CT 的倍率 |
meter_get_ct_rate | 全局(无头文件声明) | 5955 | 读直接接 CT 的倍率 |
meter_set_meter_ct_enable | 全局 | 5976 | 设置电表侧是否带 CT |
meter_get_meter_ct_enable | 全局 | 5994 | 读电表侧是否带 CT |
meter_set_meter_ct_rate | 全局 | 6015 | 设置电表侧 CT 倍率 |
meter_get_meter_ct_rate | 全局 | 6033 | 读电表侧 CT 倍率 |
meter_set_meter_negate | 全局 | 6054 | 设置方向取反 |
meter_get_meter_negate | 全局(无头文件声明) | 6072 | 读方向取反 |
meter_get_meter_type_now | 全局 | 6092 | 读当前识别到的型号(GRID/PV/MID) |
meter_get_meter_type_old | 全局 | 6115 | 读上次保存的型号(GRID/PV) |
meter_get_pv_meter_addr_change_flag | 全局 | 6135 | GRID 侧为 BB10K/23K 且 gridforming 使能 → PV 地址改用 0x0B |
meter_read_bb_sn | 全局 | 6153 | 返回 BB 序列号字符串(23K 用 ASCII SN) |
meter_read_bb_software_version | 全局 | 6183 | 返回 BB 软件版本字符串("V%u.%02u") |
meter_get_meter_modle | 全局 | 6207 | 按型号枚举返回类型字符串,ALPHA/ADL400N 返回保存的型号原串 |
meter_read_meter_sn | 全局 | 6335 | 返回电表 SN 字符串 |
meter_read_software_version | 全局 | 6386 | 返回电表软件版本字符串 |
meter_read_hard_version | 全局 | 6431 | 返回电表硬件版本字符串(BB 固定 "V1.02") |
meter_read_voltage | 全局 | 6476 | 读某位置某相电压 |
meter_read_current | 全局 | 6501 | 读某位置某相电流 |
meter_read_actpower | 全局 | 6526 | 读某位置某相有功功率 |
meter_read_reactpower | 全局 | 6551 | 读某位置某相无功功率 |
meter_read_apparent | 全局 | 6576 | 视在功率(软件 U×I/1000 计算,非电表值) |
meter_read_factor | 全局 | 6609 | 读某位置某相功率因数 |
meter_read_frequent | 全局 | 6634 | 读频率 |
meter_read_einput | 全局 | 6654 | 读正向电能(PV 侧仅支持 PHASE_T) |
meter_read_eoutput | 全局 | 6685 | 读反向电能(PV 侧仅支持 PHASE_T) |
meter_read_connect_status | 全局 | 6716 | 连接状态 = 未丢失 且 已找到 |
meter_set_connect_status | 全局 | 6738 | 设置连接状态并强制 u8_found = FALSE |
meter_set_clear_flag | 全局 | 6773 | 置位清表请求 |
meter_clear_run_data_flag | 全局 | 6790 | 置位清运行数据请求 |
get_energy_data_enable | 全局 | 6813 | 读电能上报使能 |
IsUpgradeMeter | 全局 | 6824 | 云端是否要求升级电表 |
GetPercentUpgradeMeter | 全局 | 6835 | 电表/BB 升级进度百分比 |
meter_set_run_data | 全局 | 6882 | P1 通道注入整块电网数据(含无功反算、周期自适应) |
meter_set_grid_hw_ver | 全局 | 6983 | 外部写入 GRID 硬件版本(首次置保存标志) |
meter_set_grid_com_sta | 全局 | 7003 | 外部设置 GRID 通信状态 |
meter_set_clear_grid_data | 全局 | 7021 | 外部设置 GRID 清数据标志 |
meter_set_grid_wifi_P1_fault | 全局 | 7039 | 设置/清除 WIFI-P1 配对故障位 |
meter_set_refresh_flag | 全局 | 7057 | 设置 P1 刷新标志 |
meter_read_refresh_flag | 全局 | 7075 | 读 P1 刷新标志 |
meter_read_P1_refresh_time | 全局 | 7086 | 读 P1 刷新周期(1 或 10,单位秒) |
meter_set_P1_lose_flag | 全局 | 7097 | 设置 P1 通信丢失标志 |
meter_get_P1_lose_flag | 全局 | 7108 | 读 P1 丢失标志(仅 P1 采样时有效) |
meter_set_chnt_wireless_module_enable | 全局 | 7124 | 使能/禁止无线模组,禁止时清其运行数据 |
meter_get_chnt_wireless_module_enable | 全局 | 7138 | 读无线模组使能 |
meter_set_chnt_wireless_module_regional_freq_bands | 全局 | 7150 | 设置无线模组频段 |
meter_get_chnt_wireless_module_regional_freq_bands | 全局 | 7162 | 读无线模组频段 |
meter_set_chnt_wireless_bound | 全局 | 7172 | 写 0xEB00 下发无线模组绑定码 |
meter_set_saveConfigFlag | 全局 | 7201 | 外部置位配置保存标志 |
meter_set_ct_sampling | 全局 | 7212 | 解析云端位掩码设置 GRID/PV 采样来源 |
4. 核心逻辑解析
4.1 任务框架与调度时序
task_meter()(meter.c:5710)是唯一驱动点。启动时先调 4 次 meter_table_init(),然后进入无限循环:
for(;;) {
if (Get_Status_Is_Updated_From_Server(UPDT_Meter)) { ...升级流程...; OSTimeDlyHMSM(0,0,0,50); IWDG_Feed(); continue; }
meter_config_handle(); // 配置落盘
meter_fault_handle(); // 清数据请求 + 故障位
meter_control_single_negate_handle(); // 单相接反校准
meter_query_triphase_sequnece_adjust_scheme(); // 相序自适应
chnt_wireless_moudle_check(); // 无线模组
if (t_grid_info.u8_detect_flag) { ...GRID 分支... }
if (!(GRID 是 BB 且 BB 未运行)) {
if (t_pv_info.u8_detect_flag) { OSTimeDlyHMSM(0,0,0,100); ...PV 分支... }
else { t_pv_info.e_type_now = MAX_METER_TYPE; t_pv_info.u8_comm_lost_err = FALSE; }
}
if (u8_mid_meter_function_enable) meter_handle(MID_METER, &t_mid_info);
genset_comm_handle(); // 油机控制器
wireless_read_P1_data(); // P1 电网数据
OSTimeDlyHMSM(0, 0, 0, 300);
IWDG_Feed();
}
实际节拍:主延时 300ms,PV 分支额外 100ms,因此 GRID 侧每约 400ms 完成一次「发一帧 + 收一帧 + 解析」,PV 侧每约 400ms 一次,MID 每次循环一次。GRID/PV/MID 共用 s_dataTmp.com_switch 这一 u8 计数器各自独立(三个函数各有自己的 static),所以单侧完整轮询 256 次 ≈ 100s 才回到同一状态。
互斥:全程无信号量/邮箱。原因是 GRID/PV/MID/无线全部挤在 USART1/USART3 两条 485 上,只能串行;任务的单线程执行天然保证串行。唯一的跨任务写入者是 wireless_read_P1_data()(P1 无线采集通道)通过 meter_set_run_data() 注入,以及 CT 采样任务通过 ct_handle() 写入 —— 这两个入口都没有加锁,与 task_meter 对 g_t_meter.t_grid_info.t_data 的写存在理论上的一致性窗口(见风险点)。
GRID 的三种采样来源(meter.c:5783-5818):
METER_SAMPLING:走正常 485 流程,无线电表走wireless_meter_handle(),有线走meter_handle()。CT_SAMPLING:e_type_now = CT,不再做 485 通信;由 CT 采样任务调grid_ct_handle()填数;若|OSTimeGet() - u32_get_data_last_time| > 15*1000则清运行数据(15s 看门狗式保护)。P1_SAMPLING:e_type_now = SMILE_P1_WB01,数据由meter_set_run_data()注入。
4.2 Modbus RTU 帧格式与收发/校验
读命令(8 字节)
[0] 从站地址 [1] 0x03 [2] 起始寄存器高 [3] 起始寄存器低
[4] 寄存器数高 [5] 寄存器数低 [6] CRC低 [7] CRC高
CRC 由 GetCrcCode_Modbus(l_u8_tx_buf, 6) 计算(标准 CRC-16/Modbus,由 crc.c 提供)。
读应答
[0] 从站地址 [1] 0x03 [2] 字节数 = 寄存器数 × 2 [3..] 数据 [n-2] CRC低 [n-1] CRC高
写单寄存器(0x06,8 字节)
[0] 地址 [1] 0x06 [2] 寄存器高 [3] 寄存器低 [4] 值高 [5] 值低 [6] CRC低 [7] CRC高
写多寄存器(0x10,MID 清零用 11 字节)
[0] 地址 [1] 0x10 [2][3] 起始寄存器 [4][5] 寄存器数 [6] 字节数 [7][8] 数据 [9] CRC低 [10] CRC高
三重校验:所有调用点都按 (len > 0) && (buf[0] == 目标地址) && (buf[1] == 功能码) && (buf[2] == 寄存器数×2) 判定。meter_recv_data()(meter.c:66)内部另做 CRC 校验,不匹配则 *len = 0。
超时与重试:
- 发送超时由
usart1_send_data(buf, len, 1000)的 1000ms 参数承担;meter_recv_data()本身不重试,只做一次「有无异步数据」判断。 - 需要可靠性的命令由调用点自己写
for (i = 0; i < 3; i++)重试循环:mid_meter_send_clear_data()(带 50ms 间隔)、set_meter_triphase_sequnece_adjust_mark()、set_meter_phase_sequnece_adjust_mark()、meter_query_*_adjust_ready()、ADL400N 探测时的连读 3 次。 - 周期性读数据不做重试,靠
u8_lost_cnt连续失败计数实现软容错。
注意:meter_recv_data() 在 l_u16_len < 2 时直接 return,不写 *len(meter.c:81-84),mid_meter_recv_data() 同样(meter.c:145-148)。调用方多数把 l_u16_recv_num 在函数开头初始化为 0,但 mid_meter_send_clear_data() 的循环复用同一变量,存在沿用上一轮值的风险。
4.3 电表探测与型号识别
探测在 u8_found == FALSE 时由 meter_handle() 每 1000ms 触发一次(meter.c:5372-5396),内部是 static u8 s_u8_index 每次自增取模的轮询表:
GRID(7 项,meter.c:300-308):
| index | 地址 | 命令 | 识别判据 | |
|---|---|---|---|---|
| 0 | 0x03 | 03 0015 0001 | buf[3] == 0x03 → SM60A | |
| 1 | 0x01 | 03 003F 0001 | 再读 01 03 F000 000B,buf[17] 含 "ADL4" → ADL400,否则 ADL3000 | |
| 2 | 0x01 | 03 005C 0005 | buf[12] == 0x00AB(BACKUPBOX_ID)后再读 0x0059 得型号码:0/12/67/78→BACKUPBOX_5K,64→BACKUPBOX_10K,302→BACKUPBOX_23K | |
| 3 | 0x05 | 03 0000 0001 | buf[4] == 0x05 → ACR10R | |
| 4 | 0x01 | 03 0000 0019 | buf[5] 含 "DTSU666" 或 "DDSU666",再由 buf[4]:1→ALPHA_NCT,2→ALPHA_SINGLE_LOOP,3→ALPHA_DOUBLE_LOOP;同时抽取 buf[21..36] SN、buf[37..44] 软件版本、buf[45..52] 硬件版本 | |
| 5 | 0x07 | 03 9C40 0001 | `(buf[3]<<8\ | buf[4]) == 202 → AGF200` |
| 6 | 0x01 | 03 F009 0006 | buf[3] 含 "ADL200M" 或 "ADL400M",再按 buf[14]/buf[6] 判 ADL400M_CT2('2'+'4');随后连读 3 次 01 03 1009 0007 取 SN |
PV(8 项,meter.c:662-671):结构与 GRID 一致,但第 1/2/4 项在 meter_get_pv_meter_addr_change_flag() == TRUE 时把地址改为 PV_GRIDFORMING_EN_METER_ADDR(0x0B);另外第 5 项用于 ALPHA_DOUBLE_LOOP(地址 GRID_ALPHA_METER_ADDR,偏移 0x8000)。
探测壳(*_meter_detect())的丢失判决:连续 30 次探测失败后,GRID 侧仅在「并网模式 + 系统控制已启动 + 非 UPS 模式 + 电网频率 ≥ 45.00Hz」时才置 u8_comm_lost_err = TRUE(meter.c:623-641);PV 侧在有 u8_gridforming_en 时改用 inverter_read_backup_freq() < 4500 判定(meter.c:975-1000)。
4.4 com_switch 轮询调度:电参量 / 总电能 / 分相电能交替
meter_read_data_for_* 与 grid_meter_read_data() 间的耦合全靠 METER_DATA_TEMP.com_switch 这一个 u8 计数器。每次进入 grid_meter_read_data() / pv_meter_read_data() / mid_meter_read_data() 都先 s_dataTmp.com_switch++,然后:
com_switch 取值 | 读什么 | 报文(以 ADL400 为例) |
|---|---|---|
| 124 / 126 / 128 / 其余(默认电参量) | 每 3 次中取 3 次读电能,其余 253 次读电参量 | 电能 00 0A 00 0C;电参量 00 60 00 18 |
| 130 / 132 / 134(仅 ADL400 / ADL400N) | 第 2 段电能读:分相正反向电能 | E2 6C 00 0C |
由于计数器每次循环只加 1,电能在 256 次中只有 3 次命中(或 6 次,ADL400 含分相段),即约 100s 一个电能刷新周期,这是刻意的「电参量高频 / 电能低频」设计。
三次电能的中值表决由 midvalue(a,b,c) 完成:只有第 3 次(com_switch == 128 或 134)才把 3 次采样取中位数写入 t_data.u32_energy_input/output[PHASE_T],用中位数抗单次通信误码。
4.5 各厂商解析与量纲换算
所有换算目标量纲见 METER_DATA_T 注释(电压 0.1V、电流 0.01A、有功 0.001kW、无功 0.001kvar、频率 0.01Hz、电能 0.01kWh)。
| 型号 | 电参量编码 | 电压 | 电流 | 功率 | 频率 | 电能 |
|---|---|---|---|---|---|---|
| SM60A | 16 位大端 | buf[3..4](仅 A 相,B/C 置 0) | buf[5..6] | buf[7..8] | buf[15..16] | 32 位大端 buf[3..6]/buf[19..22] |
| ADL3000 / BB | 16 位大端,起始 0x0060 | buf[5..10] | buf[11..16] | buf[17..24] | buf[49..50] × 10 | buf[3..6]/buf[23..26] |
| ADL400 | 同上 | buf[5..10] | buf[11..16] | buf[17..24] | buf[49..50] × 10 | buf[3..6]/buf[23..26];分相段 buf[3..26] |
| ADL400N | IEEE754 大端浮点,起始 0x2100 | buf[3..6]/[7..10]/[11..14] × 10 | buf[27..38] × 100 | buf[43..55] | buf[107..110] × 100 | double 大端 × 100(偏移 11/19/67/75/115/123/163/171) |
| ACR10R | 32 位大端 | buf[3..8] | buf[15..20] | buf[23..38] | buf[21..22] | buf[3..6]/buf[7..10] |
| ALPHA(正泰) | 32 位大端 | buf[3..8] × 10 | buf[9..14] × 10 | buf[17..32] | buf[15..16] | 分相 buf[3..26],总 buf[27..34] |
| AGF200 | 16 位大端 | buf[21..22]/[15..16]/[17..18] | buf[5..10] | buf[37..42]、总 buf[35..36] | buf[31..32] | buf[3..6]/buf[19..22],再 ÷10 |
| MID 三相表 | IEEE754 大端浮点 | buf[3..14] | buf[15..26] ÷ 10 | buf[27..42] ÷ 10 | buf[127..130] | buf[3..6]/buf[23..26] × 100 |
CT 倍率缩放由 meter_read_data_3ct(l_u16_ct_rate, div) 完成:电流/功率 = 原值 × rate ÷ div。注意 ADL3000/ADL400 用 div = 1(倍率直接相乘),ACR10R 用 div = 100(原始单位为 0.01A/0.001kW 需先归一到 0.01A)。ACR10R 在未接 CT 时会把电流与功率全部强制清零(meter.c:2829-2846)。
方向取反由 meter_read_data_negate() 统一完成:对 A/B/C/T 四个索引的 s16_current、s32_actpower、s32_reactpower、s16_factor 全部取负。
4.6 电能三次取中值 与「EMS 自算总电能」
两条电能获取路径:
- 电表直接给总电能(
u8_ems_calculate_total_phase_energy_flag == FALSE):直接用三次读数的中位数。 - 电表不给总电能(
flag == TRUE,正泰软件版本为V103/V104时由grid_meter_find()自动置位,meter.c:492-508):EMS 用瞬时功率积分自算,函数为grid_total_pahse_grid_energy_statistics()(GRID)与grid_total_pahse_pv_energy_statistics()(PV)。
自算公式(meter.c:1984-2113):
Δt = OSTimeGet() - s_u32Time_tick (ms,含 u32 回绕处理)
s_u32Time_sum += Δt
if (P > 0) s_u32_input_temp += |P| * Δt // P 单位 0.001kW
else s_u32_output_temp += |P| * Δt
if (s_u32Time_sum >= 60000) { // 满 1 分钟
s_u32_input += s_u32_input_temp / (s_u32Time_sum * 60);
s_u32_input_temp %= (s_u32Time_sum * 60);
t_data.u32_total_phase_energy_input += s_u32_input / 10;
s_u32_input %= 10; // 保留 0.1 单位余数
... 输出侧同理 ...
s_u32Time_sum = 0;
}
尺度推导:P(0.001kW) × Δt(ms) 即 W·ms;除以 s_u32Time_sum × 60 得到 0.001kW·h 量纲,再 /10 转成 0.01kWh。余数保留是为了避免每分钟截断造成的累积误差。
超时保护:每次进入先判 abs(OSTimeGet() - s_u32Time_tick) > 5000,超过 5s 认为本轮无效,s_u32Time_tick 重取并 return,避免长间隔采样导致能量被高估。
4.7 清表(恢复出厂)
meter_handle() 中 u8_clear_flag == TRUE 时走 meter_clear(site)(meter.c:1921),先 meter_run_data_clear() 清本地,再下发型号相关的清表命令:
| 型号 | 命令(含 CRC) | 应答比对 |
|---|---|---|
| ADL3000 / BB 系列 | 01 AA 64 06 00 01 02 00 00 08 FB | 01 AA 64 06 00 01 27 23 |
| SM60A | 03 AA 00 00 00 0A 14 + 20 字节 0 + CRC | 03 AA 00 00 00 0A 98 37 |
| ACR10R | 05 10 1388 0001 02 A5A5 13F2 | 05 10 1388 0001 84 E3 |
| ALPHA | 01 10 00F3 0001 02 029A 3398 | 01 10 00F3 0001 F1 FA |
| AGF200 | 先 9CAC 使能握手,延时 30ms,再 9CAD 清表 | 07 10 9CAD 0001 BE 1E |
| ADL400 | 01 AA 0001 0001 02 0000 2C8A | 01 AA 0001 0001 89 D2 |
| ADL400M_CT2 | 先 6400 0000 使能,延时 30ms,再 6400 0002 | 01 64 0002 0001 10 02 |
失败重试上限 3 次(u8_clear_cnt >= 3 后清标志放弃)。PV 侧 AGF200 不额外延时,pv_meter_clear() 会重新计算 CRC(meter.c:1805-1807),因为 PV 型号的地址可能被 gridforming 改为 0x0B。
4.8 通信丢失判决与故障上报
两层判决:
- 数据层(
grid_meter_read_data()/pv_meter_read_data()/mid_meter_read_data()):每轮u8_lost_cnt++,成功解析则清 0。超过阈值后清运行数据、u8_found = FALSE、按并/离网条件决定u8_comm_lost_err。- GRID 阈值:默认
> METER_LOST_MAX_CNT(15);若g_t_inv_info.t_config_data的软/硬件限功率使能,阈值降到>= 3(meter.c:3622-3636)——限功率场景要求更快感知丢表。 - PV 阈值:固定
> 15(meter.c:4062)。 - MID 阈值:固定
> 15(meter.c:4330)。 - 不报丢失的例外:
get_offgrid_mode_grid_state() == FALSE(离网且未接市电)时强制u8_comm_lost_err = FALSE;inverter_read_run_mode() == cUPS或inverter_read_grid_freq() < 4500(<45Hz)时也不报。
- GRID 阈值:默认
- 无损层(
grid_meter_detect()/pv_meter_detect()):连续 30 次探测失败才判丢失。
故障位映射(meter_fault_handle(),meter.c:4539):
| 条件 | 动作 |
|---|---|
t_grid_info.u8_comm_lost_err | ems_set_fault_bit(4, ...) |
t_pv_info.u8_comm_lost_err | ems_set_fault_bit(5, ...) |
u8_mid_meter_function_enable && t_mid_info.u8_comm_lost_err | ems_set_fault_bit(29, ...),MID 未使能时强制清 bit 29 |
t_*_info.u8_clear_run_data_flag | 调 meter_run_data_clear() 并清标志(GRID/PV/MID/无线四处) |
4.9 正泰 DTS 相序自适应与单相接反
相序自适应(总入口 meter_query_triphase_sequnece_adjust_scheme(),meter.c:5028):
u8_meter_triphase_sequnece_flag == 1(开启):meter_get_meter_type()为Chint走chint_adjust_state_machine();为Acrel则对 GRID/PV 各发一次set_meter_triphase_sequnece_adjust_mark(site, 1)。u8_meter_triphase_sequnece_flag == 2(关闭):正泰额外meter_reset()并清标志;安科瑞只发关闭命令。
正泰状态机(chint_adjust_state_machine(),meter.c:4910):
state 0 : set_meter_triphase_sequnece_adjust_mark(GRID, 1) → state 1,记 grid_start_time
state 1 : 距开始 >1s 后查 meter_query_grid_adjust_ready()
TRUE → state 2
距开始 >30s 仍失败 → 复位 state=0,清 u8_meter_triphase_sequnece_flag
state 2 : set_meter_triphase_sequnece_adjust_mark(PV, 1) → state 3,记 pv_start_time
state 3 : 距开始 >1s 后查 meter_query_pv_adjust_ready()
TRUE → state 4
>30s → 复位 state=0,清 flag
state 4 : s_adjust_state.u8_all == BOTH_ADJUST_READY_MASK(0x03) → meter_reset(),清 flag,复位 state=0
即「先 GRID 后 PV,串行 + 各 30s 超时」,全部完成后强制 meter_reset() 让新相序生效。
命令细节(set_meter_triphase_sequnece_adjust_mark(),meter.c:5073):只对型号字符串含 "DTSU666-100/40mA"、版本 ≥ "V127" 且 u8_found 的电表生效。命令字为「地址 + 0x06 + 寄存器高 + 寄存器低」,GRID 写 0x0128,PV 写 0x8128,写入值在 l_value == 1 时被映射为 0x0002。命令地址的选择分两种情况:
- 单回路(
DDSU666iDW433/iDM433):GRID 用GRID_ALPHA_METER_ADDR(0x01),PV 用PV_ALPHA_METER_ADDR(0x02),靠从站地址区分两侧。 - 双回路(
DDSU666iDW446/iDM446):两侧地址都是 0x01,靠寄存器号高低位区分(GRID 0x0128 / PV 0x8128)。
安科瑞 ADL400 走 0x10 写 0x9220,数据 0x0101 开启 / 0xFFFF 关闭,成功后调 meter_reset() 并清 u8_meter_triphase_sequnece_flag。
单相接反校准(meter_control_single_negate_handle(),meter.c:5328):
- 查询:
grid_meter_query_dts_special_command()/pv_meter_query_dts_special_command()读0x0127/0x8127起 2 个寄存器。buf[3..4]的 bit1/bit3/bit5 分别是 A/B/C 相接反标志((temp & 0x03) >> 1等);buf[5..6]是相序调整状态字u8_phase_sequence_adjust_mark。 - 判决:查询值与配置值
u_*_meter_single_negate_flag.u8_all不同时——- 状态字 == 1:调
grid_meter_set_single_negate()/pv_meter_set_single_negate()下发。下发值 =0x15 + (C<<4 | B<<2 | A),即基准 0x15 叠加三相位掩码;写0x0127/0x8127。 - 状态字 == 2:只把「立即重查」标志置
TRUE,下轮重新读一遍。
- 状态字 == 1:调
- 触发源:
meter_handle()中当u8_grid_meter_query_dts_special_cmd_flag == TRUE时调查询(meter.c:5434、5441);电表未找到时该标志被强制置TRUE(转入「发现后立即重查」)。
厂商识别:meter_get_meter_type()(meter.c:4889)——u8_grid_meter_type 或 u8_pv_meter_type 命中 5 种正泰型号(is_chint_meter_type())返回 Chint(1);命中 "ADL400" 返回 Acrel(2);否则 0。
4.10 正泰无线模组(AP + 双 STA)
读状态机 chnt_wireless_detect()(meter.c:1344),1s 一拍:
state 0 : chnt_wireless_config_info() 读 AP(0xE1) 的 0xE300 起 27 个寄存器
→ 返回 (child_nodes_state << 1 | 0x01) & 0x07(bit0 恒 1 代表 AP 存在)
首次变化时:值为 0 → meter_clear_run_data_flag(CHNT_WIRELESS)
非 0 → 清三个节点的型号/版本/SN 缓存
若非 0 → state 1
state 1 : 若 bit0 置位 → get_chnt_wireless_module_info(0xE0|1, 0) → state 2
state 2 : 若 bit1 置位 → get_chnt_wireless_module_info(0xE0|2, 1) → state 3
state 3 : 若 bit2 置位 → get_chnt_wireless_module_info(0xE0|3, 2),读成功且 GRID/PV 电表
都在线 → u8_found = TRUE,否则 FALSE;→ state 0
寄存器映射(chnt_wireless_config_info(),meter.c:1222):
| 报文偏移 | 含义 |
|---|---|
buf[3..4] / [27..28] / [47..48] | 三个节点发射功率 u8_module_eirp[0..2] |
buf[5..6] / [29..30] / [49..50] | 三个节点频段 u8_regional_freq_bands[0..2] |
buf[7..8] / [31..32] / [51..52] | 三个节点信号强度 s16_module_rssi[0..2] |
buf[9..10] | 模组连接状态 u8_module_connect_state |
buf[11..12] | 子节点数量 u8_module_child_nodes_num |
buf[13..14] | 子节点连接状态 u8_module_child_nodes_state |
写状态机 set_chnt_wireless_moudle_info()(meter.c:5567),1s 一拍,只有当 chnt_wireless_moudle_info_check() 发现某节点实际频段 ≠ 配置频段才启动:
state 0 : child_nodes_state > 1(至少有一个 STA) → state 1
state 1 : bit2 置位 → set_chnt_wireless_config_info(0xE3);成功 → state 2;未置位直接 → state 2
state 2 : bit1 置位 → set_chnt_wireless_config_info(0xE2);成功 → state 3;未置位直接 → state 3
state 3 : bit0 置位 → set_chnt_wireless_config_info(0xE1);成功 → state 0;未置位 → state 0
写命令为 0x06 写 0xE301,数据取 meter_get_chnt_wireless_module_regional_freq_bands()。
总入口 chnt_wireless_moudle_check()(meter.c:5637):若 GRID 或 PV 型号字符串含 "iDW" 则自动使能无线模组,否则自动禁用并清数据;使能后按 u8_found 决定走读状态机还是写状态机;最后把模组 SN/型号/软硬件版本与静态镜像比较,变化则 Set_Setup_Config_Extend2_Status(FALSE) 触发云端上传配置。另外 u8_grid_info.u8_found / u8_pv_info.u8_found 变化会强制 t_chnt_wireless_info.u8_found = FALSE 重新探测。
无线模组丢失:wireless_meter_handle()(meter.c:5485)按 u32_get_data_last_time 判 10s(WIRELESS_METER_LOST_TIME)无更新,调 wireless_meter_handle_lost() 清标志、清数据、按并/离网条件置故障。
4.11 CT 与 P1 两条替代通道
CT 通道 ct_handle()(meter.c:4381):带 500ms 节流(static u32 s_u32_meter_handle_tick[MAX_CT_SITE]),从 ct.c 的 ct_read_voltage/current/actpower/reactpower/einput/eoutput/frequent 取数填入传入运行表。方向取反时电流、有功、无功取负且输入输出电能互换(u32_energy_input = ct_read_eoutput())。同时把该表的软件版本与 SN 清零、硬件版本清零并置 s_u8_save_flag。注释说明它从 task_meter 移到了 CT 采样任务,目的是避免 485 超时拖慢刷新。
P1 通道 meter_set_run_data(void* meter_data_p)(meter.c:6882):由 net/ems/wireless/wireless_driver.c 的 wireless_read_P1_data() 调用,把 P1(SMILE-P1-WB01)解析出的整块数据搬进 g_t_meter.t_grid_info,并做三件额外的事:
- 无功反算:
Q = sqrt((U×I)² − P²),对 A/B/C 三相逐相计算,PHASE_T为三相之和。 - 频率来源切换:直接取
parallel_read_system_inv_grid_freq()(并机系统的逆变器电网频率),不用电表值。 - 刷新周期自适应:P1 上报的
u32_p1_cycle(单位 10ms)≥ 500(即 5s)时把刷新周期定为 10S,否则 1S。
最后置 u8_refresh_flag = TRUE 通知上层数据已更新。
采样来源设置 meter_set_ct_sampling(u32 l_u32_res)(meter.c:7212):位定义 bit0 = PV 侧(0=CT,1=电表)、bit1 = GRID 侧(0=CT,1=电表)、bit2 = GRID 侧(0=无 P1,1=有 P1);只有在值变化时才改写,并连带把 CT 使能/倍率清零、连接状态与运行数据清零、无线模组数据清零。
4.12 电表与 BackupBox 升级入口
task_meter() 循环开头查 Get_Status_Is_Updated_From_Server(UPDT_Meter)。为真则:
- 清 GRID/PV/MID 运行数据,四处
u8_found = FALSE。 - 按
get_meter_updt_object()分派:GRID_METER:上次型号是 BB 三种之一 →t_bb_info.u8_bb_running_state = FALSE后走bb_update()(bb_update.c:305);否则meter_upgrade_handle()(meter_upgrade.c:285)。PV_METER:型号为 BB 三种之一或ALPHA_DOUBLE_LOOP→ 直接Set_Status_Is_Updated_From_Server(UPDT_Meter, FALSE)放弃(这些型号不支持升级);否则meter_upgrade_handle()。CHNT_WIRELESS:meter_upgrade_handle()。- 其他:放弃。
OSTimeDlyHMSM(0,0,0,50)+IWDG_Feed()后continue。
升级期间锁表:meter.c:5820-5826 判断「GRID 是 BB 且 BB 未运行」时,跳过 PV 与后续所有电表通信,把 485 总线让给 BB 升级。进度由 GetPercentUpgradeMeter() 聚合 GetPercentUpgradeBB() / get_percent_upgrade_meter()。
4.13 配置持久化
s_u8_save_flag 是文件级静态标志,任何位置检测到配置变化即置位。meter_config_handle()(meter.c:4465)每轮检查:
- GRID 软件版本与静态镜像不同 →
Set_Setup_Config_Status(FALSE)(通知云端上传配置)。 e_grid_meter_type_old != t_grid_info.e_type_now(且e_type_now != 0)→ 更新并置s_u8_save_flag;PV 同理。- GRID 侧是 BB 时,把
t_bb.u32_sn/t_bb.u16_ver与实测值同步;不是 BB 时把 BB 相关字段与整个t_bb_debug清零。 s_u8_save_flag == TRUE→save_job_post(METER_CONFIG_PAGE)(data/system_config/system_config.c:2688),并清标志。
5. 对外接口
5.1 本文件导出(供外部调用)
核心数据
| 名称 | 说明 |
|---|---|
g_t_meter | 电表子系统总表,被 logic/hmi/net/data 广泛直接访问 |
g_u8_recv_buf | 共享接收缓冲(外部一般只读调试用) |
函数(133 个中,在 meter.h 中声明或有跨文件使用者的):
- 任务入口:
task_meter - 收发原语:
meter_write_data、meter_recv_data、be_to_float、midvalue - 配置读写:
meter_set_meter_or_ct/meter_get_meter_or_ct、meter_set_ct_rate/meter_get_ct_rate、meter_set_meter_ct_enable/meter_get_meter_ct_enable、meter_set_meter_ct_rate/meter_get_meter_ct_rate、meter_set_meter_negate/meter_get_meter_negate、meter_set_ct_sampling、meter_set_saveConfigFlag - 数据读取(logic/hmi 主力接口):
meter_read_voltage、meter_read_current、meter_read_actpower、meter_read_reactpower、meter_read_apparent、meter_read_factor、meter_read_frequent、meter_read_einput、meter_read_eoutput、meter_read_connect_status、meter_set_connect_status - 标识信息:
meter_get_meter_type_now、meter_get_meter_type_old、meter_get_meter_modle、meter_read_meter_sn、meter_read_software_version、meter_read_hard_version、meter_read_bb_sn、meter_read_bb_software_version、meter_get_pv_meter_addr_change_flag - 控制:
meter_set_clear_flag、meter_clear_run_data_flag、meter_run_data_clear、set_meter_phase_sequnece_adjust_mark、set_meter_triphase_sequnece_adjust_mark、mid_meter_clear - CT / P1 通道:
ct_handle、meter_set_run_data、wireless_meter_handle_lost、wireless_meter_is_all_lost - P1 状态:
meter_set_grid_hw_ver、meter_set_grid_com_sta、meter_set_clear_grid_data、meter_set_grid_wifi_P1_fault、meter_set_refresh_flag、meter_read_refresh_flag、meter_read_P1_refresh_time、meter_set_P1_lose_flag、meter_get_P1_lose_flag - 无线模组:
meter_set_chnt_wireless_module_enable/meter_get_chnt_wireless_module_enable、meter_set_chnt_wireless_module_regional_freq_bands/meter_get_chnt_wireless_module_regional_freq_bands、meter_set_chnt_wireless_bound、clear_chnt_wireless_moudle_info、chnt_wireless_moudle_check - 升级:
IsUpgradeMeter、GetPercentUpgradeMeter、get_energy_data_enable
⚠ 定义了但未在 meter.h 声明的全局函数(其他 .c 无法安全调用):mid_meter_write_data、mid_meter_recv_data、meter_set_ct_rate、meter_get_ct_rate、meter_get_meter_negate、meter_get_meter_type、chnt_wireless_moudle_check(此项被 net/ 等直接 extern 使用的情况需另行确认)。
5.2 本文件调用的外部函数/变量
| 来源模块 | 被调用的符号 |
|---|---|
common/crc.c | GetCrcCode_Modbus |
midware/meter/ct.c | ct_read_voltage、ct_read_current、ct_read_actpower、ct_read_reactpower、ct_read_einput、ct_read_eoutput、ct_read_frequent、CT_PHASE_A/B/C/T |
midware/meter/backupbox.c | bb_load_manage |
midware/meter/bb_update.c | bb_update、GetPercentUpgradeBB |
midware/meter/meter_upgrade.c | meter_upgrade_handle、get_meter_updt_object、get_percent_upgrade_meter |
midware/meter/diesel_generator.c | genset_comm_handle |
midware/inverter/inverter.c | inverter_read_run_mode、inverter_read_grid_freq、inverter_read_backup_freq、inverter_get_sys_ctrl_state、cUPS、g_t_inv_info |
midware/inverter/parallel.c | parallel_read_system_inv_grid_freq |
midware/ethernt/wireless/wireless_driver.c | wireless_read_P1_data |
data/data.c | meter_reset、ems_set_fault_bit、g_t_ems_info |
data/system_config/system_config.c | save_job_post、METER_CONFIG_PAGE |
net/ems.c | Set_Setup_Config_Status、Set_Setup_Config_Extend2_Status、Get_Status_Is_Updated_From_Server、Set_Status_Is_Updated_From_Server、UPDT_Meter |
logic/generator.c | get_offgrid_mode_grid_state |
logic/logic.h | g_t_logic_info(系统模式判断)、AC_SYSTEM_MODE、HYBIRD_SYSTEM_MODE |
hardware/ | get_board_type、BOARD_EMS3_6_3_E/BOARD_EMS3_6_4_E |
hardware/(看门狗) | IWDG_Feed |
common/(串口) | usart1_clear_buffer、usart1_send_data、usart1_is_recv_async_data、usart1_get_data_len、usart1_read_data_async、uart3_clear_buffer、uart3_send_data、uart3_is_recv_async_data、uart3_get_data_len、uart3_read_data_async、uart3_read_data_timeout |
| uCOS-II | OSTimeGet、OSTimeDlyHMSM |
| 标准库 | memcpy、memset、memcmp、strstr、strncmp、strcmp、sprintf、snprintf、sqrt、abs |
6. 依赖关系
依赖(#include)
meter.h、ct.h、diesel_generator.h、includes.h、inverter.h、logic.h、usart1.h、usart3.h、crc.h、system_config.h、generator.h、bb_update.h、iwdg.h、data.h、upd_download.h、meter_download.h、meter_upgrade.h、wireless_driver.h、board_init.h、<math.h>
被依赖(谁会用到本文件)
| 使用方 | 用途 |
|---|---|
user/main.c | 静态创建 task_meter 任务,配置优先级/栈 |
midware/meter/ct.c | 通过 meter_set_run_data 之外的方式共享 g_t_meter;MAX_CT_SITE 与之耦合 |
midware/meter/backupbox.c | 读/写 g_t_meter.t_bb_info、t_bb_debug |
midware/meter/bb_update.c、meter_upgrade.c | 升级前后由 task_meter 调用,并共享 u8_found 等状态 |
midware/meter/diesel_generator.c | 由 task_meter 调用 genset_comm_handle |
logic/(logic.c、parallel.c、南澳/英国 G100 等策略) | 几乎全部通过 meter_read_voltage/current/actpower/reactpower/frequent/einput/eoutput 取电网与光伏功率,是功率调度与并机算法的输入源 |
data/data.c | meter_reset 被本文件调用;data 侧通过 g_t_meter 生成上报帧 |
net/ems.c | 云端上报电表型号/SN/版本/功率;设置相序自适应与单相接反配置 |
hmi/ui_*.c | 菜单显示电表数据、型号、版本、相序校准入口 |
midware/ethernt/wireless/wireless_driver.c | 反向调用 meter_set_run_data、meter_set_grid_wifi_P1_fault、meter_set_refresh_flag 等 |
7. 备注与风险点
7.1 确定性缺陷
meter_read_data_for_ALPHA()的s16_factor[PHASE_C]被漏写(meter.c:2971-2974):[65..66] → PHASE_A [67..68] → PHASE_B [69..70] → PHASE_B ← 应为 PHASE_C(复制粘贴错误) [71..72] → PHASE_T
后果:C 相功率因数永远不会被更新(保留上一次或初值 0),B 相被中间一帧覆盖。ALPHA 协议电表(正泰)的 HMI 显示与任何依赖 C 相 PF 的逻辑都会得到错误值。
meter_read_data_3ct()硬编码写g_t_meter.t_grid_info(meter.c:2166-2173)。该函数被meter_read_data_for_ADL400/ADL3000/ACR10R调用,而这三个函数同时服务 GRID 与 PV。当 PV 侧 ACR10R 接 CT 时,CT 倍率会被错误地乘到 GRID 的电流/功率上,同时 PV 自己的电流/功率完全没有被缩放。这是一个明确的功能性 bug。
grid_meter_find()/pv_meter_find()中硬件版本字段被张冠李戴(meter.c:383与meter.c:745)。同一函数里两个相邻分支的写法互相矛盾:
| 位置 | 分支 | 实际访问的字段 | 应为 |
|---|---|---|---|
meter.c:369 | GRID, ADL400 | u8_grid_merter_hw_ver | ✓ 正确 |
meter.c:383 | GRID, ADL3000 | u8_pv_merter_hw_ver | ✗ 应为 grid |
meter.c:745 | PV, ADL400 | u8_grid_merter_hw_ver | ✗ 应为 pv |
meter.c:759 | PV, ADL3000 | u8_pv_merter_hw_ver | ✓ 正确 |
后果:用 ADL3000 作 GRID 表时会错误地清掉 PV 侧的硬件版本并触发一次无谓的配置落盘;用 ADL400 作 PV 表时会错误清掉 GRID 的硬件版本。功能影响限于「硬件版本显示/上报值可能丢失并重新探测」,不会误判型号,但会造成 Flash 频繁写入。
meter_read_bb_software_version()的sprintf潜在溢出(meter.c:6185-6191):
static u8 s_u8_bb_sw_ver[7],而 "V%u.%02u" 最坏 "V655.35"(7 字符 + '\0' = 8 字节),越界 1 字节。
meter_set_run_data()的无功反算可能对负数开根,且量纲与全局约定不一致(meter.c:6891-6892):l_s32_tem = meter_run_data_p->t_data.s16_current[i] * meter_run_data_p->t_data.u16_volt[i]; meter_run_data_p->t_data.s32_reactpower[i] = sqrt((l_s32_tem * l_s32_tem) - (actpower * actpower));- 负数开根:当
P > U×I(功率因数 > 1,或电压/电流/功率来自 P1 的不同采样时刻)时被开方数为负,sqrt返回 NaN,再隐式转s32是未定义行为。输入是 P1 上报数据,未做任何合理性校验。 - 量纲口径:全局约定无功单位为 0.001kvar(即 var),而此处
U×I用的是u16_volt(0.1V) ×s16_current(0.01A) = 0.001VA(即 mVA),比s32_actpower(0.001kW,即 W) 大 1000 倍。对比meter_read_apparent()的同类公式显式÷1000(meter.c:6594),此处没有除 1000,反算结果会比真实值大三个数量级。需与 P1 上报单位定义核对,是明确的量纲可疑点。
- 负数开根:当
- PV 侧 CT 分支的软件版本取错对象(
meter.c:5852):memcpy(g_t_meter.t_pv_info.u8_software_version, g_t_meter.t_grid_info.u8_vave_software_version, 8);
GRID 侧对应语句(meter.c:5804)取 t_grid_info.u8_vave_software_version 正确;PV 侧应取 t_pv_info.u8_vave_software_version,此处误取了 GRID 的。PV 侧 CT 模式下软件版本会显示成 GRID 的版本。
- ADL400N 电参量解析的频率偏移越界(
meter.c:2697):
电参量读命令是 0x2100 起 0x34(52) 个寄存器 → 数据域为 buf[3..106](52×2 = 104 字节),而频率取 bytes_to_float_be(&g_u8_recv_buf[107]),读的是本次响应数据域之外的字节,实际取到上一帧残留值。需与 ADL400N 协议文档核对寄存器编号(很可能是 0x2100+56 而非 +52,或应改读命令长度)。
mid_meter_parse_data()参数与实参不一致(meter.c:4133):函数签名含METER_RUN_TABLE_T *meter_run_info,但函数体内电能分支直接写全局g_t_meter.t_mid_info.t_data,非电能分支才写meter_run_info->t_data。同一函数内两套写路径,且函数注释里提到的meter_ct_enable/ct_rate参数在实际签名中已不存在(注释未同步)。
- MID 电能三次取中值被
#if 0屏蔽(meter.c:4156-4192):MID 的电能解析直接取单次读数写入u32_energy_input/output[PHASE_T],表决逻辑整段被条件编译关掉,einput_temp2/3、eoutput_temp2/3成为死代码。
7.2 可疑写法与魔法数字
com_switch的魔数 124/126/128/130/132/134:这些数字源自「u8 计数到接近 256 时连续命中 3 次」的时序技巧,代码里没有任何注释解释为什么是这几个值,也没有断言保证它们与 256 的整除周期一致。若有人调整任务延时(300ms/100ms),电能刷新周期会从约 100s 变成其他值且无人察觉。
g_u8_recv_buf是全局共享的单缓冲,而 GRID/PV/正泰无线三者都在 USART1 上使用它。虽然任务串行保证了不会并发,但set_chnt_wireless_config_info()、grid_meter_query_dts_special_command()等函数在返回后仍依赖g_u8_recv_buf的内容(例如grid_meter_find()中连续两次收发后直接用第二次的g_u8_recv_buf[3]),一旦中间插入任何别的收发就会读到脏数据。
meter_recv_data()/mid_meter_recv_data()在len < 2时提前return且不写*len(meter.c:81-84、145-148)。mid_meter_send_clear_data()的 3 次重试循环里l_u16_recv_num只在循环外初始化一次,第二、三次若命中早退分支会沿用上一轮的l_u16_recv_num值,可能造成「假成功」。
meter_read_data_for_ADL400/ADL3000用g_u8_recv_buf[4] == BACKUPBOX_ID区分 BB(meter.c:2455、2559)。这是拿数据域第 1 个字节猜测设备类型,触发时直接把u8_lost_cnt置为METER_LOST_MAX_CNT + 1(即判决为丢失)。逻辑非常脆弱——任何正常 ADL3000 恰好在该字节读到 0xAB 都会误判为丢表。
set_meter_triphase_sequnece_adjust_mark()里u8 l_u8_tx_buf[32];未初始化即使用(meter.c:4610):memcpy只覆盖前 4 字节,memset只写在if (site == GRID_METER)/else if (site == PV_METER)分支内;若site是其他值,第 4~7 字节来自栈垃圾且会参与 CRC 计算。
l_value == 1被映射为「写 0x0002」(meter.c:4676-4680、4713-4717):调用方传 1 表示「开始调整」,但实际协议值必须是 2。这层映射是隐式的,meter_control_single_negate_handle()里又用「状态字 == 1 / == 2」判断流程阶段,两套 1/2 语义并存,极易混淆。
meter_set_run_data()直接用memcpy尺寸取自实参表达式(meter.c:6899-6900):sizeof(meter_run_data_p->u8_software_version)是 9、u8_meter_sn是 31,与目标字段一致,但同样写法在别处(如meter_config_handle的sizeof(s_u8_software_version))依赖静态数组尺寸,风格不统一,重构时容易出错。
7.3 竞态与越界
- 跨任务无保护:
g_t_meter.t_grid_info.t_data被task_meter(本文件)、CT 采样任务(ct_handle)、P1 无线任务(meter_set_run_data)三方写入,无任何临界区保护。grid_total_pahse_grid_energy_statistics()读取s32_actpower[PHASE_T]做积分,若此刻被另一任务清零或改写,会得到跳变的电能累计值。
meter_read_data_negate()对PHASE_T也取负(meter.c:2152-2163):当电表上报的总有功不是严格等于三相之和(部分表在空载/单相缺失时给出不同值)时,总值与分相之和不再一致,上层做「总和校验」会失败。
- BB23K 的 SN 读取刚好贴着缓冲区上沿:读命令
0x20起0x76(118) 个寄存器 → 数据域buf[3..238],代码取buf[3 + 206 + i](i = 0..29)即buf[209..238],恰好用满最后一个字节。若将来型号寄存器数再增加 1,就会越界读g_u8_recv_buf[239](仍在 256 缓冲内,但已是无效数据)。
adl400n_grid_meter_clear()/agf200_grid_meter_clear()用memcmp对比 8 字节定长应答,但只校验了memcmp == 0,没有先确认l_u16_recv_num >= 8(只确认了> 0)。若电表返回短帧(如异常码响应 5 字节),memcmp会读到上一次的残留数据。
grid_meter_read_data()中s_dataTmp与p_meter_read_data()的s_dataTmp各自独立,但meter_read_data_for_ALPHA/ADL400N使用传入的s_dataTmp指针 + 全局g_u8_recv_buf混合取数;meter.c:3590后对grid_meter_read_data()的g_t_meter.t_config_data.u8_ems_calculate_total_phase_energy_flag判断与meter_read_data_for_ALPHA内部判断重复,两处若未来不一致会产生逻辑分叉。
meter_get_meter_type_now()对MID_METER返回g_t_meter.t_mid_info.e_type_now,但mid_meter_find()里对t_mid_info.e_type_now的赋值被注释掉了(meter.c:1127)。因此 MID 电表的e_type_now恒为 0,meter_get_meter_type_now(MID_METER)返回 0,而meter_handle()中meter_clear()对 MID 也不依赖型号,暂无功能影响,但语义上是个未完成的接口。
#if 1 //(MID_METER_DEBUG_INFO == 1)(meter.c:1142):MID 探测的printf被无条件编译进来,量产固件会持续往串口打印版本号,占用串口带宽并可能干扰其他调试输出。
7.4 版本遗留与死代码
mid_meter_table_init()强制把u8_mid_meter_function_enable置 FALSE(meter.c:238):每次上电都关闭 MID 使能,必须由云端/配置重新打开。而该字段位于持久化结构METER_CONFIG_TAB_T中,上电被覆盖意味着配置项实际不生效——是刻意的「默认关闭」还是漏改,需要与产品定义确认。
meter_data_printf()与#if (METER_DEBUG_INFO)混用(meter.c:3617、4801、5163等):文件中同时存在#if (METER_DEBUG_INFO == 1)、#if (METER_DEBUG_INFO)、#if METER_DEBUG_INFO三种写法,且METER_DEBUG_INFO定义为(0),#if (METER_DEBUG_INFO)与#if METER_DEBUG_INFO等价于关闭,但与前一种写法混用容易在改为 1 时产生「部分生效」的错觉。
grid_ct_handle()/pv_ct_handle()调用点被注释停用(meter.c:5799、5847),改为在 CT 采样任务里调ct_handle();头文件仍声明grid_ct_handle/pv_ct_handle,实现在ct.c。
meter_set_grid_com_sta()语义反直觉(meter.c:7003-7013):注释写「0:通信失败,1:通信成功」,但函数体在入参0时置u8_comm_lost_err = FALSE(= 通信正常),入参1时置TRUE(= 丢失)。注释与实现相反,调用方(P1 无线通道)若是按注释传参会导致故障位反向。
meter_read_apparent()未使用电表原始视在功率:内部用U×I/1000反算。对 SM60A/AGF200 等实际读取了S寄存器的型号,返回值与电表显示值必然不一致,且对非线性负载误差较大。命名与行为不符。
meter_recv_data()中的u8 data = 0;变量在 MID 版本中赋值后仅用于调试打印(meter.c:137、152),在MID_METER_DEBUG_INFO == 0时是纯浪费;属编译告警级瑕疵。
- 无 CRC 重算的写命令:
set_meter_triphase_sequnece_adjust_mark()、grid_meter_set_single_negate()等直接在栈上memcpy常量前缀后追加值再算 CRC,逻辑正确;但pv_meter_clear()预先写死 CRC 后又重算一遍(meter.c:1805),说明常量表里的 CRC 是手工算的、可信度不高——维护时若改地址必须记得重算,容易漏。