You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何等待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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 20:15:06