如何等待serial port完成数据接收,将设备数据传至PC并在data grid view展示
串口完整数据接收实现方案
你的现有代码核心缺陷是:执行byte[] byteToReceiv = new byte[serialPort.BytesToRead];时,串口缓冲区仅存储了当前已到达的部分字节,设备发送的完整数据还在逐字节传输过程中,因此你读到的永远是不完整的片段,自然无法正常运行。
推荐实现方式(基于事件驱动,不阻塞UI)
串口提供DataReceived事件,会在有新数据到达缓冲区时触发,配合全局接收缓存+帧规则判断即可稳定拿到完整数据,根据你的设备协议分为两种场景:
场景1:设备数据有明确结束标识(比如帧尾是\r\n、特定字节序列)
// 串口初始化时添加以下配置 serialPort.ReadTimeout = 200; serialPort.DataReceived += SerialPort_DataReceived; // 全局接收缓存,存储未处理的收到的字节 private List<byte> _receiveBuffer = new List<byte>(); // 替换为你的设备实际帧结束标识,比如以下是0x0D、0x0A作为帧尾 private readonly byte[] _frameEndMarker = new byte[] { 0x0D, 0x0A };
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) { // 读取当前缓冲区所有新到的字节,加入全局缓存 int bytesToRead = serialPort.BytesToRead; byte[] tempBuffer = new byte[bytesToRead]; serialPort.Read(tempBuffer, 0, bytesToRead); _receiveBuffer.AddRange(tempBuffer); // 循环检查缓存中是否存在完整帧 while (HasCompleteFrame(_receiveBuffer, out int endIndex)) { // 提取完整帧 byte[] completeFrame = _receiveBuffer.Take(endIndex + _frameEndMarker.Length).ToArray(); // 移除已处理的帧数据 _receiveBuffer.RemoveRange(0, endIndex + _frameEndMarker.Length); // 跨线程更新UI,DataReceived事件在非UI线程触发,必须用Invoke this.Invoke(new Action(() => { // 如需显示到DataGridView,此处替换为往绑定的DataTable添加行的逻辑即可 textBox1.Text = displayValue(completeFrame); })); } } // 检查缓存中是否存在完整帧的辅助方法 private bool HasCompleteFrame(List<byte> buffer, out int endMarkerStartIndex) { endMarkerStartIndex = -1; if (buffer.Count < _frameEndMarker.Length) return false; for (int i = 0; i <= buffer.Count - _frameEndMarker.Length; i++) { bool isMatch = true; for (int j = 0; j < _frameEndMarker.Length; j++) { if (buffer[i + j] != _frameEndMarker[j]) { isMatch = false; break; } } if (isMatch) { endMarkerStartIndex = i; return true; } } return false; }
场景2:设备数据是固定长度帧
// 替换为你的设备实际单帧字节长度 private const int FixedFrameLength = 32; private List<byte> _receiveBuffer = new List<byte>(); private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) { int bytesToRead = serialPort.BytesToRead; byte[] tempBuffer = new byte[bytesToRead]; serialPort.Read(tempBuffer, 0, bytesToRead); _receiveBuffer.AddRange(tempBuffer); // 缓存中字节数大于等于单帧长度时就处理 while (_receiveBuffer.Count >= FixedFrameLength) { byte[] completeFrame = _receiveBuffer.Take(FixedFrameLength).ToArray(); _receiveBuffer.RemoveRange(0, FixedFrameLength); this.Invoke(new Action(() => { textBox1.Text = displayValue(completeFrame); })); } }
同步读取方案(仅适用于必须同步等待结果的场景)
如果你需要在单方法内同步等待完整数据,不要用固定Thread.Sleep,要基于实际接收字节数判断,同时加超时机制避免卡死:
// 替换为你需要的单帧长度,或者根据帧结束标识判断 int expectLength = 32; // 超时时间设为3秒,可根据实际传输速率调整 DateTime timeoutTime = DateTime.Now.AddSeconds(3); // 循环等待直到收到足够字节或超时 while (serialPort.BytesToRead < expectLength && DateTime.Now < timeoutTime) { Thread.Sleep(10); } if (serialPort.BytesToRead >= expectLength) { byte[] byteToReceiv = new byte[expectLength]; int offset = 0; int count = expectLength; while (count > 0) { var readCount = serialPort.Read(byteToReceiv, offset, count); offset += readCount; count -= readCount; } textBox1.Text = displayValue(byteToReceiv); } else { textBox1.Text = "数据接收超时"; }
注意事项
- 禁止用固定时长休眠的方式等待数据,不同工况下传输延迟不稳定,要么收不全要么浪费性能
- 跨线程操作UI控件必须使用
Invoke/BeginInvoke,否则会抛出跨线程访问异常 - 建议根据设备协议添加校验逻辑(比如CRC校验、和校验),进一步确保收到的数据准确性
内容的提问来源于stack exchange,提问作者Reza Heydari
相关产品推荐
相关产品推荐

