Serial Port Polling and Data handling
Asked Answered
C

3

5

I am trying to read from several serial ports from sensors through microcontrollers. Each serial port will receive more than 2000 measurements (each measurement is 7 bytes, all in hex). And they are firing at the same time. Right now I am polling from 4 serial ports. Also, I translate each measurement into String and append it to a Stringbuilder. When I finish receiving data, they will be ouput in to a file. The problem is the CPU consumption is very high, ranging from 80% to 100%.

I went though some articles and put Thread.Sleep(100) at the end. It reduces CPU time when there is no data coming. I also put Thread.Sleep at the end of each polling when the BytesToRead is smaller than 100. It only helps to a certain extent.

Can someone suggest a solution to poll from serial port and handle data that I get? Maybe appending every time I get something causes the problem?

//I use separate threads for all sensors
private void SensorThread(SerialPort mySerialPort, int bytesPerMeasurement, TextBox textBox,     StringBuilder data)
    {
        textBox.BeginInvoke(new MethodInvoker(delegate() { textBox.Text = ""; }));

        int bytesRead;
        int t;
        Byte[] dataIn;

        while (mySerialPort.IsOpen)
        {
            try
            {
                if (mySerialPort.BytesToRead != 0)
                {
                  //trying to read a fix number of bytes
                    bytesRead = 0;
                    t = 0;
                    dataIn = new Byte[bytesPerMeasurement];
                    t = mySerialPort.Read(dataIn, 0, bytesPerMeasurement);
                    bytesRead += t;
                    while (bytesRead != bytesPerMeasurement)
                    {
                        t = mySerialPort.Read(dataIn, bytesRead, bytesPerMeasurement - bytesRead);
                        bytesRead += t;
                    }
                    //convert them into hex string
                    StringBuilder s = new StringBuilder();
                    foreach (Byte b in dataIn) { s.Append(b.ToString("X") + ","); }
                    var line = s.ToString();

                                            var lineString = string.Format("{0}  ----          {2}",
                                                      line,
                                                    mySerialPort.BytesToRead);
                    data.Append(lineString + "\r\n");//append a measurement to a huge Stringbuilder...Need a solution for this.

                    ////use delegate to change UI thread...
                    textBox.BeginInvoke(new MethodInvoker(delegate() { textBox.Text = line; }));

                    if (mySerialPort.BytesToRead <= 100) { Thread.Sleep(100); }
                }
            else{Thread.Sleep(100);}

            }
            catch (Exception ex)
            {
                //MessageBox.Show(ex.ToString());
            }
        }


    }
Cioffi answered 27/2, 2013 at 22:54 Comment(2)
Don't use a loop. Use on received events, if possible, or a multimedia timer, if not.Heliotropin
@DannyVarod What's the multimedia timer for in this case?Cioffi
T
6

this is not a good way to do it, it far better to work on the DataReceived event.

basically with serial ports there's a 3 stage process that works well.

  • Receiving the Data from the serial port
  • Waiting till you have a relevant chunk of data
  • Interpreting the data

so something like

class DataCollector
{
    private readonly Action<List<byte>> _processMeasurement;
    private readonly string _port;
    private SerialPort _serialPort;
    private const int SizeOfMeasurement = 4;
    List<byte> Data = new List<byte>();

    public DataCollector(string port, Action<List<byte>> processMeasurement)
    {
        _processMeasurement = processMeasurement;
        _serialPort = new SerialPort(port);
        _serialPort.DataReceived +=SerialPortDataReceived;
    }

    private void SerialPortDataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        while(_serialPort.BytesToRead > 0)
        {
           var count = _serialPort.BytesToRead;
           var bytes = new byte[count];
           _serialPort.Read(bytes, 0, count);
           AddBytes(bytes);
        }
    }

    private void AddBytes(byte[] bytes)
    {
        Data.AddRange(bytes);
        while(Data.Count > SizeOfMeasurement)            
        {
            var measurementData = Data.GetRange(0, SizeOfMeasurement);
            Data.RemoveRange(0, SizeOfMeasurement);
            if (_processMeasurement != null) _processMeasurement(measurementData);
        }

    }
}

Note: Add Bytes keeps collecting data till you have enough to count as a measurement, or if you get a burst of data, splits it up into seperate measurements.... so you can get 1 byte one time, 2 the next, and 1 more the next, and it will then take that an turn it into a measurement. Most of the time if your micro sends it in a burst, it will come in as one, but sometimes it will get split into 2.

then somewhere you can do

var collector = new DataCollector("COM1", ProcessMeasurement);

and

  private void ProcessMeasurement(List<byte> bytes)
            {
                // this will get called for every measurement, so then
                // put stuff into a text box.... or do whatever
            }
Tag answered 27/2, 2013 at 23:6 Comment(9)
just remember, your computer can handle a lot more data than can come over a serial port going full speed!Tag
also another pro tip, if you are finding you are having to put sleeps in threads because they are using too much CPU, you are doing something wrong / taking the wrong approach ( though sometimes you are forced to do it that way because there is no event you can block on)Tag
So the first step, I will use a ReceingEvent to wait for data coming in. The second step, I will still use mySerialPort.Read(dataIn, 0, bytesPerMeasurement); and put a loop there and wait for a complete chunk of data. The third step, I should interpret data and put them into an array. Then exit the event handler?Cioffi
@Cioffi just put some example code in my answer, you'd probablly want to customize it to what you wantTag
just put a note in about how AddBytes works.... also you may want extra logic if there is a delay of a certain size, then throw away all the current data because its probablly new data rather than continuing old dataTag
also note, very occassionally you will get corrupted bytes over serial, if having corrupted data is not big deal, then cool, but otherwise it is worth sending your data in a packet with a CRCTag
What will happen if one ReceivingEvent is happening some other bytes are coming in? Does it fire another ReceivingEvent or just wait until the first one finishes?Cioffi
its blocking on a separate thread. So it will raise another after its no longer blocked. Which I should mention, as to modify the textbox, you will have to use the dispatcher to invoke it on your main UI thread.Tag
Nice. I think as long as I check bytesToRead and read them all there shouldn't be a buffer overflow. Thank you sir, your code helps a lot.Cioffi
B
2

First of all consider reading Using Stopwatches and Timers in .NET. You can break down any performance issue with this and tell exactly which part of Your code is causing the problem.

Use SerialPort.DataReceived Event to trigger data receiving process.

Separate receiving process and data manipulation process. Store Your data first then process.

Do not edit UI from reading loop.

Blacklist answered 27/2, 2013 at 23:34 Comment(2)
Do you mean just to make the Receiving event as short as possible and then use another thread to interpret data and update UI?Cioffi
@Timtianyang: It's all about Separation of concerns and Modular programming. Keith Nicholas code is a good example of how to structure Your code.Blacklist
B
1

I guess what you should be doing is adding an event handler to process incoming data:

mySerialPort.DataReceived += new SerialDataReceivedEventHandler(mySerialPort_DataReceived);

This eliminates the need to run a separate thread for each serial port you listen to. Also, each DataReceived handler will be called precisely when there is data available and will consume only as much CPU time as is necessary to process the data, then yield to the application/OS.

If that doesn't solve the CPU usage problem, it means you're doing too much processing. But unless you've got some very fast serial ports I can't imagine the code you've got there will pose a problem.

Beore answered 27/2, 2013 at 23:14 Comment(2)
I will give event handler a try. But I know the serial ports will fire data continuously (from microcontrollers), isn't polling a better solution for that?Cioffi
Not really, no. Polling is only a good idea when there's no way for the data source to steal the attention of your thread, or when the overhead of the event handling mechanism is considerable. In your case the overhead is tiny, given the speed of the serial port vs the speed of your CPU.Beore

© 2022 - 2024 — McMap. All rights reserved.