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

set_comm_can.c

源文件路径:midware/inverter/set/set_comm_can.c 所属模块:midware/inverter/set(盛能杰 S5 / S5-II / M10 逆变器通信) 代码行数:896

1. 文件职责

本文件实现 EMS 主控与盛能杰(Senergy)Smile5 / S5-II / M10 逆变器之间的 CAN 总线实时控制通道。它把上层能量调度算出的"AC 侧并网功率限幅百分比"和"DC 侧充放电电流"翻译成 0x199 控制帧下发,同时接收逆变器回传的 0x26B/0x26C/0x26D 运行帧,填充 g_t_inv_info.t_run_info 里的并网/备电/电池电压电流等实时量。它在数据流上是"调度结果 → 执行机构"的最后一跳,同时把执行机构的实测值回灌给上层。驱动方式是由 user 侧静态创建的 task_inverter_can 任务(midware/inverter/inverter.c:1199)驱动,经 OSFlagPend(PEND_INV_CALCULATE_DONE) 与功率计算任务同步后调用 set_inverter_can_data_check() 与 set_inverter_comm_can()。

2. 关键数据结构与宏

名称类型说明
SET_COMM_CAN_T结构体EMS 侧 CAN 触发标志与节拍容器,定义在 set_comm_can.h:36-44,实例 g_t_set_comm_can_info 于本文件第 26 行定义
CAN_MSG_TYPEDEF_T结构体CAN 报文容器,定义在 hardware/can/can0.h:19-27:frame_id/frame_type/frame_len/frame_data[8]/reading_flag。#pragma pack(1) 紧凑排布
SET_EMS_INV_IDCODE_PACK宏 0x181EMS→INV 握手帧 ID
SET_EMS_INV_ACCURACY_CALIBRATION宏 0x198EMS→INV 电池电流校准帧 ID
SET_EMS_CTRL_INV_DC_POWER1宏 0x199EMS→INV DC/AC 控制帧 ID(45 ms 周期)
SET_EMS_SEND_BAT_PROTECT_ARG宏 0x1A1EMS→INV 电池保护参数帧 ID
SET_INV_EMS_IDCODE_PACK宏 0x201INV→EMS 握手应答帧 ID
SET_INV_EMS_PARA_SET_RESULT_PACK宏 0x202INV→EMS 参数设置结果应答帧 ID
SET_INV_EMS_RETURN_RUN_INFO_PACK1/2/3宏 0x26B/0x26C/0x26DINV→EMS 运行数据回读帧 ID
CAN_FT_DATA宏 0x00000000数据帧类型(定义于 HALLIB/GD32F4xx_standard_peripheral/Include/gd32f4xx_can.h:673)
SELF_USED_MODE枚举值 0自发自用工作模式(set_comm_usart_s5.h:304,SENERGYS5_HYBRID_WORK_MODE_E)
BACK_UP_MODE枚举值 3备电优先工作模式(set_comm_usart_s5.h:307)
MPPT_DEFAULT / MPPT_STOP枚举值 0 / 1MPPT 启停状态(set_comm_usart_s5.h:286-287,MPPT_CTRL_E)
SUB_S5 / SUB_S5_II / SUB_M10枚举值逆变器子型号(midware/inverter/inverter.h:311/332/333),用于区分 CAN 波特率与协议分支
G98 / VDE4105_11_18枚举值 20 / 1安规类型(midware/inverter/inverter.h:213 / 194),用于 AC 功率硬限幅
SET_TYPE / INVERTER_MAX宏 2 / 7逆变器类型判别宏(midware/inverter/inverter.h:165/170)
TRUE / FALSE宏工程统一布尔常量

3. 函数清单

函数名作用域说明
CAN_Send_Msg(CAN_MSG_TYPEDEF_T *TxMsg)staticCAN 发送封装,发送前保证与上一帧至少间隔 15 ms(不足则 OSTimeDlyHMSM 主动让出 15 ms)
send_ems_inv_idcode_cmd(void)static组帧并发送 0x181 握手帧,数据域为 ASCII "AlphaESS"
senergy_comm_init_can(void)全局按子型号设置 CAN1 预分频(S5→8,S5-II/M10→4),并把握手标志、电池保护参数标志置 TRUE
senergys5_inv_set_ac_power_percent(s32 s32_ac_power)static把 AC 侧功率需求换算成 0~10000(即 0~100.00%)的并网功率限幅百分比写入 s_senergys5_control_info_tab
senergys5_inv_bat_parameter_calculate(s32 s32_dc_power)static由 DC 侧功率需求(+放电/−充电)反算电池充/放电最大电流,并决定逆变器内部工作模式
send_dc_ctrl_inv_power_1(void)static0x199 主控制帧的组帧发送函数,内部串联限幅、静默功率斜坡、加热电流、MPPT 启停全部逻辑
send_ems_set_bat_protect_arg(void)static组帧发送 0x1A1 电池保护参数(充放电截止电压与最大电流),成功后清除待发标志
send_ems_set_bat_cur_calibration(void)static组帧发送 0x198 电池电流校准帧(当前电池电流/10),其余字节填 0
set_inverter_can_data_check(void)全局下发前的参数准备:限幅电池最大充放电功率、比较 BMS 与本地配置的充放电截止电压/电流,变化时置位 0x1A1 待发标志
set_inverter_comm_ate_can_test(void)全局产线 ATE 直通模式专用:1 s 周期只发 0x181 握手帧,不做数据收发
set_inverter_comm_can(void)全局CAN 通道主循环:门控判断 → 周期/事件发送 → 收帧解析 → 通讯丢失判定与清零

4. 核心逻辑解析

4.1 初始化与速率自适应

senergy_comm_init_can()(第 84 行)按 get_inverter_sub() 分支设置 CAN1 预分频:

子型号预分频说明
SUB_S58老 S5 平台,对应较低速率
SUB_S5_II / SUB_M104二代机 / M10,速率翻倍

只有当前预分频值与目标不一致时才调用 can1_mode_init(prescaler) 重新配置,避免重复初始化打断通信。初始化末尾无条件把两个"待发标志"置 TRUE,使复位、DSP 重启、型号识别成功后都能强制重新握手。

4.2 发送节拍与 CAN 帧限速

CAN_Send_Msg()(第 38 行)用文件级 static u32 s_u32_send_tick 实现了一个全局最小帧间隔:

if (abs(OSTimeGet() - s_u32_send_tick) < 15)   // OS_TICKS_PER_SEC = 1000 → 15 ms
    OSTimeDlyHMSM(0, 0, 0, 15);
s_u32_send_tick = OSTimeGet();
return can1_send_standard_pack(TxMsg);

本文件一帧 0x199 内实际会连续调用 CAN_Send_Msg 一次,但 0x199(45 ms 周期)与 0x198/0x1A1(事件帧)可能在同一轮内相邻发出,这个限速就是防止总线瞬时拥塞。注意:OSTimeDlyHMSM 会让出整个 task_inverter_can 任务的 CPU,因此 CAN 发送是"阻塞式节流"。

4.3 控制帧 0x199 的帧格式与数据域语义

send_dc_ctrl_inv_power_1()(第 241 行)负责组装:

字节内容单位/说明
d0u8_set_inv_hybrid_work_mode0=自发自用,3=备电优先
d1u8_set_mppt_start_stop_flag0=MPPT_DEFAULT(自控),1=MPPT_STOP
d2~d3u16_set_inv_ac_power_limit_percent大端,0.01% 精度(10000 = 100%)
d4~d5u16_set_bat_dischg_curr大端,0.1 A
d6~d7u16_set_bat_chg_curr大端,0.1 A

固定 frame_len = 8、frame_type = CAN_FT_DATA。帧内不含长度/校验字段——CAN 控制器的 CRC 与 DLC 已保证完整性,这是标准 CAN 应用层"裸载荷"写法。

4.4 功率限幅链(0x199 的数据来源)

发送前的限幅顺序(第 253~301 行):

  1. 取源:l_s16_dc_set_power = parallel_read_dc_total_set_bat_power()(并机逻辑汇总后的电池侧设定),l_s16_ac_set_actpower = parallel_read_dc_total_set_inv_power()。l_s16_ac_set_reactpower 被硬置 0(无功不下发)。
  2. 四级限幅:分别按 u32_dc_rate_discharge_power / u32_dc_rate_charge_power / u32_ac_rate_discharge_power / u32_ac_rate_charge_power 做正负方向钳位。
  3. if (l_s16_ac_set_actpower < 50) l_s16_ac_set_actpower = 50;——AC 需求被强制抬到 50 W 以上。
  4. 安规限幅:u8_safety == G98 时 AC 上限 3680 W;VDE4105_11_18 且 S5 且并机使能时上限 4680 W。
  5. 静默功率斜坡(第 311~375 行):当 DC 需求为 0 且子型号不是 S5-II/M10 时,用局部 static s_s16_battery_static_power 输出一个缓变量替代 0。两条斜坡规则:
    • (u8_allow_charg_discharg_flag & 0x30) == 0(电池不允许充放电)时,按 pvcharger_read_bat_curr():电流 ≥0 时每 3 s 减 2 W(下限 −100),电流 ≤25 时每 3 s 加 2 W(上限 +100)。
    • 否则按更快的 1 s 节拍:电流 >30 时每 1 s 减 2 W,电流 <0 时每 1 s 加 2 W。

目的是让逆变器输出一个 ±100 W 以内的"静默功率"来抵消待机功耗,避免电表侧出现持续反送电。该值同时写入全局 g_s16_battery_static_power。

4.5 AC 限幅百分比换算(senergys5_inv_set_ac_power_percent,第 120 行)

if (abs(s32_ac_power) < 60)
    s32_set_ac_power_temp = (s32_inv_rated_output_power < 60) ? 5 : 0;   // 需求<60W → 视为"不馈网"
else
    s32_set_ac_power_temp = s32_ac_power;
钳位到 [0, u32_nominal_ac_active_power]
percent = s32_set_ac_power_temp * 10000 / u32_nominal_ac_active_power     // 0.01% 精度

4.6 DC 侧电流反算与工作模式决策(senergys5_inv_bat_parameter_calculate,第 182 行)

输入 s32_dc_power 约定 正=放电,负=充电。

  1. 先按 g_t_inv_info.t_run_info.u16_dc_max_charge_power / u16_dc_max_dischg_power 双向钳位。
  2. 分流:
    • s32_dc_power >= 0(放电):l_s32_dc_dischrage_power = s32_dc_power,l_s32_dc_chrage_power = s32_bat_charge_power_limit,工作模式 = SELF_USED_MODE。若电池加热方式为逆变器加热(u8_heat_mode == 2)且无加热故障(read_bat_heat_fault_flag() == 0),改为 BACK_UP_MODE。
    • s32_dc_power < 0(充电):放电电流置 0,充电功率取绝对值,工作模式 = BACK_UP_MODE。
  3. 电流反算公式(第 225~226 行):
I_set = min( P_dc * 10000 / U_bat_total , I_max_rate * 10 )

其中 bms_read_total_volt() 为 BMS 总压,*10000/ 与 *10 把结果统一到 0.1 A;u16_chg_max_rate_cur / u16_dischg_max_rate_cur 是逆变器侧的最大倍率电流(0.1 A 精度)。写入 u16_set_bat_chg_curr / u16_set_bat_dischg_curr。

4.7 加热电流覆盖(第 393~420 行)

若 u8_set_heat_power_flag == TRUE:

这段逻辑会覆盖 4.6 算出的充电电流,是加热优先级高于能量调度的体现。

4.8 MPPT 启停

g_t_inv_info.t_run_info.u16_mppt_percentage == 0 → MPPT_STOP(1),否则 MPPT_DEFAULT(0)。即 MPPT 百分比为零时彻底关断 PV 输入。

4.9 事件帧 0x1A1 / 0x198

帧触发条件数据域
0x1A1g_t_set_comm_can_info.u8_ems_set_inv_bat_protect_arg_flag1 == TRUEd0~d1 放电截止电压,d2~d3 放电最大电流×10,d4~d5 充电截止电压,d6~d7 充电最大电流×10(均大端)
0x198s_senergys5_task_tab.u8_inv_old_version_flag == FALSE 且距上次 >200 ms 且 g_t_bat_info.t_run_info.u8_ems_send_bat_cur_flag == TRUEd0~d1 = pvcharger_read_bat_curr()/10(大端),d2~d7 全 0

0x1A1 由 set_inverter_can_data_check() 在检出参数变化时置位,发送成功后(CAN_Send_Msg 返回 TRUE)立即清标志,属于"一次性事件帧"。0x198 由 BMS 模块(midware/bms/alpha/battery_comm.c:1361)置位 u8_ems_send_bat_cur_flag 触发,且带 200 ms 最小间隔;老版本协议(u8_inv_old_version_flag == TRUE,即 S5 全系或主版本号 ≤401 的 S5-II)不支持该校准帧,直接跳过。

4.10 参数变化检测(set_inverter_can_data_check,第 550 行)

职责是把 BMS 能力与本地配置合成一份"最终下发给逆变器的保护参数",流程:

  1. 最大充放电功率:取 abs(pvcharger_read_bat_max_charge_power()) 与放电对应值,按 t_config_data.u32_dc_rate_*_power 钳位后写入 g_t_inv_info.t_run_info.u16_dc_max_charge_power / u16_dc_max_dischg_power,仅在数值变化时才写(避免无谓刷新)。
  2. 截止电压兜底:若电池已连接(pvcharger_read_bat_connect_state() != 0)且本地配置的充/放电截止电压为 0,则用 BMS 上报值补齐。
  3. 四项参数合成(第 611~637 行):
    • 放电截止电压:BMS 值低于本地配置则取本地配置(取高者);为 0 也取本地配置。
    • 充电截止电压:BMS 值高于本地配置则取本地配置(取低者);为 0 也取本地配置。
    • 充/放电最大电流:超过本地配置上限则钳到上限。没有 0 值兜底(若 BMS 返回 0,会直接把最大电流下发成 0)。
  4. 变化判定与置标志(第 639~650 行):电压不等、或电流差绝对值 >10(0.1 A 精度即 1 A)时,刷新 g_t_inv_info.t_run_info 的四个字段并置 u8_ems_set_inv_bat_protect_arg_flag1 = TRUE,驱动 0x1A1 重发。

4.11 主循环门控与超时状态机(set_inverter_comm_can,第 679 行)

进入正常通信的门控条件(第 685 行):

(!(is_inverter_firm_download_finished() || Get_Status_Is_Updated_From_Server(UPDT_INV3)))
&& (s_senergys5_control_info_tab.s8_dcdc_evocation_cnt >= 5)

门控通过时的发送序列:

条件动作周期/节拍变量
u8_ems_inv_idcode_flag == TRUE发 0x181u32_ems_inv_idcode_tick 1000 ms
↑ 为 FALSE发 0x199u32_ems_send_inv_ctrl_power_tick1 45 ms
老版本标志为 FALSE 且 0x198 待发发 0x198u32_ems_set_inv_accuracy_calibration_tick 200 ms
u8_ems_set_inv_bat_protect_arg_flag1 == TRUE发 0x1A1无节拍,事件驱动

关键状态迁移:收到 0x201(SET_INV_EMS_IDCODE_PACK,第 742 行)→ u8_ems_inv_idcode_flag = FALSE 且 u8_inverter_can_comm_lost_flag = FALSE。即"握手完成才进入数据交换态"。

每个节拍判断都带一段"时钟回绕保护":

else if (g_t_set_comm_can_info.u32_xxx_tick > OSTimeGet())
    g_t_set_comm_can_info.u32_xxx_tick = OSTimeGet();

这是为了处理 OSTimeGet() 回绕后节拍基准值"超前于当前时间"的情况,直接把它拉回当前时刻。

收帧循环(第 735~805 行):

l_u32_inv_recv_tick = OSTimeGet();
while (get_can1_standard_pack(&l_t_inveter_info) == TRUE)
{
    s_u32_inv_lost_tick = OSTimeGet();
    switch (frame_id) { ... }
    if (abs(l_u32_inv_recv_tick - OSTimeGet()) > 50) break;   // 单次最多处理 50 ms
}

三帧运行数据的解析映射:

帧字节目标变量换算
0x26Bd0~d1s32_grid_actpower_a取负(-(s16)),说明逆变器以"反送电为正"上报,EMS 内部以"取电为正"
0x26Bd2~d3u16_grid_freq原值
0x26Bd4~d5s32_backup_actpower_a有符号
0x26Bd6~d7u16_backup_freq若 u16_backup_volt_a == 0 则强制写 0(无备电输出时不报频率)
0x26Cd0~d1u16_grid_volt_a/10 → 0.1 V
0x26Cd2~d3u16_backup_volt_a/10 → 0.1 V
0x26C—s16_backup_cur_a由 backup_actpower_t * 10 / backup_volt_a 间接算出
0x26Dd0s_senergys5_device_info_tab.u8_inv_hybrid_work_mode实际工作模式回读
0x26Dd1s_senergys5_device_info_tab.u8_mppt_start_stop_flagMPPT 实际状态
0x26Dd2~d3s32_inv_actpower_a有符号
0x26Dd4~d5s16_bat_power有符号
0x26Dd6~d7u16_bat_volt(/10)与 s_senergys5_device_info_tab.u16_inv_bat_volt(原值)同一字节段两种精度各存一份

每帧解析末尾都会执行 s32_xxx_t = s32_xxx_a(把瞬时值拷贝成"平滑/供上层使用"的镜像)并刷新 u32_inv_send_ems_can_tick1。此外 0x26B 会把 s32_backup_actpower_t 存入 g_t_inv_info.t_run_info.u16_dsp_debug[18] 作为调试快照。

4.12 通讯丢失判定与数据清零

两道独立的超时:

  1. 收帧超时(第 808 行):abs(OSTimeGet() - s_u32_inv_lost_tick) > 60 * 1000(60 s)→ 首次触发时 can1_clear_standard_pack() 清空接收环形缓冲、置 u8_inverter_can_comm_lost_flag = TRUE、重新置位握手标志 u8_ems_inv_idcode_flag = TRUE。
  2. 运行帧超时(第 887 行):OSTimeGet() - u32_inv_send_ems_can_tick1 > 60 * 1000 → 同样重新置位握手标志。二者互为补充:前者看"有没有任何帧",后者看"有没有 0x26B/C/D 这组关键帧"。

丢失态数据处理(第 819~838 行):把并网/备电/逆变/电池相关的 13 个量全部写 0,并把 MPPT 与工作模式回读量清零,同时重新置位两个待发标志。这段清零在丢失标志为 TRUE 期间每一轮都会执行,属于"持续归零"策略而非一次性归零。

门控不通过时的 else 分支(第 863~884 行)做同样的一轮清零,但额外把 u8_inverter_can_comm_lost_flag = FALSE(第 866 行)——即在升级/未识别型号期间对上层宣称"CAN 未丢失",这是一个语义上容易引起误判的处理,详见风险点。

4.13 与 uCOS-II 的配合

5. 对外接口

本文件导出的函数(在 set_comm_can.h:51-54 声明):

函数调用者
senergy_comm_init_can()midware/inverter/inverter.c:76(初始化)、midware/inverter/set/set_comm_usart_s5.c:475(型号识别成功后重建 CAN)
set_inverter_can_data_check()midware/inverter/inverter.c:1223
set_inverter_comm_can()midware/inverter/inverter.c:1224
set_inverter_comm_ate_can_test()midware/inverter/inverter.c:1229(ATE 直通模式)

本文件导出的全局变量:

变量类型使用方
g_t_set_comm_can_infoSET_COMM_CAN_Tset_comm_usart_s5.c:4433-4434(DSP 复位时重置 0x181/0x1A1 待发标志);无 extern 声明于任何头文件,靠 set_comm_can.h:47 暴露
g_s16_battery_static_powers16本文件写(第 369 行),全工程无任何读取点,也无 extern 声明

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

来源模块符号
hardware/can/can1.hcan1_send_standard_pack、get_can1_standard_pack、can1_mode_init、get_can1_prescaler、can1_clear_standard_pack
midware/inverter/pvcharger.hpvcharger_read_bat_curr、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_charge_cut_off_volt、pvcharger_read_bat_dischg_cut_off_volt、pvcharger_read_bat_connect_state
midware/bms/bms.hbms_read_total_volt、g_t_bat_info(t_static_data.u8_heat_mode、t_run_info.u8_heat_mode/u8_request_heat/u8_request_heat_bat_num/u8_bms_heat_cmd/u8_allow_charg_discharg_flag/u8_ems_send_bat_cur_flag/u16_dischg_cut_rate_volt/u16_chg_cut_rate_volt)
midware/inverter/parallel.hparallel_read_dc_total_set_bat_power、parallel_read_dc_total_set_inv_power
midware/inverter/inverter.hg_t_inv_info(t_run_info/t_config_data/t_safty_info)、get_inverter_type、get_inverter_sub
midware/inverter/set/set_comm_usart_s5.hs_senergys5_control_info_tab、s_senergys5_device_info_tab、s_senergys5_task_tab(三者定义于 set_comm_usart_s5.c:37/43/44)、SELF_USED_MODE、BACK_UP_MODE、MPPT_DEFAULT/STOP
data/fault_warn_log/fault_warn_log.hread_bat_heat_fault_flag
net/inv_download.h / net/upd_download.his_inverter_firm_download_finished、Get_Status_Is_Updated_From_Server、UPDT_INV3
logic/logic.hg_t_logic_info(t_config_data.u8_max_feedin_percent、t_logic_control_parameter.s32_inv_rated_output_power、s32_bat_charge_power_limit)
uCOS-IIOSTimeGet、OSTimeDlyHMSM

6. 依赖关系

依赖(#include 链,第 13~22 行):

头文件用途
set_comm_can.h本模块的宏、结构体与函数声明
set_comm_usart_s5.hs_senergys5_control_info_tab / _device_info_tab / _task_tab、工作模式与 MPPT 枚举
inverter.hg_t_inv_info、get_inverter_type/sub、INVERTER_TYPE 编译期开关
meter.h电表侧接口(本文件未见直接引用,疑为模板遗留)
bms.hg_t_bat_info、bms_read_total_volt
logic.hg_t_logic_info 充电功率上限、额定输出功率
can1.hCAN1 收发 API 与 CAN_MSG_TYPEDEF_T
inv_download.his_inverter_firm_download_finished
system_config.h系统配置层(本文件未见直接引用,疑为模板遗留)
fault_warn_log.hread_bat_heat_fault_flag

被依赖:

7. 备注与风险点

  1. 函数头注释张冠李戴:第 52~58 行的注释块名为 kelong_inverter_comm,实际函数是 send_ems_inv_idcode_cmd;第 77~83 行注释名为 ginlong_comm_init_can,实际是 senergy_comm_init_can;第 114 行、第 176 行的 @Name 也是科隆模块残留。属模板拷贝遗留,会误导后续维护。
  2. senergys5_inv_set_ac_power_percent() 里的 <60 W → 0% 与调用侧的 if (l_s16_ac_set_actpower < 50) = 50 相互纠缠:第 293~296 行先把小于 50 的值(含所有负值)抬到 50,而 50 < 60 又会被折算成 0%。结果是第 279~282 行"AC 充电方向钳位"以及第 288~291 行无功钳位全部是死代码(永远被 50 覆盖)。若将来有人改动 60 这个阈值,这段逻辑会突然"复活",需特别小心。
  3. u16_ac_rate_power_temp 被声明为 u16 却接收 u32 的 u32_nominal_ac_active_power(第 124/145 行)。当额定 AC 有功功率超过 65535 W(大型机)时高 16 位被截断,百分比会算错。当前 S5 平台功率较小,暂不触发。
  4. 0x198 帧组帧对有符号电流不友好(第 522 行):pvcharger_read_bat_curr() 返回 s16,若为负(放电或反充),(curr/10) >> 8 是算术右移,& 0x00ff 后得到 0xFF,与正数的高字节取值域混淆;接收端必须靠低字节+符号约定还原。建议只发绝对值或显式 (s16)(curr/10)。
  5. g_s16_battery_static_power 是只写不读的全局量(第 27 行定义、第 369 行写),且 send_dc_ctrl_inv_power_1() 内部又用同名 static s_s16_battery_static_power 遮蔽它(第 257 行),两个变量名几乎相同极易误读。
  6. u8_inverter_can_comm_lost_flag 在门控不通过时被强制写成 FALSE(第 866 行),即"CAN 完全不通信"期间上报层看到的是"未丢失"。若上层仅凭此标志判断逆变器在线,会在升级期间得到错误结论。
  7. s_u32_inv_lost_tick 初始为 0:上电后若 60 s 内无任何帧到达,第一次判断 abs(OSTimeGet() - 0) > 60000 成立即进入丢失态,这个行为本身是正确的;但 g_t_set_comm_can_info.u32_inv_send_ems_can_tick1 初始亦为 0,第 887 行的判断在开机 60 s 后会无条件把 u8_ems_inv_idcode_flag 置 TRUE,与初始化时的置位重复但无害。
  8. 静默功率斜坡是"每 3 s 变 2 W"的开放式积分,上限 ±100 W(第 320/332/348/360 行)。多次进入/退出该分支时用 s_u32_battery_static_tick = OSTimeGet() + 清零做复位,但斜坡变量本身只在两块里共用一个 static,若两块分支在相邻轮次交替进入(u8_allow_charg_discharg_flag 抖动),斜坡会保留历史值继续积分,收敛时间不确定。
  9. u16_dsp_debug[18] 接收一个 s32 有功功率(第 774 行):inverter.h:1699 中 u16_dsp_debug[30] 是 u16 数组,超过 ±32767 W 的功率会溢出,仅作调试快照用,不影响控制。
  10. 第 819~838 行的清零代码块与第 867~881 行的清零代码块内容几乎完全重复(13 个赋值),仅丢失标志的写法不同,建议抽成一个 static void set_can_run_info_zero(void) 以消除重复带来的维护风险。
  11. 0x202 应答帧的处理为空壳(第 747~760 行):switch 内只有 case SET_EMS_INV_IDCODE_PACK: break; 和 default: break;,即收到逆变器的参数设置结果后不做任何校验或重发,参数是否被接受完全不可知。所有下发帧都是"发出去就算成功"的盲发模型。
  12. meter.h / system_config.h 两个 include 在本文件中未见任何符号引用,属冗余包含。