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

parallel_can.c

源文件路径:midware/inverter/parallel_comm/parallel_can.c 所属模块:midware/inverter/parallel_comm(并机通信 / CAN 通道) 代码行数:870

1. 文件职责

本文件实现 EMS 并机系统在 CAN1 扩展帧通道上的主从机组网与功率调度通信,是并机方案两条物理通道(CAN 与 SCI/485)中的 CAN 分支。它同时包含主机角色(parallel_can_comm_host_machine_handle,parallel_can.c:184)与从机角色(parallel_can_comm_from_machine_handle,parallel_can.c:564)两套完整收发逻辑,由 midware/inverter/parallel.c:1037 / parallel.c:1041 依据 g_t_inv_info.t_config_data.u8_parallel_mode(1=主机,0=从机)二选一调用。

数据流上,本文件处在"能量调度结果 → 各台从机逆变器"的下行末端,以及"各台从机运行数据 → 主机聚合"的上行末端:主机侧把 g_t_host_machine_running_info[i](每台从机的功率指令)逐台单播下发,并把收到的从机遥测写回 g_t_host_machine_running_info[i];从机侧把本机 g_t_host_machine_running_info[0](本机实时数据)周期上报给主机,并把主机指令写入 g_t_from_machine_running_info。整个文件由 uCOS-II 任务 task_inverter_can(midware/inverter/inverter.c:1199,30 ms 周期)非阻塞轮询驱动,不使用信号量或消息邮箱,仅靠 OSTimeGet() 打时间戳做软定时。

2. 关键数据结构与宏

本文件不定义结构体,全部复用 midware/inverter/parallel.h 与 hardware/can/can0.h 的类型。

名称类型说明
PARALLEL_HOST_MACHINE_NETWORKING_PARAMETER_T结构体(parallel.h:82-90)一台从机的组网参数:u8_unique_device_id[2]、u8_mac[3]、u8_parallel_addr、u8_parallel_addr_en、u8_parallel_lost_flag、u32_rcv_tick。buf[0] 为本机数据
PARALLEL_HOST_MACHINE_RUNNING_INFO_T结构体(parallel.h:101-217)主机视角下每台机(含本机 [0])的"指令 + 遥测"大结构。前半段为下发的控制量,后半段为回读的逆变器/电池数据
PARALLEL_FROM_MACHINE_NETWORKING_PARAMETER_T结构体(parallel.h:92-98)从机侧组网状态:u8_parallel_addr、u8_parallel_addr_en、u8_parallel_lost_flag、u32_rcv_tick
PARALLEL_FROM_MACHINE_RUNNING_INFO_T结构体(parallel.h:220-260)从机侧"接收主机指令"的结构:模式标志、DC/AC 总量与三相功率设定、升/降级标志
BAT_WORK_FLAG_U联合体(parallel.h:48-58)电池工作标志字节:bit_bms_comm_flag:2、bit_bms_work_flag:2、bit_bms_relay_flag:2、bit_bat_force_charge_flag:2
UPGRADE_FLAG_U联合体(parallel.h:60-70)升级标志字节:bit_ems_upgrade_flag:2、bit_inv_upgrade_flag:2、bit_bms_upgrade_flag:2、bit_reserved:2
CAN_MSG_TYPEDEF_T结构体(hardware/can/can0.h:19-27)CAN 报文载体:frame_id、frame_type、frame_len、frame_data[8]、reading_flag。#pragma pack(1)
PARALLEL_MAX_NUM宏 = 16(parallel.h:15)并机最大台数,也是收帧地址合法性上界
PARALLEL_BROADCAST_ADDR宏 = 0(parallel_can.h:15)广播地址,等价于"主机"地址(主机在 CAN 上的地址恒为 0)
PARALLEL_COMMUNICATION_ADDRESS_OFFSET宏 = 0x10000000(parallel_can.h:17)扩展 ID bit28,标记"这是并机报文"
CAN_FT_DATA / CAN_FT_REMOTE宏(HALLIB gd32f4xx_can.h:673-674)帧类型,用于区分 0x201 是"远程帧请求"还是"数据帧应答"
PARALLEL_HOST_MACHINE_NETWORKING_0X181宏 = 0x181地址分配答复帧(主机发、从机收)
PARALLEL_HOST_MACHINE_NETWORKING_0X201 / PARALLEL_FROM_MACHINE_NETWORKING_0X201宏 = 0x201(两个名字同值)组网请求(远程帧)/ 组网上报(数据帧),靠 frame_type 区分
PARALLEL_HOST_MACHINE_POWER_CONTROL_0X190宏 = 0x190广播控制模式帧
PARALLEL_HOST_MACHINE_POWER_CONTROL_0X191/0X192/0X195/0X196/0X197宏单播功率指令帧(DC 总量 / DC 三相 / AC 总量 / AC 三相 / AC 三相电池限值)
PARALLEL_HOST_MACHINE_SET_CONTROL_PARAMETER_0X19A宏 = 0x19A广播控制参数帧(MPPT%、N-N 短接检测、DRM、备电)
PARALLEL_HOST_MACHINE_SET_SAFETY_REGULATION_0X19B宏 = 0x19B广播安规帧(safety / safesub)
PARALLEL_HOST_MACHINE_EMS_RUNNING_DATA_0X1C0 / PARALLEL_FROM_MACHINE_EMS_RUNNING_DATA_0x2C0宏主机 / 从机 EMS 升级标志帧
PARALLEL_FROM_MACHINE_INVERTER_SPECIFICATION_0X220宏 = 0x220从机逆变器规格(4 个额定功率)
PARALLEL_FROM_MACHINE_INVERTER_RUNNING_DATA_0X260 ~ 0X26A宏(11 个)从机逆变器遥测分片。0x262 只收不发,0x267 双方均未使用
PARALLEL_FROM_MACHINE_BATTERY_INFORMATION_0X290宏 = 0x290从机电池额定容量
PARALLEL_FROM_MACHINE_BATTERY_RUNNING_DATA_0x2A0 ~ 0x2A2宏(3 个)从机电池状态/簇数/SOC、母线电压电流功率、最大充放功率
PARALLEL_FROM_MACHINE_DATA_CHECK_0X202 / PARALLEL_FROM_MACHINE_SAFETY_REGULATION_0X240宏定义了但本文件(及全工程)均未使用的死宏

3. 函数清单

函数名作用域说明
can_send_exten_msgstatic(定义 parallel_can.c:46)发送前做最小 2 ms 间隔节流,给 frame_id 打上 bit28 标志与 bit24-27 目标地址,再调用 can1_send_exten_pack
can_get_exten_packstatic(定义 parallel_can.c:70)get_can1_exten_pack 的一层薄封装,取一帧扩展帧
parallel_host_machine_networking_handlestatic(定义 parallel_can.c:86)主机侧处理从机组网请求:校验 CRC8/MAXIM、按"已登记 ID→指定地址→空闲地址"三级策略分配地址、下发 0x181 并登记组网信息
parallel_can_comm_host_machine_handle全局(定义 parallel_can.c:184)主机角色 CAN 通信主循环:3500 ms 发组网远程帧、500 ms 广播控制/参数/安规/EMS 帧、50 ms 逐台单播功率帧、收帧解析从机数据、15 s 心跳失联处理
send_from_machine_networking_infostatic(定义 parallel_can.c:513)从机组帧上报:0x201 数据帧,携带本机 uid[2]+mac[3]+已分配地址,末字节 CRC8/MAXIM(未组网时周期发 / 收到远程帧时立即答)
parallel_from_machine_networking_handlestatic(定义 parallel_can.c:538)从机侧处理主机下发的 0x181:校验 CRC、比对 uid+mac 是否为本机、匹配则采纳分配到的地址
parallel_can_comm_from_machine_handle全局(定义 parallel_can.c:564)从机角色 CAN 通信主循环:未组网时 1000 ms 上报组网帧;已组网时 500 ms 上报规格/电池/EMS、50 ms 上报逆变器遥测、应答远程帧、收主机指令并做 15 s 心跳

4. 核心逻辑解析

4.1 扩展 CAN ID 的编码与过滤

发送侧统一入口 can_send_exten_msg(parallel_can.c:46-61):

static u32 s_u32_send_tick = 0;
if (abs(OSTimeGet() - s_u32_send_tick) < 2)
    OSTimeDlyHMSM(0, 0, 0, 2);          // 两次并机发送之间硬性插入 2ms 延时
s_u32_send_tick = OSTimeGet();

TxMsg->frame_id |= PARALLEL_COMMUNICATION_ADDRESS_OFFSET;  // bit28 = 1
TxMsg->frame_id |= (send_id & 0xF) << 24;                  // bit24-27 = 目标地址
return can1_send_exten_pack(TxMsg);

由此得出 29 位扩展 ID 的分层:

bit28    : 并机报文标志(恒 1)
bit24-27 : 目标/来源机器地址(0 = 广播 = 主机)
bit0-23  : 基础 ID(0x181 / 0x190 / ... / 0x2C0)

接收侧(parallel_can.c:335-340)反向还原并做双重过滤:

l_u8_parallel_comm_rx_addr = (l_t_parallel_comm_rxbuf.frame_id >> 24) & 0xF;
if (((frame_id & PARALLEL_COMMUNICATION_ADDRESS_OFFSET) == FALSE) ||
    (l_u8_parallel_comm_rx_addr >= PARALLEL_MAX_NUM))
    continue;

随后用 frame_id & 0xFFFFFF 剥离 bit24-31,仅剩基础 ID 用于 switch 分派(parallel_can.c:345、369、776)。

4.2 主机侧时序(parallel_can.c:184-500)

三条独立的软定时分支,时间戳均为 static u32,靠 abs(OSTimeGet() - tick) 判断:

周期发送内容位置
3500 ms0x201 远程帧(组网询问)parallel_can.c:197-203
500 ms0x190(frame_data[0..6] = 模式 7 字节)、0x19A(MPPT 2B + N-N + DRM + 备电)、0x19B(safety + safesub)、0x1C0(升级标志)parallel_can.c:206-256
50 ms对 i = 1..15 中 u8_parallel_addr_en == TRUE && u8_parallel_lost_flag == FALSE 的每台从机依次单播 0x191、0x192、0x195、0x196、0x197parallel_can.c:259-328

注意 500 ms 分支内对 s_u32_parallel_send_tick_500ms 重复写了 4 次(parallel_can.c:233、245 等),实际只是把时间戳刷新到最后一帧发出之后,属于冗余写法。

0x1C0 是唯一的主机 EMS 上报帧,只用了 frame_data[0](parallel_can.c:247),其余 7 字节强制 0。

接收循环(parallel_can.c:331-486):

l_u32_parallel_recv_tick = OSTimeGet();
while (can_get_exten_pack(&l_t_parallel_comm_rxbuf) != FALSE) {
    ... 分派 ...
    if (abs(l_u32_parallel_recv_tick - OSTimeGet()) > 10)  // 循环处理超时退出
        break;
}

分派分两支:

地址分配算法(parallel_host_machine_networking_handle,parallel_can.c:98-134)用 switch(0) + 无 break 的 fallthrough 实现三级优先:

0 → 遍历 i=1..15,若 g_t_host_machine_networking_info[i] 前 5 字节与收到的 uid+mac 相同,复用地址 i(重连)
    (若找到则 break 跳出,不再往下 fallthrough)
1 → 报文的 send_id 非 0 且该地址未被占用(u8_parallel_addr_en == FALSE),直接用 send_id
2 → 遍历 1..15 找第一个 u8_parallel_addr_en == FALSE 的空闲地址

分配不到(l_u8_from_machine_addr_temp == 0)直接 return,即并机数量超限时静默丢弃。分配成功后在 0x181 数据帧的 frame_data[5] 回填地址、frame_data[7] 填前 7 字节的 GetCrcCode_8_MAXIM(parallel_can.c:152),并把 uid/mac/地址/u8_parallel_addr_en=TRUE/u32_rcv_tick 全部落库(parallel_can.c:156-166)。

心跳超时(parallel_can.c:488-499):任一台 u8_parallel_addr_en == TRUE 且 u32_rcv_tick 距今 ≥ 15000 ms 时,置 u8_parallel_lost_flag = TRUE 并 memset 清空该台的运行数据结构。注意此处不清 u8_parallel_addr_en,与 SCI 实现(parallel_sci.c:655-657 会清)不一致。

4.3 从机侧时序(parallel_can.c:564-869)

状态由 g_t_from_machine_networking_info.u8_parallel_addr_en / u8_parallel_lost_flag 决定:

两类帧的数据源都是 g_t_host_machine_running_info[0](在从机上 buf[0] 指代本机),即"本机采集到的实时数据"。

接收侧(parallel_can.c:765-859)过滤条件是 addr == 广播 || addr == 本机地址(parallel_can.c:771),分派表:

基础 ID行为
0x181parallel_from_machine_networking_handle:CRC8 校验 + uid/mac 比对,通过则 u8_parallel_addr = frame_data[5]、u8_parallel_addr_en = TRUE(parallel_can.c:538-555)
0x201 远程帧立即 send_from_machine_networking_info() 应答,清 u8_parallel_lost_flag、置 g_t_parallel_total_machine_network.u8_comm_success_flag = TRUE、刷新 u32_rcv_tick(parallel_can.c:783-790)
0x190 / 0x191 / 0x192 / 0x195 / 0x196 / 0x197 / 0x19A / 0x19B / 0x1C0逐字节解包写入 g_t_from_machine_running_info(parallel_can.c:793-849)

心跳超时(parallel_can.c:861-869):u32_rcv_tick 距今 ≥ 15000 ms → 置 u8_parallel_lost_flag = TRUE、清 u8_parallel_addr_en、清空 g_t_from_machine_running_info。清 u8_parallel_addr_en 之后 parallel_can.c:578 的判据生效,从机退回 1000 ms 组网请求循环,形成"失联 → 重新入网"的自愈路径。

4.4 帧格式与校验

帧数据域布局校验
0x201(远程帧)frame_len = 8,无数据无
0x201(从机上报数据帧)[0..1] uid、[2..4] mac、[5] 并行地址、[6]=0、[7] CRC8GetCrcCode_8_MAXIM(data, 7) == data[7]
0x181(地址分配)[0..4] uid+mac、[5] 分配地址、[6]=0、[7] CRC8同上
0x190~0x197、0x19A、0x19B、0x1C0、0x220、0x260~0x26A、0x290、0x2A0~0x2A2、0x2C0固定 frame_len = 8,字段语义完全由 ID 决定;多字节字段大端(>>8 高字节在前,32 位 >>24/16/8/0)无应用层校验,依赖 CAN 控制器 CRC + ACK

因此并机 CAN 通道不存在"超时重试"机制:所有帧都是周期性无条件重发(50 ms / 500 ms / 1000 ms / 3500 ms),丢帧由下一周期自然补偿。

4.5 与 uCOS-II 的配合

本文件不使用信号量、消息邮箱或事件标志。所有时序都靠:

task_inverter_can(midware/inverter/inverter.c:1199-1287)在每次循环末尾 OSTimeDlyHMSM(0, 0, 0, 30),因此本文件三条定时分支的实际分辨率受 30 ms 任务周期限制。

4.6 关键全局变量的读写

变量写读
g_t_host_machine_networking_info[i]parallel_can.c:156-166(登记)、parallel_can.c:495-496(置 lost + 清遥测)parallel_can.c:101-131(地址分配查重)、263-266(单播过滤)
g_t_host_machine_running_info[i]parallel_can.c:377-475(从机遥测解包)parallel_can.c:212-322(读 [0] 的广播模式 + [i] 的功率设定组帧)
g_t_from_machine_networking_infoparallel_can.c:553-554、787-789、863-866parallel_can.c:528(组帧时取本机地址)、578-579(状态判据)、771(收帧地址过滤)
g_t_from_machine_running_infoparallel_can.c:794-848(主机指令解包)、867(失联清空)由 parallel.c 的能量调度逻辑读取
g_t_parallel_total_machine_network.u8_comm_success_flagparallel_can.c:165、788由 parallel.c 判断并机通信整体是否正常

5. 对外接口

5.1 本文件导出

符号类型调用方
parallel_can_comm_host_machine_handle全局函数midware/inverter/parallel.c:1037(parallel_can_comm_handle() 内)
parallel_can_comm_from_machine_handle全局函数midware/inverter/parallel.c:1041(同上)

其余 5 个函数均为 static,仅本文件内可见。

5.2 本文件引用的外部符号

符号来源
can1_send_exten_pack、get_can1_exten_packhardware/can/can1.c(声明于 can1.h:28,29)
GetCrcCode_8_MAXIMcommon/crc.c:126(声明于 crc.h)
OSTimeGet、OSTimeDlyHMSMuCOS-II
memcmp、memset标准 C 库
g_t_host_machine_networking_info[]、g_t_host_machine_running_info[]midware/inverter/parallel.c:36-37
g_t_from_machine_networking_info、g_t_from_machine_running_infomidware/inverter/parallel.c:39-40
g_t_parallel_total_machine_networkmidware/inverter/parallel.c:41(声明于 parallel.h:264)
PARALLEL_MAX_NUM 及全部结构体midware/inverter/parallel.h
CAN_FT_DATA、CAN_FT_REMOTEGD32 标准外设库
printf仅出现在 #if 0 屏蔽块中

6. 依赖关系

6.1 依赖(include)

parallel_can.h   → 全部 CAN ID 宏(0x181 / 0x190~0x2C0)
parallel.h       → 结构体、PARALLEL_MAX_NUM、extern 全局变量声明
CAN1.h           → can1_send_exten_pack / get_can1_exten_pack
crc.h            → GetCrcCode_8_MAXIM
includes.h       → OSTimeGet / OSTimeDlyHMSM / memcmp / memset 等聚合头

6.2 被依赖

文件关系
midware/inverter/parallel.c:1037,1041唯一调用方,按 u8_parallel_mode 分发到主机/从机入口
midware/inverter/inverter.c:1273(task_inverter_can)间接驱动(30 ms 周期调用 parallel_can_comm_handle)
midware/inverter/parallel.c 的 parallel_host_machine_logic_handle / parallel_from_machine_logic_handle数据消费者,读取本文件写入的 g_t_host_machine_running_info[i] / g_t_from_machine_running_info

7. 备注与风险点

  1. 0x262 是只收不发的"孤儿分支"。主机侧 parallel_can.c:397-402 会解析 0x262 填充 s32_Ppv_pvcharger / s32_Pbat_pvcharger,但从机组帧(parallel_can.c:664-761)从不发送 0x262。该数据源在 CAN 通道上事实不可用。
  1. 0x202、0x240、0x267 三个 ID 全工程无收发。0x202(返回数据出错信息)只在 parallel_can.h:37 定义,parallel_can.c 的接收 switch 没有该分支;0x240(从机并网参数)与 0x267 同样只在头文件定义。属于协议预留或历史遗留。
  1. 收发过滤规则不对称,continue 会绕过循环超时保护。parallel_can.c:336-340 与 770-774 在过滤失败时用 continue,而超时判断 abs(...) > 10 位于循环体末尾(parallel_can.c:482、855)。若 CAN 总线上持续有大量非并机帧,continue 会不断跳过超时检查,理论上可能长时间驻留该循环。建议把超时检查移到 continue 之前。
  1. 主机心跳失联后不清 u8_parallel_addr_en(parallel_can.c:493-497),只置 u8_parallel_lost_flag 并清遥测。SCI 实现的同一逻辑(parallel_sci.c:653-659)会同时清 u8_parallel_addr_en 与 u8_parallel_addr。两条通道的失联语义不一致,会造成"CAN 通道下从机地址长期占位、必须等 3500 ms 组网帧重新登记"这一隐性差异。
  1. 32 位大端解包存在有符号移位。parallel_can.c:398-401、452-453、465、469-470 使用形如 frame_data[0] << 24 | ... 的表达式:uint8_t 先提升为 int,<< 24 在最高位为 1 时进入有符号溢出(C 标准下 UB)。在 ARMCC/GCC 上实际按补码工作,但换编译器/优化级别有风险。同一处 (u32)(...) 强转又掩盖了这一点。
  1. 0x190 帧的 frame_data[7]、0x1C0 的 [1..7] 恒为 0,但接收侧不校验,无法用"保留字节"做版本协商或帧完整性交叉检查。
  1. send_id 与地址空间耦合未文档化。can_send_exten_msg 用 send_id & 0xF,而 PARALLEL_MAX_NUM = 16,两者恰好吻合;一旦把 PARALLEL_MAX_NUM 调大到 16 以上,地址会被静默截断(parallel_can.c:58)而收帧侧 addr >= PARALLEL_MAX_NUM 过滤(parallel_can.c:337)却仍以 & 0xF 取值,必然出现地址冲突。
  1. OSTimeGet() 差值取 abs()。abs() 形参为 int,而 OSTimeGet() 返回 INT32U、时间戳为 u32。运行时间超过约 24.8 天(2^31 ticks,1 tick = 1 ms)后差值转 int 会溢出。全文件(parallel_can.c:50、197、206、259、482、491、861)都是同一写法,属系统性隐患。
  1. 主机 500 ms 分支重复刷新时间戳(parallel_can.c:233、245),第 4 帧(0x1C0)发出后未再刷新。因为两行之间没有引入延时,功能上无影响,但属可清理的冗余。
  1. parallel_host_machine_networking_handle 的 switch(0) 依赖 C 的 fallthrough 语义,且 case 1 内的 if 不成立时会继续落入 case 2(这是设计意图),代码可读性差、易被误改为加 break。
  1. memset(&g_t_host_machine_running_info[i], 0x00, ...) 与 frame_id 的读改写存在跨任务竞态。本文件的写操作发生在 task_inverter_can,而 g_t_host_machine_running_info[i] 中各功率设定字段由 parallel.c 的 parallel_host_machine_logic_handle() 在同一任务的后续调用点写(task_inverter_can 中 parallel_can_comm_handle() 之后紧接 parallel_data_handle(),见 inverter.c:1273-1276)。同一任务内串行,暂无实际竞态,但一旦有人把 parallel_can_comm_handle 移到别的任务,60 余个字段的读写将无任何保护。