parallel_sci.c
REG_SET_*,0x50 起)2.2 文件级静态变量3. 函数清单4. 核心逻辑解析4.1 通道选择:USART3 与 UART4 的对称切换4.2 Modbus RTU 帧格式与 CRC 约定4.3 主机侧时序(parallel_sci.c:610-662)4.4 从机侧普通 Modbus 接收状态机(parallel_modbus_comm_handle,parallel_sci.c:1223-1412)4.5 从机组网接收状态机(parallel_modbus_net_comm_handle,parallel_sci.c:1421-1522)4.6 从机主入口的随机错峰(parallel_sci.c:1531-1543)4.7 心跳与失联4.8 与 uCOS-II 的配合5. 对外接口5.1 本文件导出5.2 本文件引用的外部符号6. 依赖关系6.1 依赖(include)6.2 被依赖7. 备注与风险点源文件路径:
midware/inverter/parallel_comm/parallel_sci.c所属模块:midware/inverter/parallel_comm(并机通信 / SCI 串口通道) 代码行数:1557
1. 文件职责
本文件实现 EMS 并机系统在串口 Modbus-RTU 通道上的主从机组网、功率指令下发与遥测回读,是并机两条物理通道中的 SCI 分支(物理层可选 USART3 或 UART4,由 inverter_get_parallel_mode() 运行期决定,见 parallel_sci.c:196、parallel_sci.c:676)。它同时包含主机角色(parallel_sci_comm_host_machine_handle,parallel_sci.c:610)与从机角色(parallel_sci_comm_from_machine_handle,parallel_sci.c:1531)两套逻辑,由 midware/inverter/parallel.c:1103 / parallel.c:1107 按 g_t_inv_info.t_config_data.u8_parallel_mode 二选一调用。
数据流上,它是"主机发功率指令 → 从机执行;从机上报遥测 → 主机聚合"的完整闭环:主机侧把 g_t_host_machine_running_info[i] 的 31 个控制寄存器用功能码 0x10 逐台写入,再用功能码 0x03 读回 52 个数据寄存器写回 g_t_host_machine_running_info[i];从机侧被动响应主机的读/写请求,并把主机写下来的控制量落到 g_t_from_machine_running_info。文件由 uCOS-II 任务 task_parallel_sci(user/main.c:299 创建,midware/inverter/inverter.c:1296-1307,100 ms 周期)驱动,不使用信号量或消息邮箱。
2. 关键数据结构与宏
本文件不定义结构体/枚举,全部复用 parallel_sci.h、parallel.h 与 hardware/*。
| 名称 | 类型 | 说明 |
|---|---|---|
PARALLEL_HOST_MACHINE_ADDRESS | 宏 = 0x01(parallel_sci.h:15) | 主机地址常量(本文件实际未使用,收发都靠 g_t_..._networking_info 中的运行期地址) |
PARALLEL_FROM_MACHINE_ADDRESS | 宏 = 0x02(parallel_sci.h:16) | 从机地址常量(同样未被本文件使用) |
PARALLEL_HOST_MACHINE_READ_CODE | 宏 = 0x03 | Modbus 功能码"读保持寄存器" |
PARALLEL_HOST_MACHINE_WRITE_MULTI_CODE | 宏 = 0x10 | Modbus 功能码"写多个保持寄存器" |
PARALLEL_QUERY_DATA_REGISTER_ADDRESS | 宏 = 0x00 | 常规数据寄存器起始地址(从机 → 主机的遥测区) |
PARALLEL_QUERY_DATA_NUM | 宏 = 52 | 常规数据寄存器数量(寄存器 0x00~0x33) |
PARALLEL_CONTROL_DATA_REGISTER_ADDRESS | 宏 = 0x50 | 控制数据寄存器起始地址(主机 → 从机的指令区) |
PARALLEL_CONTROL_DATA_NUM | 宏 = 31 | 控制数据寄存器数量(寄存器 0x50~0x6E) |
PARALLEL_NETWORKING_CODE | 宏 = 0x41 | 广播组网开始指令 |
PARALLEL_NET_ASSIGN_CODE | 宏 = 0x42 | 地址分配指令 |
PARALLEL_NET_COMPLETE_CODE | 宏 = 0x43 | 组网完成应答 |
PARALLEL_BROADCAST_ADDRESS | 宏 = 0x00 | 广播地址 |
E_MODBUS_Recv_Parallel_State | 枚举(parallel_sci.h:30-44) | 从机解析普通 Modbus 帧的接收状态机状态:PARALLEL_ADDR_M(0) → PARALLEL_FUNC_M(1) → PARALLEL_REGISTER_BITS_8_15(2) → PARALLEL_REGISTER_BITS_0_7(3) → PARALLEL_REGISTER_NUM_8_15(4) → PARALLEL_REGISTER_NUM_0_7(5) → PARALLEL_LEN_8_15(6) / PARALLEL_LEN_0_7(7) → PARALLEL_DATA_RECVD(8) → PARALLEL_CRC_Bits_8_15(9) → PARALLEL_CRC_Bits_0_7(10) → PARALLEL_EXCEPTION_E(11) |
E_MODBUS_Recv_Parallel_Networking_State | 枚举(parallel_sci.h:46-58) | 从机解析组网帧的接收状态机状态:PARALLEL_START_M → PARALLEL_BROADCAST_ADDR_M → PARALLEL_NET_FUNC_M / PARALLEL_UNIQUE_DEVICE_ID_0 → PARALLEL_UNIQUE_DEVICE_ID_1 → PARALLEL_ASSIGN_ADDR / PARALLEL_NET_EXCEPTION_E |
REG_PARALLEL_QUERY_MODBUS_E | 枚举(parallel_sci.h:60-152) | 并机寄存器地址总表。前半段 0x00~0x33(52 个)为遥测寄存器;后半段 REG_SET_AEQUENCE_CONTROL_E = 0x50 起至 REG_SET_BMS_SHUTDOWN(31 个)为控制寄存器 |
PARALLEL_HOST_MACHINE_NETWORKING_PARAMETER_T | 结构体(parallel.h:82-90) | 主机侧每台从机的组网参数,[0] 为本机 |
PARALLEL_HOST_MACHINE_RUNNING_INFO_T | 结构体(parallel.h:101-217) | 主机侧"指令 + 遥测"大结构,遥测解包目标 |
PARALLEL_FROM_MACHINE_NETWORKING_PARAMETER_T | 结构体(parallel.h:92-98) | 从机组网状态 |
PARALLEL_FROM_MACHINE_RUNNING_INFO_T | 结构体(parallel.h:220-260) | 从机侧接收主机指令的结构(27 个功率/模式字段 + 升级标志) |
PARALLEL_MAX_NUM | 宏 = 16(parallel.h:15) | 并机最大台数 |
PARALLEL_MODE_E / PARALLEL_COM_TYPE_E | 枚举(parallel.h:25-39) | HOST_MACHINE/FORM_MACHINE;PARA_COM_TYPE_UART4/PARA_COM_TYPE_USART3/PARA_COM_TYPE_CAN/PARA_COM_TYPE_NONE |
BAT_WORK_FLAG_U / UPGRADE_FLAG_U | 联合体(parallel.h:48-70) | 电池工作标志位域 / 升级标志位域 |
2.1 控件寄存器编址(REG_SET_*,0x50 起)
| 地址 | 枚举名 | 目标字段(g_t_from_machine_running_info) |
|---|---|---|
| 0x50 | REG_SET_AEQUENCE_CONTROL_E | u8_sequencecontrol_mode |
| 0x51 | REG_SET_THREE_UNBALANCE_MODE_E | u8_three_unbalance_mode(只取 bit0) |
| 0x52 | REG_SET_DC_TOTAL_BAT_POWER_E | s16_dc_total_set_bat_power |
| 0x53 | REG_SET_DC_TOTAL_INV_POWER_E | s16_dc_total_set_inv_power |
| 0x54 | REG_SET_DC_TOTAL_GRID_POWER_E | s16_dc_total_set_grid_power |
| 0x55 | REG_SET_DC_THREE_PHASE_BAT_POWER_E | s16_dc_three_phase_set_bat_power |
| 0x56~0x58 | REG_SET_DC_THREE_PHASE_INV_L1/L2/L3_POWER_E | s16_dc_three_phase_set_inv_a/b/c_power |
| 0x59 | REG_SET_AC_TOTAL_INV_POWER_E | s16_ac_total_set_inv_power |
| 0x5A | REG_SET_AC_TOTAL_BAT_CHARGE_LIMIT_POWER_E | s16_ac_total_set_bat_charge_limit_power |
| 0x5B | REG_SET_AC_TOTAL_BAT_DISCHARGE_LIMIT_POWER_E | s16_ac_total_set_bat_dischg_limit_power |
| 0x5C | REG_SET_AC_TOTAL_BAT_FORCED_CHARGE_ENABLE_E | s16_ac_total_set_bat_forced_charge_en |
| 0x5D~0x5F | REG_SET_AC_THREE_PHASE_INV_L1/L2/L3_POWER_E | s16_ac_three_phase_set_inv_a/b/c_power |
| 0x60 | REG_SET_AC_THREE_PHASE_BAT_CHARGE_LIMIT_POWER_E | s16_ac_three_phase_set_bat_charge_limit_power |
| 0x61 | REG_SET_AC_THREE_PHASE_BAT_DISCHARGE_LIMIT_POWER_E | s16_ac_three_phase_set_bat_dischg_limit_power |
| 0x62 | REG_SET_AC_THREE_PHASE_BAT_FORCED_CHARGE_ENABLE_E | s16_ac_three_phase_set_bat_forced_charge_en |
| 0x63 | REG_SET_MPPT_PERCENTAGE_E | u16_mppt_percentage |
| 0x64 | REG_SET_SAFETY_E | u8_safety |
| 0x65 | REG_SET_FASESUB_E | u8_safesub |
| 0x66 | REG_SET_UPGRADE_FLAG_E | host_machine_upgrade_flag_u.u8_upgrade_flag |
| 0x67 | REG_BB_MODEL | u8_bb_model |
| 0x68 | REG_GC_CONTROL_EN | u8_gc_control_en |
| 0x69 | REG_FREQ_DISPATCH_EN | u8_freq_dispatch_en |
| 0x6A | REG_NN_SHORT_DETECT_EN | u8_nn_short_detect_state |
| 0x6B | REG_DRM_MODE | u8_drm_mode |
| 0x6C | REG_BACKUP_EN | u8_inv_backup_en |
| 0x6D | REG_SET_INV_ON_OFF | u8_set_inv_on_off |
| 0x6E | REG_SET_BMS_SHUTDOWN | u8_set_bms_shutdown |
(0x6E - 0x50 + 1 = 31,与 PARALLEL_CONTROL_DATA_NUM 一致。)
2.2 文件级静态变量
| 名称 | 类型 | 说明 |
|---|---|---|
s_u8_parallel_modbus_recv_buff | static u8[256](parallel_sci.c:24) | 并机收帧缓冲。主机侧与从机侧共用;从机解析状态机以它同时充当"原始帧缓存 + 已解析字段暂存区" |
s_u8_parallel_modbus_send_buff | static u8[256](parallel_sci.c:25) | 并机发帧缓冲。所有组帧函数(含主机 71 字节写帧、从机 109 字节读应答)都直接写它 |
s_u8_sci_timeout_cnt | static u8(parallel_sci.c:27) | 连续超时计数器。主机读失败累计(parallel_sci.c:534)、从机帧接收超时累计(parallel_sci.c:1245、1438)、成功时清零(parallel_sci.c:476、1379);> 5 时重初始化串口 |
3. 函数清单
| 函数名 | 作用域 | 说明 |
|---|---|---|
handle_join_request | static(定义 parallel_sci.c:70) | 主机侧处理从机入网应答:三级策略分配地址(复用已登记 ID→指定地址→空闲地址),组 0x42 分配帧下发,收到 0x43 后登记组网信息 |
host_send_networking_data_to_salve | static(parallel_sci.c:174) | 主机广播组网帧 [0x00][0x41][0x00][CRC_L][CRC_H](5 字节) |
parallel_write_data | static(parallel_sci.c:194) | 按当前通道(USART3/UART4)清缓冲并发送,送 uart3_send_data / uart4_send_data,超时 100 ms |
host_parallel_recv_data | static(parallel_sci.c:214) | 主机侧阻塞式读一帧:把串口异步缓冲全部取入 buf,用 GetCrcCode_Modbus 校验尾部 2 字节 CRC(Modbus 低字节在前) |
host_read_query_data_from_salve | static(parallel_sci.c:290) | 组 Modbus 0x03 读帧(8 字节)并发送 |
host_set_query_data_to_salve | static(parallel_sci.c:315) | 组 Modbus 0x10 写帧(addr+func+reg_hi/lo+num_hi/lo+bytecount+data+CRC)并发送;带 register_num<=256 && len_buff==register_num*2 的入参自检 |
parallel_comm_host_send_control_data_to_salve | static(parallel_sci.c:348) | 遍历从机,把 g_t_host_machine_running_info[0](全局模式类)与 [i](每台功率类)拼成 62 字节控制数据,用 0x10 写入各从机 0x50 起的 31 个寄存器 |
parallel_comm_host_read_query_data_from_salve | static(parallel_sci.c:459) | 遍历从机,用 0x03 读 52 个寄存器,字节数校验通过后把 104 字节逐字段解包进 g_t_host_machine_running_info[i];失败则累计 s_u8_sci_timeout_cnt,>5 时重初始化串口 |
parallel_sci_comm_host_machine_handle | 全局(parallel_sci.c:610) | 主机角色 SCI 通信主入口:限速 ≥200 ms、每 10 s 广播组网、每次轮询做一轮"读全部 + 写全部"、最后 15 s 心跳失联处理 |
parallel_modbus_send_data | static(parallel_sci.c:674) | 从机侧发送封装:按通道清缓冲后调 uart3_send_data / uart4_send_data(超时 100 ms) |
parallel_modbus_data_is_recv_async | static(parallel_sci.c:693) | 从机侧"是否有整帧待读"判断:USART3 用 uart3_is_recv_async_data2(),否则 uart4_is_recv_async_data() |
parallel_modbus_get_data_byte | static(parallel_sci.c:711) | 从机侧从串口异步缓冲取 1 字节 |
Parallel_Set_Para_to_Reg_Addr | static(parallel_sci.c:729) | 寄存器地址 → 从机字段的写映射:仅当 device_addr == 本机地址 时生效,31 个 case 把 value 写入 g_t_from_machine_running_info 对应字段 |
Parallel_get_para_from_reg_addr | static(parallel_sci.c:870) | 寄存器地址 → 主机字段的读映射:52 个 case 把 g_t_host_machine_running_info[0] 的遥测字段(含 32 位字段的高/低 16 位拆分)打成 16 位寄存器值 |
parallel_handle_modbus_response | static(parallel_sci.c:1119) | 从机应答 0x03 读请求:组 [addr][0x03][104][52×2 字节数据][CRC] 共 109 字节并发送 |
parallel_handle_modbus_write_response | static(parallel_sci.c:1152) | 从机处理 0x10 写请求的数据域:把 buf 中 31 个大端 16 位值按 0x50 起的地址逐个交给 Parallel_Set_Para_to_Reg_Addr |
parallel_handle_networking_response | static(parallel_sci.c:1173) | 从机应答广播组网:[0x00][0x41][uid0][uid1][mac0][mac1][mac2][CRC] 共 9 字节 |
parallel_handle_assign_response | static(parallel_sci.c:1199) | 从机应答地址分配:[本机地址][0x43][uid0][uid1][CRC] 共 6 字节 |
parallel_modbus_comm_handle | static(parallel_sci.c:1223) | 从机侧普通 Modbus 从站接收状态机(地址匹配 → 功能码 → 寄存器地址 → 寄存器数 → 长度 → 数据 → CRC → 应答),300 ms 超时退出 |
parallel_modbus_net_comm_handle | static(parallel_sci.c:1421) | 从机侧组网帧接收状态机(广播地址 → 0x41/0x42 分支 → uid 匹配 → 采纳地址并应答),300 ms 超时退出 |
parallel_sci_comm_from_machine_handle | 全局(parallel_sci.c:1531) | 从机角色 SCI 通信主入口:先按 10~99 ms 随机延时错峰,再按"未分配地址走组网状态机 / 已分配地址走 Modbus 状态机"分流,最后 15 s 心跳失联处理 |
4. 核心逻辑解析
4.1 通道选择:USART3 与 UART4 的对称切换
全部底层 I/O 都收敛到 4 个 if (inverter_get_parallel_mode() == PARA_COM_TYPE_USART3) 分支:
| 功能 | USART3 分支 | 其他(UART4)分支 | 位置 |
|---|---|---|---|
| 主机发送 | uart3_clear_buffer() + uart3_send_data | uart4_clear_buffer() + uart4_send_data | parallel_sci.c:194-206 |
| 主机接收 | uart3_is_recv_async_data() + uart3_read_data_async() | uart4_is_recv_async_data2() + uart4_read_data_async() | parallel_sci.c:222-281 |
| 从机发送 | uart3_clear_buffer() + uart3_send_data | uart4_clear_buffer() + uart4_send_data | parallel_sci.c:674-686 |
| 从机接收判据 | uart3_is_recv_async_data2() | uart4_is_recv_async_data() | parallel_sci.c:693-703 |
注意主机侧与从机侧使用的"接收就绪"函数不同:主机 USART3 用 uart3_is_recv_async_data、UART4 用 uart4_is_recv_async_data2;从机 USART3 用 uart3_is_recv_async_data2、UART4 用 uart4_is_recv_async_data。四个组合互不相同,说明 USART3/UART4 各自维护了两套接收上下文(大小写与 2 后缀),切换通道时必须成套更换,任何一处漏改都会导致收发错配。
4.2 Modbus RTU 帧格式与 CRC 约定
统一采用标准 Modbus RTU:[从站地址][功能码][数据域...][CRC_L][CRC_H],CRC 低字节在前,由 GetCrcCode_Modbus()(common/crc.c:144)计算。
| 帧 | 字节布局 | 长度 |
|---|---|---|
| 主机读请求 | [addr][0x03][reg_hi][reg_lo][num_hi][num_lo][CRC_L][CRC_H] | 8 |
| 主机写请求(控制) | [addr][0x10][0x00][0x50][0x00][0x1F][62][62 字节数据][CRC_L][CRC_H] | 71 |
| 从机读应答 | [addr][0x03][104][104 字节数据][CRC_L][CRC_H] | 109 |
| 主机广播组网 | [0x00][0x41][0x00][CRC_L][CRC_H] | 5 |
| 从机组网应答 | [0x00][0x41][uid0][uid1][mac0][mac1][mac2][CRC_L][CRC_H] | 9 |
| 主机地址分配 | [0x00][0x42][uid0][uid1][addr][mac0][mac1][mac2][CRC_L][CRC_H] | 10 |
| 从机分配应答 | [addr][0x43][uid0][uid1][CRC_L][CRC_H] | 6 |
寄存器映射由 Parallel_get_para_from_reg_addr(读,parallel_sci.c:870-1111)与 Parallel_Set_Para_to_Reg_Addr(写,parallel_sci.c:729-862)两张大 switch 承载,是主机侧 Modbus 字节偏移与结构体字段之间唯一的对应表。
32 位字段按"高 16 位寄存器 + 低 16 位寄存器"拆分,例如 u32_bat_capacity 对应 REG_BAT_CAPACITY_H_E(0x04) 取 >>16、REG_BAT_CAPACITY_L_E(0x05) 取 &0xffff(parallel_sci.c:895-901)。因此主机侧解包时对应反拼:buff[11]<<24 | buff[12]<<16 | buff[13]<<8 | buff[14](parallel_sci.c:482-483)。
4.3 主机侧时序(parallel_sci.c:610-662)
if (abs(OSTimeGet() - s_u32_send_tick_200ms) < 200) return; // 限速:最小 200ms 一轮
s_u32_send_tick_200ms = OSTimeGet();
if (abs(s_u32_parallel_send_tick_4000ms - OSTimeGet()) >= 10000) { // 每 10s 广播组网
s_u32_parallel_send_tick_4000ms = OSTimeGet();
host_send_networking_data_to_salve(); // 发 [0x00][0x41][0x00][CRC]
memset(recv_buff, 0, 256);
host_parallel_recv_data(recv_buff, &l_u16_recv_num); // 阻塞收应答
if (recv_buff[1] == PARALLEL_NETWORKING_CODE) // 0x41
handle_join_request(&recv_buff[2], 0); // 从 recv_buff[2] 起是 uid0,uid1,mac0..2
}
parallel_comm_host_read_query_data_from_salve(); // 读全部从机
parallel_comm_host_send_control_data_to_salve(); // 写全部从机
for (i = 1; i < 16; i++) // 15s 心跳
if (addr_en && abs(u32_rcv_tick - OSTimeGet()) >= 15000) { ... }
实际节拍:任务周期 100 ms(inverter.c:1305)+ 本函数 200 ms 门限 → 每 200 ms 完成一轮"读 15 台 + 写 15 台"。由于 parallel_comm_host_read_query_data_from_salve 内部对每台从机都做一次发送 + 一次 host_parallel_recv_data,在从机数量多、或某台从机不在线时,一轮的最坏耗时 = 15 × 100 ms(串口发送超时)= 1.5 s,会远超 200 ms;这是纯阻塞式实现,会拖慢整个 task_parallel_sci。
地址分配算法(handle_join_request,parallel_sci.c:83-124)与 CAN 版本完全同构,同样用 switch(0) fallthrough:
case 0: 遍历 i=1..15,memcmp(unique_id, &g_t_host_machine_networking_info[i], 5) == 0 → 复用地址 i
(ps: 结构体前 5 字节 = u8_unique_device_id[2] + u8_mac[3],pack(1) 保证连续)
case 1: send_id != 0 且地址未占用 → 用 send_id
case 2: 遍历找第一个 u8_parallel_addr_en == FALSE 的空闲地址
但本文件的 send_id 恒为 0(调用点 parallel_sci.c:639 硬编码 0),所以 case 1 实际不可达,地址分配退化为"查重复 ID → 找空闲地址"两级。分配失败(返回 0)静默 return。
分配帧下发后紧接 host_parallel_recv_data 收 0x43 应答(parallel_sci.c:141),只有 recv_buff[1] == PARALLEL_NET_COMPLETE_CODE(0x43)才落库组网信息(parallel_sci.c:143-166)。这是"两段式握手":0x42 分配 → 0x43 确认。
心跳超时(parallel_sci.c:648-661)与 CAN 版不同:除了置 u8_parallel_lost_flag = TRUE、清遥测,还额外 清 u8_parallel_addr_en = FALSE 和 u8_parallel_addr = 0。清地址后该台从机在下一轮不再被读/写,也不会再被单播,必须靠 10 s 一次的广播组网重新登记。
4.4 从机侧普通 Modbus 接收状态机(parallel_modbus_comm_handle,parallel_sci.c:1223-1412)
这是个 1 字节 1 步的状态机,state_recv 初值 PARALLEL_ADDR_M,300 ms 无进展即超时返回。
PARALLEL_ADDR_M(0) : byte == 本机地址 ? → 清 recv_buff、存 byte、state=FUNC_M : 保持
PARALLEL_FUNC_M(1) : byte == 0x03 或 0x10 ? 存 byte、state=REGISTER_BITS_8_15 : 回 ADDR_M
PARALLEL_REGISTER_BITS_8_15 : 存 byte,register_temp = byte<<8,state=REGISTER_BITS_0_7
PARALLEL_REGISTER_BITS_0_7 : 存 byte,register_temp |= byte
recv_buff[0] == 本机地址 ? state=REGISTER_NUM_8_15 : state=EXCEPTION_E
PARALLEL_REGISTER_NUM_8_15 : register_num_temp = byte<<8,存 byte,state=REGISTER_NUM_0_7
PARALLEL_REGISTER_NUM_0_7 : register_num_temp += byte
recv_buff[1] == 0x03 ? state=CRC_Bits_0_7(读请求无数据域,直接进 CRC)
recv_buff[1] == 0x10 ? state=LEN_0_7(写请求先收字节计数)
else state=ADDR_M
PARALLEL_LEN_0_7 : byte == register_num_temp*2 ? data_len_temp=byte、存 byte、state=DATA_RECVD
else state=EXCEPTION_E
PARALLEL_DATA_RECVD : 存 byte;data_len_temp + 7 <= idx ? state=CRC_Bits_0_7
PARALLEL_CRC_Bits_0_7 : crc_16 = byte(不存缓冲),state=CRC_Bits_8_15
PARALLEL_CRC_Bits_8_15 : crc_16 |= byte<<8;crc_tmp = GetCrcCode_Modbus(recv_buff, idx)
crc_16 == crc_tmp ?
buff[1] == 0x03 → parallel_handle_modbus_response() // 回 109 字节
buff[1] == 0x10 → parallel_handle_modbus_write_response(&buff[7], buff[0])
并置 lost=FALSE、comm_success=TRUE、addr_en=TRUE、刷新 rcv_tick
return
else state=EXCEPTION_E
关键点:
idx只在CRC_Bits_0_7/CRC_Bits_8_15两步不递增,因此GetCrcCode_Modbus(recv_buff, idx)的idx恰好等于"帧长 - 2",CRC 计算范围正确。PARALLEL_REGISTER_NUM_0_7之后读请求直接跳到 CRC,是因为0x03请求本身只有 8 字节,寄存器数后紧跟 CRC —— 状态枚举里PARALLEL_CRC_Bits_0_7(10) 排在PARALLEL_CRC_Bits_8_15(9) 之后,正是为了"先收低字节"。- 写请求的地址守卫:
Parallel_Set_Para_to_Reg_Addr首先判断device_addr == g_t_from_machine_networking_info.u8_parallel_addr(parallel_sci.c:731),即广播地址 0x00 的写帧会被整帧丢弃,主机必须单播写入。 - 收到合法
0x10写帧本身即视为心跳(parallel_sci.c:1388-1391),因为从机的读应答是被动回 109 字节,而"主机还在写我"才是从机方向最强的存活信号。
4.5 从机组网接收状态机(parallel_modbus_net_comm_handle,parallel_sci.c:1421-1522)
PARALLEL_START_M : byte == 0x00(广播地址)? 清缓冲、存 byte、state=BROADCAST_ADDR_M
PARALLEL_BROADCAST_ADDR_M: byte == 0x41(NETWORKING) → 存 byte、state=NET_FUNC_M
byte == 0x42(NET_ASSIGN) → 存 byte、state=UNIQUE_DEVICE_ID_0
PARALLEL_NET_FUNC_M : 本机地址 == 0 ? 调 parallel_handle_networking_response()(回 9 字节)并 return
: state=NET_EXCEPTION_E(已有地址,忽略)
PARALLEL_UNIQUE_DEVICE_ID_0: 存 byte(uid0),state=UNIQUE_DEVICE_ID_1
PARALLEL_UNIQUE_DEVICE_ID_1: 存 byte(uid1),state=ASSIGN_ADDR
PARALLEL_ASSIGN_ADDR : recv_buff[2]==本机 uid0 && recv_buff[3]==本机 uid1 ?
存 byte(recv_buff[4] = 分配到的地址)
u8_parallel_addr = recv_buff[4]
parallel_handle_assign_response()(回 6 字节)
return
: state=NET_EXCEPTION_E
完整入网时序:主机 10 s 广播 0x41 → 未入网从机回 0x41 + uid + mac(9 字节)→ 主机 handle_join_request 分配地址后下发 0x42 + uid + addr + mac(10 字节)→ 从机比对 uid 匹配后采纳地址并回 0x43 + uid(6 字节)→ 主机收到 0x43 落库。此后从机走 parallel_modbus_comm_handle 分支。
同样 300 ms 超时保护,超时累计 > 5 时重初始化 UART4(parallel_sci.c:1442,注意此处硬编码 uart4_init,没有按通道判断,见风险点)。
4.6 从机主入口的随机错峰(parallel_sci.c:1531-1543)
if (g_t_from_machine_networking_info.u8_parallel_addr == 0) {
u16 delay = 10 + (rand() % 90);
OSTimeDlyHMSM(0, 0, 0, delay);
parallel_modbus_net_comm_handle();
} else {
u16 delay = 10 + (rand() % 90);
OSTimeDlyHMSM(0, 0, 0, delay);
parallel_modbus_comm_handle();
}
用途是避免多台从机在同一 tick 同时应答广播帧造成 485 总线碰撞。rand() 在工程内没有任何 srand() 调用,因此上电后序列固定(同一固件的多台设备会得到相同的伪随机序列),错峰效果只在"上电时刻不同"这一维度上成立。
4.7 心跳与失联
| 方向 | 判据 | 动作 | 位置 |
|---|---|---|---|
| 主机判定某从机失联 | u8_parallel_addr_en && (now - u32_rcv_tick) >= 15000 | 清 addr_en、清 addr、置 lost、清该台 g_t_host_machine_running_info[i] | parallel_sci.c:648-661 |
| 从机判定主机失联 | (now - u32_rcv_tick) >= 15000 | 置 lost、清 addr_en、清 addr、清 g_t_from_machine_running_info | parallel_sci.c:1546-1555 |
u32_rcv_tick 的刷新点:主机侧仅在读应答字段校验通过时(parallel_sci.c:475)与 handle_join_request 落库时(parallel_sci.c:154);从机侧仅在收到合法 0x10 写帧时(parallel_sci.c:1391)。从机对 0x03 读请求不做 rcv_tick 刷新(parallel_sci.c:1381-1384 只调 parallel_handle_modbus_response()),这是合理设计——"主机还在下发控制"才算存活。
失联后 s_u8_sci_timeout_cnt 与串口重初始化形成第二层自愈:连续 5 次以上收发失败会 uart3_init(115200) / uart4_init(115200)(parallel_sci.c:536-546、1246-1257)。
4.8 与 uCOS-II 的配合
- 无信号量、无邮箱。全部同步依赖
OSTimeGet()tick 比较与OSTimeDlyHMSM。 - 任务
task_parallel_sci(user/main.c:299创建,TASK_PARALLEL_SCI_PRIO)循环体只有parallel_sci_comm_handle()+OSTimeDlyHMSM(0, 0, 0, 100)(inverter.c:1300-1306),周期 100 ms。 - 通道切换只做一次:
parallel_sci_comm_handle(parallel.c:1055-1091)用static u8 s_u8_parallel_mode记忆上次模式,变化时按模式uart3_init/uart4_init(USART3 模式下把另一路初始化为 9600 后复用给外部 485)。 - 两个
while(1)状态机内部靠(OSTimeGet() - start_temp_tick) >= 300兜底,不会永久阻塞任务。
5. 对外接口
5.1 本文件导出
| 符号 | 类型 | 调用方 |
|---|---|---|
parallel_sci_comm_host_machine_handle | 全局函数 | midware/inverter/parallel.c:1103(parallel_sci_comm_handle() 内) |
parallel_sci_comm_from_machine_handle | 全局函数 | midware/inverter/parallel.c:1107(同上) |
其余 19 个函数均为 static。文件级 3 个缓冲/计数器变量也均为 static,不对外暴露。
5.2 本文件引用的外部符号
| 符号 | 来源 |
|---|---|
uart3_init / uart3_clear_buffer / uart3_send_data / uart3_read_data_async / uart3_get_data_len / uart3_is_recv_async_data / uart3_is_recv_async_data2 | hardware/usart3/usart3.c(usart3.h) |
uart4_init / uart4_clear_buffer / uart4_send_data / uart4_read_data_async / uart4_get_data_len / uart4_is_recv_async_data / uart4_is_recv_async_data2 | hardware/usart4/usart4.c(usart4.h) |
GetCrcCode_Modbus | common/crc.c:144(crc.h) |
GetCrcCode_8_MAXIM | common/crc.c:126(仅在 parallel_sci.c:78-82 的注释代码中引用) |
inverter_get_parallel_mode | midware/inverter/inverter.c:2957(inverter.h:1971) |
OSTimeGet / OSTimeDlyHMSM | uCOS-II |
memcmp / memset / memcpy / rand | 标准 C 库 |
printf / SERIAL_DEBUG_LOG_OUT | #if (MODBUS_RTU_DEBUG == 1) / #if (DEBUG==1)&(DEBUG_MODBUS==1) 块内,默认为关 |
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_* 宏与全部结构体/枚举 | midware/inverter/parallel_sci.h、midware/inverter/parallel.h |
6. 依赖关系
6.1 依赖(include)
parallel_sci.h → 寄存器枚举 REG_PARALLEL_QUERY_MODBUS_E、两个接收状态机枚举、Modbus/组网功能码宏
parallel.h → 结构体、PARALLEL_MAX_NUM、extern 全局变量
usart3.h → USART3 通道 I/O
usart4.h → UART4 通道 I/O
crc.h → GetCrcCode_Modbus
includes.h → OSTime* / mem* / rand 等聚合头
inverter.h → inverter_get_parallel_mode()
6.2 被依赖
| 文件 | 关系 |
|---|---|
midware/inverter/parallel.c:1103,1107 | 唯一调用方,按 u8_parallel_mode 分发 |
midware/inverter/inverter.c:1296-1307(task_parallel_sci) | 间接驱动(100 ms 周期调用 parallel_sci_comm_handle) |
user/main.c:299 | 创建 task_parallel_sci 任务 |
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. 备注与风险点
Parallel_Set_Para_to_Reg_Addr中REG_SET_INV_ON_OFF缺少break(parallel_sci.c:851-856):写寄存器 0x6D 时,u8_set_inv_on_off赋值后会直落REG_SET_BMS_SHUTDOWN分支,把同一个value也写进u8_set_bms_shutdown。即"逆变器开关机指令"会同时触发 BMS 关机指令,是实打实的逻辑缺陷。
Parallel_get_para_from_reg_addr的地址参数判断语义错误(parallel_sci.c:874):函数形参名为device_addr(从站地址),却与PARALLEL_QUERY_DATA_REGISTER_ADDRESS(寄存器地址 0x00)比较。两个调用点(parallel_sci.c:1133)都传字面量 0,所以条件恒真、功能恰好正确,但这是"巧合式正确"——任何按名字理解而传入真实从站地址的调用都会让整张映射表静默失效(返回全 0)。
parallel_handle_modbus_response的应答是给"任意"读请求的固定内容:无论主机读 0x00 起的 52 个寄存器还是其它地址,从机都回[0x03][104][52×2 字节];register_temp/register_num_temp在收到读请求时被解析但从未被使用(parallel_sci.c:1321、1302的赋值在parallel_sci.c:1381之后无消费者)。主机侧也只按固定位置解包(parallel_sci.c:478-530)。即协议只支持一种读写布局,寄存器地址字段形同装饰。
- 从机组网状态机完全不校验 CRC(
parallel_modbus_net_comm_handle,parallel_sci.c:1421-1522):帧尾的两个 CRC 字节被当作普通字节读入(PARALLEL_UNIQUE_DEVICE_ID_1之后即进入PARALLEL_ASSIGN_ADDR处理recv_buff[4]),从未调用GetCrcCode_Modbus。总线上任何形如00 42 xx xx addr的干扰字节流都可能让从机错误采纳一个不存在的地址。
PARALLEL_NET_EXCEPTION_E/PARALLEL_EXCEPTION_E在两个switch中都没有case(parallel_sci.c:1454-1520、1268-1410):一旦进入异常态,状态机不会自复位,只能靠 300 ms 超时退出。异常态期间所有字节被丢弃,等于丢了最多 300 ms 的数据,属于可接受的降级,但状态枚举里给了"异常态"却没有恢复路径,设计不完整。
- 组网超时重初始化硬编码 UART4(
parallel_sci.c:1442):uart4_init(115200)没有像其余 4 处那样按inverter_get_parallel_mode()分支。若通道配成 USART3,组网失败时会把另一路串口的波特率改成 115200,而 USART3 侧的问题不被修复。相比parallel_sci.c:1249-1256与538-545的正确写法,这里明显漏改。
handle_join_request的 CRC8 校验被整段注释掉(parallel_sci.c:78-82):从机上报的组网应答只靠memcmp(unique_id, ..., 5)做身份确认,无任何校验。对比 CAN 版本parallel_can.c:92-96有 CRC8/MAXIM 保护。
host_parallel_recv_data中的s_u32_loss_cnt是死变量(parallel_sci.c:221):声明为static u32并++,但全文件无任何读取点,也不上报。
u8_parallel_addr != 0 || u8_parallel_addr_en != FALSE用的是||而非&&(parallel_sci.c:354-355、465-466、354):只要地址非 0 就进入读写流程,即使u8_parallel_addr_en == FALSE。失联清地址后addr也置 0,所以当前恰好成立;但一旦有任何路径只清addr_en不清addr(CAN 侧就是这种写法,parallel_can.c:493-497),同一条件在 SCI 侧会变成"对已标记失联的机器持续读写",白白消耗 100 ms 的串口超时。
- 一轮轮询是全阻塞串行(
parallel_sci.c:463-600):15 台从机 × (发送 ≤100 ms + 接收 ≤100 ms) 最坏可占用约 3 s,超出task_parallel_sci的 100 ms 周期与主函数 200 ms 限速,会显著拉长并机刷新周期。parallel_comm_host_send_control_data_to_salve同样是 15 次阻塞写,且不读应答(0x10的正常响应被下一轮host_parallel_recv_data当作垃圾丢弃或误判)。
- 魔法数字 5 / 6 用于串口重初始化门限(
parallel_sci.c:535、1246、1439):注释缺失,含义是"连续 6 次超时"。文档化后应提为宏。
Parallel_Set_Para_to_Reg_Addr无条件截断语义的多处隐式转换:u8_sequencecontrol_mode = (value)、u8_safety = (value)(parallel_sci.c:736、816)直接把 16 位寄存器值截到 8 位;u8_three_unbalance_mode = ((value & 0x01) == TRUE)(parallel_sci.c:740)只保留 bit0。主机侧按 16 位大端发送这些 8 位量时高字节恒为 0(参见parallel_sci.c:357-418大量>>8 & 0xff对u8字段的冗余写法),读写路径的位宽语义不一致。
abs()作用于u32tick 差值(parallel_sci.c:617、631、651、1546):运行超过约 24.8 天后差值转int溢出,与parallel_can.c同源的系统性问题。
rand()无srand()(parallel_sci.c:1535、1540):仅出现在rand()的global位置(工程内grep srand无结果),上电后错峰序列确定,多台同固件设备的初始延时分布可能高度相关。