zoukankan      html  css  js  c++  java
  • 【Delphi】基于状态机的串口通信

    通信协议

      串行通信接口(如RS232、RS485等)作为计算机与单片机交互数据的主要接口,广泛用于各类仪器仪表、工业监测及自动控制领域中。

      通信协议是需要通信的双方所达成的一种约定,它对包括数据格式、同步方式、传送速度、传送步骤、检纠错方式以及控制字符定义等问题作出统一规定,在双方的通信中必须共同遵守。在实际应用系统中,如果缺少一个严格、合理、规范的串口通信协议,将无法保证数据传输的正确性及通信的可靠性。

      因此,需要提出一种基于状态机串口通信协议的设计方法:通过合理地设置数据包格式来保证了数据传输的正确性:引入了状态机方法,简化了协议的实现难度,提高了通信的可靠性,同时使通信过程具有较高的容错能力。


    定义数据包格式

      串口通信中最小的的信息单元是数据帧。一个数据帧通常包括起始位、数据位、结束位,另外还可以包含用于检测传输错误的“奇偶校验位”,每个数据帧中传输的数据位可以有5、6、7、8或9个。
      实际通信过程中,数据的发送是一帧一帧地进行,当被传输的数据超过一帧时(例如浮点型数据),如果没有对数据帧进行必要的打包,发送出去的数据将会很难被数据接收方解释与分析,进而造成数据传输混乱与错误。因此,在一般应用中有必要将数据帧组装成数据包再发送。

    1. 起始标志表示开始接收一个新的数据包。
    2. 数据长度命令和附加数据共占的字节数。设置此字段,可方便接收方识别数据包的长度并能够准确地接收数据包。
    3. 命令用来说明数据包的用途。
    4. 附加数据 当命令不同时,含义不同。
    5. 校验是对命令字段与附加数据字段的所有字节数据的异或校验。
    6. 结束标志表示该数据包结束。

      另外,在多机通信中,数据包中还应增加源地址与设备地址等字段。


    通信状态机

    状态机简介

      状态机由事物所处的状态及引发状态变化的外部事件两部分组成。

      在软件编程中,事物所处的状态可以描述为某个程序片断或函数,而引发状态变化的处部条件可以理解为条件判断语句,当条件为真时,事物的状态发生变化。事物发生变化前的状态称为现态,变化后的状态称为次态,程序中可以通过不同的数字对不同的状态进行编号。现态到次态的变化可以通过状态变量值的改变来描述。
      在协议中需要传输的基本信息单元是数据包,数据包一般包含多个数据帧。实际传输过程中,数据的传输通常是一帧一帧地进行,数据包是被拆分成若干帧数据后再进行传输,数据接受方也是分帧接受一个数据包。

      数据接受方在解释与分析数据包时可能存在两个问题:

    1.识别并接收完整的数据包

    • 对于数据接收方,一个数据包是分若干批到来,在识别包头与包尾时,也就是帧同步问题;
    • 具体编程时存在难度,特别对于已接收部分与未接收部分以及数据接收的进度及状态的处理。

    2.数据传输时的容错能力

    • 数据传输过程中已经出现错误时,系统应该具有摆脱错误状态,恢复到正常状态的能力。
    • 例如,当一个数据包只传输完一部分时,因为未知故障,下一个数据包就开始传输,系统应该能识别出传输错误,抛弃前一个出错的数据包,并且能正确接收下一个数据包。
    • 实际编程时处理这种问题难度较大,结果很可能会出现将第一个数据包的前一部分与第二个数据包的前一部分拼装成一个新的数据包的情况,这就损失了两个数据包,最严重的结果可能是系统无法从错误中恢复,这就严重降低了系统的安全性与可靠性。

      为解决上面的两个问题,在协议中引入了状态机。

      在状态机中,状态的变化依赖于外部触发条件,当条件满足时,状态将发生变化。

      在协议中将数据包接收的各个阶段定义为不同的状态,将接收一帧新的数据或数据处理的结果作为外部触发条件,从而达到状态改变的目的,最终完成一个数据包的接收与校验。


    串口通信状态图

      串口通信协议中,发送数据包时一般不需引入状态机,这主要是为提高发送速率和简化编程模型而考虑。

      在协议中主要针对数据接收过程建立状态机。

      


    串口通信数据接收过程

    1. 当未开始接收数据包或发现数据传输出错时,系统进入空闲状态;
    2. 当数接收到数据包起始标志时,变为收到起始标志状态,如果收到的数据不为起始标志,系统继续保持空闲状态;
    3. 进入收到起始标志状态后,新接收到的任何数据将被当作数据包中命令与附加数据的总字节数(记为LEN),系统进入收到数据长度状态;
    4. 继续接收新的数据,直至接到新收到的数据总字节数达到LEN +2,进入检验结束标志状态;
    5. 这时可以检验结束标志是否为协议定义的标志值,如果是,说明传输正确,否则传输出错,出错后应查找接收缓冲区中本数据包的起始标志后有无其它起始标志,如果没有发现起始标志,系统应进入空闲状态,否则应直接进入接收到起始标志状态,这样可提高系统容错能力,方便系统从错误中恢复。
    6. 检验结柬标志正确后,进入数据校验状态;
    7. 校验结果如果正确,数据包接收完成,否则说明传输出错,系统进入空闲状态。


    上位机软件编程逻辑

      上位机软件中,当接收到数据时,串口控件会触发一个事件,在事件处理代码中应及时将收到的数据存入接收冲区,同时不应该把串口通信协议接收部分的代码放置在此事件中,否则后面到来的数据可能因为前面先到的数据没有及时处理完毕而被冲掉,导致数据丢失。

    1. 在上位机软件运行时,应该启动一个Windows线程,用于不断检测接收缓冲区是否为空,不为空时则对缓冲中的数据进行处理;
    2. 线程类创建好后,应具体编写线程类执行函数的处理过程,在其中通过状态指示变量sp实现状态机机制;
    3. 数据包的接收进度依据于状态指示变量sp。

      当数据接收顺利时,sp的变化将会引导完成一个数据包的接收过程。这样处理可以简化编程的模型,使协议易于实现;数据包接收过程中,一旦发现数据传输出错,立即将sp置为0(空闲状态),也就是状态复位,使系统进入准备接收下一个数据包的状态,这样可提高通信过程的可靠性及容错能力。


    状态机机制实现

    {------------------------------
    @功能:状态机机制实现串口通讯
    @author:成鹏致远
    @net:lcw.cnblogs.com
    -------------------------------}
    procedure TBufferThread.Execute;
    var
      s,a :string;
      sp,mylen,oddEvenCheck,i :integer;
    begin
      sp :=0; {指示读数据状态}
      a  :='';
      while True do
      begin {quelist为接收缓冲区}
        if quelist.Count <>0 then {缓冲区取数}
        begin
          s :=quelist.Strings[0];
          quelist.Delete(0);
          a :=a+s;
        end;
        if a='' then Continue;
        {是否空闲状态}
        if sp =0 then
        begin
          if ord(a[1]) =0xFE then
          begin
            sp :=1; {进入到起始标志状态}
          end
          else {起始标志错误}
          begin
    
          end;
          Delete(a,1,1);
        end
        {是否进入收到起始标志状态}
        else if sp =1 then
        begin
          mylen :=Ord(a[1]);
          sp :=2; {进入长度状态}
          Delete(a,1,1);
        end
        {是否进入长度状态}
        else if sp =2 then
        begin
          if Length(a) <=mylen +1 then Continue;
          {数据结束标志:正确}
          if ord(a[mylen +2])=0xFD then
          begin
            sp :=3; {进入数据校验状态}
          end
          else {数据结束标志:错误}
          begin
            sp :=0; {重新进入空闲状态}
          end;
        end
        {是否进入数据校验状态}
        else if sp =3 then
        begin
          for i:=2 to mylen do
          begin
            oddEvenCheck :=a[1] xor a[i];
          end;
          if oddEvenCheck =Ord(a[mylen +1]) then
          begin
            sp :=4; {校验正确,进入完成状态}
          end
          else {检验错误,进入空闲状态}
          begin
            sp :=0;
          end;
        end
        else if sp =4 then
          begin
            {这里省略处理命令与附加数据代码}
            sp :=0; {进入空闲状态}
            Delete(a,1,mylen+2);
          end;
      end;
    end;
    View Code

  • 相关阅读:
    Android Touch事件的分发过程
    使用runOnUiThread更新UI
    Sqlite访问数据库很慢的问题
    资源收集
    mongdb shard集群均衡导致宿主机CPU飙到100%处理
    Harbor安装
    springboot 启动脚本获取pid问题
    androidstudio build 时间太长处理
    修改 Docker 的 daemon.json后启动失败
    关于在centos7 64为引用android so引发的问题修复
  • 原文地址:https://www.cnblogs.com/lcw/p/3378971.html
Copyright © 2011-2022 走看看