物理连接示意图如下所示,每个串口挂接多个采集器。
通信协议:
包头(1B) + 地址码(1B) + 命令字(1B) + 数据长度(1B) + 校验码1(1B) + 数据正文(nB) + 校验码2(1B)。
其中,校验码1校验地址码、命令字、数据长度,校验码2校验数据正文。
1. 并发通信,性能能高。下发命令顺序与返回数据的顺序可能不一致,要保证并发能够正常通信有个前提——返回数据包在接收缓冲区中不会断包,即不会出现A包前6字节 + B包 + A包后10字节之类数据。可能存在问题:a>连续向发送缓冲区写多(如50)条命令,发送缓冲区会混乱吗? b>怎么计算某个采集设备超时? c>发送命令的速度与各路数据返回的速度的平衡能控制吗?
首先应该知道,多个采集设备通过串口R485总线连接在同一个串口上,而总线在某一时刻只能允许一路设备发送数据,其他各路设备都能收到此条数据。
采用串口并发通信有3个前提:a>使用R485总线,而不是R232;b>同一串口挂载了多路设备;c>下位机采集设备处理返回数据时间较长。如果挂接了20路设备,每路收到命令后需10ms(5ms通信+5数据准备)返回数据,则单步轮询一次共需200ms,这样的实时性应该可以接受。但是如果每路收到命令后需200ms(5ms通信+195ms数据准备)返回数据,则单步轮询一次共需3900ms,这样的实时性是很差的,往往满足不了需求。我们完全可以让195ms并发。
图4 串口通信数据流图
串口并发以及时间如下:
a> 一次轮询下发所有(20路)设备数据准备命令;
b> 各路设备收到自己地址码的查询命令后,开始准备数据(195ms并发);
c> 上位机等待300ms;
d> 上位机逐路请求采集器数据;
e> 某一个接收到请求自己数据命令后,返回b>准备好的数据(10ms);
f> 上位机接收到某路下位机数据后,处理并显示,再执行d>
共耗时:300(等各路完成195) + 10*20 = 500ms
图2串口并发流程其实是不可能出现的,因为串口总线的控制只能通过d-f步骤来。
串口同步以及时间如下:
a> 上位机逐路请求采集器数据;
b> 某一个接收到请求自己数据命令后,准备数据(195ms),返回数据(5);
c> 上位机接收到下位机数据后,处理并显示,再执行a>
共耗时:(195 + 5) *20 = 4000ms
2. 单步通信,单步通信性能稍差。逐路发送命令,a>怎么更有效的等待当前路数据返回?
代码段一(串口单步方案1,发送线程和接收事件线程通过IsWaitingReceive同步。请问IsWaitingReceive多线程控制,会不会有无问题?):
private bool IsWaitingReceive = false;
private void SendCmdThread()
{
while (true)
{
if (!IsWaitingReceive) //可以下发逐路轮询命令了
{
SendOneCmd();
IsWaitingReceive = true;
}
Thread.Sleep(50);
}
}
private int CurDeviceNo = 0;
private int TotalDeviceCnt = 20;
private void SendOneCmd()
{
//向地址码为CurDeviceNo设备发命令请求
m_SerialPort.Write(buf, offset, count);
//轮询设备
++CurDeviceNo;
if (CurDeviceNo >= TotalDeviceCnt) CurDeviceNo = 0;
}
private byte[] m_RcvBuf = new byte[512];
private int RcvCnt = 0;
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
int nLen = m_SerialPort.Read(m_RcvBuf, RcvCnt, m_SerialPort.BytesToRead);
RcvCnt += nLen;
//接下来在全局m_RcvBuf中查找合法完整数据包,解析、处理业务
······
if (CurDeviceNo == 当前数据包中地址码)
IsWaitingReceive = false;
}
代码段二(串口单步方案2,同一个线程处理发送和接收):
private int TotalDeviceCnt = 20;
private void ComCmdThread()
{
byte[] bArr = null;
while (true)
{
for (int i = 0; i < TotalDeviceCnt; i++ ) //轮询一轮
{
bArr = SendOneCmd(i);
if (bArr != null)
{
DealData(bArr);
}
}
Thread.Sleep(200);
}
}
private byte[] SendOneCmd(int curNo)
{
byte[] bRcv = null;
m_SerialPort.DiscardInBuffer(); //清空接收缓存区以前多余的数据
m_SerialPort.Write(buf, offset, count); //向curNo路设备发送命令
Thread.Sleep(50); //延时,等待下位机回数据
int nStartTime = Environment.TickCount;
while (true)
{
if (Environment.TickCount - nStartTime > 100) //超时
{
Console.WriteLine("等待超时...");
break;
}
if (m_SerialPort.BytesToRead >= 25) //如果一个完整的数据包固定25字节
{ //如果不是固定长度,可以进全局Buf,然后查找解析合法数据包
bRcv = new byte[25];
int nLen = m_SerialPort.Read(bRcv, 0, 25); //这三个25,可以用其他变量代替
break;
}
//Thread.Sleep(10); //看情况是否允许需要
}
return bRcv;
}
private void DealData(byte[] data)
{
//分析验证数据包的合法性
······
//处理合法包的业务数据
······
}