接收到的串口数据处理太慢

本文关键字:数据处理 串口 | 更新日期: 2023-09-27 18:18:48

我正在以115200的波特率从arduino读取数据。数据以字符串的形式出现在自己的行中,格式为:<ID,Name/Data> .

我认为我的代码的问题是它没有足够快地处理传入的数据,传入的数据被迫等待旧数据被处理。

输入的字符串被分成三个独立的类别(ID, Name, Data),并添加到一个名为dtFromGrid的数据表中,该数据表与dataGridView1绑定。

是否有任何错误或建议如何提高我的代码性能?单独的线程处理函数会比BeginInvoke更好吗?

serialPort1.DataReceived += new SerialDataReceivedEventHandler(serialPort1_DataReceived);
    private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
    {
        string inData = serialPort1.ReadLine();
        if (PauseButton.Text == "Pause" && inData.StartsWith("<"))
        {
            try
            {
                this.BeginInvoke(new SetGridDeleg(DoUpdate), new object[] {inData});
            }
            catch
            {
            }
        }
    }
    private void DoUpdate(string inData)   //inData passed in so that Serial port read only once
    {
        if (dtFromGrid == null)
        {
            dtFromGrid = new DataTable();
            dtFromGrid.Columns.Add("Time", typeof(String));
            dtFromGrid.Columns.Add("ID", typeof(String));
            dtFromGrid.Columns.Add("Name", typeof(String));
            dtFromGrid.Columns.Add("Data", typeof(String));
        }
        DataRow dr = dtFromGrid.NewRow();
        TimeSpan ts = stopWatch.Elapsed;
        dr["Time"] = String.Format("{0:00}:{1:00}:{2:00}.{3:000}",
        ts.Hours, ts.Minutes, ts.Seconds,
        ts.Milliseconds);
        dr["ID"] = inData.Split(new char[] { '<', ',' })[1];
        dr["Name"] = inData.Split(new char[] { ',', '/' })[1];
        dr["Data"] = inData.Split(new char[] { '/', '>' })[1];
        dtFromGrid.Rows.InsertAt(dr, 0);
        //Replace old data with new data if ID's are the same to showo list of only newest data per each ID
        if (NewestButton.Text == "Chronological")
        {
            for (int i = 1; i < dataGridView1.Rows.Count; i++)
            {
                if (dtFromGrid.Rows[i].ItemArray[1].ToString() == dtFromGrid.Rows[0].ItemArray[1].ToString())
                {
                    dtFromGrid.Rows[i].Delete();
                    break;
                }
            }
        }
        //Keep a maximum of 50 rows of data
        if (dtFromGrid.Rows.Count == 51)
        {
            dtFromGrid.Rows[50].Delete();
        }
        dtFromGrid.AcceptChanges();
        dataGridView1.DataSource = dtFromGrid;
        //keep focus of dataGridView on top row
        dataGridView1.CurrentCell = dataGridView1.Rows[0].Cells[0];
        // add newest row to a logfile if the user has set one
        if (logFile != "")
        {
            using (StreamWriter sw = File.AppendText(logFile))
            {
                DataRow row = dtFromGrid.Rows[0];
                object[] array = row.ItemArray;
                int col = 0;
                for (col = 0; col < array.Length - 1; col++)
                {
                    sw.Write(array[col].ToString() + "'t|'t");
                }
                sw.Write(array[col].ToString());
                sw.WriteLine();
                sw.Close();
            }
        }
    }

我现在按照建议使用一个单独的线程,但我在调用该线程内部有错误。我随机得到多个错误,但最常见的是"索引超出范围"。我的调用代码如下: this.Invoke((MethodInvoker) delegate { dtFromGrid.AcceptChanges(); dataGridView1.DataSource = dtFromGrid; dataGridView1.CurrentCell = dataGridView1.Rows[0].Cells[0]; });

接收到的串口数据处理太慢

将数据存储在队列中,并将工作卸载给次要线程。一般来说,只有当您能够以传入的速度处理数据时,这才有效。否则,当您落后时,队列的大小将不断增长。

首先,从Queue<T>周围的包装器开始,它将允许一个线程以线程安全的方式向队列写入,另一个线程从队列中读取。另外,允许读取线程阻塞等待数据。

public class ThreadedQueue<T>
{
    private readonly Queue<T> _queue = new Queue<T>();
    private readonly ManualResetEvent _notEmptyEvt = new ManualResetEvent(false);
    public WaitHandle WaitHandle { get { return _notEmptyEvt; } }
    public void Enqueue(T obj)
    {
        lock (_queue)
        {
            _queue.Enqueue(obj);
            _notEmptyEvt.Set();
        }
    }
    public T Dequeue()
    {
        _notEmptyEvt.WaitOne(Timeout.Infinite);
        lock (_queue)
        {
            var result = _queue.Dequeue();
            if (_queue.Count == 0)
                _notEmptyEvt.Reset();
            return result;
        }
    }
}
在串行端口处理程序中,将数据写入队列:
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
    string inData = serialPort1.ReadLine();
    if (PauseButton.Text == "Pause" && inData.StartsWith("<"))
    {
        _queue.Enqueue(inData);
    }
}

在次要线程中,从队列中读取并执行对GUI线程的调用:

private void ThreadProc()
{
    while (true)
    {
        string inData = _queue.Dequeue();
        this.Invoke(new SetGridDeleg(DoUpdate), new object[] {inData});
    }
}

启动二级线程:

Thread th = new Thread(ThreadProc);
th.IsBackground = true;
th.Start();

当然你需要创建一个队列的实例:

ThreadedQueue<string> _queue = new ThreadedQueue<string>();

我通常设计一个SerialService类来管理SerialPort。下面是SerialService类的一个简单版本。

SerialService类的作用是尽可能快地读取串行缓冲区。这将清除缓冲区并防止任何串行端口错误。然后将原始数据传递给解析器。

性能的技巧在解析器中。YourParser还应该能够快速地将原始数据格式化为您期望的字符串。一旦你的数据被解析,你可以使用回调或事件。使用回调或事件,解析器将继续解析新到达的数据。YourParse现在是一个可测试的类。

一旦你从解析器的回调中获得了良好的数据,使用BeginInvoke将数据发送到主线程,然后你的ui可以显示它。

如果你不在主UI线程中,而你试图从另一个线程更新UI,你将遇到交叉标题问题。

好运。

class Program
{
    private static YourDataParser _parser;
    static void Main(string[] args)
    {
        _parser = new YourDataParser();
        var serial = new SerialService("COM1");
        serial.DataReceived += serial_DataReceived;
    }
    static void serial_DataReceived(object sender, DataReceivedEventArgs e)
    {
        _parser.HandleTheData(e.Data, good =>
        {
            // here is your good data
            // This is not the main thread invoke your UI from here with the good data
            // Use BeginInvoke to invoke the main thread
        });
    }
}
public class YourDataParser
{
    private List<byte> _buffer = new List<byte>(); 
    public void HandleTheData(byte[] rawdata, Action<string> goodData)
    {
        _buffer.AddRange(rawdata);
        foreach (var b in _buffer)
        {
            var thechar = (char) b;
            // handle your raw data... like look for the character '<'
            // or look for the end of line this would be CR (0x0D) LF (0x0A) 
            // you can reference the ASCII table for the characters byte values
        }
        // and return the good data 
        var data = "your good data after parsing it";
        goodData(data);
    }
}
public class DataReceivedEventArgs : EventArgs
{
    public DataReceivedEventArgs(byte[] data)
    {
        Data = data;
    }
    public byte[] Data { get; private set; }
}
class SerialService
{
    public event EventHandler<DataReceivedEventArgs> DataReceived;
    private SerialPort _port;
    public SerialService(string comm)
    {
        _port = new SerialPort(comm)
        {
            // YOUR OTHER SETTINGS HERE...
            ReceivedBytesThreshold = 1 // I think is better to increase this number if you know the minimum number of bytes that will arrive at the serial port's buffer
        };
        // Note that the ReceivedBytesThreshold is set to 1. 
        // That means that the port_DataReceived event will fire with a minimun of 1 byte in the serial buffer
        _port.DataReceived += port_DataReceived;
    }
    void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        if (e.EventType != SerialData.Chars) return;
        while (_port.IsOpen & _port.BytesToRead != 0)
        {
            // important to get all the bytes off the buffer
            var size = _port.BytesToRead; 
            var buffer = new byte[size];
            var sizeRead = _port.Read(buffer, 0, size);
            OnDataReceived(buffer);
        }
    }
    protected virtual void OnDataReceived(byte[] data)
    {
        var ev = DataReceived;
        if (ev != null) ev(this, new DataReceivedEventArgs(data));
    }
}

正如您所说,您的代码正在减慢数据接收速度。您可以通过将数据排队到队列列表中来解决这个问题,并且后台进程将逐个处理这个列表。另一种方法是在接收每批数据时创建一个新线程。示例(第二种方法)

private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
    {
       string inData = serialPort1.ReadLine();
       System.Threading.Thread T = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(ProcessData));
       T.Start(inData);
}
public void ProcessData(Object data)
{
....
}