如何正确地从.NET中的SerialPort读取

时间:2014-09-10 01:01:44

标签: c# io serial-port

我不好意思要问这样一个问题,但我很难搞清楚如何通过.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错误,即我处理每条消息的时间太长而且缓冲区溢出。

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

3 个答案:

答案 0 :(得分:8)

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

使用port.BaseStream.ReadAsync在您想要的线程上获取事件驱动的数据。我在http://www.sparxeng.com/blog/software/must-use-net-system-io-ports-serialport

写了更多关于这种方法的文章

答案 1 :(得分:4)

要正确处理串口数据,您需要做一些事情。

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

其次,您无法保证在收到数据时只收到一个数据包或完整的数据包 - 它可能会以小碎片的形式发送给您。

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

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

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

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

答案 2 :(得分:0)

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