1. 引言
1.1 工控软件的一般问题
工控软件设计可分为基于控制环和基于实时操作系统两大类。控制环是把各个功能模块连接成首尾相接的环状结构。其特点为任何一个功能模块都不能出现死循环,甚至循环次数太多的循环语句都应避免出现。以保证能够在实时意义上尽可能快地遍历各功能模块,从而满足实时多任务的需求。在各功能模块中一般用状态机来描述模块所处的状态。而实时操作系统则可以通过一套底层机制根据优先级和各任务状态调度各功能模块。此时各功能模块就以“任务”作为表现形式。但是在每个任务内部仍然为一个独立的控制环结构,仍然需要用状态机描述。本文将结合工程实践论述状态机在工控中的应用,给出通用模型和注意要点。
1.2 有限状态机
有限状态机是一种重要的思想方法。从数学的角度看,它实际是一个五元组M = (I, O, S, δ, λ),其中I,O分别表示输入输出,S为状态向量,δ为次态方程(δ: S×I ->S), 表示输出方程(λ: S×I -> O)。有限状态机从结构体系上有层级状态机,并发状态机等。层级状态机类似于软件中的子程序调度:更高层的一个状态对应于较低层的一个状态机。这个高层的状态处于底层状态机的某个状态中。这个低层状态称为子状态。与子程序调用受到系统堆栈深度制约不一样,层级状态机可以由开发者根据控制对象的层次性运动规律任意指定深度。与子程序的目的一样,层级状态机也是为了提高控制软件的模块化程度,降低状态分析的复杂度。并发状态机偏重于描述状态机的调度。状态机本身不能实现什么并发功能,并发的实现是通过软件调度的。如果把状态机理解成一个任务,那么就能理解并发的实现。在控制环中,一个“任务”就是一个功能模块,我们只需要把多个状态机串联在环中,也就是实现了多输入多输出的并发控制。此时,多个状态机在空间上是并存的,然而却是分时调用的,调用的周期等同于控制环扫描一周的时间。不过如果CPU运算速度足够快,这个周期将会足够的快,达到“实时”的程度,从而这多个状态机也就实现了“并发”运行。同理,在多任务操作系统中,“并发”的实现就更容易理解了,除了在单个任务内存在控制环的并发控制外,在任务之间也同样存在多状态机的并发运行。当然,从CPU的角度而言,只要是单核的,也就从来不存在真正的“并发”,它在任何一个特定的时间点都只能处理某个特定状态机。不过多任务操作系统却提供了一套底层机制来调度原来仅靠控制环来调度的任务。
2.有限状态机在前后台信息交互中的作用
工控系统一般都具有人机对话界面。其通常的操作模式为用户进入某个页面,选取某项操作并执行。人机对话界面通常被设定为一个独立模块。该模块软件结构为一个消息控制环。用户在硬件接口的操作会通过接口的驱动程序封装成消息加入到专属界面模块的消息队列中。消息控制环循环扫描该队列,如有新消息则提取并解释然后封装成新消息发往后台执行。前后台软件的接口模块负责分发界面消息到各个执行模块。消息应包括目标模块的编码,命令编码以及命令参数。前后台接口模块的软件结构多采用以下两种模式。
![clip_image001 clip_image001](http://www.cppblog.com/images/cppblog_com/woaidongmao/WindowsLiveWriter/9999b055a95c_F180/clip_image001_thumb.jpg)
图1 两种消息分发结构
模式一的输出结构根据消息数据的目标模块编码直接分发消息到各模块中。模式二则是根据当前系统所处的状态再分发消息到各模块中。也就是说模式二在模式一的基础上增加了一个系统级的状态机。下面我们看看两种不同的输出结构会带来何种影响。
工控软件设计者通常会碰到两种情况。一是在研发阶段,界面任务与控制任务联调时,双方均有可能出错。对于界面任务而言,有可能自身原因误发消息;而对控制任务,也有可能输出时序出错。此时需要在联调中快速定位故障,缩短研发周期。二是在产品运行中由于恶劣工况的影响,导致缓冲区数据发生异常。比如消息头的模块编码发生位翻转,则会直接导致控制任务接收到错误的界面消息。对于模式一,如果界面消息出错则会出现全局的混乱。比如模块1收到消息后开始输出一个控制时序,期间界面层又发来一个错误的消息,使其分发到模块2,于是模块2马上开始输出时序。这个不希望输出的时序在工控中有可能会导致灾难。而在联调时出现这种现象,则无法立刻判断到底是模块1还是界面层出的问题。但如果采用模式二则可以屏蔽这种混乱。如下图
![clip_image002 clip_image002](http://www.cppblog.com/images/cppblog_com/woaidongmao/WindowsLiveWriter/9999b055a95c_F180/clip_image002_thumb.jpg)
图2 不同分发结构对错误消息的处理示意图
我们可以看到由于模式二采用全局状态机标定当前软件所处的状态,消息首先会到达相应的状态处理程序,然后才进行分发。此时分发语句可以根据当前的状态屏蔽不应该被调用的模块。即使消息出现错误,也会过滤掉,等待正确消息的到来。而且可进一步优化为当收到错误消息可以通知界面层。可见在控制软件前后台的接口层增加一个标记后台状态的全局状态机有助于增强软件的健壮性。
3.状态机的错步问题
工控软件本质上是根据一定的逻辑条件给出有序的输出。根据输出的次序可以划分不同的状态。逻辑条件在嵌入式领域中就是用户的输入和传感器的状态。正是这些条件决定了状态的跃迁。在这里我们探讨的是根据传感器输入而建立的状态机。很明显,它的运行速度比前述系统级状态机高很多。这种状态机分布在软件的控制层中,正是它们使得受控对象能够有序精确高速的运行。对于这样高速运转的状态机,如果考虑不周全,会使其产生失步或者跳步,即高速状态机中的错步现象。由于控制层的状态机的跃迁条件来源于传感器信号,如果不能完全跟踪到传感器信号的变化,则跃迁条件将被遗漏,导致状态机不能跃迁到新的状态。这就会导致失步。有两种情况会导致传感器信号的检测遗漏:一是采样频率不够高,漏掉了一些保持时间较短的信号。这可以通过硬件上提高采样频率得到解决。二是状态机设计的缺陷,详见以下例子。
![clip_image003 clip_image003](http://www.cppblog.com/images/cppblog_com/woaidongmao/WindowsLiveWriter/9999b055a95c_F180/clip_image003_thumb.jpg)
图3 出现失步的状态机
由图3可以看出,状态1根据传感器a信号跃迁到状态2,状态2根据传感器b信号跃迁到状态3。如果b信号在a信号前发出了一个完整的脉冲,由于根据状态图在状态1时并不需要检测b信号,因此当跃迁到状态2以后,状态机就出现失步了。解决这个问题需要预先分析好a,b信号的关系。如果是b信号一定出现在a信号前,那不妨把状态1和2的条件判断对调,如果两个信号是并发关系的,那就要合并状态机1,2,把a,b信号作为跃迁到3的综合条件。因此解决失步问题的要点在于仔细考察受控对象处于此状态时所可能出现的传感器信号变化及其变化关系。
在处理“输入-输出对”时要注意防止状态机跳步。“输入-输出对”是嵌入式领域中经常遇到的控制模式,类似于应答机制。控制层给出一个输出,使得传感器信号产生变化并反馈,过一段时间后,控制对象运动完成,传感器信号恢复初态,此时控制层可以撤消原输出并给出相关处理。设计者会有意无意的把注意力放在“什么时候撤消输出”,因此设计出如图4(a)所示的有潜在问题的状态机。
![clip_image004 clip_image004](http://www.cppblog.com/images/cppblog_com/woaidongmao/WindowsLiveWriter/9999b055a95c_F180/clip_image004_thumb.jpg)
图4 出现跳步的状态图
可是控制对象在收到控制层输出的驱动产生运动,传感器感知运动并给出信号变化是需要时间的。根据图4(a)的状态机,很可能跳过传感器信号变化的状态,而直接到达“撤消输出”的状态。结果导致控制层的输出仅仅是一瞬而过甚至是无法输出,这就是跳步。为解决跳步问题,就需要设计者仔细分析所有的“输入-输出对”,把状态细分。如图4(b)所示,增加一个等待对象运动的新状态,确保上一状态的输出驱使对象真正运动以后才判断对象运动停止。然而在细分状态的同时也要注意防止失步。状态分得越细,越要注意分析此状态中所有可能出现的信号变化。