【C#】设备回帧太快、数据没收完整就被读取,导致帧被拆、混、丢,很常见,尤其在高频通信设备,解决方案

发布于:2025-04-01 ⋅ 阅读:(20) ⋅ 点赞:(0)

像这种,回帧回得太快了,怎么处理?

 private void  Sp_DataReceived(object sender, SerialDataReceivedEventArgs e) {
     this.Invoke((EventHandler)(delegate
     {

         try
         {
             Byte[] ReceivedData = new byte[serialPort.BytesToRead];
             serialPort.Read(ReceivedData, 0, ReceivedData.Length);
             string RecvDataText = "";
             for (int i = 0; i <= ReceivedData.Length - 1; i++)
             {
                 RecvDataText += (ReceivedData[i].ToString("X2"));
             }
             Log($"回帧 : { BitConverter.ToString(ReceivedData).Replace("-", " ")}" );
             // QueryHead.endReceiveData = false;
             return;
         }
         catch (Exception)
         {
             ;
         }
     }));
 }

设备回帧太快、数据没收完整就被读取,导致帧被拆、混、丢,很常见,尤其在高频通信设备中


✅ 问题根源

这段代码:

Byte[] ReceivedData = new byte[serialPort.BytesToRead];
serialPort.Read(ReceivedData, 0, ReceivedData.Length);

💥 会在 每次串口收到部分数据时就立即触发 DataReceived
而不是“整帧都到了才触发”。


🎯 正确思路 ——【接收缓冲 + 延迟判断尾部】

使用一个缓存池接收数据,每次触发 DataReceived 事件时只追加数据不立刻处理
然后设置一个延迟定时器Task.Delay(),如果一段时间内没有再收到新数据,就处理这个缓冲。


✅ 推荐改法(思路 + 代码)

✅ 步骤:

  1. 建一个全局缓冲区 List<byte> _recvBuffer
  2. DataReceived 中追加数据到缓冲区
  3. 启动或重置一个延迟处理器(50ms内不再触发,就认为一帧收完)

✅ 示例代码实现(完整)

private List<byte> _recvBuffer = new List<byte>();
private CancellationTokenSource _recvDelayToken = null;

private void Sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    int len = serialPort.BytesToRead;
    if (len == 0) return;

    byte[] tempBuf = new byte[len];
    serialPort.Read(tempBuf, 0, len);

    lock (_recvBuffer)
    {
        _recvBuffer.AddRange(tempBuf);
    }

    // 重启延迟处理
    _recvDelayToken?.Cancel(); // 如果已有任务,取消它
    _recvDelayToken = new CancellationTokenSource();
    var token = _recvDelayToken.Token;

    Task.Delay(50, token).ContinueWith(t =>
    {
        if (!t.IsCanceled)
        {
            ProcessFullFrame();
        }
    }, token);
}

✅ 接收完整帧处理方法:

private void ProcessFullFrame()
{
    List<byte> frame;
    lock (_recvBuffer)
    {
        frame = new List<byte>(_recvBuffer);
        _recvBuffer.Clear(); // 清空缓存
    }

    if (frame.Count == 0) return;

    string hex = BitConverter.ToString(frame.ToArray()).Replace("-", " ");
    Invoke(new Action(() => {
        Log($"  回帧完成: {hex}");
    }));

    // TODO: 你可以在这里做校验、解帧等逻辑
}

✅ 优势:

问题 原代码 改进方案
回帧不完整 BytesToRead 不保证收全 缓冲 + 延时处理
多帧合并粘包 多次触发拆掉数据 缓冲中可处理多帧识别
UI卡顿 Invoke 主线程频繁触发 延时合并触发,提高效率

🧪 扩展

: 想调试是否真的收到了完整帧?还可以加 CRC 校验 & 帧头定位,一步步拆多帧组合。


💡建议: 还可以在日志中加上时间戳,方便定位帧间时间问题


🔥 一句话总结:

串口 DataReceived 触发是按“字节”来的,不按“帧”来!必须加缓冲 + 延迟,你才能完整抓住一帧!