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

pvcharger.c

源文件路径:midware/inverter/pvcharger.c 所属模块:midware/inverter(逆变器中间件 / PV 充电器模式) 代码行数:514

1. 文件职责

本文件实现 EMS 的"PV 充电器"模式数据通路:把电池数据(无论来自本地 BMS 还是来自外部 Modbus 主机)统一收敛成一张 PVCHARGR_INFO_T 表,并对充放电能力做安全限值处理,再发布给下游使用。它处于"电池/BMS 与外部 Modbus 主机 → 统一电池信息表 → 能量调度与 Modbus 从机寄存器"的中间转换层。角色由 g_t_inv_info.t_config_data.u8_pv_charger_mode 决定:非 PVCHARGR_FROM_E 时由本地 BMS 填表(pvcharger_local_info_handle),为 PVCHARGR_FROM_E 时由外部 Modbus 主机写入的 g_modbus_pvchargr_info 经换算得到结果;另外提供主机侧的 Modbus 广播发送(pvcharger_send_host_machine_info)。驱动来源是 inverter.c:1168 的逆变器任务(周期调用 inverter_pvcharger_info_handle)与 interface.c:328(触发主机广播),从机侧收帧由 modbus.c:8073-8103 的寄存器写回调反向通知(pvcharger_from_machine_recv_sync)。

2. 关键数据结构与宏

名称类型说明
PVCHARGR_INFO_T结构体(pvcharger.h:49-69,#pragma pack(1))电池信息主表,字段顺序即 Modbus 寄存器顺序(状态位、SOC、电压、电流、功率、充放最大功率、充放截止电压/限流、型号、额定电压/容量/DOD、瞬时额定电压、充放电第一段限流)
BATTERY_STATE_U联合体(pvcharger.h:25-37)电池状态位域(u16):bit_bms_comm_flag:2、bit_bms_work_flag:4、bit_bms_relay_flag:2、bit_bms_upgrade_flag:2、bit_bat_ready_flag:2、bit_reserve_flag:4
BATTERY_TYPE_U联合体(pvcharger.h:39-47)电池类型(u16):byte_battery_series / byte_battery_model
PVCHARGR_MODE_E枚举(pvcharger.h:17-22)PVCHARGR_SINGLE_E=0 / PVCHARGR_FROM_E=1 / PVCHARGR_HOST_E=2
g_modbus_pvchargr_info全局变量(第 25 行)从机角色下由外部 Modbus 主机写入的原始寄存器镜像
g_control_pvchargr_info全局变量(第 26 行,头文件未 extern)中间"受控量":把 g_modbus_pvchargr_info 的充电功率 /4 并夹到逆变器额定值后暂存
g_inverter_pvchargr_info全局变量(第 27 行)对下游发布的最终数据;本地模式由 BMS 直接填,从机模式由 g_control_pvchargr_info 拷贝而来
s_u8_pvcharger_comm_flagstatic(第 29 行)PV 充电器通信健康标志,初值 TRUE;15 s 无帧置 FALSE
s_u32_pvcharger_recv_tickstatic(第 30 行)最后一次收到外部主机帧的 tick;pvcharger_from_machine_recv_sync 刷新
PVCHARGR_BROADCAST_ADDR宏(modbus.h:21)0,PV 充电器广播地址(主机→从机)
FUNC_WRITE_REGISTER宏(modbus.h:26)0x10,Modbus 写多个保持寄存器功能码
REG_PV_CHARGER_BATTERY_STATE_E宏(modbus.h:1298)0x2340,PV 充电器电池状态起始寄存器
REG_PV_CHARGER_CONFIG_MAX_E宏(modbus.h:1314)寄存器区间上界(不含),MAX - BATTERY_STATE 即需要下发的寄存器个数
buff_send_modbus[]全局数组(modbus.c 定义)Modbus 发送缓冲区,pvcharger_send_host_machine_info 直接借用组帧
ems_set_fault_bit函数(data.c:338)置/清 g_t_ems_info.u_ems_fault.u32_all 的某一位
UPDT_BMS / Get_Status_Is_Updated_From_Server / GetPercentDownloadBMS / IsDownloadBMS宏/函数(upd_download.h、bms_download.h)BMS 升级状态标志来源
FORCE_CHG_FORBID_DISCHG_E / FORCE_CHG_ALLOW_DISCHG_E宏(bms.h)BMS 强制充电相关状态(本文件未用到,见 parallel.c 同类判定)

3. 函数清单

函数名作用域说明
pvcharger_local_info_handlestatic,第 44 行从本地 BMS 采集全部电池信息填入 g_inverter_pvchargr_info,并做状态位与限值归零
pvcharger_send_host_machine_info全局,第 101 行以 Modbus 功能码 0x10 广播本机电池寄存器区间给外部从机,500 tick 限流
pvcharger_from_machine_recv_sync全局,第 146 行置通信健康标志 TRUE 并刷新接收时间戳(由 Modbus 写回调触发)
inverter_pvcharger_info_handle全局,第 159 行主入口:按角色选数据源,从机模式做 15 s 超时/换算/夹紧,最后置故障位 22
pvcharger_read_machine_mode全局,第 259 行返回配置的 PV 充电器角色
pvcharger_read_bat_connect_state全局,第 271 行返回 bit_bms_comm_flag
pvcharger_read_bat_work_state全局,第 283 行返回 bit_bms_work_flag
pvcharger_read_bat_relay_state全局,第 295 行返回 bit_bms_relay_flag
pvcharger_read_bat_soc全局,第 307 行返回 u16_battery_soc
pvcharger_read_bat_volt全局,第 319 行返回 s16_battery_volt
pvcharger_read_bat_curr全局,第 331 行返回 s16_battery_curr
pvcharger_read_bat_power全局,第 343 行返回 s32_battery_power
pvcharger_read_bat_max_charge_power全局,第 355 行返回 s32_battery_max_charge_power
pvcharger_read_bat_max_dischg_power全局,第 367 行返回 s32_battery_max_dischg_power
pvcharger_read_bat_charge_cut_off_volt全局,第 379 行返回 s16_battery_charge_cut_off_volt
pvcharger_read_bat_max_charge_current全局,第 391 行返回 s16_battery_max_charge_current
pvcharger_read_bat_dischg_cut_off_volt全局,第 403 行返回 s16_battery_dischg_cut_off_volt
pvcharger_read_bat_max_dischg_current全局,第 415 行返回 s16_battery_max_dischg_current
pvcharger_read_bat_series全局,第 427 行返回 byte_battery_series
pvcharger_read_bat_model全局,第 439 行返回 byte_battery_model
pvcharger_read_bat_rated_volt全局,第 451 行返回 u16_battery_rated_volt
pvcharger_read_bat_rated_volt_instant全局,第 463 行返回 u16_battery_rated_volt_instant
pvcharger_read_bat_charge_pro_cur_step_1全局,第 475 行返回 u16_charge_pro_cur_step_1
pvcharger_read_bat_discharge_pro_cur_step_1全局,第 487 行返回 u16_discharge_pro_cur_step_1
pvcharger_read_bat_rated_capacity全局,第 499 行返回 u32_battery_rated_capacity
pvcharger_read_bat_rated_dod全局,第 511 行返回 u8_battery_rated_dod

4. 核心逻辑解析

4.1 主状态机:inverter_pvcharger_info_handle()(第 159 行)

只有一个二分支角色判断,没有多状态迁移:

if (g_t_inv_info.t_config_data.u8_pv_charger_mode != PVCHARGR_FROM_E)
        /* 单机 / 主机 分支 */
        pvcharger_local_info_handle();          // 由本地 BMS 填表
        s_u8_pvcharger_comm_flag = TRUE;        // 永远视为通信正常
        s_u32_pvcharger_recv_tick = OSTimeGet();
else
        /* PVCHARGR_FROM_E 分支:本机是外部 Modbus 主机的从机 */
        ...
ems_set_fault_bit(22, (s_u8_pvcharger_comm_flag == FALSE));

关键点:非从机模式下通信健康标志被无条件置真并刷新时间戳(第 164-165 行),因此故障位 22 只在 PVCHARGR_FROM_E 模式下才可能被置位。这是"单机模式不存在通信"的自然结果,但也意味着故障位 22 的语义严格是"PV 充电器从机模式通信丢失"。

4.2 本地采集:pvcharger_local_info_handle()(第 44 行)

逐字段从 BMS 取数(单位换算点见下表),共 6 组:

目标字段来源备注
bit_bms_comm_flagbms_get_connect_state()
bit_bms_work_flagbms_read_work_status()4 bit 位域,可容纳多档工况
bit_bms_relay_flagbms_read_relay_status()
bit_bms_upgrade_flag`Get_Status_Is_Updated_From_Server(UPDT_BMS) \\(GetPercentDownloadBMS() > 50 && IsDownloadBMS())`与 parallel.c:261 同一判据:"云端已标记升级"或"下载进度已过 50% 且正在下载"
bit_bat_ready_flagbms_get_ready_enable()
u16_battery_socbms_read_total_soc()单位 0.1%
s16_battery_voltbms_read_total_volt() / 10唯一做了单位换算的字段:BMS 给 0.1 V,这里转成 1 V
s16_battery_currbms_read_total_cur()
s32_battery_powerbms_read_total_power()
s32_battery_max_charge_power / _dischg_powerbms_read_max_charge_power() / bms_read_max_dischg_power()
s16_battery_charge_cut_off_volt / _max_charge_currentbms_read_chg_cut_rate_volt() / bms_read_chg_max_rate_cur()
s16_battery_dischg_cut_off_volt / _max_dischg_currentbms_read_dischg_cut_rate_volt() / bms_read_dischg_max_rate_cur()
byte_battery_series / byte_battery_modelbms_read_battery_series_para_state() / bms_get_store_type()
u16_battery_rated_volt / _rated_volt_instantbms_cell_rated_vot() / bms_read_rated_vot_instant()
u16_charge_pro_cur_step_1 / u16_discharge_pro_cur_step_1bms_read_charge_pro_cur_step_1() / bms_read_discharge_pro_cur_step_1()
u32_battery_rated_capacitybms_read_battery_capacity()
u8_battery_rated_dodbms_read_battery_available_capacity()名字是 DOD,取的是"可用容量"

采集完做两级归零:

第一级(第 76-80 行)——"电池就绪"短路 BMS 状态位:

if (bit_bat_ready_flag != FALSE) {
    bit_bms_comm_flag = FALSE;
    bit_bms_work_flag = FALSE;
}

第二级(第 82-91 行)——任一门禁触发即清零全部能力量:

if (bit_bms_comm_flag == FALSE || bit_bms_work_flag == FALSE ||
    bit_bms_upgrade_flag != FALSE || bit_bat_ready_flag != FALSE) {
    s32_battery_max_charge_power = 0;
    s32_battery_max_dischg_power = 0;
    s16_battery_max_charge_current = 0;
    s16_battery_max_dischg_current = 0;
}

因为第一级已经把 bit_bat_ready_flag != FALSE 时的前两位清成 FALSE,所以第二级的条件在这个场景下必然成立——"电池就绪"等价于"禁止一切充放电"。这是本文件最需要注意的语义。

4.3 主机广播:pvcharger_send_host_machine_info()(第 101 行)

定时(第 107-113 行):

static u32 s_u32_send_tick = 0;
if (abs(OSTimeGet() - s_u32_send_tick) < 500) return;   // 500 tick 限流
s_u32_send_tick = OSTimeGet();

组帧(Modbus RTU,功能码 0x10 写多个保持寄存器):

偏移内容
0PVCHARGR_BROADCAST_ADDR(0x00,广播地址)
1FUNC_WRITE_REGISTER(0x10)
2-3起始寄存器 REG_PV_CHARGER_BATTERY_STATE_E(0x2340)大端
4-5寄存器个数 = REG_PV_CHARGER_CONFIG_MAX_E - REG_PV_CHARGER_BATTERY_STATE_E 大端
6数据字节数 = 寄存器个数 × 2
7+各寄存器值,逐个大端两字节
末尾 2 字节CRC16(低字节在前)

取值方式(第 124-130 行)是逐寄存器调 get_para_from_reg_addr(PVCHARGR_BROADCAST_ADDR, reg)——即用"寄存器地址"为键向 Modbus 模块索取本机值,而不是直接读 g_inverter_pvchargr_info 字段。这保证了"主机广播的寄存器映射"和"从机接收的寄存器映射"共用同一张表,是这套设计能自洽的关键。CRC 用 GetCrcCode_Modbus(),最后 modbus_send_data() 发出。

4.4 从机接收与心跳:pvcharger_from_machine_recv_sync()(第 146 行)

只有两行:

s_u8_pvcharger_comm_flag = TRUE;
s_u32_pvcharger_recv_tick = OSTimeGet();

由 modbus.c:8103 在写寄存器回调处理完 s32_battery_max_charge_power 时调用(即"外部主机每写一次寄存器区间就算一次心跳")。因为没有独立的收帧中断,心跳的粒度取决于主机写频率。

4.5 从机数据处理(inverter_pvcharger_info_handle 的 else 分支,第 168-245 行)

按顺序执行 6 步:

步骤 1:15 s 超时检测(第 169-176 行)

if (abs(s_u32_pvcharger_recv_tick - OSTimeGet()) >= 15000) {   // 15 s 无帧
    if (s_u8_pvcharger_comm_flag != FALSE) {                    // 只在"由好变坏"的沿上清一次
        s_u8_pvcharger_comm_flag = FALSE;
        memset(&g_modbus_pvchargr_info, 0x00, sizeof(PVCHARGR_INFO_T));
    }
}

用"当前标志是否为真"做边沿检测,避免每一拍都 memset;g_modbus_pvchargr_info 清零后后续步骤自然得到全 0 结果。

步骤 2:拷贝并换算充电功率(第 178-183 行)

g_control_pvchargr_info = g_modbus_pvchargr_info;                                  // 整表拷贝
g_control_pvchargr_info.s32_battery_max_charge_power = g_modbus_pvchargr_info.s32_battery_max_charge_power / 4;
g_control_pvchargr_info.s32_battery_max_charge_power = MIN(该值, inverter_read_rated_dc_charge_power());
g_control_pvchargr_info.s32_battery_max_dischg_power = MIN(该值, inverter_read_rated_dc_discharge_power());

/4 是一个无注释的魔法数(见第 7 节):充电功率被除以 4,而放电功率不作除——两者处理不对称。随后两个功率分别夹到逆变器额定 DC 充/放功率。

步骤 3:与本地采集同构的两级归零(第 185-200 行)——把 bit_bat_ready_flag 短路与"任一门禁清零"逻辑在 g_control_pvchargr_info 上重放一遍。注意这里是重复代码:同样的判据在第 76-91 行已经写过一次,只是作用对象不同。

步骤 4:负值钳位(第 202-220 行)——4 个字段分别 if (< 0) = 0:s32_battery_max_charge_power、s16_battery_max_charge_current、s32_battery_max_dischg_power、s16_battery_max_dischg_current。这 4 句是逐字段展开的同一段逻辑,可用循环或 if (<0) =0 一行处理。

步骤 5:发布(第 222 行)——g_inverter_pvchargr_info = g_control_pvchargr_info; 整表拷出。

步骤 6(第 249 行,两个分支之外):ems_set_fault_bit(22, (s_u8_pvcharger_comm_flag == FALSE));

注意:步骤 4 只夹紧了"能力量",没有夹紧 u16_battery_soc、s16_battery_volt 等状态量,这些字段直接来自外部主机,未经合理性校验。

4.6 pvcharger_read_* 系列:纯访问器

第 259-514 行共 23 个函数,除 pvcharger_read_machine_mode 读配置外,其余 22 个全部是 return g_inverter_pvchargr_info.<字段>; 的单行直读,没有任何判空、单位换算或范围检查。它们的存在意义是让 g_inverter_pvchargr_info 可以保持为全局变量而外部模块通过函数取数(一种弱封装),但实际上 modbus.c 是直接访问 g_inverter_pvchargr_info 字段的,两种风格并存。

4.7 关键全局变量的读写关系

变量写方读方触发条件
g_modbus_pvchargr_infomodbus.c:8073-8135 寄存器写回调(逐字段,32 位分高低字两次写);本文件第 174 行 memset 清零本文件第 178-183 行外部主机下发 / 15 s 超时
g_control_pvchargr_info本文件第 178-220 行本文件第 222 行每周期从机模式
g_inverter_pvchargr_info本文件第 46-91 行(本地模式)、第 222 行(从机模式)本文件 pvcharger_read_* 全系列;modbus.c:5260-5321每周期
s_u8_pvcharger_comm_flag第 148 行置 TRUE(收帧)、第 164 行置 TRUE(非从机模式)、第 173 行置 FALSE(超时)第 171、249 行收帧 / 超时 / 角色
s_u32_pvcharger_recv_tick第 149、165 行第 169 行收帧 / 角色
buff_send_modbus[]第 115-135 行组帧modbus_send_data 发出每 500 tick(主机模式)
g_t_ems_info.u_ems_fault.u32_all 第 22 位第 249 行告警上报每周期

5. 对外接口

5.1 本文件导出的函数

pvcharger_send_host_machine_info、pvcharger_from_machine_recv_sync、inverter_pvcharger_info_handle、pvcharger_read_machine_mode,以及 22 个 pvcharger_read_bat_*(完整清单见第 3 节)。唯一静态函数是 pvcharger_local_info_handle。

主要调用方:

调用方用到的接口
midware/inverter/inverter.c:1168inverter_pvcharger_info_handle()(逆变器任务周期)
midware/interface/interface.c:328pvcharger_send_host_machine_info()
midware/interface/modbus/modbus.c:8103pvcharger_from_machine_recv_sync()
midware/interface/modbus/modbus.c:8461,8491pvcharger_read_machine_mode()

5.2 本文件导出的全局变量

5.3 本文件调用的外部函数/变量

按 #include(第 13-20 行)分组:

来源模块使用的函数/变量
inverter.hinverter_read_rated_dc_charge_power、inverter_read_rated_dc_discharge_power、g_t_inv_info
bms.hbms_get_connect_state、bms_read_work_status、bms_read_relay_status、bms_get_ready_enable、bms_read_total_soc/volt/cur/power、bms_read_max_charge_power、bms_read_max_dischg_power、bms_read_chg_cut_rate_volt、bms_read_chg_max_rate_cur、bms_read_dischg_cut_rate_volt、bms_read_dischg_max_rate_cur、bms_read_battery_series_para_state、bms_get_store_type、bms_cell_rated_vot、bms_read_rated_vot_instant、bms_read_charge_pro_cur_step_1、bms_read_discharge_pro_cur_step_1、bms_read_battery_capacity、bms_read_battery_available_capacity
upd_download.hGet_Status_Is_Updated_From_Server、UPDT_BMS
bms_download.hGetPercentDownloadBMS、IsDownloadBMS
modbus.hbuff_send_modbus、get_para_from_reg_addr、modbus_send_data、PVCHARGR_BROADCAST_ADDR、FUNC_WRITE_REGISTER、REG_PV_CHARGER_BATTERY_STATE_E、REG_PV_CHARGER_CONFIG_MAX_E
data.hems_set_fault_bit
crc.hGetCrcCode_Modbus
pvcharger.h / chip.h / includes.hPVCHARGR_INFO_T、OSTimeGet、abs、memset、TRUE/FALSE

6. 依赖关系

6.1 依赖(本文件包含/使用的模块)

pvcharger.h、inverter.h、bms.h、upd_download.h、bms_download.h、modbus.h、data.h、crc.h。

依赖方向:pvcharger.c → {bms(本地电池数据 + 升级状态)、inverter(额定 DC 充放功率)、modbus(寄存器取值、发送缓冲、发送函数)、data(故障位)}。

6.2 被依赖

7. 备注与风险点

  1. "电池就绪"等价于"禁止一切充放电",且该逻辑被写了两遍。第 76-91 行(g_inverter_pvchargr_info)与第 185-200 行(g_control_pvchargr_info)是同一段判据的复制粘贴。第一级把 bit_bat_ready_flag 为真时的 bit_bms_comm_flag / bit_bms_work_flag 强清为 FALSE,第二级的条件因而必然成立——bit_bat_ready_flag == TRUE 的净效果是把最大充/放功率与最大充/放电流全部清零。如果 bms_get_ready_enable() 的语义是"电池准备好、可以接受功率指令",那么这段代码的方向是相反的。需要以 BMS 侧实现来判定,但从纯代码看这是可疑写法。
  1. 充电功率 /4 而放电功率不除(第 180 行)。g_control_pvchargr_info.s32_battery_max_charge_power = g_modbus_pvchargr_info.s32_battery_max_charge_power / 4; 没有注释,且同一段代码里紧邻的放电功率没有对应除法(第 183 行只做 MIN)。若 /4 的意图是"外部主机上报的是 4 机总量、本机取 1/4 份额",那么放电功率同样应该除 4,否则本机在放电方向上会企图承担 4 倍能力。这是一个高风险的不对称。
  1. 手工展开的负值钳位(第 202-220 行)。4 个字段 4 个 if (< 0) = 0,没有覆盖 u16_battery_soc、s16_battery_volt、s16_battery_curr、s32_battery_power、截止电压、额定电压、容量等字段。来自外部 Modbus 主机的状态量完全没有合理性校验,会被原样发布给 modbus.c:5260-5321 的从机寄存器与下游读取方。
  1. g_control_pvchargr_info 是未导出的全局变量。定义在 pvcharger.c:26,头文件没有 extern,本文件之外无人使用。应改为 static,否则既污染全局命名空间又给外部"绕过限值处理直接读写中间量"留了后门。
  1. buff_send_modbus[] 是 Modbus 模块的共享全局缓冲区。pvcharger_send_host_machine_info() 直接往里面组帧并调用 modbus_send_data(),而 Modbus 任务/外部 485 通道同样在使用这个缓冲区。1600 字节左右的区域没有互斥保护,若主机广播(interface.c:328 触发)与 Modbus 从机响应/轮询并发,存在组帧被覆盖的风险。由于调用点 interface.c:328 上下文是"EVC(充电桩)配置分支"之后紧接着的 OSTimeDlyHMSM(0,0,0,100),看起来是靠任务时序错开的。
  1. 心跳粒度依赖外部主机的写行为。pvcharger_from_machine_recv_sync() 只在 Modbus 写寄存器回调里被调用(modbus.c:8103),且调用点在"s32_battery_max_charge_power 低字写完之后"。如果外部主机只读不写、或写入的寄存器区间不覆盖该点,心跳不会刷新,15 s 后会被误判为通信丢失并把 g_modbus_pvchargr_info 清零。
  1. s_u32_pvcharger_recv_tick 的 abs() 比较(第 169 行)。OSTimeGet() 返回 INT32U,相减后传给 abs();tick 回绕时该判据行为不可靠(parallel.c:1781 有专门的回绕保护,本文件没有)。
  1. emulator 级的 printf 调试代码被注释保留了 22 行(第 224-245 行),且是用 /* */ 块注释包住的 printf。这类"随时准备打开"的调试代码长期留在源码里会导致误开启后阻塞任务,属版本遗留。
  1. 单位换算只做了一处。s16_battery_volt = bms_read_total_volt() / 10(第 53 行)把 BMS 的 0.1 V 转成 1 V;而 SOC、电流、功率、截止电压、额定电压、容量等字段没有任何换算注释或缩放。同一结构体内不同字段单位不一致,且结构体定义(pvcharger.h)里也没有单位说明。
  1. REG_PV_CHARGER_BATTERY_STATE_E 处的寄存器个数由宏相减得到(第 119-121 行)。REG_PV_CHARGER_CONFIG_MAX_E - REG_PV_CHARGER_BATTERY_STATE_E 被计算 3 次,且"数据字节数"字段直接写 (寄存器个数) * 2(没有先转 u8,赋给 buff_send_modbus[] 的 u8 元素时隐式截断)。若寄存器区间超过 127 个,字节数字段会溢出。当前区间远小于该值,但这是一个隐式截断点。
  1. for (int i = 0; ...) 使用 C99 循环内声明(第 124 行、第 1131 行等同类写法在 parallel_sci.c 也有)。Keil MDK 默认 C90 模式下会报错,说明工程实际开启了 C99(或 --c99);这是编译配置依赖点,不是缺陷,但更换工具链时需注意。