• 串口通信之并发与单步


    物理连接示意图如下所示,每个串口挂接多个采集器。

    通信协议:

    包头(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)

            {

                //分析验证数据包的合法性

               ······

                //处理合法包的业务数据

        ······

            }

  • 相关阅读:
    php增加自动刷新当前页面
    liunx环境下安装mysql5.7及以上版本
    mysql的主从级联复制的配置
    windowns常用命令
    liunx之使用(mount)挂载技术
    在burpsuite中为什么不能选中设置好的代理?
    c++中向任意目录下写文件
    Hbase——JavaAPI操作笔记
    每周总结(6)(补)
    每周总结(5)
  • 原文地址:https://www.cnblogs.com/fyhui/p/2489189.html
Copyright © 2020-2023  润新知