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

ginlong_comm_can.c

源文件路径:midware/inverter/ginlong/ginlong_comm_can.c 所属模块:midware / inverter / ginlong(锦浪逆变器通信) 代码行数:374

1. 文件职责

本文件实现 EMS 与锦浪(Ginlong/Solis)储能逆变器之间的 CAN 实时控制通道。它负责三件事:上电后通过 0x181 握手报文与逆变器建立联系(对方回 0x201 后才认为通道可用);按固定节拍向逆变器下发功率设定值(单相 DC 控制用 0x191,三相不平衡控制用 0x198);并从逆变器上报的 0x269 运行信息包中回读 T 相有功功率、电网频率与电池功率,写入全局运行数据结构 g_t_inv_info.t_run_info。

该通道是"下发为主、回读为辅"的实时控制链路,与同目录的 ginlong_comm_usart.c(Modbus-RTU 参数配置主通道)互补:串口通道负责大量寄存器读写与参数配置,CAN 通道负责毫秒级功率跟踪。它由 midware/inverter/inverter.c 的逆变器轮询任务周期调用(inverter.c:1239),自身不含任务与阻塞等待(除固定的发送间隔延时)。

2. 关键数据结构与宏

本文件自身不定义结构体,使用的宏全部来自 ginlong_comm_can.h,关键结构来自头文件。

名称类型说明
GINLONG_EMS_INV_IDCODE_PACK宏0x181,EMS 握手报文 ID,数据域为 ASCII AlphaESS
GINLONG_EMS_CTRL_INV_DC_POWER1宏0x191,DC 功率控制报文 ID
GINLONG_EMS_CTRL_THREE_UNBALANCE_POWER宏0x198,三相不平衡功率控制报文 ID
GINLONG_INV_EMS_IDCODE_PACK宏0x201,逆变器握手应答 ID
GINLONG_INV_EMS_RETURN_RUN_INFO_PACK5宏0x269,逆变器运行信息包 5
GINLONG_INV_CAN_DEBUG宏调试打印开关,默认 0
GINLONG_COMM_CAN_T结构体CAN 通道节拍状态表(ginlong_comm_can.h:31),含握手标志与 3 个发送节拍
g_t_ginlong_comm_can_info全局变量GINLONG_COMM_CAN_T 的唯一实例,本文件第 26 行定义
CAN_MSG_TYPEDEF_T结构体CAN 帧描述结构(hardware/can/can0.h:21,#pragma pack(1)):frame_id / frame_type / frame_len / frame_data[8] / reading_flag
CAN_FT_DATA宏数据帧类型标志,传给 frame_type
GINLONG_TABLE_T结构体锦浪全量数据表,经 g_t_ginlong_info 访问其 t_ginlong_comm_info.u8_ems_inv_comm_flag
INVERTER_TABLE_T结构体逆变器全量表,g_t_inv_info 提供 t_config_data(额定功率)与 t_run_info(运行数据)
cNormalMode宏3(midware/inverter/inverter.h:23),逆变器正常运行模式

3. 函数清单

函数名作用域说明
CAN_Send_MsgstaticCAN 发送封装:保证相邻两帧间隔 ≥15 ms(不足则 OSTimeDlyHMSM 补齐),再调 can1_send_standard_pack
send_ems_inv_idcode_cmdstatic组帧并发送 0x181 握手报文,数据域填 ASCII AlphaESS(8 字节)
ginlong_comm_init_can全局初始化 CAN1:若分频系数非 8 则调用 can1_mode_init(8),并把握手标志置 TRUE
send_dc_ctrl_inv_power_1static组织并发送 0x191 单相 DC 功率控制帧:直流设定功率、有功设定、T 相电表有功、无功设定
send_three_unbalance_power_ctrlstatic组织并发送 0x198 三相不平衡控制帧:直流设定功率 + A/B/C 三相独立有功设定
ginlong_inverter_comm_can全局CAN 通道周期主函数:固件升级期间挂起;未握手时 1 s 发一次握手;已握手时按 600 ms / 2000 ms 分频下发控制帧,并循环收包解析

4. 核心逻辑解析

4.1 握手状态机

握手用两个标志表示,状态迁移如下(ginlong_comm_can.c:304-346):

[上电] ginlong_comm_init_can() → u8_ems_inv_idcode_flag = TRUE
   │
   ├─ flag == TRUE(未握手)
   │     └─ (OSTimeGet() - u32_ems_inv_idcode_tick) > 1000ms
   │           → send_ems_inv_idcode_cmd()  (0x181, "AlphaESS")
   │           → u32_ems_inv_idcode_tick = OSTimeGet()
   │
   └─ 收到 frame_id == 0x201(GINLONG_INV_EMS_IDCODE_PACK)
         → u8_ems_inv_idcode_flag = FALSE   ← 进入控制态

4.2 通道使能条件(ginlong_comm_can.c:301-302)

if(!(is_inverter_firm_download_finished() || Get_Status_Is_Updated_From_Server(UPDT_INV3))
   && TRUE == g_t_ginlong_info.t_ginlong_comm_info.u8_ems_inv_comm_flag)

两个条件都必须满足才进入通信体:

  1. 未在固件升级流程中:is_inverter_firm_download_finished()(固件已下载完、正在串口烧写)为假,且服务器下发的 UPDT_INV3(ARM 升级任务)状态为假。升级期间 CAN 通道必须静默,防止干扰串口烧写。
  2. 串口主通道已建立:u8_ems_inv_comm_flag == TRUE(该标志由 ginlong_comm_usart.c 维护)。

也就是说,CAN 通道从属于串口通道:串口没通,CAN 不发。

4.3 帧格式(CAN 标准帧,11 位 ID)

所有帧均为 frame_type = CAN_FT_DATA、frame_len = 8,数据域大端(高字节在前):

报文ID方向周期Byte0-1Byte2-3Byte4-5Byte6-7
握手0x181EMS→INV1000 msAlphaESS ASCII(8 字节固定)
DC 控制0x191EMS→INV600 mss16 DC 设定功率s16 AC 有功设定s16 T 相电表有功s16 AC 无功设定
三相不平衡0x198EMS→INV2000 mss16 DC 设定功率s16 A 相有功s16 B 相有功s16 C 相有功
握手应答0x201INV→EMS—无数据解析,仅改标志
运行信息 50x269INV→EMS逆变器自定s16 T 相有功u16 电网频率Byte4 丢弃;Byte5-6 s16/u16 电池功率Byte7 丢弃

组帧写法统一为 (u8)(val >> 8 & 0x00ff) 与 (u8)(val >> 0 & 0x00ff)(ginlong_comm_can.c:170-177、277-284),即大端序。CAN 层不做校验和,校验依赖 CAN 控制器自身的 CRC 字段(硬件实现)。

4.4 0x191 DC 功率控制的下发限幅逻辑(ginlong_comm_can.c:102-180)

数据来源:parallel_read_dc_total_set_bat_power()(并机模块算出的直流设定总电池功率)与 parallel_read_dc_total_set_inv_power()(逆变侧设定有功)。

  1. 直流功率限幅:正数(放电)不得超过 g_t_inv_info.t_config_data.u32_dc_rate_discharge_power;负数(充电)绝对值不得超过 u32_dc_rate_charge_power(:121-128)。
  2. 防掉零延时:若本次设定功率非 0,刷新 s_u32_dc_set_power_tick;随后若「电池禁充(pvcharger_read_bat_max_charge_power() == 0)且可放电(pvcharger_read_bat_max_dischg_power() != 0)且设定值在 [0,100) 且距上次非零设定不足 60 s」,则强制把设定功率抬到 100(:137-141)。目的是避免电池禁充期间功率被压到 0 导致逆变器停机,强制留一个小功率维持运行。
  3. 有功设定特殊处理(:143-150):超过 u32_ac_rate_discharge_power 则钳位;<= 90 则无条件改写为 40。这是锦浪逆变器的经验值处理,属于隐式约定。
  4. Byte4-5 填入 T 相电表有功:meter_read_actpower(GRID_METER, PHASE_T)(:174-175),供逆变器做并网点功率闭环参考。
  5. 无功设定 l_s16_ac_set_reactpower 恒为 0(:113),随后的限幅代码(:152-159)对其不产生实际效果。

4.5 0x198 三相不平衡控制的下发逻辑(ginlong_comm_can.c:189-287)

  1. 数据来源:parallel_read_dc_three_phase_set_bat_power() 与 parallel_read_dc_three_phase_set_inv_power(PHASE_L1/L2/L3)。
  2. 单相额定功率 l_u16_ac_single_rate_power = u32_ac_rate_charge_power / 3(:205),每相设定值以此为上限钳位。
  3. 随机化抖动:任一相设定值 < 20 时改为 rand() % 20(:230、:239、:248)。代码注释明确说明原因——"固定值改随机值是因为锦浪接收到各相设置功率一致时会控不住"(:223)。
  4. 非正常模式强制 5000 W:若 inverter_read_run_mode() != cNormalMode,三相设定全部强制为 5000,并记 s_u32_delay_tick = OSTimeGet();切回 cNormalMode 后仍需保持 5000 输出 5 s(:251-264)。这是逆变器离开并网模式(如故障/待机)时的保持逻辑。

4.6 接收解析与超时保护(ginlong_comm_can.c:348-372)

l_u32_inv_recv_tick = OSTimeGet();
while (get_can1_standard_pack(&l_t_inveter_info) == TRUE) {
    switch (frame_id) { ... }
    if (abs(l_u32_inv_recv_tick - OSTimeGet()) > 50) break;   // 50 系统节拍总时长上限
}

4.7 发送节拍与互斥

CAN_Send_Msg()(:38-50)通过静态变量 s_u32_send_tick 实现全模块级的最小 15 ms 帧间隔:

if (abs(OSTimeGet() - s_u32_send_tick) < 15) OSTimeDlyHMSM(0,0,0,15);
s_u32_send_tick = OSTimeGet();

这是唯一会阻塞的地方(最长 15 ms),目的是避免 CAN 总线突发拥塞。注意:延时用的是固定 15 ms 而非差额,因此实际帧间隔会略大于 15 ms。由于该函数被 ginlong_inverter_comm_can() 主线调用,而主线本身可能同时发握手帧和控制帧(理论上不会,因为握手与控制在 if/else 两个分支),实际不会造成叠加。

4.8 节拍回绕保护

三处节拍比较都附带 else if (tick > OSTimeGet()) tick = OSTimeGet();(:311、:327、:341)。这是 OSTimeGet() 32 位毫秒计数器回绕(约 49.7 天)时的兜底:一旦发现记录值超前于当前值,直接同步回当前值,避免后续 OSTimeGet() - tick 出现巨大正数而永久跳过发送。

5. 对外接口

本文件导出(供外部调用):

符号类型调用方
ginlong_comm_init_can函数inverter.c:83(inverter_state_init())
ginlong_inverter_comm_can函数inverter.c:1239(逆变器轮询任务,仅在 get_inverter_type() == GINLONG_TYPE 且非透传模式时)
g_t_ginlong_comm_can_info全局变量本文件内部使用;因头文件未 extern,外部实际不可见

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

名称来源模块
can1_send_standard_pack、get_can1_standard_pack、can1_mode_init、get_can1_prescaler、CAN_MSG_TYPEDEF_T、CAN_FT_DATAhardware/can/can1.h、hardware/can/can0.h
OSTimeGet、OSTimeDlyHMSMuCOS-II(UCOSII/)
parallel_read_dc_total_set_bat_power、parallel_read_dc_total_set_inv_power、parallel_read_dc_three_phase_set_bat_power、parallel_read_dc_three_phase_set_inv_powermidware/inverter/parallel.h(并机功率分配)
pvcharger_read_bat_max_charge_power、pvcharger_read_bat_max_dischg_powermidware/inverter/pvcharger.h
meter_read_actpower、GRID_METER、PHASE_Tmidware/meter/meter.h
inverter_read_run_mode、inverter_get_three_unbalance_mode、cNormalMode、g_t_inv_infomidware/inverter/inverter.h
is_inverter_firm_download_finishednet/inv_download.h
Get_Status_Is_Updated_From_Server、UPDT_INV3、g_u8_fw_sdram1/2net/upd_download.h、net/server.h
g_t_ginlong_infomidware/inverter/ginlong/ginlong_comm_usart.h
abs、randC 标准库

6. 依赖关系

依赖(include): ginlong_comm_can.h、ginlong_comm_usart.h、inverter.h、meter.h、bms.h、logic.h、upd_download.h、inv_download.h、can1.h(ginlong_comm_can.c:13-21)。其中 bms.h 与 logic.h 在文件中未见直接引用,属于冗余 include。

被依赖:

7. 备注与风险点

  1. 固定 15 ms 延时可能拉长控制周期:CAN_Send_Msg()(:42-47)在间隔不足时无条件 OSTimeDlyHMSM(0,0,0,15),若一次轮询内连续发两帧会阻塞 15 ms × N。当前控制周期 600 ms / 2000 ms 余量充足,但若将来缩短周期需重新评估。
  2. abs() 用于 u32 运算:abs(OSTimeGet() - s_u32_send_tick)、abs(l_u32_inv_recv_tick - OSTimeGet()) 的实参是 u32 相减,先隐式转 int 再取绝对值。当差值超过 INT_MAX(约 24.8 天)时结果未定义,长期运行(>24 天)理论上存在隐患。
  3. rand() % 20 依赖全局随机数种子::230/239/248 处使用 rand() 但本文件未 srand(),若工程其他位置也未播种,抖动序列每次上电相同。这是锦浪控不住问题的"规避手段",并无协议依据,属于经验性 workaround。
  4. <= 90 → 40 硬编码(:147-150):AC 有功设定值在 (0, 90] 区间被无条件改写为 40,既无注释也无配置项,容易被误认为 bug,实为逆变器特性适配。
  5. 0x269 数据解析跨字节错位嫌疑::361 用 frame_data[5]/frame_data[6] 组合电池功率,跳过了 frame_data[4];:359-360 只用了 [0..3]。若逆变器实际协议布局不同,s16_bat_power 会取错字节。建议对照锦浪 CAN 协议文档复核。
  6. s16_bat_power 赋值类型不匹配::361 右侧为 (u16) 强制转换,左侧为 s16,虽结果等价于位重解释,但写法上易误导。
  7. 握手无重试回转:一旦 u8_ems_inv_idcode_flag 被清成 FALSE,本文件不再重发 0x181(:304)。若逆变器中途重启,只能依赖上层重新调用 ginlong_comm_init_can()。
  8. 接收循环的 50 拍限制::368 的 abs(...) > 50 若因回绕产生大正数会立即 break,属于安全方向的失效(少收而非死循环),可接受。
  9. 冗余 include:bms.h、logic.h 未在本文件直接使用;函数注释多处沿用 kelong 模块的模板(:53、:78、:95、:182、:289),说明由其他厂商模块复制演进而来。