【问题标题】:How to Properly Read from a SerialPort in .NET如何在 .NET 中正确读取串行端口
【发布时间】:2014-11-03 12:49:12
【问题描述】:

我很尴尬不得不问这样一个问题,但我很难弄清楚如何使用 .NET SerialPort 类通过串行端口可靠地读取数据。

我的第一种方法:

static void Main(string[] args)
{
    _port = new SerialPort
    {
        PortName = portName,
        BaudRate = 57600,
        DataBits = 8,
        Parity = Parity.None,
        StopBits = StopBits.One,
        RtsEnable = true,
        DtrEnable = false,
        WriteBufferSize = 2048,
        ReadBufferSize = 2048,
        ReceivedBytesThreshold = 1,
        ReadTimeout = 5000,
    };    

    _port.DataReceived += _port_DataReceived;
    _port.Open();

    // whatever
}

private void _port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{           
    var buf = new byte[_port.BytesToRead];
    var bytesRead = _port.Read(buf, 0, buf.Length);

    _port.DiscardInBuffer();
    for (int i = 0; i < bytesRead; ++i)
    {
        // read each byte, look for start/end values, 
        // signal complete packet event if/when end is found
    }
}

所以这有一个明显的问题;我正在调用DiscardInBuffer,因此在事件触发后传入的任何数据都将被丢弃,即我正在删除数据。

现在,the documentation for SerialPort.Read() 甚至没有说明它是否推进了流的当前位置(真的吗?),但我发现其他来源声称它确实如此(这是有道理的)。但是,如果我不调用DiscardInBuffer,我最终会收到RXOver 错误,也就是说,我处理每条消息的时间太长并且缓冲区溢出。

所以...我真的不喜欢这个界面。如果我必须在单独的线程上处理每个缓冲区,我会这样做,但这会带来一系列问题,我希望我错过了一些东西,因为我对这个接口没有太多经验。

【问题讨论】:

  • 听起来您需要将阅读与处理分离。如果你删除所有的处理,你会得到一个干净的阅读吗? TPL DataFlow 浮现在脑海中。
  • 这个问题非常有问题。 DisardInBuffer() 实际上丢弃任何东西的几率非常低。您刚刚使用 Read() 调用清空了接收缓冲区。首先打开握手,这是防止超支的主要防御措施。如果这不起作用(DtrEnable = false 是 非常 奇数)然后降低波特率。
  • @HansPassant:嗯,我知道数据正在被删除,但你可能是对的。我盲目地遵循设备制造商的指示。我会调查的。
  • 文档没有说明Read() 是否推进位置,因为串行端口不可搜索——它甚至没有 位置。 (缓冲区也是 FIFO,不提供随机访问)
  • @BenVoigt:当然,但底层流肯定是。当然,它从流中缓冲,因此并不简单。我只是不明白没有指定我是否真的从流中删除字节。你真的需要知道这一点。

标签: c# io serial-port


【解决方案1】:

Jason 提出了一些关于减少来自工作线程的 UI 访问的好观点,但更好的选择是首先不在工作线程上接收数据。

使用port.BaseStream.ReadAsync 在您想要的线程上获取事件驱动的数据。我在http://www.sparxeng.com/blog/software/must-use-net-system-io-ports-serialport 写了更多关于这种方法的文章

【讨论】:

  • 哈,我自己才找到那篇文章。我试试看,谢谢。
  • @Ben:你刚刚救了我的命! BaseStream Assync-Read 就像一个魅力!
  • @MeisterSchnitzel:很高兴能够提供帮助。
【解决方案2】:

要正确处理来自串行端口的数据,您需要做几件事。

首先,不要在接收事件中处理数据。将数据复制到其他地方并在另一个线程上进行任何处理。 (大多数事件都是如此 - 在事件处理程序中进行任何耗时的处理都是一个坏主意,因为它会延迟调用者并可能引入问题。您还需要小心,因为您的事件是在不同的线程上引发的您的主要应用程序)

其次,您不能保证在接收数据时会收到一个数据包或一个完整数据包 - 它可能会以小片段的形式出现。

因此,这样做的结果是您应该创建自己的缓冲区(大到足以容纳多个数据包),并且当您接收到数据时,将其附加到缓冲区中。然后在另一个线程中,您可以处理缓冲区,查看是否可以从中解码数据包,然后使用该数据。在找到有效数据包的开头之前,您可能必须跳过部分数据包的结尾。如果您没有足够的数据来构建完整的数据包,那么您可能需要等待一段时间,直到更多数据到达。

您不应该在端口上调用 Discard - 只需读取数据并使用它。每次调用时,都会有另一个数据片段需要处理。它不记得以前调用的数据——每次调用你的事件时,它都会收到自上次调用以来到达的一小部分数据。只需使用您获得的数据并返回即可。

最后的建议是:除非您特别需要使其正常运行,否则不要更改端口的任何设置。因此,您必须设置波特率、数据/停止位和奇偶校验,但避免尝试更改 Rts/Dtr、缓冲区大小和读取阈值等属性,除非您有充分的理由认为自己比串行端口的作者更了解.如今,大多数串行设备都以行业标准方式工作,更改这些低级选项很可能会导致麻烦,除非您正在与一些不寻常的设备交谈并且您非常了解硬件。

特别是将 ReceivedBytesThreshold 设置为 1 可能是导致您提到的失败的原因,因为您要求串行端口一次只调用一个字节的事件处理程序,每秒 57,600 次 - 给您的事件处理程序只需 0.017 毫秒来处理每个字节,然后您将开始获得重入调用。

【讨论】:

  • 握手应该始终明确配置;不要依赖之前的应用程序,让它处于您需要的状态。
  • 谢谢。昨晚我确实有机会测试了一下,就像我想的那样,我正在拖延处理。我可以在另一个线程上处理数据没问题,但接收字节阈值不是可选的。就像你说的,我可能不会每次都得到完整的数据包。此外,数据包的长度是可变的。我的代码处理了这个问题,但这意味着我必须在每次通过一个字节时检查数据。另外,阈值 1 并不能保证每次调用事件时只读取一个字节,它只是意味着只要接收到一个字节就会调用它
  • 哦,这些设置确实来自设备制造商。但是,丢弃输入缓冲区的建议也可以,所以我现在有点质疑他们的文档。
  • 端口将在超过其阈值、缓冲区已满或传输失败时传递数据,因此您可以正常接收单字节数据包,而无需尝试“强制”串行端口使用阈值。设备制造商可能会列出甚至建议许多不需要的详细低级设置 - 我的方法是从简单开始,仅在(如果)您发现默认选项无法正常工作时增加复杂性。
  • 从 SerialPort 对象接收数据时,在辅助线程上引发 DataReceived 事件。 msdn.microsoft.com/en-us/library/…
【解决方案3】:

DiscardInBuffer 通常仅在打开串行端口后立即使用。标准串行端口通信不需要它,因此您不应在 dataReceived 处理程序中使用它。

【讨论】:

  • 我明白了,但它并没有回答我的问题。我还没有时间研究@HansPassants 的建议,但是要么我做错了(正如他所建议的那样),要么我真的花了太长时间来处理一个数据包。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-09-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-10
  • 1970-01-01
  • 2012-05-10
相关资源
最近更新 更多