goodwe_comm.c
goodwe_inverter_comm 的三分支与时间片4.3 设备信息查询与版本/子型号识别4.4 运行数据解析与寄存器映射4.5 故障位与告警位映射4.6 安规码双向映射4.7 功率模式状态机4.8 电池参数下发与 BMS 数据上报4.9 LED 显示下发4.10 一致性维护 goodwe_inverter_data_check4.11 一次性配置状态机 set_goodwe_inv_config4.12 升级分支的衔接5. 对外接口6. 依赖关系7. 备注与风险点源文件路径:
midware/inverter/goodwe/goodwe_comm.c所属模块:midware/inverter/goodwe(古瑞瓦特 GoodWe 逆变器通信) 代码行数:2596
1. 文件职责
本文件是 EMS 主控与古瑞瓦特(GoodWe)混合逆变器之间的 Modbus-RTU 主站通信实现,EMS 侧作为主站、逆变器为从站(从站地址固定 0xF7)。它负责三件事:周期查询逆变器设备信息 / 运行数据 / 安规参数,把寄存器原始字节翻译成 g_t_inv_info(inverter.h)中的电压、电流、功率、能量、故障位;按调度逻辑下发的功率指令把工作模式与功率写入逆变器,并下发 BMS 汇总数据、LED 显示、安规、ISO 限值等配置。文件由 task_inverter()(midware/inverter/inverter.c:1045,在 user/main.c:189 创建)在每一轮循环中通过 goodwe_inverter_comm() 与 goodwe_inverter_data_check() 两个入口驱动。它处在数据流的中间层:向下经 hardware/usart4 收发帧,向上把结果写入 g_t_inv_info / g_t_meter / g_t_bat_info,供 logic 层调度与 net 层上报使用;当云端下发固件时它把控制权交给 goodwe_upgrade.c 的升级状态机。
2. 关键数据结构与宏
| 名称 | 类型 | 说明 |
|---|---|---|
GOODWE_TABLE_T | 结构体 | 本文件核心状态表,定义于 goodwe_comm.h:350。前半段是 15 个 u8_xxx_flag 触发标志位(安规设置、清能量、离网自启动、查型号、EBMS 通信、电池参数、查设备信息、AFCI、重启、微网 SOC/频率保护、ISO、读安规数据),中间是 8 个 u32_xxx_tick 周期时间戳,再是查询/回读缓存(u16_inv_system_type、u8_inv_device_type[10]、u8_inv_model_sub、u16_feedin_*、u16_bat_* 等),最后内嵌故障/告警联合体 |
g_t_goodwe_comm_info | 全局变量 | GOODWE_TABLE_T 唯一实例,goodwe_comm.c:30 定义,goodwe_comm.h:410 导出 |
s_u8_goodwe_recv_buf | static 数组 | u8[256](INV_RECV_BUFFER_SIZE),所有收帧的落地缓冲,goodwe_comm.c:31 |
GOODWE_ERROR_U | 联合体(位域) | goodwe_comm.h:263,u32_all 与 4 字节位域共用,32 个故障位:GFCI 自检失败、AC HCT 失败、AFCI 失败、DCI 一致性、继电器自检、电网丢失、接地电流异常、DC Bus 偏高、内部/外部风扇、过温、PV 过压、隔离失效、直流注入偏高、备份过载、相角失败、DSP 通信失败、EEPROM 失败等 |
GOODWE_WARNNING_U | 联合体(位域) | goodwe_comm.h:321,u16_all 与 2 字节位域共用 16 个告警位:逆变电流不平衡、CPLD 告警使能、PV/Bus/电池电压低、PLL 告警、Bus 不平衡、逆变失控、电流超限(软/硬)、软控关机、系统假故障、孤岛、离网相位异常 |
GOODWE_POWER_MODE_E | 枚举 | goodwe_comm.h:147,写入 47511 的工作模式字:AUTO=0x0001、CHARGE_PV、DISCHARGE_PV、IMPORT_AC、EXPORT_AC、CONSERVE、OFF_GRID、BATTERY_STANBY、BUY_POWER、SELL_POWER、CHARGE_BAT、DISCHARGE_BAT、STOP=0x00FF |
GOODWE_LED_DISPLAY_E | 枚举 | goodwe_comm.h:164,LED 显示语义:LED_OFF=1、LED_ON、LED_FLASH_1X、LED_FLASH_2X、LED_FLASH_4X |
GOODWE_OPERATION_MODE_E | 枚举 | goodwe_comm.h:173,逆变器上报运行态:WAIT_E=0、ON_GRID_E、OFF_GRID_E、FAULT_E、FLASH_E、CHECK_E |
GOODWE_SAFETY_COUNTRY_E | 枚举 | goodwe_comm.h:183,GoodWe 安规国家码表,从 GW_ITALY=0 递增到 GW_AUSTRALIA_P_C,另有跳跃值 GW_UL1741=0x61、GW_PR_240VAC=0x77 |
GOODWE_INV_DEBUG | 宏 | goodwe_comm.h:17,置 0,关闭全部 printf 调试输出 |
MAX_REGISTER_NUM | 宏 | 100,单帧最多读写寄存器数,ems_send_ctrl_data_to_inv 的入参合法性上界 |
INV_RECV_BUFFER_SIZE | 宏 | 256,收帧缓冲长度 |
GOODWE_DAT_PCK_INTERVAL_INV | 宏 | 100(ms),两次发送之间的最小间隔,goodwe_write_data 用于节流 |
GOODWE_SLAVE_ADDRESS | 宏 | 0xF7,Modbus 从站地址 |
FUNC_READ_HOLDING_REGISTER / FUNC_WRITE_SIGNAL_HOLDING_REGISTER / FUNC_WRITE_MULTIPLE_HOLDING_REGISTER | 宏 | 0x03 / 0x06 / 0x10 三个 Modbus 功能码 |
ALPHA_SAFETY / GOODWE_SAFETY | 宏 | 0 / 1,parse_goodwe_safety() 的方向选择参数 |
INV_TYPE_ALPHA | 宏 | 10,写入 47011 把逆变器系统类型标为 Alpha |
GW_ABP_SOC_PROTECT | 宏 | 95,离网(GridForming)充电截止 SOC 上限保护值 |
ADDR_REGISTER_* / NUM_* | 宏 | 约 40 组「寄存器起始地址 + 寄存器个数」常量对,例如设备信息 35000/21、运行数据 35100/122、能量数据 35191/21、ARC 模块版本 36067/2、清能量 45217/1、复位 45220、SPS 复位 45221、安规设置 45244/1、ISO 限值 45245/1、详细报文 45250/12、备份使能 45252/1、离网自启动 45253/1、AFCI 45270、并网延迟 45276/1、电池参数 45350/9、微网 SOC 保护 45387/1、安规数据 45400/28、功率因数 45443/1、微网频率保护 45446/1、QU 曲线 45456/1、安规数据 2 45495/4、LED 组 47008~47028、系统类型 47011/1、停机 SOC 保护 47500/2、馈电控制 47509/2、功率模式 47511/2、电池协议 47514/1、下发 BMS 数据 47900/16、EBMS 通信丢失使能 47916/1 |
3. 函数清单
| 函数名 | 作用域 | 说明 |
|---|---|---|
goodwe_write_data | 全局(仅本文件调用) | 带 100ms 发送节流的 UART4 裸数据发送,先清收缓冲再发 |
send_query_data_to_inv | 全局(仅本文件调用) | 组 0x03 读保持寄存器帧(8 字节)并发送 |
ems_send_ctrl_data_to_inv | 全局(仅本文件调用) | 组 0x06/0x10 写寄存器帧,带寄存器数与长度一致性校验 |
goodwe_recv_data | 全局(仅本文件调用) | 从 UART4 取一帧、逐字节搬入缓冲、做 Modbus CRC 校验,失败返回长度 0 |
parse_goodwe_safety | 全局(仅本文件调用) | 安规码双向翻译:GOODWE_SAFETY 方向 GoodWe→Alpha,ALPHA_SAFETY 方向 Alpha→GoodWe |
parse_goodwe_work_mode | 全局(仅本文件调用) | 逆变器运行态枚举转 EMS 内部 cWaitMode/cNormalMode/cUPS/cFaultMode/cCheckMode |
parse_inv_err_msg | 全局(仅本文件调用) | 把 u32_inv_fault 位域选择性映射到 g_t_inv_info.t_run_info 的故障码位域 |
goodwe_read_type | 全局 | 按逆变器 SN 第 2~8 字节识别子型号(SUB_SP9_6/SP7_6/SPB9_6/SPB7_6) |
query_inv_arc_module_ver | 全局 | 读 ARC 模块版本寄存器 36067,掉 0 时保持上次有效值 |
query_inv_device_info | 全局 | 读设备信息 35000 起的 21 个寄存器,填 SN、额定功率、主/从/ARM 版本号、硬件版本 |
query_inv_running_data | 全局 | 读运行数据 35100 起的 122 个寄存器,解析 PV/电网/备电/电池/能量/故障/告警/诊断量 |
query_inv_safety_info | 全局 | 读安规数据 45400/28 与 45495/4,填 t_safty_info 各类保护阈值与恢复时间 |
set_inv_bat_para | 全局 | 写电池参数 45350 起 9 个寄存器(容量、串数、充放电压电流、保留 SOC) |
query_inv_bat_para | 全局 | 读回电池参数 45350/9 到 g_t_goodwe_comm_info(当前无调用者) |
set_bakcup_enable | 全局 | 写备份使能 45252(当前无调用者) |
send_bms_data_to_inv | 全局 | 把 BMS/PV 充电器汇总数据(32 字节 = 16 寄存器)写到 47900 |
query_afci_enable | 全局 | 读 AFCI 使能状态(45270) |
set_afci_enable | 全局 | 写 AFCI 使能(45270),成功后清 u8_set_afci_enable_flag |
query_low_pv_afci_check_enable | 全局 | 读低 PV 时 AFCI 检测使能 |
set_low_pv_afci_check_enable | 全局 | 写低 PV AFCI 检测使能,成功后清对应标志 |
query_offgrid_autostart | 全局 | 读离网自启动状态(45253) |
set_offgrid_autostart | 全局 | 写离网自启动(45253) |
query_system_type | 全局 | 读系统类型(47011) |
set_system_type | 全局 | 把系统类型写为 INV_TYPE_ALPHA(10) |
set_led_control | 全局 | 写 47028 解锁 LED 控制(固定 0xA5 0x67) |
set_inv_led_disp | 全局 | 依次下发 WIFI/COM/BAT/SYS/FAULT 五类 LED 与 4 段 SOC 灯 |
set_inv_wait_mode | 全局(导出) | 写功率模式 STOP(0x00FF) 让逆变器进入待机,返回是否收到应答 |
set_inv_safety | 全局 | 先切待机再写安规 45244,成功后备份配置并触发重新查询 |
set_inv_offgrid_mode | 全局 | 写功率模式 OFF_GRID,前置判断电池可放功率与 PV 电压/电池 SOC |
set_inv_ongrid_mode | 全局 | 写功率模式 CHARGE_BAT/DISCHARGE_BAT + 功率值,含电网侧功率限幅 |
set_inv_power_mode | 全局 | 按电网频率选并网/离网分支;电网状态为 0 时先延迟 5 秒 |
clear_inv_energy | 全局 | 写 45217 清除逆变器电量统计,成功后清 u8_clear_inv_energy_flag |
get_inv_feedin | 全局 | 读馈电使能与馈电功率(47509/2)(当前无调用者) |
set_inv_feedin | 全局(导出) | 写馈电使能与功率,功率先减 30W 采样补偿并做下限 0 处理 |
set_bat_protocol | 全局 | 写电池协议为锂电池(0x011F) |
set_ebms_comm_status | 全局 | 写 EBMS 通信超时告警使能(47916) |
set_ongrid_delay | 全局 | 写并网延迟秒数(45276)(当前无调用者) |
query_ebms_comm_status | 全局 | 读 EBMS 通信丢失使能状态 |
query_iso_limit | 全局 | 读 ISO 限值(45245) |
set_iso_limit | 全局 | 写 ISO 限值(45245) |
query_micro_grid_soc_protect | 全局 | 读微网 SOC 保护值(45387),成功后清查询标志 |
set_micro_grid_soc_protect | 全局 | 写微网 SOC 保护值,成功后清设置标志 |
query_micro_grid_freq_protect | 全局 | 读微网频率保护值(45446),成功后清查询标志 |
set_inv_restart | 全局 | 写 45220 = 0x01 0x69 复位逆变器,返回是否成功 |
restart_inv_process | 全局 | 先最多 3 次切待机,延时 500ms,再最多 3 次复位逆变器 |
goodwe_comm_init | 全局(导出) | 初始化各触发标志为 TRUE、恢复安规缓存、设置微网充电 SOC 为 85 |
set_goodwe_inv_config | 全局 | 按标志位逐项执行一次性配置下发(自启动、系统类型、EBMS、电池参数、AFCI、微网保护、ISO) |
goodwe_inverter_data_check | 全局(导出) | 电池保护参数钳位、清能量/安规/重启/AC 额定功率变化检测、离网充电 SOC 与 PV 电表位置落盘 |
goodwe_inverter_comm | 全局(导出) | 主调度器:升级分支判断 + 各周期任务的时间片触发 + 通信丢失检测 |
4. 核心逻辑解析
4.1 通信帧格式与收发机制
本文件使用标准 Modbus-RTU,帧结构由 send_query_data_to_inv()(goodwe_comm.c:76)与 ems_send_ctrl_data_to_inv()(goodwe_comm.c:101)组装:
读(0x03):[0]=0xF7 从站地址 | [1]=0x03 功能码 | [2..3]=起始地址大端 | [4..5]=寄存器数大端 | [6]=CRC低 | [7]=CRC高 —— 共 8 字节
写单(0x06):[0]=0xF7 | [1]=0x06 | [2..3]=地址 | [4..5]=数据 | [6..7]=CRC
写多(0x10):[0]=0xF7 | [1]=0x10 | [2..3]=地址 | [4..5]=寄存器数 | [6]=字节数N | [7..7+N-1]=数据 | CRC低 | CRC高
CRC 由 GetCrcCode_Modbus()(common/crc)计算,按 Modbus 规范低字节在前。响应帧不做独立的命令字匹配,只做两件事(goodwe_recv_data,goodwe_comm.c:142):把 uart4_get_data_len() 指定的全部字节搬进 s_u8_goodwe_recv_buf,然后取末两字节作为 CRC、对前 len-2 字节重算 CRC 比对;一致才把 *len 置为真实长度,否则置 0。上层每个查询函数随后还会额外校验 buf[2] == 寄存器数*2 来确认确实是本次请求的应答(例如 goodwe_comm.c:512)。
重试与超时:本文件没有重试计数,采用的是「时间片轮询 + 标志位保持」的策略——所有「设置类」函数只在收到非零应答时清除对应 u8_xxx_flag,因此一条配置命令会以 50ms / 100ms 的节拍被反复下发,直到逆变器应答为止。goodwe_write_data(goodwe_comm.c:43)用函数内 static s_u32_send_pack_tick 保证两次发送间隔不小于 GOODWE_DAT_PCK_INTERVAL_INV(100ms),收到响应前用 OSTimeDlyHMSM 主动补足剩余间隔,形成「发—等—发」的自然节流。
4.2 主调度器 goodwe_inverter_comm 的三分支与时间片
goodwe_inverter_comm()(goodwe_comm.c:2444)是每轮 task_inverter 都调用的总入口,分三个互斥分支:
- 内部固件升级分支(
is_inverter_firm_download_finished()为真,即UPDT_INV1 && UPDT_INV2):首次进入时置s_u8_upgrade_flag、调goodwe_comm_init()、清零t_run_info中从u16_pv1_volt到u8_run_mode的连续内存段(goodwe_comm.c:2455),然后按goodwe_read_upgrade_flag_master()/goodwe_read_upgrade_flag_slave()把运行模式设为cUpdateMaster/cUpdateSlave,最后交给goodwe_upgrade_handle()。 - 外部(ARC/AFCI)升级分支(
Get_Status_Is_Updated_From_Server(UPDT_INV3)):同样先初始化,运行模式设为cUpdateArm,交给goodwe_upgrade_external_handle()。 - 常规通信分支:见下节时间片表。
常规分支的时间片(全部基于 OSTimeGet() 毫秒计数,并统一带「时钟回绕保护」——若记录的 tick 大于当前 tick 则强制重置为当前 tick):
| 周期 | 触发条件 | 动作 |
|---|---|---|
| 30 000 ms | OSTimeGet() - u32_ems_inv_comm_tick > 30000 | 置 u8_inverter_comm_lost_flag = TRUE 并重新 goodwe_comm_init()(通信丢失) |
| ≥500 ms(首次) | u8_query_inv_device_info_flag 为真 | query_inv_device_info() |
| 每轮 | u8_query_inv_device_info_flag 为假 | set_goodwe_inv_config()(内部再按各自标志做一次性配置) |
| 每轮 | u8_set_inv_restart_flag | restart_inv_process() |
| 每轮 | u8_set_inv_safety_flag | set_inv_safety() |
| 每轮 | u8_clear_inv_energy_flag | clear_inv_energy() |
| 每轮 | u8_read_safety_data_flag | query_inv_safety_info() |
| 180 000 ms | 距 u32_query_inv_device_info_tick | 重新置 u8_query_inv_device_info_flag = TRUE(每 3 分钟重查一次设备信息) |
| 50 ms | u32_query_inv_running_data_tick | query_inv_running_data() |
| 1000 ms | u32_set_led_disp_tick | set_inv_led_disp() |
| 50 ms | u32_set_inv_power_mode_tick | set_inv_power_mode() |
| 100 ms | u32_send_bms_data_to_inv_tick | send_bms_data_to_inv() |
| 2000 ms | u32_set_inv_feedin_tick 且运行模式为 cNormalMode | set_inv_feedin(ON, parallel_read_dc_total_set_inv_power()) |
注意 u32_query_inv_device_info_tick 被两个地方共用:既做「重查设备信息」的计时(180s),又做「首次查询后的 500ms 抑制」。因此首次上电后 500ms 内不会重复查设备信息。
u32_ems_inv_comm_tick 是所有成功收帧函数共用的「活体心跳」,每个查询函数在应答有效时都会写 OSTimeGet(),通信丢失判定即基于它。
4.3 设备信息查询与版本/子型号识别
query_inv_device_info()(goodwe_comm.c:496)读 35000 起 21 个寄存器,字节偏移映射为:
| 缓冲区偏移 | 含义 |
|---|---|
[5..6] | 额定 AC 充/放电功率(同一值同时赋给 u32_ac_rate_charge_power 与 u32_ac_rate_discharge_power) |
[9..24] | 逆变器 SN(16 字节,直接 memcpy 到 u8_inv_sn) |
[25..34] | 设备型号字符串(10 字节 → u8_inv_device_type) |
[35..36] / [39..40] | 主机版本高/低 |
[41..42] / [43..44] | 从机(ARM)版本高/低 |
版本或 ARC 版本变化时调 Set_Setup_Config_Status(FALSE) 让配置区重新落盘。子型号由 goodwe_read_type()(goodwe_comm.c:438)按 SN 第 2~8 字节与 "9600AES"/"7600AES"/"9600ABP"/"7600ABP" 比对,得出 SUB_SP9_6/SUB_SP7_6/SUB_SPB9_6/SUB_SPB7_6/SUB_NULL;子型号变化会调 set_inverter_sub() + save_job_post(INVERTER_CONFIG_PAGE)。子型号进一步决定 g_t_meter.t_config_data.u8_gridforming_en:SP* 系列关闭 GridForming,SPB*(带 ARC/AFCI 模块)系列打开。最后用 snprintf 组装 u8_master_ver(V%03d.%03d)、u8_slave_ver(V%02d)、u8_arm_ver(V%03d.%03d) 三个显示字符串。
4.4 运行数据解析与寄存器映射
query_inv_running_data()(goodwe_comm.c:590)一次读 122 个寄存器(244 字节 + 首部 3 + CRC 2 = 249 字节,落在 256 字节缓冲内)。请求起始地址 35100,因此缓冲区下标 = 2 + (寄存器偏移 × 2),下表给出关键量:
| 缓冲偏移 | 寄存器 | 解析 | 目标变量 / 系数 |
|---|---|---|---|
| 9,10 / 11,12 / 13-16 | 35103~35106 | PV1 电压/电流/功率 | u16_pv1_volt(÷10)、u16_pv1_cur、u32_pv1_power(32 位大端) |
| 17-24 | 35107~35110 | PV2 同构 | u16_pv2_*、u32_pv2_power |
| 25-32 | 35111~35114 | PV3 同构 | u16_pv3_*、u32_pv3_power |
| 33-40 | 35115~35118 | PV4 同构 | u16_pv4_*、u32_pv4_power |
| 45,46 / 47,48 / 49,50 | 35121... | A 相电压(÷10)、电流(s16)、电网频率 | u16_grid_volt_a、s16_grid_cur_a、u16_grid_freq(单位 0.01Hz) |
| 53,54 / 63,64 / 73,74 | — | A/B/C 相有功 | s32_grid_actpower_a/b/c(s16 扩展),三者相加得 s32_grid_actpower_t |
| 55-76 | — | B/C 相电压电流 + 电网状态 | u16_grid_status([75..76]) |
| 79,80 / 83,84 / 85,86 / 89,90 | — | 逆变 A 相有功、总有功、总无功、总视在功率 | s32_inv_actpower_a/t、s32_inv_reactpower_t、s32_inv_apparentpower_t |
| 93-144 | — | 备电(Backup) A/B/C 相电压电流与有功、备电总功率 | u16_backup_*、s32_backup_actpower_* |
| 155,156 | — | 机温 | s8_machine_temp(÷10) |
| 159,160 | — | 母线电压 | u16_bus_volt(÷10) |
| 163,164 / 165,166 / 169,170 | — | 电池总压(÷10)、电流(s16)、功率(s16) | u16_bat_volt、u16_bat_curr、s16_bat_power |
| 173,174 | — | 告警字 | u16_inv_warning.u16_all |
| 175,176 | — | 安规码 | t_safty_info.u8_safety = parse_goodwe_safety(ALPHA_SAFETY, ...) |
| 177,178 | — | 运行模式 | t_run_info.u8_run_mode = parse_goodwe_work_mode(...) |
| 181-184 | — | 32 位故障字 | u32_inv_fault.u32_all → parse_inv_err_msg() |
| 185-188 / 215-218 / 221-224 | — | PV 发电量 / 电池充电量 / 电池放电量 | u32_energy_pv_input、u32_energy_bat_charge、u32_energy_bat_dischg |
| 229,230 | — | CPLD 告警 | u16_cpld_warning |
| 239-246 | — | 4 个 DSP 调试字 | u16_dsp_debug[0..3],另 [4]=告警字、[5]=CPLD 告警 |
离网工况下还有一段修正(goodwe_comm.c:665):当运行模式为 cUPS 时,用 pv_power(ALL) + bat_power 得到直流侧总功率 l_s16_inv_dc_power,正值时 s32_inv_actpower_a = 直流功率 × 0.966,负值时 = 直流功率 ÷ 0.966——0.966 是逆变器效率近似值,用于把直流功率折算成交流侧实际输出。
4.5 故障位与告警位映射
parse_inv_err_msg()(goodwe_comm.c:396)先把 GoodWe 故障位选择性搬到 EMS 统一故障模型 g_t_inv_info.t_run_info.u_fault_code / u_fault_extend1_code,映射关系见 goodwe_comm.c:407-424(例如 u8_utility_loss→u8_utility_loss、u8_isolation_failure→u8_insulation_fault、u8_dc_bus_high→u8_bus_ovp1、u8_afci_failure→u8_afci_check_protect)。开头有一段特殊处理:离网模式且电网频率 < 45Hz,或 CheckMode 且频率 > 45Hz 时,强制清掉 u8_fac_failure、u8_vac_failure、u8_utility_loss 三个位,避免无市电场景被误判成电网故障。注意 32 位故障字的 byte4(bit24~31)在头文件中被整体注释掉(goodwe_comm.h:310-317),因此该字节原始数据被丢弃、不做映射。
告警 u16_inv_warning 与 u16_cpld_warning 不做位映射,原样转存到 u16_dsp_debug[4]/[5] 供上位机与日志读取。
4.6 安规码双向映射
parse_goodwe_safety(type, safety)(goodwe_comm.c:193)是一个双向查表函数:
type == GOODWE_SAFETY:把 EMS/Alpha 侧安规码(枚举来自midware/inverter/inverter.h:193,如VDE4105_11_18、AS4777_2、CEB、JET_GR_SERIES)翻译成 GoodWe 国家码(GW_GERMANY、GW_AUSTRALIAN、GW_CHINA_STD_RHVOLT、GW_JP_50HZ…)用于下发。type == ALPHA_SAFETY:反向,用于把逆变器回读的 GoodWe 国家码翻译回 EMS 内部码。
匹配不到时返回 0,调用方不做额外判错。
4.7 功率模式状态机
由 set_inv_power_mode()(goodwe_comm.c:1768)分流:
┌── u16_grid_status == 0x00 ─→ OSTimeDlyHMSM(0,0,5,0) 延时 5s
set_inv_power_mode ──┤
├── inverter_read_grid_freq() < 4500 (即 <45.00Hz) ─→ set_inv_offgrid_mode()
└── 否则 ────────────────────────────────────────────→ set_inv_ongrid_mode()
离网分支 set_inv_offgrid_mode()(goodwe_comm.c:1627)带 1500ms 自节流(s_u32_offgird_ctrl_tick,注释说明「逆变器响应需要时间」):
SP7_6/SP9_6:若bms_read_max_dischg_power()==0且 PV1~PV4 电压全部 < 250(即 25.0V)→ 转set_inv_wait_mode()待机并返回;SPB7_6/SPB9_6:若bms_read_max_dischg_power()==0或pvcharger_read_bat_soc() < 110(<11.0%)→ 转待机并返回;- 否则写
OFF_GRID到 47511。
并网分支 set_inv_ongrid_mode()(goodwe_comm.c:1680)同时决定模式字与功率:
parallel_read_dc_total_set_bat_power()为负 →CHARGE_BAT,否则DISCHARGE_BAT;- 放电功率上限钳位:
dc_set_power > 额定放电功率 - PV功率时钳到该值(避免 PV + 电池叠加超过 AC 额定); - 电网侧充电功率 PI 型限幅:取三相电网电流最大值 × A 相电压 ÷ 10 得
l_s32_max_inv_grid_power,与额定充电功率求差l_s32_inv_grid_power_diff;差值绝对值 < 100 时按 1/3 步长累加(软逼近),否则整值累加,累加结果s_s32_bat_charge_power_limit用bms_read_max_charge_power()做上限、0 做下限;最后若请求充电功率绝对值超过该限值,则改为-bms_read_max_charge_power()。这是一段带积分累加器的软限幅,静态变量使限值在多次调用间保持并逐步收敛。 - 帧体为 4 字节:模式字大端 +
abs(功率)大端(NUM_SET_INV_POWER_MODE = 2个寄存器)。
待机 set_inv_wait_mode()(goodwe_comm.c:1555)写 STOP(0x00FF),返回 TRUE/FALSE 表示是否收到应答,是升级流程的进入条件之一。
4.8 电池参数下发与 BMS 数据上报
set_inv_bat_para()(goodwe_comm.c:877)写 45350 起 9 个寄存器,当前固件里是硬编码常量:容量 1000、串数 5、充电电压 2880、充电电流 500、放电电压 2300、放电电流 500;放电保留 SOC 按子型号取值(SP* 为 30,SPB* 为 4),随后同一组放电电压/SOC 又重复写入一次(对应 UPS 放电参数)。
send_bms_data_to_inv()(goodwe_comm.c:1010)把 BMS 汇总数据打包成 32 字节写入 47900 起的 16 个寄存器,字段顺序为:软件版本(2) + 电池串数(2,固定 5) + 充电截止电压(2) + 最大充电电流(2) + 放电截止电压(2) + 最大放电电流(2) + 电池总压(2) + 电池电流绝对值(2) + SOC(2) + SOH(2,固定 100) + 温度(2,固定 300) + 告警码(4,全 0) + 报警码(4,全 0) + 保留(2)。其中充放电流被钳到 ≤500(goodwe_comm.c:1026-1027),充电截止电压固定 2880、放电截止电压固定 2400(goodwe_comm.c:1020-1021)。SOC 的取值分两条路径:
SP*:根据bms_read_max_dischg_power()是否为 0 以及运行模式是否为cNormalMode决定u16_backup_status;若 UPS 被禁用或 backup 不可用则 SOC 固定填 20,否则填 90。另外若g_t_dispatch_t.t_stsystemcmdinfo.u8ControlMode == BAT_CHARGE_FROM_PV,则把最大放电电流强制置 0(只许 PV 充电)。SPB*:SOC 取pvcharger_read_bat_soc()/10,下限 10;若 SOC ≤ 10 且处于cUPS,最大放电电流置 0。
4.9 LED 显示下发
set_inv_led_disp()(goodwe_comm.c:1364)每 1 秒执行一次,先 set_led_control()(写 47028 = 0xA5 0x67 解锁 LED 控制),然后依次下发 6 组寄存器,每次都是「清缓冲 → 填 2 字节 → ems_send_ctrl_data_to_inv → 收帧」:
| 目标 | 条件 → 灯效 |
|---|---|
| WIFI(47013) | g_stEMSInfo.bIsLogin == TRUE → LED_ON,否则 LED_OFF |
| COM(47014) | u8_inverter_comm_lost_flag == TRUE → LED_OFF,否则 LED_ON |
| BAT(47024) | pvcharger_read_bat_connect_state() → LED_ON/LED_OFF |
| SYS(47025) | 运行模式为 cNormalMode 或 cUPS → LED_ON,否则 LED_OFF |
| FAULT(47026) | ems_read_fault_code() 或 inverter_read_fault_code() 或 u_fault_extend1_code.u64_all 或 bms_read_fault_code() 或模式为 cFaultMode → LED_ON |
| SOC1(47020,4 个寄存器) | 按 g_t_bat_info.t_run_info.u16_total_soc 分档:≤250 亮 1 格、≤500 亮 2 格、≤750 亮 3 格、≤1000 亮 4 格、>1000 全灭 |
寄存器字节排列为「偶数下标填 0、奇数下标填灯效值」,即每个 16 位寄存器只有低字节有效。
4.10 一致性维护 goodwe_inverter_data_check
goodwe_inverter_data_check()(goodwe_comm.c:2306)在每轮 task_inverter 中与 goodwe_inverter_comm() 一起被调用,负责把 BMS/配置层与逆变器层的边界值对齐并触发落盘:
- 电池保护参数钳位:从
pvcharger读放电/充电截止电压与最大电流,放电截止电压若小于t_config_data.u16_dischg_cut_rate_volt或为 0 则抬到配置值;充电截止电压若大于配置值或为 0 则压到配置值;充放电流超过配置上限则截断。任一值变化即同步到t_run_info的四个对应字段。 - 清能量标记:
t_control_cmd.u8_clean_energy为真时转成u8_clear_inv_energy_flag并把源标记清零。 - 安规一致性:
t_config_data.u8_safety != t_safty_info.u8_safety→ 置u8_set_inv_safety_flag。 - 重启请求:
t_control_cmd.u8_reset→ 置u8_set_inv_restart_flag并清源标记。 - AC 额定功率识别:
u32_ac_rate_discharge_power == 9600→ DC 充放额定都设 9600、u16_max_ac_from_grid_current = 400;== 7600→ 设 7600、= 380。这是 9.6kW / 7.6kW 两档机型的硬编码识别。 - 配置落盘:AC 充/放额定功率变化时同步到
t_config_data的 AC/DC 四个额定字段并save_job_post(INVERTER_CONFIG_PAGE)。 - 离网充电截止 SOC:
inverter_read_micro_grid_chg_soc()落在 (0, 95] 区间且与上次不同 → 置u8_set_micro_grid_soc_protect_flag并准备下发;越界则强制写回 95 并置标志。 - PV 电表安装位置:
g_t_meter.t_config_data.u8_gridforming_en变化 →save_job_post(METER_CONFIG_PAGE)。
4.11 一次性配置状态机 set_goodwe_inv_config
set_goodwe_inv_config()(goodwe_comm.c:2198)采用「读回比对 → 不符则写 → 相符则清标志」的收敛模式,每项独立:
- 离网自启动(45253):读回不为 TRUE 则写 TRUE,否则清
u8_offgrid_autostart_flag; - 系统类型(47011):读回不为
INV_TYPE_ALPHA(10) 则写 10,否则清u8_query_system_type_flag; - EBMS 通信丢失使能(47916):同上模式;
- 电池参数:
u8_set_inv_bat_para_flag为真时执行set_inv_bat_para()+set_bat_protocol()(该标志只在set_inv_bat_para()收到应答后清除,且每次通信丢失后的goodwe_comm_init()会重新置位); - AFCI:
SP*系列要求使能(读回不为 TRUE 则写 TRUE),SPB*系列要求关闭(读回不为 FALSE 则写 FALSE);低 PV AFCI 检测使能只在SP*系列处理; - 微网保护:
SPB*系列额外查询频率保护(45446)与 SOC 保护(45387),并在标志置位时下发 SOC 保护值; - ISO 限值:
u8_set_iso_limit_flag为真时先set_iso_limit()再query_iso_limit(),回读与t_run_info.u16_run_isolation_limit_value相等才清标志(形成写入-回读确认闭环)。
4.12 升级分支的衔接
goodwe_inverter_comm() 的两个升级分支都不再执行任何数据查询,只更新 u32_ems_inv_comm_tick(防止被误判为通信丢失)、设置 t_run_info.u8_run_mode = cUpdateMaster/cUpdateSlave/cUpdateArm 以让 HMI 显示升级中,然后每轮调用升级状态机。升级状态机本身在 goodwe_upgrade.c:goodwe_upgrade_handle() 处理主/从机(UPDT_INV1/UPDT_INV2),goodwe_upgrade_external_handle() 处理外部 ARC 模块(UPDT_INV3)。升级结束后由 GW_UPGRADE_INFO_INIT 状态清掉云端更新标志,is_inverter_firm_download_finished() 转假,本文件随即回到常规分支。
5. 对外接口
本文件导出(goodwe_comm.h:412-416)
| 符号 | 说明 | 主要调用者 |
|---|---|---|
g_t_goodwe_comm_info | GOODWE_TABLE_T 全局实例 | inverter.c:4056(置 u8_set_iso_limit_flag),其余多处通过 goodwe_comm.h 引用 |
goodwe_comm_init | 复位标志并恢复安规缓存 | inverter.c:96(task_inverter 初始化阶段) |
goodwe_inverter_comm | 主调度入口 | inverter.c:1125(GOODWE_TYPE 分支) |
goodwe_inverter_data_check | 一致性维护与落盘触发 | inverter.c:1126(紧随 goodwe_inverter_comm) |
set_inv_wait_mode | 使逆变器进入待机 | goodwe_upgrade.c:584(外部升级完成后)、本文件离网分支 |
set_inv_feedin | 下发馈电使能/功率 | goodwe_upgrade.c:642/683/703(升级前把馈电置 0) |
除以上 6 项外,本文件其余函数(goodwe_write_data、send_query_data_to_inv、ems_send_ctrl_data_to_inv、goodwe_recv_data、parse_goodwe_safety、parse_goodwe_work_mode、parse_inv_err_msg、query_*、set_*)虽未加 static,但工程内没有其它翻译单元引用,实际为文件内可见。
本文件调用外部
- 硬件层:
hardware/usart4的uart4_clear_buffer、uart4_send_data2、uart4_is_recv_async_data2、uart4_get_data_len、uart4_read_data_async。 - 通用库:
common/crc的GetCrcCode_Modbus。 - 逆变器抽象层(
inverter.c/h):inverter_read_run_mode、inverter_read_grid_freq、inverter_read_pv_power、inverter_read_pv_volt、inverter_read_bat_power、inverter_read_rated_ac_charge_power、inverter_read_rated_ac_discharge_power、inverter_read_grid_cur、inverter_read_fault_code、inverter_read_backup_cur、inverter_read_micro_grid_chg_soc、inverter_set_micro_grid_chg_soc、get_inverter_sub、set_inverter_sub、g_t_inv_info。 - 电表 / BMS / PV 充电器:
meter_read_current、g_t_meter、bms_read_max_charge_power、bms_read_max_dischg_power、bms_read_fault_code、pvcharger_read_bat_curr/volt/soc/connect_state/max_charge_current/max_dischg_current/charge_cut_off_volt/dischg_cut_off_volt、g_t_bat_info。 - 逻辑与调度层:
g_t_dispatch_t.t_stsystemcmdinfo.u8ControlMode(logic/logic.h)、parallel_read_dc_total_set_bat_power、parallel_read_dc_total_set_inv_power(logic/parallel)。 - 数据/存储层:
save_job_post、Set_Setup_Config_Status(data)、ems_read_fault_code。 - 升级与网络层:
is_inverter_firm_download_finished、Get_Status_Is_Updated_From_Server(net/inv_download、net/upd_download)、goodwe_upgrade_handle、goodwe_upgrade_external_handle、goodwe_read_upgrade_flag_master/slave。 - 其它:
g_stEMSInfo.bIsLogin(net/ems)、OSTimeGet、OSTimeDlyHMSM(uCOS-II)、VERSION_INV_HARD(user版本信息)。
6. 依赖关系
依赖(include 列表见 goodwe_comm.c:13-26):goodwe_comm.h、goodwe_upgrade.h、inverter.h、meter.h、bms.h、usart4.h、data.h、logic.h、system_config.h、wireless_driver.h、ems.h、inv_download.h、crc.h;头文件再链到 includes.h。
被依赖:
midware/inverter/inverter.c:goodwe_comm_init()(L96)、goodwe_inverter_comm()(L1125)、goodwe_inverter_data_check()(L1126)、g_t_goodwe_comm_info.u8_set_iso_limit_flag(L4056);inverter.c:21include 了goodwe_comm.h。midware/inverter/goodwe/goodwe_upgrade.c:set_inv_wait_mode()、set_inv_feedin()。- 间接:凡是读
g_t_inv_info/g_t_goodwe_comm_info的 net、hmi、logic 模块都间接依赖本文件产出的数据。
7. 备注与风险点
ems_send_ctrl_data_to_inv潜在栈溢出(goodwe_comm.c:103):局部缓冲u8 l_u8_tx_buf[128],但入参校验只要求len_buff <= MAX_REGISTER_NUM*2 = 200,且l_u8_len = 7 + len_buff。若将来出现register_num=100, len_buff=200的调用,会写入 207 字节并越界约 79 字节。当前所有调用都不超过 32 字节,属潜伏缺陷。l_u8_len类型为u8而缓冲长度为 128:同样的越界风险在类型层面被放大(u8上限 255,不构成溢出保护)。- 未匹配功能码时发送 0 字节(
goodwe_comm.c:117-133):若func_code既非 0x10 也非 0x06,l_u8_len保持 0,会执行goodwe_write_data(buf, 0, ...)空发送;当前无此调用路径。 goodwe_recv_data不校验长度上限(goodwe_comm.c:142):直接把uart4_get_data_len()个字节写入s_u8_goodwe_recv_buf[256],未判断l_u16_len <= 256,超长帧会越界。query_inv_running_data存在无效赋值(goodwe_comm.c:650-651):s32_inv_actpower_b/_c被硬写 0,第 652 行原取[79..80]的赋值被注释,注释说明该值刻意不用。parse_inv_err_msg中运算符优先级(goodwe_comm.c:399-400):(a) && (b)与(c) && (d)之间的||结合性正确,但整行用\续行、形式晦涩,易被后人误改;且第 400 行条件为「CheckMode 且频率 > 45Hz」才清故障,语义与上一分支(UPS 且频率 < 45Hz)不对称,需对照 GoodWe 文档确认。snprintf目标长度存疑(goodwe_comm.c:556-558):snprintf(..., 10, "V%03d.%03d", ...)最多需 8 字符 +\0,恰好放得下,但若u8_master_ver字段实际小于 10 字节仍会截断,依赖inverter.h中字段定义。goodwe_read_type只比较 7 字节型号串:"9600AES"等常量声明为u8[10](goodwe_comm.c:440-443),但只memcmp前 7 字节,型号后缀变化会被误判为SUB_NULL。- 安规映射大量回落到
GW_AUSTRALIAN(goodwe_comm.c:201-258):VDE0126_50HZ、C10_C11、TOR_ERZEUGER、EN50549_NL/DK/GR、CEI021、NRS097_2_1、UTE_C15_712、IEC61727、G59_3全部映到GW_AUSTRALIAN,明显是未逐一适配的占位实现,跨国机型需重点核对。 - 安规回读方向不完整(
goodwe_comm.c:296-343):ALPHA_SAFETY分支只覆盖 10 个国家码,其它值返回 0,t_safty_info.u8_safety会被清零并可能在goodwe_inverter_data_check中误触发安规重设。 - 安规阈值时间被硬编码(
goodwe_comm.c:790/793/796/799/827/830):u16_ovp1_time=100、u16_uvp1_time=200、u16_ovp2_time=16、u16_uvp2_time=100、u16_ovp3_time=16、u16_uvp3_time=16,原来按/0.6换算的代码被注释掉。这是明确的技术债,若安规一致性要求严格会直接不合规。 query_inv_safety_info第二次收帧未校验(goodwe_comm.c:822-830):第二次请求(45495/4)后不判断l_u16_inv_recv_num是否非零,也不清s_u8_goodwe_recv_buf,若第一次的响应残留或本次超时,会把上一次的数据当作 45495 的结果解析。set_inv_safety中的 2 秒阻塞(goodwe_comm.c:1617):OSTimeDlyHMSM(0,0,0,2000)会阻塞整个task_inverter,期间所有查询与功率控制停摆;配合set_inv_wait_mode()内部的发送等待,最坏情况可造成数秒的调度真空。set_inv_power_mode中的 5 秒阻塞(goodwe_comm.c:1772):u16_grid_status == 0x00时无条件OSTimeDlyHMSM(0,0,5,0),即市电未接入时每个 50ms 周期都会睡 5 秒,实际把功率模式下发周期拉长到 5 秒以上。s_s32_bat_charge_power_limit无边界积分(goodwe_comm.c:1691,1719-1736):累加器可能在电网侧功率异常时长期累积到上限(由bms_read_max_charge_power()封顶)或被压到 0 后难以恢复,缺少复位/衰减逻辑。send_bms_data_to_inv版本字段写入可疑(goodwe_comm.c:1064-1065):两次l_u8_tx_buf[l_u8_len++] = g_t_bat_info.t_run_info.u16_soft_ver;(u16截断为u8)写同一低字节两次,本意应为「高字节+低字节」;另外缓冲声明 100 字节但只写 32 字节,其余未初始化字节虽未被发送(len_buff = 32),仍是易错点。send_bms_data_to_inv报送的是常量而非实测值:充电截止电压 2880、放电截止电压 2400、SOH 100、温度 300 全部硬编码,与goodwe_inverter_data_check里从pvcharger读到的实际边界值不一致,二者可能互相矛盾。set_inv_led_disp每秒 6 次同步收发:每次都要等逆变器应答,若逆变器慢应答会累积占用 UART4 时间,挤压同总线上更高优先级的查询(同一 50ms 周期内还排了运行数据查询与功率模式下发)。u8_query_inv_device_info_flag一旦为真就跳过全部控制逻辑(goodwe_comm.c:2496):设备信息查询期间set_goodwe_inv_config、功率下发、BMS 上报、LED 全部不执行;而query_inv_device_info内部又依赖子型号(决定 AFCI/微网分支),若查询长期失败会陷入「不配置 ↔ 查不到」的死循环依赖。- 无编译单元内的
static收窄:除s_u8_goodwe_recv_buf外所有函数与解析辅助函数都是外部链接,工程内存在多个厂商同名函数(如send_query_data_to_inv_alphaess命名带后缀规避),本文件靠「不带后缀」维持唯一性,后续加入新厂商时容易撞名。 - 调试宏与调试打印不一致:
query_inv_running_data第 600 行使用#if (GOODWE_INV_DEBUG == 1),而同文件其它位置用#if (GOODWE_INV_DEBUG),定义若改成非 1 的真值会漏掉这一段。 - 未使用的可达代码:
query_inv_bat_para、set_bakcup_enable、get_inv_feedin、set_ongrid_delay在工程内没有任何调用者(已用全仓 grep 确认),属版本遗留。