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

kelong_comm.c

源文件路径:midware/inverter/kelong/kelong_comm.c 所属模块:midware/inverter/kelong(科隆逆变器 CAN 通信协议) 代码行数:2350

1. 文件职责

本文件是 EMS 主控与科隆(Kelong)逆变器之间的 CAN 通信实现主体,承担"EMS 主动轮询 + 参数下发 + 状态回读"的全部双向数据流。它通过 CAN1(11 位标准帧,DLC 固定 8 字节)把 EMS 的调度结果翻译成科隆协议帧下发(开关机命令、DC 充放电功率给定、电池限值/保护参数、并网保护参数 9 帧、安规参数 15 帧),并把逆变器上报的运行数据、安规配置、额定参数、故障告警字解码进全局 g_t_inv_info。文件被 midware/inverter/inverter.c 的 task_inverter 任务以 kelong_inverter_comm() + kelong_inverter_data_check() 两个函数成对调用,是"逻辑调度 → 逆变器执行"这条链路的最后一公里。当云端下发了逆变器固件时,本文件会把主循环整体切换到升级通道,把收发权交给 kelong_upgrade.c 的 kelong_upgrade_handle()。

2. 关键数据结构与宏

名称类型说明
g_t_kelong_comm_info全局变量(KELONG_TABLE_T)定义于本文件 kelong_comm.c:31,存放全部下发触发标志与 3 个周期帧的时间戳,是 kelong_comm_init / kelong_inverter_comm / kelong_inverter_data_check 三者共享的状态板
s_u8_temp_work_modestatic u8当前工作模式影子变量,初值 0x02(开机)。kelong_inverter_data_check 按电池状态改写它,send_ems_ctrl_inv_command1 用它作第 0 字节;批改参数时临时置 0x01(关机)
s_u_error_mapping_table1static KELONG_ERROR_MAPPING_TABLE1_U0x270 帧解码的 64 位故障字缓存
s_u_error_mapping_table2static KELONG_ERROR_MAPPING_TABLE2_U0x271 帧解码的 64 位故障/告警字缓存
s_u32_send_tickstatic u32(函数内)CAN_Send_Msg 的发送节流基准
s_u8_upgrade_flagstatic u8(函数内)标记"已进入固件传输模式并执行过一次初始化",防止升级期间重复 memset 运行数据
s_u32_inv_lost_tickstatic u32(函数内)最近一次收到任意逆变器帧的时刻,用于 15 s 通讯丢失判定
s_t_run_control_cmdstatic INVERTER_CONTROL_CMD_TABLE_T(函数内)kelong_inverter_data_check 中"上一次已下发控制命令"的影子副本,用于变化检测
KELONG_INV_DEBUG宏来自 kelong_comm.h:17,控制 printf 调试与 debug_inv_info() 编译
EMS_INV_* / INV_EMS_*宏来自 kelong_comm.h 的全部帧 ID(详见 kelong_comm.h.md)

3. 函数清单

函数名作用域说明
get_kelong_safetystatic把 EMS 侧 u8_safety 安规编号转换为科隆协议编号;当前实现为恒等映射(直接 return 入参)
get_kelong_inv_alpha_safetystatic反方向映射:科隆逆变器上报的 u8_safety → EMS 侧 u8_safety;同为恒等映射
get_kelong_safesubstatic安规子编号(u8_safesub)EMS→科隆恒等映射
get_kelong_inv_alpha_safesubstatic安规子编号科隆→EMS 恒等映射
get_error_mapping_table1static把 s_u_error_mapping_table1 的 64 位按位写入 g_t_inv_info.t_run_info.u_fault_code;BMS 就绪时先强制清掉电池相关故障位
get_error_mapping_table2static把 s_u_error_mapping_table2 分别写入 u_fault_code、u_warning_code、u_fault_extend1_code;BMS 就绪时清电池过压/欠压告警,并无条件清 battery_stops_running_alarm
kelong_comm_init全局逆变器断线或固件传输开始时调用:把所有标志置 TRUE 做全量同步,设 s_u8_temp_work_mode/控制命令初值,强制子型号为 SUB_S6 并保存配置,必要时重设 CAN1 分频为 8
CAN_Send_Msgstatic统一发送出口:保证两次发送间隔 ≥ 15 ms(不足则 OSTimeDlyHMSM 补足),再调 can1_send_standard_pack
send_ems_inv_idcode_cmdstatic组帧并发送 0x181 握手帧,数据域为 ASCII "AlphaESS"
send_ems_ctrl_inv_command1static组帧并发送 0x184 开关机/控制命令;发送成功后清复位、清清能、复位自检状态,并在复位时重触发全量参数同步;末尾延时 2 s 预留逆变器响应时间
send_ems_ctrl_inv_command2static组帧并发送 0x185 MPPT 扫描命令(data[0]=0x03 同时扫描两路)
send_set_inv_power_ctrl_mode1static组帧并发送 0x190,data[0]=0x00 指定 DC 控制模式
send_dc_ctrl_inv_power_1static组帧并发送 0x191 DC 功率给定帧:读取并机总量 parallel_read_dc_total_set_bat_power()/parallel_read_dc_total_set_inv_power(),按额定充放电功率限幅后大端填入,并附带上网电表 T 相有功
send_ems_set_inv_bat_limit_power1static组帧并发送 0x194 电池最大充/放电功率限值
send_ems_set_inv_ctrl_arg1static组帧并发送 0x195 MPPT 百分比与备电输出频率偏置
send_battery_running_datastatic组帧并发送 0x1A0 电池运行数据(电池电压、继电器状态、SOC/10)
send_ems_set_bat_protect_argstatic组帧并发送 0x1A1 电池保护参数(放电截止电压/最大电流、充电截止电压/最大电流)
send_ems_set_grid_protect_arg1static组帧发送 0x1B0:MPPT 启动电压、并网等待时间、直流分量保护高/低点
send_ems_set_grid_protect_arg2static组帧发送 0x1B1:OVP/UVP/OFP/UFP 恢复点
send_ems_set_grid_protect_arg3static组帧发送 0x1B2:一阶 OVP/UVP 值与时间
send_ems_set_grid_protect_arg4static组帧发送 0x1B3:二阶 OVP/UVP 值与时间
send_ems_set_grid_protect_arg5static组帧发送 0x1B4:三阶 OVP/UVP 值与时间
send_ems_set_grid_protect_arg6static组帧发送 0x1B5:10 min OVP 值与时间
send_ems_set_grid_protect_arg7static组帧发送 0x1B6:一阶 OFP/UFP 值与时间
send_ems_set_grid_protect_arg8static组帧发送 0x1B7:二阶 OFP/UFP 值与时间
send_ems_set_grid_protect_arg9static组帧发送 0x1B8:三阶 OFP/UFP 值与时间
send_ems_set_inv_safety_arg1static组帧发送 0x1D0:安规编号、子编号、安规模式字、功率因数、无功百分比
send_ems_set_inv_safety_arg2static组帧发送 0x1D1:反孤岛速率、调节率、P-V 模式响应时间
send_ems_set_inv_safety_arg3static组帧发送 0x1D2:P-V 模式 (V1,P1)、(V2,P2)
send_ems_set_inv_safety_arg4static组帧发送 0x1D3:P-V 模式 (V3,P3)、触发电压、降载使能
send_ems_set_inv_safety_arg5static组帧发送 0x1D4:Q-V 模式 (V1,Q1)、(V2,Q2)
send_ems_set_inv_safety_arg6static组帧发送 0x1D5:Q-V 模式 (V3,Q3)、(V4,Q4)
send_ems_set_inv_safety_arg7static组帧发送 0x1D6:Q-V 模式 Pin/Pout/K/响应时间
send_ems_set_inv_safety_arg8static组帧发送 0x1D7:S-PF 模式三组 (P,Pf) 点
send_ems_set_inv_safety_arg9static组帧发送 0x1D8:S-PF 模式 Pin/Pout
send_ems_set_inv_safety_argastatic组帧发送 0x1D9:P-F 模式及过/欠频降载速率
send_ems_set_inv_safety_argbstatic组帧发送 0x1DA:P-F 模式触发/退出延时
send_ems_set_inv_safety_argcstatic组帧发送 0x1DB:P-F 模式过频 f1/f2/f3
send_ems_set_inv_safety_argdstatic组帧发送 0x1DC:P-F 模式欠频 f1/f2/f3
send_ems_set_inv_safety_argestatic组帧发送 0x1DD:OFP/UFP 降载过零点频率
send_ems_set_inv_safety_argfstatic组帧发送 0x1DE:高低电压穿越标志
debug_inv_infostatic(#if KELONG_INV_DEBUG 内)打印主/从机版本与硬件版本字符串,仅供调试
kelong_inverter_comm全局本文件核心。科隆协议收发主循环:升级通道分流 → 常规帧发送调度 → 回读帧 switch 解析 → 配置落盘 → 通讯丢失判定
kelong_inverter_data_check全局由 BMS/并机/安规数据计算下发值(限幅、上下限约束、变化检测),并把需要重发的标志置 TRUE

4. 核心逻辑解析

4.1 通信物理层与帧格式

4.2 主循环结构:升级/常规双通道

kelong_inverter_comm()(kelong_comm.c:1355)以 is_inverter_firm_download_finished()(来自 net/inv_download.h)作为总开关,形成两个互斥分支:

if (固件下载完成) {
    if (s_u8_upgrade_flag == FALSE) {   // 首次进入本分支
        s_u8_upgrade_flag = TRUE;
        kelong_comm_init();             // 全量同步标志复位
        u8_inverter_comm_lost_flag = FALSE;
        memset(&u16_pv1_volt, 0, &u8_run_mode - &u16_pv1_volt);   // 清运行数据区
    }
    s_u32_inv_lost_tick = OSTimeGet();  // 升级期间视为在线,不报通讯丢失
    if (kelong_read_upgrade_flag_master() == TRUE) u8_run_mode = cUpdateMaster;
    else if (kelong_read_upgrade_flag_slave() == TRUE) u8_run_mode = cUpdateSlave;
    kelong_upgrade_handle();            // 收发权交给升级模块
} else {
    s_u8_upgrade_flag = FALSE;
    ... 常规发送 + 接收解析 ...
}

关键点:进入升级通道后本文件完全不发常规帧、不解析常规帧,0x181~0x28C 全部让位;s_u32_inv_lost_tick 被每轮刷新,避免升级长阻塞期间误报通讯丢失。g_t_inv_info.t_run_info.u8_run_mode 被写入 cUpdateMaster / cUpdateSlave,供 HMI/云端显示升级中状态。

4.3 常规发送调度与状态迁移

常规通道内部再分两级:握手未完成与握手完成。

阶段 A:握手(u8_ems_inv_idcode_flag == TRUE)

if (OSTimeGet() - u32_ems_inv_idcode_tick > 2000) {   // 2 s 周期
    send_ems_inv_idcode_cmd();                        // 发 0x181 "AlphaESS"
    u32_ems_inv_idcode_tick = OSTimeGet();
}

握手帧唯一作用是被逆变器识别为"AlphaESS 上位机";收到 0x201 (INV_EMS_IDCODE_PACK) 后(kelong_comm.c:1618)延时 2 s、清 u8_ems_inv_idcode_flag、清通讯丢失标志、调 Set_Setup_Config_Status(FALSE),然后 fall through 到阶段 B。整个 A 阶段除握手帧外不发任何其它帧,这是协议强制的"先建链再配置"时序。

阶段 B:常规循环(u8_ems_inv_idcode_flag == FALSE),发送顺序固定为:

  1. 边沿帧:u8_ems_set_inv_ctrl_command_flag1 → 0x184;..._flag2 → 0x185;u8_ems_set_inv_power_ctrl_mode_flag1 → 0x190;u8_ems_set_inv_bat_limit_power_flag1 → 0x194;u8_ems_set_inv_ctrl_arg_flag1 → 0x195;u8_ems_set_inv_bat_protect_arg_flag1 → 0x1A1。均为"发一帧即清标志"。
  2. 周期帧:0x191 每 50 ms(u32_ems_send_inv_ctrl_power_tick1);0x1A0 每 500 ms(u32_ems_send_inv_bat_run_data_tick1)。
  3. 参数批下发(关机→改参→开机):kelong_comm.c:1453-1606 用一个巨型或判断覆盖了 9 个并网保护标志 + 22 个安规标志(其中 arge、argf 在判断中未包含,见风险点)。命中后执行固定时序:
l_u8_temp_value = s_u8_temp_work_mode;
s_u8_temp_work_mode = 0x01;                    // 强制关机
send_ems_ctrl_inv_command1();                  // 发 0x184 关机(内部再延时 2 s)
... 依次下发 0x1B0~0x1B8 与 0x1D0~0x1DE 中所有被置位的帧 ...
(0x1D0 下发后额外 OSTimeDlyHMSM(0,0,2,0) 再延 2 s)
s_u8_temp_work_mode = l_u8_temp_value;        // 恢复原模式
send_ems_ctrl_inv_command1();                  // 发 0x184 开机
u8_ems_inv_idcode_flag = TRUE;                 // 重新走一遍握手+全量同步

即:"并网/安规参数只能在停机态写入",且改完必须重新握手让逆变器回读全量配置。这一段单次阻塞时间可达 4 s 以上(两次 2 s 延时 + 大量 15 ms 节流的帧),是该模块最大的实时性风险来源。

4.4 CAN_Send_Msg 的发送节流

static u32 s_u32_send_tick = 0;
if (abs(OSTimeGet() - s_u32_send_tick) < 15)  OSTimeDlyHMSM(0, 0, 0, 15);
s_u32_send_tick = OSTimeGet();
return can1_send_standard_pack(TxMsg);

OS_TICKS_PER_SEC = 1000(UCOSII/uCOS-CONFIG/os_cfg.h:51),所以这是 15 ms 最小帧间隔。注意:不足时 OSTimeDlyHMSM(0,0,0,15) 是无条件再等 15 ms(不是补足差额),实际间隔会变成"上次发送 + 15 ms 后 + 15 ms",属偏保守实现。

4.5 接收解析:switch 分发 + 时间窗

接收在 kelong_comm.c:1612 起:

s_u32_inv_recv_tick = OSTimeGet();                       // 本轮起始时刻
while (get_can1_standard_pack(&l_t_inveter_info) == TRUE) {
    s_u32_inv_lost_tick = OSTimeGet();                   // 每收到一帧刷新在线时刻
    switch (l_t_inveter_info.frame_id) { ... 约 100 个 case ... }
    if (abs(s_u32_inv_recv_tick - OSTimeGet()) > 50) break;   // 单轮最多占用 50 ms
}

即每轮最多连续处理 50 ms 的积压帧,防止 CAN 洪泛时饿死同任务内的其它厂商协议分支(task_inverter 是一个大 for(;;) 依次轮询所有逆变器类型)。

各 case 的解码分组:

组帧 ID落点
握手0x201清 u8_ems_inv_idcode_flag、清通讯丢失、Set_Setup_Config_Status(FALSE),并延时 2 s
应答0x202内层 switch 目前只有空 case EMS_INV_IDCODE_PACK,实际未做任何校验;仅在 KELONG_INV_DEBUG 下打印非 0x01 的应答
版本/序列号0x205~0x207snprintf 按 V%X.%X%02X 从字节半字节拼版本串;SN 分 8+7 字节拼接后清尾部 5 字节
额定参数0x220~0x221写 t_run_info.u32_*_rate_*_power / u16_*_rate_*;与 t_config_data 比对,不同则置 l_u8_config_save_flag
并网保护回读0x230~0x238直接写 t_safty_info.u16_ovp/uvp/ofp/ufp 各阶 _value/_time、mppt_start_voltage、grid_waiting_time、dc_component_protect_*
安规信息回读0x240~0x24E写 `t_safty_info.u8_safety/u8_safesub/safty_mode_u/u16_rev_machine_rate/u16_regulation_rate/u16_p_v_mode_*/u16_q_v_mode_*/u8_s_pf_mode_*/s8_s_pf_mode_pf*/u16_p_f_*/u16_ofpufp_down_load_zero_crossing_freq/u8_high_low_cross`
运行数据0x260~0x26A写 PV 电压/功率并反算电流(u16_pvN_cur = u32_pvN_power*10/u16_pvN_volt)、并网 A/B/C/T 相电压与有功、电网频率、备电各相、逆变各相、u8_run_mode、电池功率/电压/电流、机温、母线电压、功率因数
安规测试状态0x26Ft_safty_tset_info[0].u8_safty_test_state
故障字0x270~0x271拆 4 个 16 位大端字填入联合体 word.word0..3,随后调 get_error_mapping_table1/2()
能量统计0x275~0x27732 位大端写 u32_energy_pv_input / u32_energy_inv_output / `u32_energy_bat_chargedischg / u32_energy_inv_input`
安规自检结果0x280~0x28C写 t_safty_tset_info[0].s16_test_*(_vt/_tt/_vs/_vo/_to 等),并在 0x28C 末尾置 u8_get_safty_test_info_state = TRUE

4.6 故障/告警码映射

get_error_mapping_table1/2()(kelong_comm.c:102 / 165)是纯位搬运:把静态联合体的 bit.* 逐位赋给 g_t_inv_info.t_run_info 的三个位域。两处特殊处理:

注意 s_u_error_mapping_table1/2 是每次收到 0x270/0x271 后整体覆盖的(word.word0..3 四个 16 位字全写),因此逆变器"只发变化位"也不会造成残留。

4.7 版本联动与配置落盘

0x205 帧处理里有两段业务规则(kelong_comm.c:1652-1667):

  1. 从机新版本禁用从机升级:若 u8_slave_ver 前 6 字节 == "V1.010" 且配置位 u8_inv2_app_upgrade_disable == FALSE,则置 TRUE 并请求保存;反之若版本不再匹配且配置为 TRUE 则清回 FALSE。
  2. 硬件版本变化记录:u8_hw_ver 与 t_config_data.u8_hw_ver 不同则拷贝并请求保存。

整轮解析结束时(kelong_comm.c:2120)统一 if (l_u8_config_save_flag == TRUE) save_job_post(INVERTER_CONFIG_PAGE); —— 这是攒批落盘:所有"配置变了"的事件只置一个局部标志,循环末尾一次性投递保存作业(INVERTER_CONFIG_PAGE = 4075,来自 data/system_config/system_config.h:27),避免回读帧密集时反复擦写 EEPROM/Flash。

4.8 通讯丢失检测与恢复

if (abs(OSTimeGet() - s_u32_inv_lost_tick) > 15*1000) {         // 15 s
    if (u8_inverter_comm_lost_flag == FALSE) {
        can1_clear_standard_pack();                             // 清空接收队列
        u8_inverter_comm_lost_flag = TRUE;
        kelong_comm_init();                                     // 回到握手态,全量重同步
    }
}

15 s 无任何帧即判定断线:清接收队列(防止恢复后处理陈旧帧)→ 置丢失标志 → 重新执行 kelong_comm_init() 把状态机拉回握手阶段(因为 kelong_comm_init 会置 u8_ems_inv_idcode_flag = TRUE)。这是一条"自动重连"路径。

4.9 kelong_inverter_data_check:下发值计算与触发

入口即短路:if (g_t_kelong_comm_info.u8_ems_inv_idcode_flag == TRUE) return; —— 未握手时不做任何数据检查。

计算四组量:

  1. 电池最大充/放电功率:取自 pvcharger_read_bat_max_charge_power()/...dischg_power(),取绝对值后用 t_config_data.u32_dc_rate_charge_power / u32_dc_rate_discharge_power 封顶,放电侧再保底 1000(W)。有变化则更新 t_run_info.u16_dc_max_*_power 并置 u8_ems_set_inv_bat_limit_power_flag1。
  2. 工作模式决定:电池未启动 && BMS 未就绪 && 不在升级 BMS → s_u8_temp_work_mode = 0x01(关机);否则取 t_control_cmd.u8_start_stop。另按 t_run_info.u8_bb_enable 把 relay_n_test_flag / backup_gfci_test_flag / n_pe_short_flag 在 0x01(开)/0x02(关) 之间切换。MPPT 扫描请求 u8_mpptscan_start 被消费后置 u8_ems_set_inv_ctrl_command_flag2 = TRUE。
  3. 控制命令变化检测:把 8 个控制位与影子 s_t_run_control_cmd 逐项比较,任一变则整体拷贝并存"模式",置 u8_ems_set_inv_ctrl_command_flag1。
  4. 电池保护参数:电流取 pvcharger_read_bat_max_dischg|charge_current();放电截止电压 = pvcharger_read_bat_dischg_cut_off_volt() * 92 / 100(留 8% 裕量的魔法数),充电截止电压 = pvcharger_read_bat_charge_cut_off_volt()。随后与 t_config_data 的额定值取"更保守者"(放电电压取下限保护、充电电压取上限保护、电流取小值),并对放电电流保底 100(0.1 A)。有变化则置 u8_ems_set_inv_bat_protect_arg_flag1。
  5. 安规与恢复点:u8_safety/u8_safesub 与回读值不一致 → 置 u8_ems_set_inv_safety_arg_flag1;u16_regulation_rate 强制为 6000 → 置 ..._arg_flag2;当安规为 DEFAULT_50HZ / DEFAULT_60HZ 时,把 OVP/UVP/OFP/UFP 恢复点分别设为对应一阶保护值 -100 / +100(0.1 V/0.01 Hz 单位)→ 置 u8_ems_set_inv_grid_protect_arg_flag2。

4.10 与 uCOS-II 任务/时序的配合

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

变量写入方读取方
g_t_kelong_comm_info.u8_ems_inv_idcode_flagkelong_comm_init(置 T)、0x201 解析(清 F)、参数批下发末尾(置 T)、u8_reset 命中(置 T)send_ems_ctrl_inv_command1、kelong_inverter_comm 阶段判定、kelong_inverter_data_check 入口
g_t_kelong_comm_info.u32_ems_inv_idcode_tick / u32_ems_send_inv_ctrl_power_tick1 / u32_ems_send_inv_bat_run_data_tick1kelong_inverter_commkelong_inverter_comm
g_t_kelong_comm_info.u8_ems_set_inv_*_flag*kelong_comm_init(全置 T)、kelong_inverter_data_check(按变化置 T)、各 send_* 发送成功后清 Fkelong_inverter_comm 的发送分支
s_u8_temp_work_mode初值 0x02;kelong_inverter_data_check;参数批下发临时改 0x01send_ems_ctrl_inv_command1
g_t_inv_info.t_run_info.*本文件大量写入(版本、SN、额定、运行数据、故障码)HMI、云端上报、logic/ 调度
g_t_inv_info.t_config_data.*本文件(回读比对后落盘)kelong_inverter_data_check 做限幅基准
g_t_inv_info.t_control_cmd.*本文件(u8_reset/u8_clean_energy/u8_safety_test_enable 消费后清)、外部(HMI/云端置位)send_ems_ctrl_inv_command1
g_t_inv_info.t_run_info.u8_inverter_comm_lost_flag本文件(15 s 判定置 T,收到帧清 F)上层告警

5. 对外接口

本文件导出(供谁调用)

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

来源模块使用内容
hardware/can/can1.*can1_send_standard_pack、get_can1_standard_pack、can1_clear_standard_pack、can1_mode_init、get_can1_prescaler
hardware/can/can0.h类型 CAN_MSG_TYPEDEF_T(宏 CAN_FT_DATA 实际来自 HALLIB/GD32F4xx_standard_peripheral/Include/gd32f4xx_can.h:673,经 can0.h → chip.h 引入)
midware/inverter/inverter.*g_t_inv_info(全部子结构)、get_inverter_sub、set_inverter_sub、SUB_S6、get_inverter_type、KELONG_TYPE、inverter_read_safety、DEFAULT_50HZ/DEFAULT_60HZ、NA_SELF_INSPECTION、cUpdateMaster/cUpdateSlave
midware/inverter/pvcharger.*pvcharger_read_bat_volt、pvcharger_read_bat_relay_state、pvcharger_read_bat_soc、pvcharger_read_bat_max_charge_power、pvcharger_read_bat_max_dischg_power、pvcharger_read_bat_max_charge_current、pvcharger_read_bat_max_dischg_current、pvcharger_read_bat_dischg_cut_off_volt、pvcharger_read_bat_charge_cut_off_volt、pvcharger_read_bat_work_state
midware/inverter/parallel.hparallel_read_dc_total_set_bat_power、parallel_read_dc_total_set_inv_power
midware/inverter/bms.*bms_get_ready_enable
net/bms_download.hIsUpgradeBMS
midware/meter/meter.*meter_read_actpower(GRID_METER, PHASE_T)、GRID_METER、PHASE_T
net/inv_download.his_inverter_firm_download_finished
net/server.*Set_Setup_Config_Status
data/system_config/system_config.*save_job_post、INVERTER_CONFIG_PAGE
midware/inverter/kelong/kelong_upgrade.*kelong_upgrade_handle、kelong_read_upgrade_flag_master/slave
uCOS-IIOSTimeGet、OSTimeDlyHMSM
C 库memset、memcpy、memcmp、snprintf、printf(调试)、abs

6. 依赖关系

7. 备注与风险点

  1. send_dc_ctrl_inv_power_1 强制 AC 有功下限 50 W(kelong_comm.c:458-461):前置限幅已允许该值为负(充电),紧接着 if (l_s16_ac_set_actpower < 50) l_s16_ac_set_actpower = 50; 会把停机指令 0 与所有充电指令都改成 +50 W 放电。这极可能是遗留的调试残留或未更新的保护阈值,是单元测试/现场调试必须优先确认的点。
  2. l_s16_ac_set_reactpower 恒为 0:变量声明后初始化为 0,其后的限幅分支是死代码,0x191 第 6、7 字节永远发 0。说明无功给定功能未实现。
  3. 参数批下发的标志判断不完整:kelong_comm.c:1453-1475 的长条件里包含 u8_ems_set_inv_safety_arg_flag1..flage,缺少 flagf(该帧的置位源在 kelong_inverter_data_check 中也不存在),但批下发体内(kelong_comm.c:1597-1600)却会处理 flagf。若将来有人给 flagf 置位,0x1DE 永远不会被下发(因为外层条件不成立)。
  4. 0x202 参数设置结果帧只做空 switch:kelong_comm.c:1627-1644 的内层 switch 只有 case EMS_INV_IDCODE_PACK: break; 和一个 default: break;,没有对下发结果做任何判错或重发。参数是否被逆变器接受完全不可知。
  5. 0x220 帧 AC 充/放功率取自同一字节对:kelong_comm.c:1681-1682 中 u32_ac_rate_charge_power 与 u32_ac_rate_discharge_power 都来自 frame_data[2..3],放电额定功率实际未被区分。需对照科隆协议手册确认是否本应取 [4..5](该位置被 DC 放电占用)。
  6. 巨型阻塞延时会挤占 task_inverter:send_ems_ctrl_inv_command1 内的 2 s、参数批下发中 0x1D0 后的 2 s、以及每次发送 15 ms 节流叠加,单次参数同步可能阻塞任务 4 s 以上。该任务同时承载 SET / GINLONG / ALPHAESS 等所有厂商分支,会连带拖慢它们。
  7. memset 用结构体成员地址差计算长度:kelong_comm.c:1372

memset((u8*)&g_t_inv_info.t_run_info.u16_pv1_volt, 0x00, ((u8*)&g_t_inv_info.t_run_info.u8_run_mode - (u8*)&g_t_inv_info.t_run_info.u16_pv1_volt)); 依赖 u8_run_mode 在结构体中位于 u16_pv1_volt 之后(inverter.h:1701 vs 1789,成立),但一旦将来调整结构体成员顺序会产生负长度(转为巨大 size_t),属于高危脆弱写法。

  1. 时间戳回绕保护是死代码:kelong_comm.c:1396/1423/1443 的 else if (tick > OSTimeGet()) tick = OSTimeGet(); 永远不会执行——因为无符号加法回绕后 OSTimeGet() - tick 会变成极大值,直接命中第一个 if 分支并立刻发帧。真正的时间戳回绕(约 49.7 天)会导致一帧"提前发送",影响轻微但逻辑与预期不符。
  2. abs() 用于无符号差值:abs(OSTimeGet() - s_u32_send_tick)、abs(s_u32_inv_recv_tick - OSTimeGet()) 等处的实参是 u32,超过 INT_MAX 时传给 int 形参属未定义行为(实践中 ARM 上得到原值或负值),与第 8 点叠加使超时判断在回绕窗口内不可靠。
  3. 0x205 版本判定使用字符串前 6 字节 memcmp:u8_slave_ver 为 16 字节缓冲,snprintf(...,10,...) 写入最多 9 字符 + '\0',memcmp(..., 6) 读的是 "V1.010",逻辑成立;但"V1.010"作为硬编码的功能开关判决值(禁用从机升级)语义晦涩,且一旦版本命名规则变化就会静默失效。
  4. u16_regulation_rate 被无条件强写 6000(kelong_comm.c:2317-2321):上一轮刚从 0x241 回读该值,下一轮就把本地改成 6000 并回发。若不等于 6000 就会形成"每轮都回写"的稳定帧流,需确认 6000 是否即协议规定的唯一合法值。
  5. u16_pvN_cur 反算存在除零风险:kelong_comm.c:1901/1902/1909 中 u32_pvN_power*10/u16_pvN_volt,当逆变器上报 PV 电压为 0(夜间/未接 PV)时发生整数除零。在 GD32(Cortex-M4)上整数除零默认返回 0 或触发 UsageFault(取决于是否使能 DIV_0_TRP),属明确缺陷。
  6. 故障告警位被静默屏蔽:get_error_mapping_table2 无条件清 battery_stops_running_alarm,且 bms_get_ready_enable() 时清掉逆变器自报的全部电池类故障。前者无注释解释,可能导致真实告警在 EMS/云端完全不可见。
  7. 0x1A0 电池运行数据 500 ms 周期与 0x191 50 ms 周期的节拍基准都存于 g_t_kelong_comm_info,但该结构在文件被多个上下文(kelong_comm_init 被 inverter.c:68 的外部路径调用)无锁访问,若这些调用跨任务则存在竞态。