接收到的串口数据处理太慢
本文关键字:数据处理 串口 | 更新日期: 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)
{
....
}