简体   繁体   中英

How to Properly Read from a SerialPort in .NET

I'm embarrassed to have to ask such a question, but I'm having a rough time figuring out how to reliably read data over a serial port with the .NET SerialPort class.

My first approach:

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
    }
}

So this has an obvious problem; I am calling DiscardInBuffer , so any data which came in after the event was fired is discarded, ie, I'm dropping data.

Now, the documentation for SerialPort.Read() does not even state if it advances the current position of the stream (really?), but I have found other sources which claim that it does (which makes sense). However, if I do not call DiscardInBuffer I eventually get an RXOver error, ie, I'm taking too long to process each message and the buffer is overflowing.

So... I'm really not a fan of this interface. If I have to process each buffer on a separate thread I'll do that, but that comes with its own set of problems, and I'm hoping that I am missing something as I don't have much experience with this interface.

Jason makes some good points about reducing UI access from the worker thread, but an even better option is to not receive the data on a worker thread in the first place.

Use port.BaseStream.ReadAsync to get your data, event-driven, on the thread where you want it. I've written more about this approach at http://www.sparxeng.com/blog/software/must-use-net-system-io-ports-serialport

To correctly handle data from a serial port you need to do a couple of things.

First, don't handle the data in your receive event. Copy the data somewhere else and do any processing on another thread. (This is true of most events - it is a bad idea to do any time-consuming processing in an event handler as it delays the caller and can introduce problems. You also need to be careful as your event is raised on a different thread to your main application)

Secondly, you can't guarantee that you will receive exactly one packet, or a complete packet when you receive data - it may come to you in small fragments.

So the upshot of this is that you should create your own buffer (big enough to hold several packets), and when you receive data, append it to your buffer. Then in another thread you can process the buffer, looking to see if you can decode a packet from it and then consume that data. You may have to skip the end of a partial packet before you find the start of a valid one. If you don't have enough data to build a full packet, then you may need to wait for a bit until more data arrives.

You shouldn't call Discard on the port - just read the data and consume it. Each time you are called, there will be another fragment of data to process. It does not remember the data from previous calls - each time your event is called, it is given a small burst of data that has arrived since you were last called. Just use the data you've been given and return.

As a last suggestion: Don't change any settings for the port unless you specifically need to for it to operate properly. So you must set the baud rate, data/stop bits and parity, but avoid trying to change properties like the Rts/Dtr, buffer sizes and read thresholds unless you have a good reason to think you know better than the author of the serial port. Most serial devices work in an industry standard manner these days, and changing these low-level options is very likely to cause trouble unless you're talking to some unusual equipment and you intimately know the hardware.

In particular setting the ReceivedBytesThreshold to 1 is probably what is causing the failure you've mentioned, because you are asking the serial port to call your event handler with only one byte at a time, 57,600 times per second - giving your event handler only 0.017 milliseconds to process each byte before you'll start to get re-entrant calls.

DiscardInBuffer is typically only used immediately after opening a serial port. It is not required for standard serial port communication so you should not have it in your dataReceived handler.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM