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