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

goodwe_comm.c

源文件路径: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_bufstatic 数组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 都调用的总入口,分三个互斥分支:

  1. 内部固件升级分支(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()。
  2. 外部(ARC/AFCI)升级分支(Get_Status_Is_Updated_From_Server(UPDT_INV3)):同样先初始化,运行模式设为 cUpdateArm,交给 goodwe_upgrade_external_handle()。
  3. 常规通信分支:见下节时间片表。

常规分支的时间片(全部基于 OSTimeGet() 毫秒计数,并统一带「时钟回绕保护」——若记录的 tick 大于当前 tick 则强制重置为当前 tick):

周期触发条件动作
30 000 msOSTimeGet() - 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_flagrestart_inv_process()
每轮u8_set_inv_safety_flagset_inv_safety()
每轮u8_clear_inv_energy_flagclear_inv_energy()
每轮u8_read_safety_data_flagquery_inv_safety_info()
180 000 ms距 u32_query_inv_device_info_tick重新置 u8_query_inv_device_info_flag = TRUE(每 3 分钟重查一次设备信息)
50 msu32_query_inv_running_data_tickquery_inv_running_data()
1000 msu32_set_led_disp_tickset_inv_led_disp()
50 msu32_set_inv_power_mode_tickset_inv_power_mode()
100 msu32_send_bms_data_to_inv_ticksend_bms_data_to_inv()
2000 msu32_set_inv_feedin_tick 且运行模式为 cNormalModeset_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-1635103~35106PV1 电压/电流/功率u16_pv1_volt(÷10)、u16_pv1_cur、u32_pv1_power(32 位大端)
17-2435107~35110PV2 同构u16_pv2_*、u32_pv2_power
25-3235111~35114PV3 同构u16_pv3_*、u32_pv3_power
33-4035115~35118PV4 同构u16_pv4_*、u32_pv4_power
45,46 / 47,48 / 49,5035121...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)是一个双向查表函数:

匹配不到时返回 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,注释说明「逆变器响应需要时间」):

并网分支 set_inv_ongrid_mode()(goodwe_comm.c:1680)同时决定模式字与功率:

待机 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 的取值分两条路径:

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/配置层与逆变器层的边界值对齐并触发落盘:

  1. 电池保护参数钳位:从 pvcharger 读放电/充电截止电压与最大电流,放电截止电压若小于 t_config_data.u16_dischg_cut_rate_volt 或为 0 则抬到配置值;充电截止电压若大于配置值或为 0 则压到配置值;充放电流超过配置上限则截断。任一值变化即同步到 t_run_info 的四个对应字段。
  2. 清能量标记:t_control_cmd.u8_clean_energy 为真时转成 u8_clear_inv_energy_flag 并把源标记清零。
  3. 安规一致性:t_config_data.u8_safety != t_safty_info.u8_safety → 置 u8_set_inv_safety_flag。
  4. 重启请求:t_control_cmd.u8_reset → 置 u8_set_inv_restart_flag 并清源标记。
  5. AC 额定功率识别:u32_ac_rate_discharge_power == 9600 → DC 充放额定都设 9600、u16_max_ac_from_grid_current = 400;== 7600 → 设 7600、= 380。这是 9.6kW / 7.6kW 两档机型的硬编码识别。
  6. 配置落盘:AC 充/放额定功率变化时同步到 t_config_data 的 AC/DC 四个额定字段并 save_job_post(INVERTER_CONFIG_PAGE)。
  7. 离网充电截止 SOC:inverter_read_micro_grid_chg_soc() 落在 (0, 95] 区间且与上次不同 → 置 u8_set_micro_grid_soc_protect_flag 并准备下发;越界则强制写回 95 并置标志。
  8. 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)采用「读回比对 → 不符则写 → 相符则清标志」的收敛模式,每项独立:

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_infoGOODWE_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,但工程内没有其它翻译单元引用,实际为文件内可见。

本文件调用外部

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。

被依赖:

7. 备注与风险点

  1. 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 字节,属潜伏缺陷。
  2. l_u8_len 类型为 u8 而缓冲长度为 128:同样的越界风险在类型层面被放大(u8 上限 255,不构成溢出保护)。
  3. 未匹配功能码时发送 0 字节(goodwe_comm.c:117-133):若 func_code 既非 0x10 也非 0x06,l_u8_len 保持 0,会执行 goodwe_write_data(buf, 0, ...) 空发送;当前无此调用路径。
  4. goodwe_recv_data 不校验长度上限(goodwe_comm.c:142):直接把 uart4_get_data_len() 个字节写入 s_u8_goodwe_recv_buf[256],未判断 l_u16_len <= 256,超长帧会越界。
  5. query_inv_running_data 存在无效赋值(goodwe_comm.c:650-651):s32_inv_actpower_b / _c 被硬写 0,第 652 行原取 [79..80] 的赋值被注释,注释说明该值刻意不用。
  6. parse_inv_err_msg 中运算符优先级(goodwe_comm.c:399-400):(a) && (b) 与 (c) && (d) 之间的 || 结合性正确,但整行用 \ 续行、形式晦涩,易被后人误改;且第 400 行条件为「CheckMode 且频率 > 45Hz」才清故障,语义与上一分支(UPS 且频率 < 45Hz)不对称,需对照 GoodWe 文档确认。
  7. snprintf 目标长度存疑(goodwe_comm.c:556-558):snprintf(..., 10, "V%03d.%03d", ...) 最多需 8 字符 + \0,恰好放得下,但若 u8_master_ver 字段实际小于 10 字节仍会截断,依赖 inverter.h 中字段定义。
  8. goodwe_read_type 只比较 7 字节型号串:"9600AES" 等常量声明为 u8[10](goodwe_comm.c:440-443),但只 memcmp 前 7 字节,型号后缀变化会被误判为 SUB_NULL。
  9. 安规映射大量回落到 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,明显是未逐一适配的占位实现,跨国机型需重点核对。
  10. 安规回读方向不完整(goodwe_comm.c:296-343):ALPHA_SAFETY 分支只覆盖 10 个国家码,其它值返回 0,t_safty_info.u8_safety 会被清零并可能在 goodwe_inverter_data_check 中误触发安规重设。
  11. 安规阈值时间被硬编码(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 换算的代码被注释掉。这是明确的技术债,若安规一致性要求严格会直接不合规。
  12. query_inv_safety_info 第二次收帧未校验(goodwe_comm.c:822-830):第二次请求(45495/4)后不判断 l_u16_inv_recv_num 是否非零,也不清 s_u8_goodwe_recv_buf,若第一次的响应残留或本次超时,会把上一次的数据当作 45495 的结果解析。
  13. set_inv_safety 中的 2 秒阻塞(goodwe_comm.c:1617):OSTimeDlyHMSM(0,0,0,2000) 会阻塞整个 task_inverter,期间所有查询与功率控制停摆;配合 set_inv_wait_mode() 内部的发送等待,最坏情况可造成数秒的调度真空。
  14. set_inv_power_mode 中的 5 秒阻塞(goodwe_comm.c:1772):u16_grid_status == 0x00 时无条件 OSTimeDlyHMSM(0,0,5,0),即市电未接入时每个 50ms 周期都会睡 5 秒,实际把功率模式下发周期拉长到 5 秒以上。
  15. s_s32_bat_charge_power_limit 无边界积分(goodwe_comm.c:1691,1719-1736):累加器可能在电网侧功率异常时长期累积到上限(由 bms_read_max_charge_power() 封顶)或被压到 0 后难以恢复,缺少复位/衰减逻辑。
  16. 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),仍是易错点。
  17. send_bms_data_to_inv 报送的是常量而非实测值:充电截止电压 2880、放电截止电压 2400、SOH 100、温度 300 全部硬编码,与 goodwe_inverter_data_check 里从 pvcharger 读到的实际边界值不一致,二者可能互相矛盾。
  18. set_inv_led_disp 每秒 6 次同步收发:每次都要等逆变器应答,若逆变器慢应答会累积占用 UART4 时间,挤压同总线上更高优先级的查询(同一 50ms 周期内还排了运行数据查询与功率模式下发)。
  19. u8_query_inv_device_info_flag 一旦为真就跳过全部控制逻辑(goodwe_comm.c:2496):设备信息查询期间 set_goodwe_inv_config、功率下发、BMS 上报、LED 全部不执行;而 query_inv_device_info 内部又依赖子型号(决定 AFCI/微网分支),若查询长期失败会陷入「不配置 ↔ 查不到」的死循环依赖。
  20. 无编译单元内的 static 收窄:除 s_u8_goodwe_recv_buf 外所有函数与解析辅助函数都是外部链接,工程内存在多个厂商同名函数(如 send_query_data_to_inv_alphaess 命名带后缀规避),本文件靠「不带后缀」维持唯一性,后续加入新厂商时容易撞名。
  21. 调试宏与调试打印不一致:query_inv_running_data 第 600 行使用 #if (GOODWE_INV_DEBUG == 1),而同文件其它位置用 #if (GOODWE_INV_DEBUG),定义若改成非 1 的真值会漏掉这一段。
  22. 未使用的可达代码:query_inv_bat_para、set_bakcup_enable、get_inv_feedin、set_ongrid_delay 在工程内没有任何调用者(已用全仓 grep 确认),属版本遗留。