精华内容
下载资源
问答
  • 自定义通信协议

    千次阅读 2018-01-08 09:48:59
    1.自定义数据通信协议  这里所说的数据协议是建立在物理层之上的通信数据包格式。所谓通信的物理层就是指我们通常所用到的RS232、RS485、红外、光纤、无线等等通信方式。在这个层面上,底层软件提供两个基本的操

    现在大部分的仪器设备都要求能过通过上位机软件来操作,这样方便调试,利于操作。其中就涉及到通信的过程。在实际制作的几个设备中,笔者总结出了通信程序的通用写法,包括上位机端和下位机端等。

    1.自定义数据通信协议

      这里所说的数据协议是建立在物理层之上的通信数据包格式。所谓通信的物理层就是指我们通常所用到的RS232、RS485、红外、光纤、无线等等通信方式。在这个层面上,底层软件提供两个基本的操作函数:发送一个字节数据、接收一个字节数据。所有的数据协议全部建立在这两个操作方法之上。

    通信中的数据往往以数据包的形式进行传送的,我们把这样的一个数据包称作为一帧数据。类似于网络通信中的TCPIP协议一般,比较可靠的通信协议往往包含有以下几个组成部分:帧头、地址信息、数据类型、数据长度、数据块、校验码、帧尾

      帧头和帧尾用于数据包完整性的判别,通常选择一定长度的固定字节组成,要求是在整个数据链中判别数据包的误码率越低越好。减小固定字节数据的匹配机会,也就是说使帧头和帧尾的特征字节在整个数据链中能够匹配的机会最小。通常有两种做法,一、减小特征字节的匹配几率。二、增加特征字节的长度。通常选取第一种方法的情况是整个数据链路中的数据不具有随即性,数据可预测,可以通过人为选择帧头和帧尾的特征字来避开,从而减小特征字节的匹配几率。使用第二种方法的情况更加通用,适合于数据随即的场合。通过增加特征字节的长度减小匹配几率,虽然不能够完全的避免匹配的情况,但可以使匹配几率大大减小,如果碰到匹配的情况也可以由校验码来进行检测,因此这种情况在绝大多说情况下比较可靠。

      地址信息主要用于多机通信中,通过地址信息的不同来识别不同的通信终端。在一对多的通信系统中,可以只包含目的地址信息。同时包含源地址和目的地址则适用于多对多的通信系统。 

      数据类型、数据长度和数据块是主要的数据部分。数据类型可以标识后面紧接着的是命令还是数据。数据长度用于指示有效数据的个数。

      校验码则用来检验数据的完整性和正确性。通常对数据类型、数据长度和数据块三个部分进行相关的运算得到。最简单的做法可是对数据段作累加和,复杂的也可以对数据进行CRC运算等等,可以根据运算速度、容错度等要求来选取。

     

    2.上位机和下位机中的数据发送

      物理通信层中提供了两个基本的操作函数,发送一个字节数据则为数据发送的基础。数据包的发送即把数据包中的左右字节按照顺序一个一个的发送数据而已。当然发送的方法也有不同。

      在单片机系统中,比较常用的方法是直接调用串口发送单个字节数据的函数。这种方法的缺点是需要处理器在发送过程中全程参与,优点是所要发送的数据能够立即的出现在通信线路上,能够立即被接收端接收到。另外一种方法是采用中断发送的方式,所有需要发送的数据被送入一个缓冲区,利用发送中断将缓冲区中的数据发送出去。这种方法的优点是占用处理器资源小,但是可能出现需要发送的数据不能立即被发送的情况,不过这种时延相当的小。对于51系列单片机,比较倾向于采用直接发送的方式,采用中断发送的方式比较占用RAM资源,而且对比直接发送来说也没有太多的优点。以下是51系列单片机中发送单个字节的函数。

    void SendByte(unsigned char ch)

    {

         SBUF = ch;

         while(TI == 0);

         TI = 0;

    }

      上位机中关于串口通信的方式也有多种,这种方式不是指数据有没有缓冲的问题,而是操作串口的方式不同,因为PC上数据发送基本上都会被缓冲后再发送。对于编程来说操作串口有三种方式,一、使用windows系统中自带的串口通信控件,这种方式使用起来比较简单,需要注意的是接收时的阻塞处理和线程机制。二、使用系统的API直接进行串口数据的读取,在windows和linux系统中,设备被虚拟为文件,只需要利用系统提供的API函数即可进行串口数据的发送和读取。三、使用串口类进行串口操作。在此只介绍windows环境下利用串口类编程的方式。

      CSerialPort是比较好用的串口类。它提供如下的串口操作方法:

      void WriteToPort(char* string, int len);

      串口初始化成功后,调用此函数即可向串口发送数据。为了避免串口缓冲所带来的延时,可以开启串口的冲刷机制。

     

    3.下位机中的数据接收和协议解析

      下位机接收数据也有两种方式,一、等待接收,处理器一直查询串口状态,来判断是否接收到数据。二、中断接收。两种方法的优缺点在此前的一篇关于串口通信的文章中详细讨论过。得出的结论是采用中断接收的方法比较好。

      数据包的解析过程可以设置到不同的位置。如果协议比较简单,整个系统只是处理一些简单的命令,那么可以直接把数据包的解析过程放入到中断处理函数中,当收到正确的数据包的时候,置位相应的标志,在主程序中再对命令进行处理。如果协议稍微复杂,比较好的方式是将接收的数据存放于缓冲区中,主程序读取数据后进行解析。也有两种方式交叉使用的,比如一对多的系统中,首先在接收中断中解析“连接”命令,连接命令接收到后主程序进入设置状态,采用查询的方式来解析其余的协议。

      以下给出具体的实例。在这个系统中,串口的命令非常简单。所有的协议全部在串口中断中进行。数据包的格式如下:

      0x55, 0xAA, 0x7E, 0x12, 0xF0, 0x02, 0x23, 0x45, SUM, XOR, 0x0D

      其中0x55, 0xAA, 0x7E为数据帧的帧头,0x0D为帧尾,0x12为设备的目的地址,0xF0为源地址,0x02为数据长度,后面接着两个数据0x23, 0x45,从目的地址开始结算累加、异或校验和,到数据的最后一位结束。

            协议解析的目的,首先判断数据包的完整性,正确性,然后提取数据类型,数据等数据,存放起来用于主程序处理。代码如下:

     

    if(state_machine == 0)     // 协议解析状态机

    {

        if(rcvdat == 0x55)     // 接收到帧头第一个数据

            state_machine = 1;

        else

            state_machine = 0;    // 状态机复位

    }

    else if(state_machine == 1)

    {

        if(rcvdat == 0xAA)     // 接收到帧头第二个数据

            state_machine = 2;

        else

            state_machine = 0;    // 状态机复位

    }

    else if(state_machine == 2)

    {

        if(rcvdat == 0x7E)     // 接收到帧头第三个数据

            state_machine = 3;

         else

            state_machine = 0;    // 状态机复位

    }

    else if(state_machine == 3)

    {

        sumchkm = rcvdat;     // 开始计算累加、异或校验和

        xorchkm = rcvdat;

        if(rcvdat == m_SrcAdr)    // 判断目的地址是否正确

            state_machine = 4;

        else

            state_machine = 0;

    }

    else if(state_machine == 4)

    {

        sumchkm += rcvdat;

        xorchkm ^= rcvdat;

        if(rcvdat == m_DstAdr)    // 判断源地址是否正确

            state_machine = 5;

        else

            state_machine = 0;

       }

    else if(state_machine == 5)

    {

        lencnt = 0;        // 接收数据计数器

        rcvcount = rcvdat;      // 接收数据长度

        sumchkm += rcvdat;

        xorchkm ^= rcvdat;

        state_machine = 6;

    }

    else if(state _machine == 6 || state _machine == 7)

    {

        m_ucData[lencnt++] = rcvdat;     // 数据保存

        sumchkm += rcvdat;

        xorchkm ^= rcvdat;

        if(lencnt == rcvcount)    // 判断数据是否接收完毕

            state_machine = 8;

        else

            state_machine = 7;

    }

    else if(state_machine == 8)

    {

        if(sumchkm == rcvdat)    // 判断累加和是否相等

            state_machine = 9;

        else

            state_machine = 0;

    }

    else if(state_machine == 9)

    {

        if(xorchkm == rcvdat)    // 判断异或校验和是否相等

            state_machine = 10;

        else

            state_machine = 0;

    }

    else if(state_machine == 10)

    {

        if(0x0D == rcvdat)     // 判断是否接收到帧尾结束符

        {

            retval = 0xaa;    // 置标志,表示一个数据包接收到

        }

        state_machine = 0;     // 复位状态机

    }

     

      此过程中,使用了一个变量state_machine作为协议状态机的转换状态,用于确定当前字节处于一帧数据中的那个部位,同时在接收过程中自动对接收数据进行校验和处理,在数据包接收完的同时也进行了校验的比较。因此当帧尾结束符接收到的时候,则表示一帧数据已经接收完毕,并且通过了校验,关键数据也保存到了缓冲去中。主程序即可通过retval的标志位来进行协议的解析处理。

      接收过程中,只要哪一步收到的数据不是预期值,则直接将状态机复位,用于下一帧数据的判断,因此系统出现状态死锁的情况非常少,系统比较稳定,如果出现丢失数据包的情况也可由上位机进行命令的补发,不过这种情况笔者还没有碰到。

      对于主程序中进行协议处理的过程与此类似,主程序循环中不断的读取串口缓冲区的数据,此数据即参与到主循环中的协议处理过程中,代码与上面所述完全一样。

     

    4.上位机中的数据接收和命令处理 

      上位机中数据接收的过程与下位机可以做到完全一致,不过针对不同的串口操作方法有所不同。对于阻赛式的串口读函数,例如直接进行API操作或者调用windows的串口通信控件,最好能够开启一个线程专门用于监视串口的数据接收,每接收到一个数据可以向系统发送一个消息。笔者常用的CSerialPort类中就是这样的处理过程。CSerialPort打开串口后开启线程监视串口的数据接收,将接收的数据保存到缓冲区,并向父进程发送接收数据的消息,数据将随消息一起发送到父进程。父进程中开启此消息的处理函数,从中获取串口数据后就可以把以上的代码拷贝过来使用。 

      CSerialPort向父类发送的消息号如下:

      #define WM_COMM_RXCHAR WM_USER+7 // A character was received and placed in the input buffer.

      因此需要手动添加此消息的响应函数:

      afx_msg LONG OnCommunication(WPARAM ch, LPARAM port);

      ON_MESSAGE(WM_COMM_RXCHAR, OnCommunication)

      响应函数的具体代码如下:

    LONG CWellInfoView::OnCommunication(WPARAM ch, LPARAM port)

    {

         int retval = 0;

         rcvdat = (BYTE)ch;

         if(state_machine == 0)     // 协议解析状态机

        {

          if(rcvdat == 0x55)     // 接收到帧头第一个数据

              state_machine = 1;

          else

              state_machine = 0;    // 状态机复位

        }

        else if(state_machine == 1)

        {

          if(rcvdat == 0xAA)     // 接收到帧头第二个数据

              state_machine = 2;

          else

              state_machine = 0;    // 状态机复位

        ......

     

    5.总结

      以上给出的是通信系统运作的基本雏形,虽然简单,但是可行。实际的通信系统中协议比这个要复杂,而且涉及到数据包响应、命令错误、延时等等一系列的问题,在这样的一个基础上可以克服这些困难并且实现出较为稳定可靠的系统

    展开全文
  • 这是我最近开发的通信工具,具有客户机和服务器端。具有完整的用户登陆、注册、发送信息、增加好友、删除好友、添加组、删除组、查看好友信息。并且这个软件中具有自定义的开发协议,和一些安全机制。
  • 自定义通信协议-网摘

    2013-11-11 17:32:43
    1.自定义数据通信协议  这里所说的数据协议是建立在物理层之上的通信数据包格式。所谓通信的物理层就是指我们通常所用到的RS232、RS485、红外、光纤、无线等等通信方式。在这个层面上,底层软件提供两个基本的...

    现在大部分的仪器设备都要求能过通过上位机软件来操作,这样方便调试,利于操作。其中就涉及到通信的过程。在实际制作的几个设备中,笔者总结出了通信程序的通用写法,包括上位机端和下位机端等。

    1.自定义数据通信协议

      这里所说的数据协议是建立在物理层之上的通信数据包格式。所谓通信的物理层就是指我们通常所用到的RS232、RS485、红外、光纤、无线等等通信方式。在这个层面上,底层软件提供两个基本的操作函数:发送一个字节数据、接收一个字节数据。所有的数据协议全部建立在这两个操作方法之上。

    通信中的数据往往以数据包的形式进行传送的,我们把这样的一个数据包称作为一帧数据。类似于网络通信中的TCPIP协议一般,比较可靠的通信协议往往包含有以下几个组成部分:帧头、地址信息、数据类型、数据长度、数据块、校验码、帧尾

      帧头和帧尾用于数据包完整性的判别,通常选择一定长度的固定字节组成,要求是在整个数据链中判别数据包的误码率越低越好。减小固定字节数据的匹配机会,也就是说使帧头和帧尾的特征字节在整个数据链中能够匹配的机会最小。通常有两种做法,一、减小特征字节的匹配几率。二、增加特征字节的长度。通常选取第一种方法的情况是整个数据链路中的数据不具有随即性,数据可预测,可以通过人为选择帧头和帧尾的特征字来避开,从而减小特征字节的匹配几率。使用第二种方法的情况更加通用,适合于数据随即的场合。通过增加特征字节的长度减小匹配几率,虽然不能够完全的避免匹配的情况,但可以使匹配几率大大减小,如果碰到匹配的情况也可以由校验码来进行检测,因此这种情况在绝大多说情况下比较可靠。

      地址信息主要用于多机通信中,通过地址信息的不同来识别不同的通信终端。在一对多的通信系统中,可以只包含目的地址信息。同时包含源地址和目的地址则适用于多对多的通信系统。 

      数据类型、数据长度和数据块是主要的数据部分。数据类型可以标识后面紧接着的是命令还是数据。数据长度用于指示有效数据的个数。

      校验码则用来检验数据的完整性和正确性。通常对数据类型、数据长度和数据块三个部分进行相关的运算得到。最简单的做法可是对数据段作累加和,复杂的也可以对数据进行CRC运算等等,可以根据运算速度、容错度等要求来选取。

     

    2.上位机和下位机中的数据发送

      物理通信层中提供了两个基本的操作函数,发送一个字节数据则为数据发送的基础。数据包的发送即把数据包中的左右字节按照顺序一个一个的发送数据而已。当然发送的方法也有不同。

      在单片机系统中,比较常用的方法是直接调用串口发送单个字节数据的函数。这种方法的缺点是需要处理器在发送过程中全程参与,优点是所要发送的数据能够立即的出现在通信线路上,能够立即被接收端接收到。另外一种方法是采用中断发送的方式,所有需要发送的数据被送入一个缓冲区,利用发送中断将缓冲区中的数据发送出去。这种方法的优点是占用处理器资源小,但是可能出现需要发送的数据不能立即被发送的情况,不过这种时延相当的小。对于51系列单片机,比较倾向于采用直接发送的方式,采用中断发送的方式比较占用RAM资源,而且对比直接发送来说也没有太多的优点。以下是51系列单片机中发送单个字节的函数。

    void SendByte(unsigned char ch)

    {

         SBUF = ch;

         while(TI == 0);

         TI = 0;

    }

      上位机中关于串口通信的方式也有多种,这种方式不是指数据有没有缓冲的问题,而是操作串口的方式不同,因为PC上数据发送基本上都会被缓冲后再发送。对于编程来说操作串口有三种方式,一、使用windows系统中自带的串口通信控件,这种方式使用起来比较简单,需要注意的是接收时的阻塞处理和线程机制。二、使用系统的API直接进行串口数据的读取,在windows和linux系统中,设备被虚拟为文件,只需要利用系统提供的API函数即可进行串口数据的发送和读取。三、使用串口类进行串口操作。在此只介绍windows环境下利用串口类编程的方式。

      CSerialPort是比较好用的串口类。它提供如下的串口操作方法:

      void WriteToPort(char* string, int len);

      串口初始化成功后,调用此函数即可向串口发送数据。为了避免串口缓冲所带来的延时,可以开启串口的冲刷机制。

     

    3.下位机中的数据接收和协议解析

      下位机接收数据也有两种方式,一、等待接收,处理器一直查询串口状态,来判断是否接收到数据。二、中断接收。两种方法的优缺点在此前的一篇关于串口通信的文章中详细讨论过。得出的结论是采用中断接收的方法比较好。

      数据包的解析过程可以设置到不同的位置。如果协议比较简单,整个系统只是处理一些简单的命令,那么可以直接把数据包的解析过程放入到中断处理函数中,当收到正确的数据包的时候,置位相应的标志,在主程序中再对命令进行处理。如果协议稍微复杂,比较好的方式是将接收的数据存放于缓冲区中,主程序读取数据后进行解析。也有两种方式交叉使用的,比如一对多的系统中,首先在接收中断中解析“连接”命令,连接命令接收到后主程序进入设置状态,采用查询的方式来解析其余的协议。

      以下给出具体的实例。在这个系统中,串口的命令非常简单。所有的协议全部在串口中断中进行。数据包的格式如下:

      0x55, 0xAA, 0x7E, 0x12, 0xF0, 0x02, 0x23, 0x45, SUM, XOR, 0x0D

      其中0x55, 0xAA, 0x7E为数据帧的帧头,0x0D为帧尾,0x12为设备的目的地址,0xF0为源地址,0x02为数据长度,后面接着两个数据0x23, 0x45,从目的地址开始结算累加、异或校验和,到数据的最后一位结束。

            协议解析的目的,首先判断数据包的完整性,正确性,然后提取数据类型,数据等数据,存放起来用于主程序处理。代码如下:

     

    if(state_machine == 0)     // 协议解析状态机

    {

        if(rcvdat == 0x55)     // 接收到帧头第一个数据

            state_machine = 1;

        else

            state_machine = 0;    // 状态机复位

    }

    else if(state_machine == 1)

    {

        if(rcvdat == 0xAA)     // 接收到帧头第二个数据

            state_machine = 2;

        else

            state_machine = 0;    // 状态机复位

    }

    else if(state_machine == 2)

    {

        if(rcvdat == 0x7E)     // 接收到帧头第三个数据

            state_machine = 3;

         else

            state_machine = 0;    // 状态机复位

    }

    else if(state_machine == 3)

    {

        sumchkm = rcvdat;     // 开始计算累加、异或校验和

        xorchkm = rcvdat;

        if(rcvdat == m_SrcAdr)    // 判断目的地址是否正确

            state_machine = 4;

        else

            state_machine = 0;

    }

    else if(state_machine == 4)

    {

        sumchkm += rcvdat;

        xorchkm ^= rcvdat;

        if(rcvdat == m_DstAdr)    // 判断源地址是否正确

            state_machine = 5;

        else

            state_machine = 0;

       }

    else if(state_machine == 5)

    {

        lencnt = 0;        // 接收数据计数器

        rcvcount = rcvdat;      // 接收数据长度

        sumchkm += rcvdat;

        xorchkm ^= rcvdat;

        state_machine = 6;

    }

    else if(state _machine == 6 || state _machine == 7)

    {

        m_ucData[lencnt++] = rcvdat;     // 数据保存

        sumchkm += rcvdat;

        xorchkm ^= rcvdat;

        if(lencnt == rcvcount)    // 判断数据是否接收完毕

            state_machine = 8;

        else

            state_machine = 7;

    }

    else if(state_machine == 8)

    {

        if(sumchkm == rcvdat)    // 判断累加和是否相等

            state_machine = 9;

        else

            state_machine = 0;

    }

    else if(state_machine == 9)

    {

        if(xorchkm == rcvdat)    // 判断异或校验和是否相等

            state_machine = 10;

        else

            state_machine = 0;

    }

    else if(state_machine == 10)

    {

        if(0x0D == rcvdat)     // 判断是否接收到帧尾结束符

        {

            retval = 0xaa;    // 置标志,表示一个数据包接收到

        }

        state_machine = 0;     // 复位状态机

    }

     

      此过程中,使用了一个变量state_machine作为协议状态机的转换状态,用于确定当前字节处于一帧数据中的那个部位,同时在接收过程中自动对接收数据进行校验和处理,在数据包接收完的同时也进行了校验的比较。因此当帧尾结束符接收到的时候,则表示一帧数据已经接收完毕,并且通过了校验,关键数据也保存到了缓冲去中。主程序即可通过retval的标志位来进行协议的解析处理。

      接收过程中,只要哪一步收到的数据不是预期值,则直接将状态机复位,用于下一帧数据的判断,因此系统出现状态死锁的情况非常少,系统比较稳定,如果出现丢失数据包的情况也可由上位机进行命令的补发,不过这种情况笔者还没有碰到。

      对于主程序中进行协议处理的过程与此类似,主程序循环中不断的读取串口缓冲区的数据,此数据即参与到主循环中的协议处理过程中,代码与上面所述完全一样。

     

    4.上位机中的数据接收和命令处理 

      上位机中数据接收的过程与下位机可以做到完全一致,不过针对不同的串口操作方法有所不同。对于阻赛式的串口读函数,例如直接进行API操作或者调用windows的串口通信控件,最好能够开启一个线程专门用于监视串口的数据接收,每接收到一个数据可以向系统发送一个消息。笔者常用的CSerialPort类中就是这样的处理过程。CSerialPort打开串口后开启线程监视串口的数据接收,将接收的数据保存到缓冲区,并向父进程发送接收数据的消息,数据将随消息一起发送到父进程。父进程中开启此消息的处理函数,从中获取串口数据后就可以把以上的代码拷贝过来使用。 

      CSerialPort向父类发送的消息号如下:

      #define WM_COMM_RXCHAR WM_USER+7 // A character was received and placed in the input buffer.

      因此需要手动添加此消息的响应函数:

      afx_msg LONG OnCommunication(WPARAM ch, LPARAM port);

      ON_MESSAGE(WM_COMM_RXCHAR, OnCommunication)

      响应函数的具体代码如下:

    LONG CWellInfoView::OnCommunication(WPARAM ch, LPARAM port)

    {

         int retval = 0;

         rcvdat = (BYTE)ch;

         if(state_machine == 0)     // 协议解析状态机

        {

          if(rcvdat == 0x55)     // 接收到帧头第一个数据

              state_machine = 1;

          else

              state_machine = 0;    // 状态机复位

        }

        else if(state_machine == 1)

        {

          if(rcvdat == 0xAA)     // 接收到帧头第二个数据

              state_machine = 2;

          else

              state_machine = 0;    // 状态机复位

        ......

     

    5.总结

      以上给出的是通信系统运作的基本雏形,虽然简单,但是可行。实际的通信系统中协议比这个要复杂,而且涉及到数据包响应、命令错误、延时等等一系列的问题,在这样的一个基础上可以克服这些困难并且实现出较为稳定可靠的系统

    展开全文
  • 自定义通信协议(实用)

    千次阅读 2012-03-08 17:33:56
    自定义通信协议(实用) 现在大部分的仪器设备都要求能过通过上位机软件来操作,这样方便调试,利于操作。其中就涉及到通信的过程。在实际制作的几个设备中,笔者总结出了通信程序的通用写法,包括上位机端和下位机...
    自定义通信协议(实用)
    

    现在大部分的仪器设备都要求能过通过上位机软件来操作,这样方便调试,利于操作。其中就涉及到通信的过程。在实际制作的几个设备中,笔者总结出了通信程序的通用写法,包括上位机端和下位机端等。

    1.自定义数据通信协议

      这里所说的数据协议是建立在物理层之上的通信数据包格式。所谓通信的物理层就是指我们通常所用到的RS232、RS485、红外、光纤、无线等等通信方式。在这个层面上,底层软件提供两个基本的操作函数:发送一个字节数据、接收一个字节数据。所有的数据协议全部建立在这两个操作方法之上。

    通信中的数据往往以数据包的形式进行传送的,我们把这样的一个数据包称作为一帧数据。类似于网络通信中的TCPIP协议一般,比较可靠的通信协议往往包含有以下几个组成部分:帧头、地址信息、数据类型、数据长度、数据块、校验码、帧尾。

      帧头和帧尾用于数据包完整性的判别,通常选择一定长度的固定字节组成,要求是在整个数据链中判别数据包的误码率越低越好。减小固定字节数据的匹配机会,也就是说使帧头和帧尾的特征字节在整个数据链中能够匹配的机会最小。通常有两种做法,一、减小特征字节的匹配几率。二、增加特征字节的长度。通常选取第一种方法的情况是整个数据链路中的数据不具有随即性,数据可预测,可以通过人为选择帧头和帧尾的特征字来避开,从而减小特征字节的匹配几率。使用第二种方法的情况更加通用,适合于数据随即的场合。通过增加特征字节的长度减小匹配几率,虽然不能够完全的避免匹配的情况,但可以使匹配几率大大减小,如果碰到匹配的情况也可以由校验码来进行检测,因此这种情况在绝大多说情况下比较可靠。

      地址信息主要用于多机通信中,通过地址信息的不同来识别不同的通信终端。在一对多的通信系统中,可以只包含目的地址信息。同时包含源地址和目的地址则适用于多对多的通信系统。 

      数据类型、数据长度和数据块是主要的数据部分。数据类型可以标识后面紧接着的是命令还是数据。数据长度用于指示有效数据的个数。

      校验码则用来检验数据的完整性和正确性。通常对数据类型、数据长度和数据块三个部分进行相关的运算得到。最简单的做法可是对数据段作累加和,复杂的也可以对数据进行CRC运算等等,可以根据运算速度、容错度等要求来选取。

     

    2.上位机和下位机中的数据发送

      物理通信层中提供了两个基本的操作函数,发送一个字节数据则为数据发送的基础。数据包的发送即把数据包中的左右字节按照顺序一个一个的发送数据而已。当然发送的方法也有不同。

      在单片机系统中,比较常用的方法是直接调用串口发送单个字节数据的函数。这种方法的缺点是需要处理器在发送过程中全程参与,优点是所要发送的数据能够立即的出现在通信线路上,能够立即被接收端接收到。另外一种方法是采用中断发送的方式,所有需要发送的数据被送入一个缓冲区,利用发送中断将缓冲区中的数据发送出去。这种方法的优点是占用处理器资源小,但是可能出现需要发送的数据不能立即被发送的情况,不过这种时延相当的小。对于51系列单片机,比较倾向于采用直接发送的方式,采用中断发送的方式比较占用RAM资源,而且对比直接发送来说也没有太多的优点。以下是51系列单片机中发送单个字节的函数。

    void SendByte(unsigned char ch)

    {

         SBUF = ch;

         while(TI == 0);

         TI = 0;

    }

      上位机中关于串口通信的方式也有多种,这种方式不是指数据有没有缓冲的问题,而是操作串口的方式不同,因为PC上数据发送基本上都会被缓冲后再发送。对于编程来说操作串口有三种方式,一、使用windows系统中自带的串口通信控件,这种方式使用起来比较简单,需要注意的是接收时的阻塞处理和线程机制。二、使用系统的API直接进行串口数据的读取,在windows和linux系统中,设备被虚拟为文件,只需要利用系统提供的API函数即可进行串口数据的发送和读取。三、使用串口类进行串口操作。在此只介绍windows环境下利用串口类编程的方式。

      CSerialPort是比较好用的串口类。它提供如下的串口操作方法:

      void WriteToPort(char* string, int len);

      串口初始化成功后,调用此函数即可向串口发送数据。为了避免串口缓冲所带来的延时,可以开启串口的冲刷机制。

     

    3.下位机中的数据接收和协议解析

      下位机接收数据也有两种方式,一、等待接收,处理器一直查询串口状态,来判断是否接收到数据。二、中断接收。两种方法的优缺点在此前的一篇关于串口通信的文章中详细讨论过。得出的结论是采用中断接收的方法比较好。

      数据包的解析过程可以设置到不同的位置。如果协议比较简单,整个系统只是处理一些简单的命令,那么可以直接把数据包的解析过程放入到中断处理函数中,当收到正确的数据包的时候,置位相应的标志,在主程序中再对命令进行处理。如果协议稍微复杂,比较好的方式是将接收的数据存放于缓冲区中,主程序读取数据后进行解析。也有两种方式交叉使用的,比如一对多的系统中,首先在接收中断中解析“连接”命令,连接命令接收到后主程序进入设置状态,采用查询的方式来解析其余的协议。

      以下给出具体的实例。在这个系统中,串口的命令非常简单。所有的协议全部在串口中断中进行。数据包的格式如下:

      0x55, 0xAA, 0x7E, 0x12, 0xF0, 0x02, 0x23, 0x45, SUM, XOR, 0x0D

      其中0x55, 0xAA, 0x7E为数据帧的帧头,0x0D为帧尾,0x12为设备的目的地址,0xF0为源地址,0x02为数据长度,后面接着两个数据0x23, 0x45,从目的地址开始结算累加、异或校验和,到数据的最后一位结束。

            协议解析的目的,首先判断数据包的完整性,正确性,然后提取数据类型,数据等数据,存放起来用于主程序处理。代码如下:

     

    if(state_machine == 0)     // 协议解析状态机

    {

        if(rcvdat == 0x55)     // 接收到帧头第一个数据

            state_machine = 1;

        else

            state_machine = 0;    // 状态机复位

    }

    else if(state_machine == 1)

    {

        if(rcvdat == 0xAA)     // 接收到帧头第二个数据

            state_machine = 2;

        else

            state_machine = 0;    // 状态机复位

    }

    else if(state_machine == 2)

    {

        if(rcvdat == 0x7E)     // 接收到帧头第三个数据

            state_machine = 3;

         else

            state_machine = 0;    // 状态机复位

    }

    else if(state_machine == 3)

    {

        sumchkm = rcvdat;     // 开始计算累加、异或校验和

        xorchkm = rcvdat;

        if(rcvdat == m_SrcAdr)    // 判断目的地址是否正确

            state_machine = 4;

        else

            state_machine = 0;

    }

    else if(state_machine == 4)

    {

        sumchkm += rcvdat;

        xorchkm ^= rcvdat;

        if(rcvdat == m_DstAdr)    // 判断源地址是否正确

            state_machine = 5;

        else

            state_machine = 0;

       }

    else if(state_machine == 5)

    {

        lencnt = 0;        // 接收数据计数器

        rcvcount = rcvdat;      // 接收数据长度

        sumchkm += rcvdat;

        xorchkm ^= rcvdat;

        state_machine = 6;

    }

    else if(state _machine == 6 || state _machine == 7)

    {

        m_ucData[lencnt++] = rcvdat;     // 数据保存

        sumchkm += rcvdat;

        xorchkm ^= rcvdat;

        if(lencnt == rcvcount)    // 判断数据是否接收完毕

            state_machine = 8;

        else

            state_machine = 7;

    }

    else if(state_machine == 8)

    {

        if(sumchkm == rcvdat)    // 判断累加和是否相等

            state_machine = 9;

        else

            state_machine = 0;

    }

    else if(state_machine == 9)

    {

        if(xorchkm == rcvdat)    // 判断异或校验和是否相等

            state_machine = 10;

        else

            state_machine = 0;

    }

    else if(state_machine == 10)

    {

        if(0x0D == rcvdat)     // 判断是否接收到帧尾结束符

        {

            retval = 0xaa;    // 置标志,表示一个数据包接收到

        }

        state_machine = 0;     // 复位状态机

    }

     

      此过程中,使用了一个变量state_machine作为协议状态机的转换状态,用于确定当前字节处于一帧数据中的那个部位,同时在接收过程中自动对接收数据进行校验和处理,在数据包接收完的同时也进行了校验的比较。因此当帧尾结束符接收到的时候,则表示一帧数据已经接收完毕,并且通过了校验,关键数据也保存到了缓冲去中。主程序即可通过retval的标志位来进行协议的解析处理。

      接收过程中,只要哪一步收到的数据不是预期值,则直接将状态机复位,用于下一帧数据的判断,因此系统出现状态死锁的情况非常少,系统比较稳定,如果出现丢失数据包的情况也可由上位机进行命令的补发,不过这种情况笔者还没有碰到。

      对于主程序中进行协议处理的过程与此类似,主程序循环中不断的读取串口缓冲区的数据,此数据即参与到主循环中的协议处理过程中,代码与上面所述完全一样。

     

    4.上位机中的数据接收和命令处理 

      上位机中数据接收的过程与下位机可以做到完全一致,不过针对不同的串口操作方法有所不同。对于阻赛式的串口读函数,例如直接进行API操作或者调用windows的串口通信控件,最好能够开启一个线程专门用于监视串口的数据接收,每接收到一个数据可以向系统发送一个消息。笔者常用的CSerialPort类中就是这样的处理过程。CSerialPort打开串口后开启线程监视串口的数据接收,将接收的数据保存到缓冲区,并向父进程发送接收数据的消息,数据将随消息一起发送到父进程。父进程中开启此消息的处理函数,从中获取串口数据后就可以把以上的代码拷贝过来使用。 

      CSerialPort向父类发送的消息号如下:

      #define WM_COMM_RXCHAR WM_USER+7 // A character was received and placed in the input buffer.

      因此需要手动添加此消息的响应函数:

      afx_msg LONG OnCommunication(WPARAM ch, LPARAM port);

      ON_MESSAGE(WM_COMM_RXCHAR, OnCommunication)

      响应函数的具体代码如下:

    LONG CWellInfoView::OnCommunication(WPARAM ch, LPARAM port)

    {

         int retval = 0;

         rcvdat = (BYTE)ch;

         if(state_machine == 0)     // 协议解析状态机

        {

          if(rcvdat == 0x55)     // 接收到帧头第一个数据

              state_machine = 1;

          else

              state_machine = 0;    // 状态机复位

        }

        else if(state_machine == 1)

        {

          if(rcvdat == 0xAA)     // 接收到帧头第二个数据

              state_machine = 2;

          else

              state_machine = 0;    // 状态机复位

        ......

     

    5.总结

      以上给出的是通信系统运作的基本雏形,虽然简单,但是可行。实际的通信系统中协议比这个要复杂,而且涉及到数据包响应、命令错误、延时等等一系列的问题,在这样的一个基础上可以克服这些困难并且实现出较为稳定可靠的系统

    展开全文
  • 软件做数据通信协议的时候,往往需要定义相应的协议数据结构,这个工作如果用c的struct,位域和联合这些技术的话,非常方便struct tcp {uint16 source; /* Source port */uint16 dest; /* Destination port */int32 ...

    用软件做数据通信协议的时候,往往需要定义相应的协议数据结构,

    这个工作如果用c的struct,位域和联合这些技术的话,非常方便

    struct tcp {

    uint16 source; /* Source port */

    uint16 dest; /* Destination port */

    int32 seq; /* Sequence number */

    int32 ack; /* Acknowledgment number */

    uint16 wnd; /* Receiver flow control window */

    uint16 checksum; /* Checksum */

    uint16 up; /* Urgent pointer */

    uint16 mss; /* Optional max seg size */

    uint8 wsopt; /* Optional window scale factor */

    uint32 tsval; /* Outbound timestamp */

    uint32 tsecr; /* Timestamp echo field */

    struct {

    unsigned int congest:1; /* Echoed IP congestion experienced bit */

    unsigned int urg:1;

    unsigned int ack:1;

    unsigned int psh:1;

    unsigned int rst:1;

    unsigned int syn:1;

    unsigned int fin:1;

    unsigned int mss:1; /* MSS option present */

    unsigned int wscale:1; /* Window scale option present */

    unsigned int tstamp:1; /* Timestamp option present */

    } flags;

    };

    这样无论是对数据体内的任何数据做读写操作,都非常简单;需要通信的时候,只要把结构体头地址和数据长度送给通信接口,数据就可以看成按照定义的顺序的一个大数组,可以直接做串行通信发送.

    但这个工作如果用python的话,该怎么做呢?

    不管是用字典,还是用class,都有几个问题:首先就是每数据段字节如何限定的问题? 其次是bit数据如何定义?最后,保证在发送的时候将数据结构转换成一个符合要求的串行list呢?

    笨办法倒是也能比较直观的想出来: 就是该咋设置咋设置, 最多设置的方法里加一级检查, 发送的时候, 一个个提出来append到一个bytearray里. 但觉得这样比较笨, 想问问还有没有更高级一点的解决方案没有?

    thx!

    展开全文
  • 开发嵌入式软件的时候,在两个设备的通信,通常会自定义一个内部的通信协议,按照这个通信通信协议来收发数据,解析数据。 例如: 可以看到这个数据帧有9个字段,这样的一个数据帧如果能用一个结构体来描述,那么...
  • [size=medium] 其实KTM的第五版,即实现自定义字节流协议的KTM,我早在半个月前就已经搞定了,但是由于最近在研究Android手机端的Socket通信编程,便无暇顾及博客。 今晚,由于KTM即时通信软件在Android手机端的...
  • 这是一款类似微信的即时通信软件,可以实现的基本内容是收发消息和收发文件,当然可以对文件进行断点续传等操作。通讯基础是tcp Socket,但是基于RRQMSocketFramework,已经解决了粘包的问题,不仅如此,对于文件...
  • wireshark插件开发 - 自定义协议

    千次阅读 2017-09-20 17:28:21
    虽然wireshark自带了很多知名协议的解析插件,譬如HTTP、DHCP等等,然而在实际应用环境中,有不少软件之间的通信协议都是私有的,如游戏客户端和服务器之间的交互协议通常都是私有的,wireshark无法具体解析出各种...
  • 单片机通信协议

    2015-10-12 20:41:34
    现在大部分的仪器设备都要求... 自定义数据通信协议  这里所说的数据协议是建立在物理层之上的通信数据包格式。所谓通信的物理层就是指我们通常所用到的RS232 、RS485 、红外、光纤、无线等等通信方式。在这个层
  • 单片机通信协议处理

    2014-05-06 15:01:00
    转自单片机通信协议处理 ...1、自定义数据通信协议 这里所说的数据协议是建立在物理层之上的通信数据包格式。所谓通信的物理层就是指我们通常所用到的RS232、RS485、红外、光纤、无线等等通信方式。在这个层...
  • 单片机通信协议设计

    2014-09-01 19:52:52
    现在大部分的仪器设备都要求... 自定义数据通信协议  这里所说的数据协议是建立在物理层之上的通信数据包格式。所谓通信的物理层就是指我们通常所用到的RS232 、RS485 、红外、光纤、无线等等通信方式。在这个层
  • 自定义数据通信协议 这里所说的数据协议是建立在物理层之上的通信数据包格式。所谓通信的物理层就是指我们通常所用到的RS232、RS485、红外、光纤、无线等等通信方式。在这个层面上,底层软件提供两个基本的操作...
  • 的特征值 charX基础上,添加一个用户自定义通信协议,并用安卓手机蓝牙app (这里用的是BLE调试宝软件)发送指令实现 LED 点灯,并且有状态值通知返回到调试软件上。 实验平台 CC2640R2F 平台 ①协议栈版本:...
  • CH9329 是一款串口转标准设备(键盘、鼠标、自定义 HID)芯片,根据不同的...通过提供的上位机软件,用户也可自行配置芯片工作模式、串口通信模式、串口通信波特率、多种超时时间、VID、PID,以及各种 USB 字符串描述符。
  •  前面博客大部分介绍了基于EMQ中间件,通信协议使用的是MQTT,而传输的数据为纯文本数据,采用JSON格式。这种方式,大部分一看就知道是熟悉Web开发、软件开发的人喜欢用的方式。由于我也是做web软件开发的,也是...
  • Volta 1.12介绍Volta框架是一款开源GO Socket框架,提供web项目的高并发通信,Volta支持高并发,超高稳定性,支持自定义协议。拥有异步MYSQL、异步Redis、异步消息队列等组件软件架构App -- 业务层代码Common -- ...
  •  用MINA开发通信程序,很重要的一点是自定义编码,因为他可以完成不同平台的数据交换,如手机软件终端,linux服务端,其中手机终端可能是android开发,iphone,sybian,所以,经常需要直接二进制处理。  在服务器...
  • 设备控制软件编程涉及到的基本通信方式主要有TCP/IP与串口,用到的数据通信协议有Fins与ModBus。 更高级别的通信如.net中的Remoting与WCF在进行C/S架构软件开发时会采用。 本篇文章结合Fins/ModBus协议的指令帧...
  • 格西烽火能够很方便地测试串口或网口,通过自定义通信规约来检测电子开发过程中软件定制化存在的各种问题。支持以文本或16进制方式接收和显示数据,测试后能自动保存测试数据。支持设置串口150~256000常见的波特率,...
  • 单片机通讯协议

    2020-03-08 22:36:53
    1.自定义数据通信协议 这里所说的数据协议是建立在物理层之上的通信数据包格式。所谓通信的物理层就是指我 们通常所用到的RS232、RS485、红外、光纤、无线等等通信方式。在这个层面上,底层软 件...
  • 最近一年多来,借鉴 Woomera 的模式,将 SIP 协议实现和具体的业务代码分离,两者通过自定义的简单抽象通信协议进行连接,很好的实现了通信协议实现和上层业务代码的分层,使得编写业务的程序员只要大致掌握传统...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 459
精华内容 183
关键字:

自定义软件通信协议