像这种,回帧回得太快了,怎么处理?
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()
,如果一段时间内没有再收到新数据,就处理这个缓冲。
✅ 推荐改法(思路 + 代码)
✅ 步骤:
- 建一个全局缓冲区
List<byte> _recvBuffer
- 在
DataReceived
中追加数据到缓冲区 - 启动或重置一个延迟处理器(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
触发是按"字节"来的,不按"帧"来!必须加缓冲 + 延迟,你才能完整抓住一帧!