parallel_can.c
parallel_can.c:184-500)4.3 从机侧时序(parallel_can.c:564-869)4.4 帧格式与校验4.5 与 uCOS-II 的配合4.6 关键全局变量的读写5. 对外接口5.1 本文件导出5.2 本文件引用的外部符号6. 依赖关系6.1 依赖(include)6.2 被依赖7. 备注与风险点源文件路径:
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_msg | static(定义 parallel_can.c:46) | 发送前做最小 2 ms 间隔节流,给 frame_id 打上 bit28 标志与 bit24-27 目标地址,再调用 can1_send_exten_pack |
can_get_exten_pack | static(定义 parallel_can.c:70) | get_can1_exten_pack 的一层薄封装,取一帧扩展帧 |
parallel_host_machine_networking_handle | static(定义 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_info | static(定义 parallel_can.c:513) | 从机组帧上报:0x201 数据帧,携带本机 uid[2]+mac[3]+已分配地址,末字节 CRC8/MAXIM(未组网时周期发 / 收到远程帧时立即答) |
parallel_from_machine_networking_handle | static(定义 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 ms | 0x201 远程帧(组网询问) | parallel_can.c:197-203 |
| 500 ms | 0x190(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、0x197 | parallel_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;
}
分派分两支:
- 广播地址(
rx_addr == 0)(parallel_can.c:343-366):0x181→ 注释"多主机",空实现;0x201→ 若为远程帧则空实现(多主机场景),若为数据帧则调用parallel_host_machine_networking_handle(0, &rx)做地址分配。 - 单播地址(
rx_addr != 0)(parallel_can.c:369-479):首先0x201也走parallel_host_machine_networking_handle(rx_addr, ...)(从机已带地址再报到时的重登记路径);其余0x220/0x260~0x26A(无0x267)/0x290/0x2A0~0x2A2/0x2C0逐字段解包写入g_t_host_machine_running_info[rx_addr]。
地址分配算法(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 决定:
- 未组网或已失联(
parallel_can.c:578-586):每 1000 ms 调用send_from_machine_networking_info()主动上报0x201。 - 已组网正常(
parallel_can.c:589-762):- 500 ms:
0x220(4 个额定功率)、0x290(电池容量)、0x2A0(电池标志/簇数/SOC)、0x2A1(母线电压/电流/功率)、0x2A2(最大充/放功率)、0x2C0(EMS 升级标志)。 - 50 ms:
0x260、0x261、0x263、0x264、0x265、0x266、0x268、0x269、0x26A共 9 帧逆变器遥测。
- 500 ms:
两类帧的数据源都是 g_t_host_machine_running_info[0](在从机上 buf[0] 指代本机),即"本机采集到的实时数据"。
接收侧(parallel_can.c:765-859)过滤条件是 addr == 广播 || addr == 本机地址(parallel_can.c:771),分派表:
| 基础 ID | 行为 |
|---|---|
0x181 | parallel_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] CRC8 | GetCrcCode_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 的配合
本文件不使用信号量、消息邮箱或事件标志。所有时序都靠:
OSTimeGet()(tick 计数)做软定时;OSTimeDlyHMSM(0, 0, 0, 2)(parallel_can.c:52)在发送路径内主动让出 2 ms 给 CAN 控制器发送队列;- 接收侧用
while(can_get_exten_pack(...))把can1接收缓冲排空(hardware/can/can1.c内部维护的扩展帧环形队列)。
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_info | parallel_can.c:553-554、787-789、863-866 | parallel_can.c:528(组帧时取本机地址)、578-579(状态判据)、771(收帧地址过滤) |
g_t_from_machine_running_info | parallel_can.c:794-848(主机指令解包)、867(失联清空) | 由 parallel.c 的能量调度逻辑读取 |
g_t_parallel_total_machine_network.u8_comm_success_flag | parallel_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_pack | hardware/can/can1.c(声明于 can1.h:28,29) |
GetCrcCode_8_MAXIM | common/crc.c:126(声明于 crc.h) |
OSTimeGet、OSTimeDlyHMSM | uCOS-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_info | midware/inverter/parallel.c:39-40 |
g_t_parallel_total_machine_network | midware/inverter/parallel.c:41(声明于 parallel.h:264) |
PARALLEL_MAX_NUM 及全部结构体 | midware/inverter/parallel.h |
CAN_FT_DATA、CAN_FT_REMOTE | GD32 标准外设库 |
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. 备注与风险点
0x262是只收不发的"孤儿分支"。主机侧parallel_can.c:397-402会解析0x262填充s32_Ppv_pvcharger/s32_Pbat_pvcharger,但从机组帧(parallel_can.c:664-761)从不发送0x262。该数据源在 CAN 通道上事实不可用。
0x202、0x240、0x267三个 ID 全工程无收发。0x202(返回数据出错信息)只在parallel_can.h:37定义,parallel_can.c的接收switch没有该分支;0x240(从机并网参数)与0x267同样只在头文件定义。属于协议预留或历史遗留。
- 收发过滤规则不对称,
continue会绕过循环超时保护。parallel_can.c:336-340与770-774在过滤失败时用continue,而超时判断abs(...) > 10位于循环体末尾(parallel_can.c:482、855)。若 CAN 总线上持续有大量非并机帧,continue会不断跳过超时检查,理论上可能长时间驻留该循环。建议把超时检查移到continue之前。
- 主机心跳失联后不清
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 组网帧重新登记"这一隐性差异。
- 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)(...)强转又掩盖了这一点。
0x190帧的frame_data[7]、0x1C0的[1..7]恒为 0,但接收侧不校验,无法用"保留字节"做版本协商或帧完整性交叉检查。
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取值,必然出现地址冲突。
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)都是同一写法,属系统性隐患。
- 主机 500 ms 分支重复刷新时间戳(
parallel_can.c:233、245),第 4 帧(0x1C0)发出后未再刷新。因为两行之间没有引入延时,功能上无影响,但属可清理的冗余。
parallel_host_machine_networking_handle的switch(0)依赖 C 的 fallthrough 语义,且case 1内的if不成立时会继续落入case 2(这是设计意图),代码可读性差、易被误改为加break。
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 余个字段的读写将无任何保护。