串口传文件必须分帧加crc16校验,每帧≤256字节、含0x02头/0x03尾;需设readtimeout=500ms、writetimeout=300ms;用datareceived事件+缓冲区拼帧,禁用轮询;波特率等参数须与设备完全一致,并通过握手协议协商版本。

串口传输文件必须分帧加校验,不能直接 Write 整个文件
串口(COM口)带宽低、无内置流控保障、易受干扰,直接把 FileStream 读出来塞进 SerialPort.Write 会导致严重丢包或粘包。实际必须拆成小帧(如 128–512 字节),每帧加起始标记、长度、校验和(推荐 CRC16)、结束标记。接收端严格按帧解析,失败则请求重传或跳过该帧。
常见错误现象:SerialPort.Read 返回字节数远小于预期、数据错位、接收端解出乱码或文件损坏。
- 单帧建议 ≤ 256 字节(兼顾效率与容错)
- 校验必须用
CRC16-CCITT或类似确定性算法,不用简单异或 - 帧头固定为
0x02,帧尾固定为0x03,避免和 payload 冲突 - 发送前清空
SerialPort.DiscardOutBuffer(),接收前调用DiscardInBuffer()
SerialPort 的 ReadTimeout 和 WriteTimeout 必须显式设为非零值
默认超时是 Infinite,一旦线缆松动、设备断电或对方未响应,ReadLine 或 Read 会永久阻塞线程。尤其在 WinForms/WPF 中容易卡死 UI。
合理设置:
-
ReadTimeout = 500(毫秒):足够收完一帧,又不拖慢整体流程 -
WriteTimeout = 300:写入缓冲区通常极快,超时短可早发现硬件异常 - 若需等待应答(如 ACK/NACK),应在应用层实现重试逻辑,而非依赖串口超时
- 务必包裹
try/catch (TimeoutException),并重置连接状态
接收端必须用 SerialPort.DataReceived + 缓冲区拼帧,不能轮询 BytesToRead
DataReceived 是事件驱动、线程安全的入口,而轮询 BytesToRead 极易漏字节(尤其高波特率下)。但事件回调中不能直接处理完整文件逻辑——它可能只触发一次,只来 3 个字节。
实操要点:
- 声明一个
private List<byte> _rxBuffer = new List<byte>()</byte></byte>存未解析数据 - 在
DataReceived回调里用port.Read(...)尽可能读满(循环直到BytesToRead == 0),追加到_rxBuffer - 另起一个后台任务(如
Task.Run)持续扫描_rxBuffer,按帧头/帧尾切分、校验、提取 payload - 校验失败的帧要从缓冲区移除(不是跳过),否则后续帧全偏移
波特率、停止位、奇偶校验必须和设备手册完全一致,且双方预协商好协议版本
哪怕只差一个停止位(如设备要 StopBits.One,你设了 Two),也会导致接收端采样错位,整帧报废。更隐蔽的问题是协议版本不匹配:比如 v1 帧格式无 CRC,v2 加了 CRC16,但没在握手阶段交换版本号,就会一直校验失败。
建议做法:
- 初始化时先发一条明文握手命令(如
"HELLO\r\n"),等对方回"READY:V2"再开始传文件 - 所有参数硬编码为常量,例如:
const int BaudRate = 115200;,避免从 UI 输入框直接赋值 - 使用
SerialPort.GetPortNames()检查端口是否存在,再Open(),别假设"COM3"一定可用 - 关闭端口前务必调用
Close()并置port = null,防止句柄泄漏影响下次打开
IndexOf(0x02) 找开头,却没考虑 payload 里可能恰好出现 0x02。真正在用的方案必须支持字节填充(如遇 0x02 发 0x10 0x02),或改用长度字段+校验双保险。










