热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

运动控制——强大的状态机工具

状态机在运动控制中的应用1.什么是状态机?1.1状态机的概念注意1.2状态机的思想2.状态机的种类3.状态机的写法3.1switch-case结构的状态机的实现3.2

状态机在运动控制中的应用

  • 1. 什么是状态机?
    • 1.1 状态机的概念
      • 注意
    • 1.2 状态机的思想
  • 2. 状态机的种类
  • 3. 状态机的写法
    • 3.1 switch-case结构的状态机的实现
    • 3.2 状态转移表联合函数指针数组实现[^5]
    • 3.3 其他
  • 4. 状态机在运动控制中是如何应用的?
  • 5. 总结


1. 什么是状态机?

1.1 状态机的概念

      状态机,通俗的讲可以理解为一种建模方法。当一个逻辑非常复杂的程序放在面前,是非常令人头大的,使用状态转移图(是一个有向图形,包括各节点和状态转移条件)可以很好的梳理流程。以博主的理解,实现状态转移图的模块我们便可以称之为状态机。

      在程序中,一个状态机通常包括一个起始状态、一组状态集以及多个相应的状态转移条件

      构建状态转移图时,需要注意每个节点对应一个状态,在这些节点中需要有至少一个终态,当达到终态时状态机停止。一个好的状态机应该做到设计安全(不会进入未知状态或异常死循环)、清晰易懂易维护


      百度百科把状态机归纳为了4个要素:即现态、条件、动作、次态。这样的归纳,主要是出于对状态机的内在因果关系的考虑。“现态”和“条件”是因,“动作”和“次态”是果1

  • 现态:是指当前所处的状态。
  • 条件:又称为“事件”,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
  • 动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
  • 次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。


**举个栗子**
      以人的健康情况我们来举个例子,人有三个状态:健康、感冒、康复中。触发的条件有淋雨、吃药打针、好好休息、多喝热水和感觉良好。

      假设初始状态处于健康状态下,当淋雨后人会感冒。这时候我们有三种途径解决感冒这个问题:吃药打针、好好休息、多喝热水。无论哪一种方法,都能使人的状态由感冒转移到康复中。当人感觉到状态良好时,感冒好了,人也就又处于了健康状态。
在这里插入图片描述

注意


  1. 在进行状态机建模时,要区分“动作”和“状态”,前者一旦执行完就结束了,而后者如果没有条件触发,则状态会一直持续下去。

  2. 状态划分要完整


对于更形象的说明,可参考博文《设计模式:一目了然的状态机图》

1.2 状态机的思想

      关于状态机的思想,百度文库中有一个特别形象的例子2:

      网络上经常报道特级象棋大师车和多人一起下象棋,采用的方式是“车轮战”。车轮战有两种方式:(1)象棋大师先和甲开始下象棋,直到有了结果,然后才轮到乙和象棋大师对阵,下完了之后,然后是丙…,一直到和最后一个人下完。(2)象棋大师先和A下一步棋,然后再和B下一步棋,然后再和C,和…,和所有人下完一遍后,再回头从A开始,一个人接一个人。

      很显然“车轮战”的第1种方式效率不如第2种方式效率高,报道上的“车轮战”也是指的第2种方式。原因在于象棋大师的水平远远高于其他人,如果采用第一种方式,象棋大师下一步棋很快,甲需要考虑很长时间才能落子,象棋大师在和甲下棋的过程中,其他人只能等待。如果采用第二种方式,象棋大师和甲只下一步棋,然后再和乙也下一步棋,和所有人下完一步棋之后,再从甲开始,这样看起来是所有人都在下象棋,效率自然远高于第一种方式。

      “车轮战”的第2种方式,实际上就是程序中状态机的基本原理。程序中的多个任务可以看成是其他棋手,CPU是象棋大师,CPU在执行多个任务时,不再是先执行任务1,执行完任务1后,再执行任务2,而是把每个任务又划分出多个小任务(小任务中没有时间等待),CPU每次只执行每个任务中的小任务,执行完任务1中的一个小任务后,然后快速转向任务2中的小任务,按照这种模式轮询下去,由于CPU很快(象棋大师),整个程序中的任务都得到了实时的执行。任务中的小任务是按照任务的状态来划分的,故称为“状态机”。

2. 状态机的种类

      状态机可分为有限状态机(FSM)分层状态机(HFSM),其中有限状态机又可分为Moore状态机(输出只和状态有关而与输入无关)、Mealy状态机(输出不仅和状态有关而且和输入有关系)

      在实践中状态机有一个致命的缺点,当状态一旦多了之后,它的跳转就会变的不可维护3,这时我们可以把状态进行分类,把同类型的状态作为一个状态机,然后使用一个更大的状态机去维护这些自状态机,,这时用到的便是分层状态机,可以理解为状态机的嵌套,如下图所示。

在这里插入图片描述
      关于以上几者,博文《FSM(状态机)、HFSM(分层状态机)、BT(行为树)的区别》有更生动的例子可以参考。

      维护庞大数量的知识条目是个噩梦,如果使用有限状态机(FSM)、分层有限状态机(HFSM)、决策树(Decision Tree)还不能满足你的使用要求,这时试试Next-Gen AI的行为树(Behavior Tree)吧。

3. 状态机的写法

      状态机的实现由常见的几种实现方法4:

  1. 我们可以 在无限循环for(;;)或者while(1)中使用switch-caseif-else来实现简单的状态机;
  2. 使用二维状态表state-event实现,该方法逻辑清晰,但矩阵通常比较稀疏且维护麻烦;
  3. 用状态转移表stateTransfer Table实现,数组大小等于状体转移边个数,易扩展;

3.1 switch-case结构的状态机的实现

      在程序的写法上,有横着写和竖着写两种4:

  • 横着写:在事件中根据当前的状态,执行相应的操作,完成相应的状态转换。

    //声明:本代码转自 https://blog.csdn.net/zzz1014440164/article/details/79814160
    //横着写
    void event0func(void)
    {switch(cur_state){case State0:action0;cur_state = State1;break;case State1:action1;cur_state = State2;break;case State2:action1;cur_state = State0;break;default:break;}
    }void event1func(void)
    {switch(cur_state){case State0:action4;cur_state = State1;break;default:break;}
    }void event2func(void)
    {switch(cur_state){case State0:action5;cur_state = State2;break;case State1:action6;cur_state = State0;break;default:break;}
    }

  • 竖着写:在状态中判断事件,并执行相应的操作,完成相应的状态转换。

    //声明:本代码转自 https://blog.csdn.net/zzz1014440164/article/details/79814160
    //竖着写
    switch(cur_state)
    {case State0:if(event1){action0;cur_state = State1;}else if(event2){action4;cur_state = State1;}else if(event3){action5;cur_state = State2;}break;case State1:if(event1){action1;cur_state = State2;}else if(event3){action6;cur_state = State0;}break;case State2:if(event1){action3;cur_state = State0;}break;default:break;
    }

      通常我们使用横着写的结构写!!!
      这是由于竖着写使用了if -else结构,其中if语句隐含了优先级,破坏可事件间的原有关系(各个时间应该同优先级),而且竖着写在结构上是顺序查询方式(查询事件),浪费大量的时间,而且时间不可估算。

      对于横着写的方式,因为在某个时间点上状态是唯一确定的,在时间处理函数中通过switch语句可直接定位到相同状态,执行时间也可以估算。这种方式比较直观,程序执行效率较高。

3.2 状态转移表联合函数指针数组实现5


声明:本节内容转自 https://blog.csdn.net/thisinnocence/article/details/47060285

      用枚举来定义状态和事件,操作数据节点转移到目的状态用函数实现。枚举本身默认是从0开始的int类型,利用这个特点将状态转移函数放到函数指针数组中与状态对应起来,方便操作。这种实现方法易于扩展,增加状态和事件都比较容易。

状态:枚举类型
事件:枚举类型
状态转移结构体:{当前状态、事件、下个状态},定义一个全局数组来使用
状态变更函数:到下个状态(放到数组中与状态枚举对应起来)

#include
using namespace std;typedef enum{OPENED,CLOSED,LOCKED,
} State;typedef enum{OPEN,CLOSE,LOCK,UNLOCK
} Event;typedef struct{State currentState;Event event;State NextState;
} StateTransfer;typedef struct{State state;int transferTimes;
}Door;StateTransfer g_stateTransferTable[]{{OPENED, CLOSE, CLOSED},{CLOSED, OPEN, OPENED},{CLOSED, LOCK, LOCKED},{LOCKED, UNLOCK, CLOSED},
};void toOpen(Door& door);
void toClose(Door& door);
void toLock(Door& door);
typedef void (*pfToState)(Door& door);
pfToState g_pFun[] &#61; {toOpen, toClose, toLock}; //状态枚举值对应下标void toOpen(Door& door){door.state &#61; OPENED;cout << "open the door!\n";
}void toClose(Door& door){door.state &#61; CLOSED;cout << "close the door!\n";
}void toLock(Door& door){door.state &#61; LOCKED;cout << "lock the door!\n";
}void transfer(Door& door,const Event event){for (int i &#61; 0; i < sizeof(g_stateTransferTable)/sizeof(StateTransfer); &#43;&#43;i) {if(door.state &#61;&#61; g_stateTransferTable[i].currentState &&event &#61;&#61; g_stateTransferTable[i].event){g_pFun[g_stateTransferTable[i].NextState](door);door.transferTimes&#43;&#43;;cout << "transfer ok!\n";return;}}cout << "This event cannot transfer current state!!\n";return;
}void printDoor(const Door& door){string stateNote[] &#61; {"opened","closed","locked"}; // 下标正好对应状态枚举值cout << "the door&#39;s state is: " << stateNote[door.state] << endl;cout << "the door transfer times is: " << door.transferTimes << endl;
}int main(){Door door &#61; {CLOSED, 0};printDoor(door);transfer(door, OPEN);printDoor(door);transfer(door, LOCK);printDoor(door);transfer(door, CLOSE);printDoor(door);return 0;
}

3.3 其他

      对于C&#43;&#43;中状态机的类封装&#xff0c;可以参考博文《C&#43;&#43;设计模式之状态模式(二)》

4. 状态机在运动控制中是如何应用的&#xff1f;

      下面以机械臂的控制为例&#xff0c;讲解状态机在运动控制中的应用。

      当我们给一个关节下发指令后&#xff0c;机械响应到理想位置需要一定的时间&#xff0c;而这时我们已经完成了该关节的命令下发&#xff0c;在实际设备完成相应的这段时间&#xff0c;我们完全可以干些其他事情&#xff0c;例如电机状态的反馈&#xff08;速度、位置、加速度等数据的反馈&#xff09;。这就是上文提到的“象棋大师”同时与多人进行对战的思想。

      如果在运动控制过程要实现类似于多线程的功能&#xff0c;我们便可以用状态机来实现。

      如下图所示&#xff0c;假设运动控制器在常态下处于接收数据的状态&#xff0c;当收到上位机给运动控制器下发的回零命令时&#xff0c;运动控制器需要将状态置为回零状态&#xff0c;当回零状态下的动作执行完成时&#xff0c;再将状态跳至常态(接收数据状态)&#xff1b;同样的&#xff0c;当运动控制器收到执行轨迹命令时&#xff0c;状态置为执行轨迹点状态&#xff0c;当轨迹点执行动作完成后&#xff0c;状态跳转至常态……

      这样我们便实现了在运动执行的同时&#xff0c;还能接收继续接收和反馈数据的目的。

注意&#xff0c;有些运动控制器是可以写多线程程序的&#xff0c;但运动控制器一般是基于RT-Linux开发的&#xff0c;如果使用多线程可能会产生竞态&#xff0c;从而导致一些未知错误&#xff0c;因此建议在需要多线程的功能时&#xff0c;通过状态机来实现。

在这里插入图片描述
状态机主体代码如下&#xff1a;

// Author : Jack Soong
// Created on: 2019-6-26/*
&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;状态机常量及状态变量
&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;
*/
short giStateN;
enum eMainStateMachines
{eIDLE &#61; 0, // Main state machine #0 - Receive DataeSM1 &#61; 1, // Main state machine #1 - HomeeSM2 &#61; 2, // Main state machine #2 - PVTeSM3 &#61; 3, // Main state machine #3 - Motor JogeSM4 &#61; 4, // Main state machine #3 - Stop
} ;
/*
&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;状态机函数主体
&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;
*/

void MachineSequences()
{giStateN &#61; eIDLE; // 初始化初始状态bool MachineStop &#61; false;while(!MachineStop){gbReadOver &#61; TRUE;switch(giStateN){case eIDLE: // Main state machine #0 - Receive Data & Write Data{FeedbackData();ReadCmdData();if(gbHomeEnable){giStateN &#61; eSM1;}if(gbReadEnable){ReadAxisData();giStateN &#61; eSM2;}break;}case eSM1: // Main state machine #1 - Home{GoHome();giStateN &#61; eIDLE;break;}case eSM2: // Main state machine #2 - PVT{PvtMove();giStateN &#61; eIDLE;break;}case eSM3: // Main state machine #3 - Motor Jog{JogMove();break;}case eSM4: // Main state machine #3 - Stop{StopMove();break;}default: // Main state error #x - Exit{MachineStop &#61; true;break;}}usleep(10000); // 降低CPU负载}return;
}

5. 总结

  1. 使用状态机进行运动控制时首先要建立状态机模型&#xff0c;把各状态、各状态下要执行的动作以及触发条件抽象出来&#xff1b;
  2. 务必确保状态机的安全&#xff0c;要做到逻辑完整无误&#xff1b;
  3. 在进行状态机建模时&#xff0c;务必要区分开 “动作”和“状态”&#xff1b;
  4. 由于运动控制直接对应的是实际设备&#xff0c;因此在调试之前先使用打印大法测试逻辑的正确性&#xff0c;以确保在实际调试过程中不会出现危险。

毕竟&#xff0c;设备都是挺贵的~
在这里插入图片描述



  1. https://baike.baidu.com/item/%E7%8A%B6%E6%80%81%E6%9C%BA/6548513?fr&#61;aladdin#2 ↩︎

  2. https://wenku.baidu.com/view/0574da1f640e52ea551810a6f524ccbff121ca64.html ↩︎

  3. http://www.aisharing.com/archives/393 ↩︎

  4. https://blog.csdn.net/thisinnocence/article/details/47060285 ↩︎ ↩︎

  5. https://blog.csdn.net/zzz1014440164/article/details/79814160 ↩︎



推荐阅读
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • 本文讨论了一个数列求和问题,该数列按照一定规律生成。通过观察数列的规律,我们可以得出求解该问题的算法。具体算法为计算前n项i*f[i]的和,其中f[i]表示数列中有i个数字。根据参考的思路,我们可以将算法的时间复杂度控制在O(n),即计算到5e5即可满足1e9的要求。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
author-avatar
我的菜菜呀
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有