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

meter.c

源文件路径: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_meterMETER_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_flagstatic 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_statestatic METER_ADJUST_STATE_U(文件级)相序自适应就绪位域(meter.c:41),bit.u8_grid_adjust_ready / bit.u8_pv_adjust_ready
METER_DATA_TEMPtypedef 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_infoLOGIC_TABLE_T(logic/logic.h:646)读 t_config_data.u8_system_mode 判断 DC/AC/Hybrid,决定 PV 侧电表是否需要探测
g_t_inv_infoINVERTER_TABLE_T(midware/inverter/inverter.h:1929)读 t_config_data.u8_inv_software_limit_enable / u8_inv_hardware_limit_enable 决定丢表阈值降到 3
g_t_ems_infoEMS_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_initstatic206GRID 运行表初始化:置探测标志、记 u32_find_tick
pv_meter_table_initstatic218PV 运行表初始化;仅 AC/Hybrid 系统模式置探测标志
mid_meter_table_initstatic236MID 运行表初始化,并清 u8_mid_meter_function_enable
chnt_wireless_table_initstatic247正泰无线表初始化,只记 u32_find_tick
meter_table_initstatic257按 site 分发到上述四个初始化函数
grid_meter_findstatic293GRID 电表探测:7 项轮询表逐一试探、区分型号、抽取版本/SN
grid_meter_detectstatic605GRID 探测外壳:成功清计数,连续 30 次失败后判定通信丢失
pv_meter_findstatic655PV 电表探测:8 项轮询表(含 gridforming 地址切换、ALPHA 双回路)
pv_meter_detectstatic958PV 探测外壳,丢失判定区分 gridforming 与普通模式
mid_meter_send_clear_datastatic1014MID 表用 0x10 写 0x0002=1 清零电能,重试 3 次
mid_meter_clear全局1086对外包装,直接调用 mid_meter_send_clear_data()
mid_meter_findstatic1096MID 表探测:读 0x0000 版本号并格式化为 "V%u.%u"
mid_meter_detectstatic1157MID 探测外壳,失败 30 次后置丢失并清运行数据
set_chnt_wireless_config_infostatic1188向指定无线节点写 0xE301 频段配置
chnt_wireless_config_infostatic1222读 AP(0xE1) 0xE300 起 27 个寄存器,解析三个节点汇总信息,返回子节点在线位图
get_chnt_wireless_module_infostatic1277读单个无线节点 0xEA00 起 19 个寄存器(型号/SN/软硬件版本)
clear_chnt_wireless_moudle_info全局1331按索引清空某个节点的型号/版本/SN 缓存
chnt_wireless_detectstatic1344正泰无线读状态机(state 0→3),汇总 AP/STA1/STA2 信息并判定 u8_found
meter_detectstatic1424按 site 分发到 GRID/PV/无线/MID 四个探测函数
agf200_grid_meter_clearstatic1463AGF200(GRID) 清表前的「写使能」握手
adl400n_grid_meter_clearstatic1493ADL400N(GRID) 清表前的「写使能」握手
grid_meter_clearstatic1523按当前型号选择清表命令字并比对定长应答,成功返回 TRUE
agf200_pv_meter_clearstatic1686AGF200(PV) 清表使能握手
pv_meter_clearstatic1718PV 侧清表,按型号选命令、重算 CRC、比对定长应答
meter_run_data_clear全局1884清空指定位置的运行数据(GRID 额外清 t_bb_debug)
meter_clearstatic1921先清本地数据再下发清表命令,按 site 分发
meter_data_printfstatic1956GRID 电参量 printf 打印,仅在 METER_DEBUG_INFO == 1 时编译
grid_total_pahse_grid_energy_statisticsstatic1984GRID 侧总电能软件累加(电表不支持总电能时使用)
grid_total_pahse_pv_energy_statisticsstatic2053PV 侧总电能软件累加,逻辑同上
meter_read_data_negatestatic2152对传入运行表的电流/有功/无功/功率因数整体取反(含 PHASE_T)
meter_read_data_3ctstatic2166按 CT 倍率缩放电流与功率,内部硬编码操作 g_t_meter.t_grid_info
meter_read_data_for_ADL400_3pstatic2175从响应报文偏移解析 ADL400 三相正反向电能(6 个 u32 大端)
bytes_to_doublestatic21918 字节大端 → double(ADL400N 电能)
bytes_to_float_bestatic22034 字节大端 → float(ADL400N 电参量)
grid_meter_read_data_for_ADL400N_3pstatic2215解析 ADL400N 三相分相电能(偏移 67/75/115/123/163/171)
grid_meter_read_data_for_ADL400N_3p_totalstatic2225解析 ADL400N 总电能(偏移 11/19)
grid_meter_read_data_for_ALPHA_3pstatic2231解析 ALPHA 三相分相电能(偏移 3~26)
grid_meter_read_data_for_ALPHA_3p_totalstatic2247解析 ALPHA 总电能(偏移 27~34)
meter_read_data_for_SM60Astatic2256SM60A 解析:单相电参量 + 电能三次取中值
meter_read_data_for_ADL400static2339ADL400 解析:总电能 / 分相电能(com_switch 130~134)/ 电参量三态
meter_read_data_for_ADL3000static2500ADL3000 解析:总电能 + 电参量两态
meter_read_data_for_ADL400Nstatic2604ADL400N 解析:浮点电参量 + 分相/总电能,支持 EMS 自算总值
meter_read_data_for_ACR10Rstatic2730ACR10R 解析:电参量 + 电能;无 CT 时强制电流/功率清零
meter_read_data_for_ALPHAstatic2854ALPHA(正泰/其他)解析:分相电能 + 总电能 + 电参量
meter_read_data_for_AGF200static2982AGF200 解析:电参量 + 电能(÷10 换算)
grid_meter_read_datastatic3075GRID 侧核心调度与解析总入口:组帧、发收、丢失判决
pv_meter_read_datastatic3680PV 侧核心调度与解析总入口
be_to_float全局4110大端 4 字节 → float(联合体实现),供 MID 与外部使用
mid_meter_parse_datastatic4133MID 表数据解析:浮点电参量 + 电能
mid_meter_read_datastatic4273MID 表组帧、发收、解析与丢失处理
meter_data_readstatic4349按 site 分发到 GRID/PV/MID 三个读数据函数
ct_handle全局4381CT 采样通道:500ms 节流,从 ct_read_* 取数填充运行表
meter_config_handlestatic4465检测型号/版本/BB 身份变化并置 s_u8_save_flag,触发配置落盘
meter_fault_handlestatic4539处理清数据请求,并把三处 u8_comm_lost_err 映射到故障位 4/5/29
set_meter_triphase_sequnece_adjust_mark全局4608正泰/安科瑞相序自适应开关命令下发(含 3 次重试)
meter_query_grid_adjust_readystatic4778读 GRID 侧 0x0128,判断相序自适应是否完成
meter_query_pv_adjust_readystatic4821读 PV 侧 0x8128,判断相序自适应是否完成
is_chint_meter_typestatic4864判断型号字符串是否属于 5 种正泰型号之一
meter_get_meter_type全局(无头文件声明)4889返回厂商:Chint / Acrel / 0
chint_adjust_state_machinestatic4910正泰相序自适应主状态机(state 0→4)
meter_query_triphase_sequnece_adjust_schemestatic5028相序自适应总入口,按 u8_meter_triphase_sequnece_flag(1 开/2 关) 分派
set_meter_phase_sequnece_adjust_mark全局5073正泰老表 DTSU666-100/40mA 且版本 ≥V127 时的相序命令
grid_meter_query_dts_special_commandstatic5126读 GRID 侧 0x0127,解析单相接反标志与相序调整状态字
pv_meter_query_dts_special_commandstatic5176读 PV 侧 0x8127,解析单相接反标志与相序调整状态字
grid_meter_set_single_negatestatic5226写 GRID 侧 0x0127,下发单相接反配置
pv_meter_set_single_negatestatic5277写 PV 侧 0x8127,下发单相接反配置
meter_control_single_negate_handlestatic5328单相接反控制逻辑:实际值 ≠ 配置值且状态字为 1/2 时下发或重查
meter_handlestatic5369单块电表主循环:未找到则定时探测,找到则清表或读数据
wireless_meter_is_all_lost全局5450GRID 与 PV 无线电表是否全部丢失
wireless_meter_handle_lost全局5456无线电表丢失处理:清标志、清数据、按并离网判故障
wireless_meter_handlestatic5485无线电表专用循环:10s 无数据判丢失 + 处理清表请求
chnt_wireless_moudle_handlestatic5528无线模组按 1s 节拍调用 meter_detect(CHNT_WIRELESS)
chnt_wireless_moudle_info_checkstatic5550比较配置频段与模组实际频段,不一致则返回 TRUE
set_chnt_wireless_moudle_infostatic5567无线模组写状态机(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全局6135GRID 侧为 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全局6882P1 通道注入整块电网数据(含无功反算、周期自适应)
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):

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。

超时与重试:

注意: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地址命令识别判据
00x0303 0015 0001buf[3] == 0x03 → SM60A
10x0103 003F 0001再读 01 03 F000 000B,buf[17] 含 "ADL4" → ADL400,否则 ADL3000
20x0103 005C 0005buf[12] == 0x00AB(BACKUPBOX_ID)后再读 0x0059 得型号码:0/12/67/78→BACKUPBOX_5K,64→BACKUPBOX_10K,302→BACKUPBOX_23K
30x0503 0000 0001buf[4] == 0x05 → ACR10R
40x0103 0000 0019buf[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] 硬件版本
50x0703 9C40 0001`(buf[3]<<8\buf[4]) == 202 → AGF200`
60x0103 F009 0006buf[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)。

型号电参量编码电压电流功率频率电能
SM60A16 位大端buf[3..4](仅 A 相,B/C 置 0)buf[5..6]buf[7..8]buf[15..16]32 位大端 buf[3..6]/buf[19..22]
ADL3000 / BB16 位大端,起始 0x0060buf[5..10]buf[11..16]buf[17..24]buf[49..50] × 10buf[3..6]/buf[23..26]
ADL400同上buf[5..10]buf[11..16]buf[17..24]buf[49..50] × 10buf[3..6]/buf[23..26];分相段 buf[3..26]
ADL400NIEEE754 大端浮点,起始 0x2100buf[3..6]/[7..10]/[11..14] × 10buf[27..38] × 100buf[43..55]buf[107..110] × 100double 大端 × 100(偏移 11/19/67/75/115/123/163/171)
ACR10R32 位大端buf[3..8]buf[15..20]buf[23..38]buf[21..22]buf[3..6]/buf[7..10]
ALPHA(正泰)32 位大端buf[3..8] × 10buf[9..14] × 10buf[17..32]buf[15..16]分相 buf[3..26],总 buf[27..34]
AGF20016 位大端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] ÷ 10buf[27..42] ÷ 10buf[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 自算总电能」

两条电能获取路径:

  1. 电表直接给总电能(u8_ems_calculate_total_phase_energy_flag == FALSE):直接用三次读数的中位数。
  2. 电表不给总电能(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 FB01 AA 64 06 00 01 27 23
SM60A03 AA 00 00 00 0A 14 + 20 字节 0 + CRC03 AA 00 00 00 0A 98 37
ACR10R05 10 1388 0001 02 A5A5 13F205 10 1388 0001 84 E3
ALPHA01 10 00F3 0001 02 029A 339801 10 00F3 0001 F1 FA
AGF200先 9CAC 使能握手,延时 30ms,再 9CAD 清表07 10 9CAD 0001 BE 1E
ADL40001 AA 0001 0001 02 0000 2C8A01 AA 0001 0001 89 D2
ADL400M_CT2先 6400 0000 使能,延时 30ms,再 6400 000201 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 通信丢失判决与故障上报

两层判决:

故障位映射(meter_fault_handle(),meter.c:4539):

条件动作
t_grid_info.u8_comm_lost_errems_set_fault_bit(4, ...)
t_pv_info.u8_comm_lost_errems_set_fault_bit(5, ...)
u8_mid_meter_function_enable && t_mid_info.u8_comm_lost_errems_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):

正泰状态机(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。命令地址的选择分两种情况:

安科瑞 ADL400 走 0x10 写 0x9220,数据 0x0101 开启 / 0xFFFF 关闭,成功后调 meter_reset() 并清 u8_meter_triphase_sequnece_flag。

单相接反校准(meter_control_single_negate_handle(),meter.c:5328):

厂商识别: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,并做三件额外的事:

  1. 无功反算:Q = sqrt((U×I)² − P²),对 A/B/C 三相逐相计算,PHASE_T 为三相之和。
  2. 频率来源切换:直接取 parallel_read_system_inv_grid_freq()(并机系统的逆变器电网频率),不用电表值。
  3. 刷新周期自适应: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)。为真则:

  1. 清 GRID/PV/MID 运行数据,四处 u8_found = FALSE。
  2. 按 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()。
    • 其他:放弃。
  3. 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)每轮检查:

5. 对外接口

5.1 本文件导出(供外部调用)

核心数据

名称说明
g_t_meter电表子系统总表,被 logic/hmi/net/data 广泛直接访问
g_u8_recv_buf共享接收缓冲(外部一般只读调试用)

函数(133 个中,在 meter.h 中声明或有跨文件使用者的):

⚠ 定义了但未在 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.cGetCrcCode_Modbus
midware/meter/ct.cct_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.cbb_load_manage
midware/meter/bb_update.cbb_update、GetPercentUpgradeBB
midware/meter/meter_upgrade.cmeter_upgrade_handle、get_meter_updt_object、get_percent_upgrade_meter
midware/meter/diesel_generator.cgenset_comm_handle
midware/inverter/inverter.cinverter_read_run_mode、inverter_read_grid_freq、inverter_read_backup_freq、inverter_get_sys_ctrl_state、cUPS、g_t_inv_info
midware/inverter/parallel.cparallel_read_system_inv_grid_freq
midware/ethernt/wireless/wireless_driver.cwireless_read_P1_data
data/data.cmeter_reset、ems_set_fault_bit、g_t_ems_info
data/system_config/system_config.csave_job_post、METER_CONFIG_PAGE
net/ems.cSet_Setup_Config_Status、Set_Setup_Config_Extend2_Status、Get_Status_Is_Updated_From_Server、Set_Status_Is_Updated_From_Server、UPDT_Meter
logic/generator.cget_offgrid_mode_grid_state
logic/logic.hg_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-IIOSTimeGet、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.cmeter_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 确定性缺陷

  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 的逻辑都会得到错误值。

  1. 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。
  1. grid_meter_find() / pv_meter_find() 中硬件版本字段被张冠李戴(meter.c:383 与 meter.c:745)。同一函数里两个相邻分支的写法互相矛盾:
位置分支实际访问的字段应为
meter.c:369GRID, ADL400u8_grid_merter_hw_ver✓ 正确
meter.c:383GRID, ADL3000u8_pv_merter_hw_ver✗ 应为 grid
meter.c:745PV, ADL400u8_grid_merter_hw_ver✗ 应为 pv
meter.c:759PV, ADL3000u8_pv_merter_hw_ver✓ 正确

后果:用 ADL3000 作 GRID 表时会错误地清掉 PV 侧的硬件版本并触发一次无谓的配置落盘;用 ADL400 作 PV 表时会错误清掉 GRID 的硬件版本。功能影响限于「硬件版本显示/上报值可能丢失并重新探测」,不会误判型号,但会造成 Flash 频繁写入。

  1. 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 字节。

  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 上报单位定义核对,是明确的量纲可疑点。
  1. 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 的版本。

  1. 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,或应改读命令长度)。

  1. 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 参数在实际签名中已不存在(注释未同步)。
  1. MID 电能三次取中值被 #if 0 屏蔽(meter.c:4156-4192):MID 的电能解析直接取单次读数写入 u32_energy_input/output[PHASE_T],表决逻辑整段被条件编译关掉,einput_temp2/3、eoutput_temp2/3 成为死代码。

7.2 可疑写法与魔法数字

  1. com_switch 的魔数 124/126/128/130/132/134:这些数字源自「u8 计数到接近 256 时连续命中 3 次」的时序技巧,代码里没有任何注释解释为什么是这几个值,也没有断言保证它们与 256 的整除周期一致。若有人调整任务延时(300ms/100ms),电能刷新周期会从约 100s 变成其他值且无人察觉。
  1. 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]),一旦中间插入任何别的收发就会读到脏数据。
  1. 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 值,可能造成「假成功」。
  1. 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 都会误判为丢表。
  1. 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 计算。
  1. l_value == 1 被映射为「写 0x0002」(meter.c:4676-4680、4713-4717):调用方传 1 表示「开始调整」,但实际协议值必须是 2。这层映射是隐式的,meter_control_single_negate_handle() 里又用「状态字 == 1 / == 2」判断流程阶段,两套 1/2 语义并存,极易混淆。
  1. 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 竞态与越界

  1. 跨任务无保护: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] 做积分,若此刻被另一任务清零或改写,会得到跳变的电能累计值。
  1. meter_read_data_negate() 对 PHASE_T 也取负(meter.c:2152-2163):当电表上报的总有功不是严格等于三相之和(部分表在空载/单相缺失时给出不同值)时,总值与分相之和不再一致,上层做「总和校验」会失败。
  1. 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 缓冲内,但已是无效数据)。
  1. adl400n_grid_meter_clear() / agf200_grid_meter_clear() 用 memcmp 对比 8 字节定长应答,但只校验了 memcmp == 0,没有先确认 l_u16_recv_num >= 8(只确认了 > 0)。若电表返回短帧(如异常码响应 5 字节),memcmp 会读到上一次的残留数据。
  1. 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 内部判断重复,两处若未来不一致会产生逻辑分叉。
  1. 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 也不依赖型号,暂无功能影响,但语义上是个未完成的接口。
  1. #if 1 //(MID_METER_DEBUG_INFO == 1)(meter.c:1142):MID 探测的 printf 被无条件编译进来,量产固件会持续往串口打印版本号,占用串口带宽并可能干扰其他调试输出。

7.4 版本遗留与死代码

  1. mid_meter_table_init() 强制把 u8_mid_meter_function_enable 置 FALSE(meter.c:238):每次上电都关闭 MID 使能,必须由云端/配置重新打开。而该字段位于持久化结构 METER_CONFIG_TAB_T 中,上电被覆盖意味着配置项实际不生效——是刻意的「默认关闭」还是漏改,需要与产品定义确认。
  1. 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 时产生「部分生效」的错觉。
  1. grid_ct_handle() / pv_ct_handle() 调用点被注释停用(meter.c:5799、5847),改为在 CT 采样任务里调 ct_handle();头文件仍声明 grid_ct_handle/pv_ct_handle,实现在 ct.c。
  1. meter_set_grid_com_sta() 语义反直觉(meter.c:7003-7013):注释写「0:通信失败,1:通信成功」,但函数体在入参 0 时置 u8_comm_lost_err = FALSE(= 通信正常),入参 1 时置 TRUE(= 丢失)。注释与实现相反,调用方(P1 无线通道)若是按注释传参会导致故障位反向。
  1. meter_read_apparent() 未使用电表原始视在功率:内部用 U×I/1000 反算。对 SM60A/AGF200 等实际读取了 S 寄存器的型号,返回值与电表显示值必然不一致,且对非线性负载误差较大。命名与行为不符。
  1. meter_recv_data() 中的 u8 data = 0; 变量在 MID 版本中赋值后仅用于调试打印(meter.c:137、152),在 MID_METER_DEBUG_INFO == 0 时是纯浪费;属编译告警级瑕疵。
  1. 无 CRC 重算的写命令:set_meter_triphase_sequnece_adjust_mark()、grid_meter_set_single_negate() 等直接在栈上 memcpy 常量前缀后追加值再算 CRC,逻辑正确;但 pv_meter_clear() 预先写死 CRC 后又重算一遍(meter.c:1805),说明常量表里的 CRC 是手工算的、可信度不高——维护时若改地址必须记得重算,容易漏。