通過把SerialPort進行封裝,以多線程和緩存的方式處理串口的發送和接收動作。
一、現象
不管如何設置ReceivedBytesThreshold的值,DataReceived接收到的數據都是比較混亂,不是一個完整的應答數據。
二、原因
1、上位機下發的命令比較密集,以200ms周期發送實時狀態輪詢命令。
2、在狀態實時輪詢命令中間有操作命令插入。
2、不同的命令,接收的應答格式也不同。
三、分析
不同的命令有不同的應答數據,但是不同的應答數據中都具有唯一的結束符,可以根據結束符來作為多個應答數據的分割標志。因此可以把應答數據進行緩存,然后另起一個線程對緩存的應答數據進行分析處理。
因此系統具有:
1、命令隊列用來插入操作命令,空閑時處理狀態實時輪詢命令。
2、命令發送線程,以200ms周期性的發送隊列中的命令。
3、應答集合,用來緩存DataReceived接收數據。
4、應答處理線程,對應答集合中的數據進行集中處理。
四、代碼片段
1、定義
View Code
/// 請求命令隊列
/// </summary>
private Queue<Request> requests;
/// <summary>
/// 應答數據集合
/// </summary>
private List< byte> responses;
/// <summary>
/// 發送線程同步信號
/// </summary>
private ManualResetEvent sendWaiter;
/// <summary>
/// 應答數據處理線程同步信號
/// </summary>
private ManualResetEvent receiveWaiter;
this.requests = new Queue<Request>();
this.responses = new List< byte>();
this.sendWaiter = new ManualResetEvent( false);
this.receiveWaiter = new ManualResetEvent( false);
ThreadPool.QueueUserWorkItem( new WaitCallback(Send));
ThreadPool.QueueUserWorkItem( new WaitCallback(Received));
2、開始、停止線程
View Code
/// 啟動服務
/// </summary>
public void Start()
{
try
{
if (! this.serialPort1.IsOpen)
{
this.serialPort1.Open();
}
this.requests.Clear();
// 插入初始化命令
this.Push( new Request());
this.sendWaiter.Set();
this.receiveWaiter.Set();
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// 停止服務
/// </summary>
public void Stop()
{
this.sendWaiter.Reset();
this.receiveWaiter.Reset();
if ( this.serialPort1.IsOpen)
{
this.serialPort1.Close();
}
this.requests.Clear();
this.responses.Clear();
}
3、發送線程
View Code
/// 插入操作命令
/// </summary>
/// <param name="request"></param>
public void Push(Request request)
{
Monitor.Enter( this.requests);
this.requests.Enqueue(request);
Monitor.Exit( this.requests);
}
/// <summary>
/// 發送
/// </summary>
/// <param name="obj"></param>
private void Send( object obj)
{
while ( true)
{
try
{
this.sendWaiter.WaitOne();
Monitor.Enter( this.requests);
Request request = null;
if ( this.requests.Count > 0)
{
request = this.requests.Dequeue();
}
else if ( this.Polling)
{
this.send++;
request = new Request( this.config.Zone);
}
if (request != null)
{
byte[] buffer = request.ToBytes();
this.serialPort1.DiscardInBuffer();
this.serialPort1.Write(buffer, 0, buffer.Length);
}
Monitor.Exit( this.requests);
Thread.Sleep(2 00);
}
catch (Exception ex)
{
throw ex;
}
}
}
4、接收和處理
View Code
/// 串口接收
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void SerialPortDataReceived( object sender, EventArgs e)
{
try
{
if ( this.serialPort1.BytesToRead > 0)
{
byte[] buffer = new byte[ this.serialPort1.BytesToRead];
int readCount = this.serialPort1.Read(buffer, 0, buffer.Length);
Monitor.Enter( this.responses);
this.responses.AddRange(buffer);
Monitor.Exit( this.responses);
}
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// 緩存處理
/// </summary>
/// <param name="obj"></param>
private void Received( object obj)
{
while ( true)
{
this.receiveWaiter.WaitOne();
Monitor.Enter( this.responses);
if ( this.responses.Count > 0)
{
int endIndex = this.responses.IndexOf(Request.SYMBOL_END);
if (endIndex >= 0)
{
byte[] buffer = this.responses.GetRange( 0, endIndex + 1).ToArray();
this.responses.RemoveRange( 0, endIndex + 1);
if (buffer.Length > 3)
{
int cmd = buffer[ 1];
switch (cmd)
{
case Request.CMD_QUERY_STATE_SYSTEM:
case Request.CMD_QUERY_UNKNOWN_ZONE:
{
this.config.Update(buffer, 0, buffer.Length);
this.Polling = true;
if ( this.ShelvesInitialized != null)
{
this.ShelvesInitialized( this, new ShelvesInitializedEventArgs( this.config));
}
break;
}
case Request.CMD_QUERY_STATE_LINE:
{
this.received++;
this.realtime = Realtime.Parse(buffer, 0, buffer.Length);
if ( this.ShelvesDataReceived != null && this.realtime != null)
{
this.ShelvesDataReceived( this, new ShelvesDataReceivedEventArgs( this.realtime));
}
break;
}
}
}
}
}
Monitor.Exit( this.responses);
Thread.Sleep( 200);
}
5、以事件的形式在主界面實時顯示處理后的應答數據
View Code
/// 數據接收事件參數
/// </summary>
public class ShelvesDataReceivedEventArgs : EventArgs
{
private Realtime realtime;
public ShelvesDataReceivedEventArgs(Realtime realtime)
{
this.realtime = realtime;
}
public Realtime Realtime
{
get
{
return this.realtime;
}
}
}
/// <summary>
/// 密集架數據接收事件處理
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public delegate void ShelvesDataReceivedEventHandler( object sender, ShelvesDataReceivedEventArgs e);
private void ShelvesDataReceived(object sender, ShelvesDataReceivedEventArgs e)
{
this.Invoke(new UpdateDelegate(this.Update), new object[] { e.Realtime });
}
private void Update(Realtime realtime)
{
this.lbl_send.Text = this.shelves.send.ToString();
this.lbl_received.Text = this.shelves.received.ToString();
this.txt_lowLine.Text = realtime.ActionLineOfLowZone.ToString();
this.txt_lowZoneState.Text = Request.GetStateMessage(realtime.LowZoneState);
this.txt_highLine.Text = realtime.ActionLineOfHighZone.ToString();
this.txt_highZoneState.Text = Request.GetStateMessage(realtime.HighZoneState);
this.txt_temperature.Text = realtime.Temperature.ToString();
this.txt_humidity.Text = realtime.Humidity.ToString();
}
五、結論
一般情況下,當下位機高速發送應答數據時,串口接收到的數據不會是一個完整應答數據,而是多個應答數據的混合集,因此當你以單一應答數據來解析收到的數據時往往會發現應答數據格式不正確,在界面上的表現就是“沒有收到數據”。
另外把收到的原始字節數組解析為程序能讀懂的數據也是一項費時費力的事情,因此會出現“高速收,低速埋”的矛盾。但是,如果只讓串口執行“收”,而輔助線程執行“埋”,那么就有效的解決了這個矛盾,即使下位機發的速度再高,系統也能抗得住。
