Search code examples
c#serial-portpolling

Serial Port Polling and Data handling


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());
            }
        }


    }

Solution

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