初学上位机软件编写的工程师,在利用 JS 或者 C# 编写串口读取事件时,常写出类似的代码:每个 `onDataReceived` 触发时就把数据打印出来。这在高频通信时会引发严重的**断帧(Split Frame)与粘包(Packet Aggregation)** Bug。
我们要明白,串口(UART)在物理层传输的是流式数据(Streaming Data),在它的物理导线上并没有'包'的边界概念,有的只是一串串紧随而至的起始终止逻辑电平。浏览器底层读取线程也是以固定的事件片从系统 USB FIFO 缓冲区把数据一次性读出来渲染:
- 断帧:下位机明明发的是一帧完整 20 字节的 Modbus 报文,但上位机可能在收到前 8 个字节时就强行触发了读取渲染事件,导致接收区被截断。后 12 个字节被作为新的一行渲染,这直接导致校验失败、波形绘制错乱。
- 粘包:如果下位机发包速度极快(如每 2 毫秒发一个包),上位机处理不及时,两个逻辑包就会在系统硬件 Buffer 里积压并合并在一起。读取时一次性吐出 40 个字节,导致数据粘连在一起。
解决这一问题的标准手段是:**引入基于时间分包(Frame Timeout)的防抖组装机制**。在接收到一个字节后,启动一个极小的毫秒定时器(如 5-10ms),只要后续持续有字节进来就不断刷新该定时器。一旦这个空闲定时器超时,代表物理线路上出现了一个完整的发包间歇空闲,此时将接收到的数组作为一个完整的帧合并渲染并清空缓存,即可从根本上根除断帧断包隐患。