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

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

cpp 复制代码
 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)
         {
             ;
         }
     }));
 }

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


✅ 问题根源

这段代码:

csharp 复制代码
Byte[] ReceivedData = new byte[serialPort.BytesToRead];
serialPort.Read(ReceivedData, 0, ReceivedData.Length);

💥 会在 每次串口收到部分数据时就立即触发 DataReceived

而不是"整帧都到了才触发"。


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

使用一个缓存池接收数据,每次触发 DataReceived 事件时只追加数据不立刻处理

然后设置一个延迟定时器Task.Delay(),如果一段时间内没有再收到新数据,就处理这个缓冲。


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

✅ 步骤:

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

✅ 示例代码实现(完整)

csharp 复制代码
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);
}

✅ 接收完整帧处理方法:

csharp 复制代码
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 触发是按"字节"来的,不按"帧"来!必须加缓冲 + 延迟,你才能完整抓住一帧!


相关推荐
一个public的class1 小时前
什么是 Java 泛型
java·开发语言·后端
士别三日&&当刮目相看1 小时前
JAVA学习*Object类
java·开发语言·学习
invincible_Tang1 小时前
R格式 (15届B) 高精度
开发语言·算法·r语言
一只小松许️1 小时前
Rust闭包详解
开发语言·rust
独好紫罗兰2 小时前
洛谷题单2-P5715 【深基3.例8】三位数排序-python-流程图重构
开发语言·python·算法
阳光_你好2 小时前
详细说明Qt 中共享内存方法: QSharedMemory 对象
开发语言·数据库·qt
鹿屿二向箔2 小时前
阀门流量控制系统MATLAB仿真PID
开发语言·matlab
jiet_h3 小时前
深入解析Kapt —— Kotlin Annotation Processing Tool 技术博客
android·开发语言·kotlin
anda01093 小时前
11-leveldb compact原理和性能优化
java·开发语言·性能优化
tRNA做科研3 小时前
通过Bioconductor/BiocManager安装生物r包详解(问题汇总)
开发语言·r语言·生物信息学·bioconductor·biocmanager