views:

2421

answers:

3

Hi,

I have some SerialPort code that constantly needs to read data from a serial interface (for example COM1). But this seems to be very CPU intensive and if the user moves the window or a lot of data is being displayed to the window (such as the bytes that are received over the serial line) then communication gets messed up.

Considering the following code:

void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{

byte[] buffer = new byte[port.ReadBufferSize];

var count = 0;

try
{
    count = port.Read(buffer, 0, buffer.Length);
}
catch (Exception ex)
{
    Console.Write(ex.ToString());
}

if (count == 0)
    return;

//Pass the data to the IDataCollector, if response != null an entire frame has been received


var response = collector.Collect(buffer.GetSubByteArray(0, count));

if (response != null)
{
    this.OnDataReceived(response);
}

The code needs to be collected as the stream of data is constant and the data has to be analyzed for (frames/packets).

    port = new SerialPort();

    //Port configuration code here...

    this.collector = dataCollector;

    //Event handlers
    port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);
    port.Open();

If there is no user interaction and nothing being added to the window, this works fine but as soon as there is interaction communication really gets messed up. Timeouts occur etc....

For example, this messes everything up:

Dispatcher.BeginInvoke(new Action(() =>
{
  var builder = new StringBuilder();
  foreach (var r in data)
  {
      builder.AppendFormat("0x{0:X} ", r);
  }


  builder.Append("\n\n");

  txtHexDump.AppendText(builder.ToString());

  txtHexDump.ScrollToEnd();


}),System.Windows.Threading.DispatcherPriority.ContextIdle);
});

But even simple calls to log4net cause problems.

Are there any best practices to optimize SerialPort communication or can someone tell me what I'm doing wrong...

Update:

In case the above didn't make much sence. I made a very simple (and stupid) little example:

class Program
{
    static void Main(string[] args)
    {
        var server = new BackgroundWorker();
        server.DoWork += new DoWorkEventHandler(server_DoWork);
        server.RunWorkerAsync();

        var port = new SerialPort();
        port.PortName = "COM2";
        port.Open();
        string input = "";

        Console.WriteLine("Client on COM2: {0}", Thread.CurrentThread.ManagedThreadId);
        while (input != "/quit")
        {
            input = Console.ReadLine();
            if (input != "/quit")
            {
                var data = ASCIIEncoding.ASCII.GetBytes(input);
                port.Write(data, 0, data.Length);
            }
        }

        port.Close();
        port.Dispose();
    }

    static void server_DoWork(object sender, DoWorkEventArgs e)
    {
        Console.WriteLine("Listening on COM1: {0}", Thread.CurrentThread.ManagedThreadId);
        var port = new SerialPort();
        port.PortName = "COM1";
        port.Open();

        port.ReceivedBytesThreshold = 15;
        port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);
    }

    static void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        var port = (SerialPort)sender;
        int count = 0;
        byte[] buffer = new byte[port.ReadBufferSize];
        count = ((SerialPort)sender).Read(buffer, 0, buffer.Length);

        string echo = ASCIIEncoding.ASCII.GetString(buffer,0,count);
        Console.WriteLine("-->{1} {0}", echo, Thread.CurrentThread.ManagedThreadId);
    }
}

The result might look like this:

Listening on COM1: 6 Client on COM2: 10 This is some sample data that I send ---> 6 This is some sample data that I send

So reading the data from the port happens on the main thread....

Might this be part of what's causing my problems?

+2  A: 

You should rewrite port_DataReceived procedure to read data until port.BytesToRead is greater then zero, like this:

private void port_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
  var port = (SerialPort)sender;
  while (port.BytesToRead > 0)
  {
   int byte_count = port.BytesToRead;
   byte[] buffer = new byte[byte_count];

   int read_count = port.Read(buffer, 0, byte_count);

            // PROCESS DATA HERE

  }
}

Also I would recommend you to just insert data in queue list in procedure port_DataReceived, and perform data processing in separate thread.

Gordon Freeman
If I do that, at some point it just waits forever....
TimothyP
Waits where, inside port_DataReceived() function or port_DataReceived() doesn't get called ?There are few important thing that I didn't write in original post:- port_DataReceived is call back function and it executes in it's own thread. It is called whenever there is some data written on COM port.BUT call to port_DataReceived function can be misfired in case when new data are written to COM port and you are still did not return from previous call i.e. processing data inside function, that is way it is important to check port.BytesToRead property before you leave function.
Gordon Freeman
+2  A: 

Your last conclusion, that the event runs on the Main thread, may not be true for Windows App. Don't test this in a Console.

The proper way to tune this is:

  • set a large enough buffer, although the minimum 4096 is usually Ok

  • set the ReceivedBytesThreshold as high as tolerable (and do it before the Open())

  • do as little as possible in the Received event, pass the data to a Queue or MemoryStream if you need more time

Henk Holterman
+1  A: 

The classic solution is to have a FIFO buffer. Ensure that the size of the FIFO is large enough to handle any critical case where there is a lot of input and the processor block is being taken up.

You could even have a 2-buffer system:

--->|Reader|-->FIFO-->|Processor|--->FIFO2--->|Displayer|
Paul Nathan