状态机在PLC中的应用

PLC基础

我们先来看一个简单的程序。
输入输出控制

一个输入控制一个输出,这时候我们如果需要将输出保持,就需要一些改造。

输入输出保持控制

这个时候我们发现当X0 OFF时,Y0还是处于ON状态,即达到了保持的目的。
但我们不能一直让Y0保持ON,还是需要对Y0的状态进行控制的,这时要加入一个断开的控制。

启动、保持、停止

这就是比较经典的启动、保持、停止电路。

但是在PLC程序中,我们比较少用这样的写法,这个写法算是电气时代的遗留,给电气工程师过度之用。

下面看看PLC中的写法。

SET/RESET

这样X0为启动,X1为停止,Y0为输出,语义就相对清晰了。下面我们加上注释。语义会更明显。

在这里插入图片描述

语义不明,即逻辑不明,是写程序和调试程序最大的敌人。

现在有了基础结构,我们用这个结构写一个跑马灯程序。

跑马灯

这是一个间隔10S的跑马灯程序。为了进一步说明,我们用状态图对上面的程序进行表达。

有向图

在这里,我们对定时器进行了抽象,即定时器与动作在逻辑上是等价的。在实际应用中,这个动作,可以是定时器,也可以是气缸动作,也可以是伺服运动等等,甚至可以是多个动作组成的动作集合。

到这里我们完成了PLC的基础部分,用这个逻辑,我们可以无限延展下去,一般的程序,完全可以用这个逻辑扩展。

那我们看看可不可以在上图的基础上,进一步抽象。我们换一种表达方式。二维图表。

状态1状态2状态3
动作1条件1->状态2
动作2条件2->状态3
动作3条件3->状态1

在这里我们看到了与状态图不太一样的视角,但是两种表达是等价的。那这个图表对我们有什么用呢?
我们在这个图表上进一步抽象,给每一步动作加上一个序号看看。

序号状态1状态2状态3
100动作1条件1->状态2
110动作2条件2->状态3
120动作3条件3->状态1

那这幅表格对我们写程序有什么意义呢,我们可以根据这个表格,对我们上面的程序进行一下改造。

操作票模式梯形图版

这样我们将程序改造成,序号、状态、条件、序号转移,写在一起;而动作单独分开。
这样的好处是,我们可以直接从表格出发,直接写程序,逻辑更严谨的同时,也不会增加心智负担。
并且我们将动作与之分离,动作改写更加方便,尤其是多个动作组成的动作组等情况。

我们来看看这种情况用ST表达的样式。

操作票模式ST版

在表达上更加简洁和直观,如果有从高级编程语言兼职PLC的,可以参考一下,不见得一定用梯形图来表达。

这个模式,被称为操作票模式,序号即是操作票,我个人更愿意称之为状态机竖着写

那么状态机是什么?

状态机

“一般来说,在理论上,你需要知道如何计算”时间复杂度“和”空间复杂度“(time and space complexity);如果你要写一个解析器,可能还需要知道状态机(state machine)的概念;除此之外,并不需要知道特别多的理论。”——《黑客与画家》

在plc编程中,极少需要讨论数据结构以及算法的复杂度问题,也不是本文讨论的主题。我们主要看状态机。

状态机百科

状态机由状态寄存器和组合逻辑电路构成,能够根据控制信号按照预先设定的状态进行状态转移,是协调相关信号动作、完成特定操作的控制中心。有限状态机简写为FSM(Finite State Machine),主要分为2大类:

第一类,若输出只和状态有关而与输入无关,则称为Moore状态机

第二类,输出不仅和状态有关而且和输入有关系,则称为Mealy状态机

以上是百科给的相关资料,我们在PLC只研究第二类。

状态机综述

  1. 状态机是有向图形,由一组节点和一组相应的转移函数组成。
  2. 状态机通过相应一系列事件而“运行”。
  3. 每个事件都在属于“当前”节点的转移函数的控制范围内,其中函数的范围是节点的一个子集。
  4. 函数返回“下一个”(也许是同一个)节点。
  5. 这些节点中至少有一个必须是终态。当到达终态,状态机停止。

有限状态机是一种概念性机器,它能采取某种操作来响应一个外部事件。具体采取的操作不仅能取决于接收到的事件,还能取决于各个事件的相对发生顺序。之所以能 做到这一点,是因为机器能跟踪一个内部状态,它会在收到事件后进行更新。为一个事件而响应的行动不仅取决于事件本身,还取决于机器的内部状态。另外,采取的行动还会决定并更新机器的状态。

这样一来,任何逻辑都可建模成一系列事件/状态组合。

下面这个就是状态机的有向图形的表达形式,之前我们已经见过了。

有向图

状态机四要素

现态、条件、动作、次态

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

在上文中,事件有两种,一种是启动事件;另外一种是定时器的计时事件。
上文中条件满足时,没有触发动作,都是进行的状态转移。动作都是在现态下执行的。

状态机表示方法

  1. 一组状态集(states)
  2. 一个起始状态(start state)
  3. 一组输入符号集(alphabet)
  4. 一个映射输入符号和当前状态到下一状态的转换函数(transition function)的计算模型
  5. 状态、状态转换函数、与动作映射列表

本文中的状态集合为:(状态1,状态2,状态3)

起始状态,在这里并没有明确表达,在ST表达中,我们给了一个默认的0,即开机之后,未启动之前,状态值为0,这算是本文的起始状态,也是有向图中的第一个起始点。

一组输入符号集,在本例中,集合中只有一个元素,启动。实际工作中,这个集合可能比较大,几十几百都有可能。

计算模型,这里我们是通过梯形图或者ST语言来表达的。

映射列表,这个可以看作是状态机的核心,因为如果写不出状态机的映射列表,那么就说明对业务梳理的不到位,有含糊的地方,在写程序的时候,就会不知所措。

我们可以用有向图,也可以用二维表来表示映射列表。

状态1状态2状态3
动作1条件1->状态2
动作2条件2->状态3
动作3条件3->状态1

现在再看这个表格,不知道大家有没有更清晰一些。
之前我们是从上向下的分析这个表格,写出了操作票模式的程序,那我们可不可以从左到右理解这个表格?我们横着写试试。

状态机在PLC中的应用之横着写

解释器模式1

这是按照横着阅读映射列表写出来的程序,但是这个有一点不好的地方在于,状态和动作(此处为计时器)是耦合在一起的,如果遇到的是一个多个动作的集合,程序就会显得很啰嗦。

下面是改造。

解释器2

通过增加一个解释字,我们将应用层与驱动层进行了分离。

应用层,这个层主要用来管理状态,事件,以及状态转移。
驱动层,只按照解释字进行动作,不用去管应用层的逻辑。
这样就大大的降低了心智成本。

这种写法就是解释器模式,我个人喜欢称之为状态机横着写,当然在每个人都有不同的称呼,比如有人称之为卡条件等等。

我们再看看解释器模式在ST中的表达。

在这里插入图片描述

至此,我们关于状态机在PLC中的两种应用模式就讨论完了。

当然状态机不止这两种,比如,我们在本例中,由于写得比较简单,所以实质上这个状态机也可以用独热码表示。
独热码在PLC中我们可以用一个字进行表示,然后用移位指令进行状态管理,也是可以的。

BUG

没错,写程序,怎么可能没有bug呢。

在本文中,由于只专注于讨论状态机,那么关于启动相关的状态,我们是忽略的,这就导致多次点击启动,会出现同时出现多状态的情况,这个需要在实际工作中注意。这里不做讨论,当然最后的ST表达的解释器模式实际上是解除了这个bug的,大家有兴趣可以自行尝试。

文中提及的程序在这里下载

  • 2
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
以下是一个使用有限状态机开发的简单运动控制的例程代码: ```c // 定义状态枚举类型 enum State { IDLE, ACCELERATING, CRUISING, DECELERATING, STOPPED }; // 初始化状态为 IDLE State state = IDLE; // 定义加速度、速度、位置等变量 float acceleration = 1.0; float maxSpeed = 10.0; float currentSpeed = 0.0; float position = 0.0; // 定义时间间隔(ms) int interval = 20; // 定义计时器 unsigned long previousMillis = 0; void setup() { // 初始化硬件 } void loop() { // 获取当前时间 unsigned long currentMillis = millis(); // 判断是否到达时间间隔 if (currentMillis - previousMillis >= interval) { // 更新计时器 previousMillis = currentMillis; // 根据当前状态进行不同的运动控制 switch (state) { case IDLE: // 停止运动 currentSpeed = 0.0; break; case ACCELERATING: // 加速 currentSpeed += acceleration * interval / 1000.0; if (currentSpeed >= maxSpeed) { // 达到最大速度,进入 CRUISING 状态 currentSpeed = maxSpeed; state = CRUISING; } break; case CRUISING: // 保持匀速 currentSpeed = maxSpeed; break; case DECELERATING: // 减速 currentSpeed -= acceleration * interval / 1000.0; if (currentSpeed <= 0.0) { // 停止运动,进入 STOPPED 状态 currentSpeed = 0.0; position = 0.0; state = STOPPED; } break; case STOPPED: // 停止运动 currentSpeed = 0.0; break; } // 更新位置 position += currentSpeed * interval / 1000.0; // 控制电机或执行其他运动控制操作 // ... } } ``` 以上代码,使用一个状态枚举类型来表示不同的运动状态,然后根据当前状态进行不同的运动控制操作。在 loop 函数,使用计时器来控制时间间隔,然后根据当前状态进行运动控制和更新位置等操作。最后,可以通过控制电机或执行其他运动控制操作来实现具体的运动控制功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值