SerialPort.DataReceived之“管收不管埋”
通过把SerialPort进行封装,以多线程和缓存的方式处理串口的发送和接收动作。
一、现象
不管如何设置ReceivedBytesThreshold的值,DataReceived接收到的数据都是比较混乱,不是一个完整的应答数据。
二、原因
1、上位机下发的命令比较密集,以200ms周期发送实时状态轮询命令。
2、在状态实时轮询命令中间有操作命令插入。
2、不同的命令,接收的应答格式也不同。
三、分析
不同的命令有不同的应答数据,但是不同的应答数据中都具有唯一的结束符,可以根据结束符来作为多个应答数据的分割标志。因此可以把应答数据进行缓存,然后另起一个线程对缓存的应答数据进行分析处理。
因此系统具有:
1、命令队列用来插入操作命令,空闲时处理状态实时轮询命令。
2、命令发送线程,以200ms周期性的发送队列中的命令。
3、应答集合,用来缓存DataReceived接收数据。
4、应答处理线程,对应答集合中的数据进行集中处理。
四、代码片段
1、定义
/// 请求命令队列
/// </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、开始、停止线程
/// 启动服务
/// </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、发送线程
/// 插入操作命令
/// </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(200);
}
catch (Exception ex)
{
throw ex;
}
}
}
4、接收和处理
/// 串口接收
/// </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、以事件的形式在主界面实时显示处理后的应答数据
/// 数据接收事件参数
/// </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();
}
五、结论
一般情况下,当下位机高速发送应答数据时,串口接收到的数据不会是一个完整应答数据,而是多个应答数据的混合集,因此当你以单一应答数据来解析收到的数据时往往会发现应答数据格式不正确,在界面上的表现就是“没有收到数据”。
另外把收到的原始字节数组解析为程序能读懂的数据也是一项费时费力的事情,因此会出现“高速收,低速埋”的矛盾。但是,如果只让串口执行“收”,而辅助线程执行“埋”,那么就有效的解决了这个矛盾,即使下位机发的速度再高,系统也能抗得住。