NI Developer Suite(开发者套件)中包含一个很实用的工具包——状态机工具包(State Machine Toolkit)。当然,如果你还没有的话,也可单独购买。
状态机工具包提供了在LabVIEW开发环境下,根据程序的需求设计状态图,并将设计好的状态图转换成LabVIEW状态机架构。
“状态机”的概念大约出现在上个世纪30-40年代,在学习数字电路设计时也涉及到了状态机,在数字逻辑设计中,“状态机”是一个系统设计的规范方法。在程序设计中引入状态机的概念,可使复杂的程序看起来更清晰,程序修改起来更容易。由此可见,状态机是降低软件复杂度的最佳方法。
状态机的定义:状态机是一种具有指定数目的状态的概念机,它在某个指定的时刻仅处于一个状态。状态的改变是由输入事件引起的状态变化。作为对输入事件的响应,系统可能转变到相同或不同的状态,而输出事件可能是任意产生的。
——摘自A software engineering Approach to LabVIEW 的中文译本
“状态图”是用图示的方法来描述状态及状态之间的联系,有点类似于通常程序设计中的流程图。
“LabVIEW状态机架构”是指LabVIEW开发环境下的状态机机制的LabVIEW程序代码。
本文主要讨论LabVIEW中“状态机”的概念、原理和架构以及如何使用状态机工具包实现程序设计。
“状态机”在LabVIEW那个版本中开始出现我无从考证,从检索到的资料看好像应该包含在LabVIEW 5中。现在发布的状态机工具包适用于LabVIEW 7以后的版本。
在我的“虚拟仪器设计方法之一”中提到的“轮询(polling)”的方式就是指“状态机”在GUI设计中的应用。当然,在较大的开发项目中,如果使用状态机工具包会进行设计,将会提供方便、简洁、灵活的应用程序架构。
由于状态机工具包是一个附加的工具包,只有NI Developer Suite中包含了这个工具包,所以绝大部分介绍LabVIEW的中文书籍中都没有包含这部分内容。考虑到它的实用性,觉得还是有必要将它简要的介绍给大家,正是基于这样的原因,这部分可能讲解得要相对细一些。另外,LabVIEW State Diagram Toolkit User Guide中会有更精确的描述,鉴于我的英文水平不高,所以没办法将其内容完整的表述给大家。本文的内容仅仅是我在使用中的体会,并且对它的应用也仅仅体现在程序架构设计中。其实在仪器控制(GPIB)等方面它应该更具有使用价值。
<!--[if !supportLists]-->一、 <!--[endif]-->“程序”与“状态机”间的关系
“程序”是指挥计算机并通过计算机来表达或实现我们意念(想法、要求)的一堆代码;“状态机”是用来抽象地表示这堆代码的一种机制(方法)。
实际上,我们可以将程序看作是有许多种状态的机器,这些状态相互连接,状态之间的转换是通过某些事件发生或状态结束来触发。
对于上面这种说教式的表述方式,很多初学者还是很难理解,那么就用一个生活事例来做进一步的说明吧。比如,我们早上起床后要把被子叠好,穿好衣服,拉开窗帘打开窗户置换室内的空气;然后跑到卫生间方便完,开始刮胡须、洗脸、刷牙;之后到餐厅边吃早餐边听广播;早餐后穿好外衣出门上班。基本上就是这么个程序(也可能比这个还复杂随你想象:比如把手机、钱包、带好等等)。
问题是:有没有这样的时候,连洗脸、刷牙都顾不上了,吃点东西赶紧出门上班,可能会有。
:有没有这样的时候,连吃点东西都顾不上了,赶紧出门上班,可能会有。
你看到了吧,这套程序有多复杂!其实把它抽象化后很简单,就这么几件事:起床、洗漱、早餐、上班。图1给出了用状态机表述的状态图。
图1 生活事例——状态图
图1 中:绿色椭圆——初始化,每个状态机都要求必须有一个初始化状态,也是程序的开始,用绿色表示也说明与其它的状态不同。通常的测控程序开始工作时都要有一个初始化状态,避免发生瞬变或混乱。如:做系统复位操作,程序也是从初始化开始从新运行。本例中是由“闹钟响起”导致起床的,其实还可以是恶梦惊醒、生物钟的作用等等。
在此,为了使读者有更清晰的理解,有必要再重申一下:状态机不是程序,是从程序中抽象出来的程序构架,真正的程序应该在存在于状态椭圆中。如:在初始化状态椭圆中,导致起床的事件可能有:“闹钟响起”、“恶梦惊醒”、“生物钟的作用”、“内急”、“意外的响动”等等,可以有N个。所有这些事件源都应该包含在初始化状态中,它们中任何一个事件的发生都导致“起床”(从一个状态到另一个状态)。
而引导这一过程的是图1中两个状态圆间标记:default的线段。
黄色椭圆——黄色椭圆有多少取决于满足程序要求的前提下对程序抽象的程度。如:可以把洗漱包含在起床这个状态中,那就缺少了“来不及洗脸、刷牙、吃早餐”这个事件。避免遗漏事件,这是抽象的基本原则。
红色椭圆——也是我们抽象出的一个状态,是程序的终点,所以用红色的椭圆表示。这是LabVIEW状态机(图)中所要求的。
Default ——是默认的意思,或理解为程序的正常流程。
上面我们通过生活中的事例了解了程序与状态机间的关系,同时也了解了LabVIEW状态图的结构。下面还要了解一下LabVIEW状态机的代码。
二、LabVIEW状态机程序代码
图2表示的就是LabVIEW状态机的代码。从图2可知LabVIEW中的状态机是由 while 循环与移位寄存器, case 结构及case 选择器组成。枚举类型(Enums)
枚举类型是从C语言中借用的概念,在枚举类型中可将每个连续的整数值从零开始按顺序分配给每一个名称(或字符串)。例如:红、黄、蓝、绿就分别对应于0、1、2、3。显然,使用文字或字符串要比使用数字更直观、方便、概念性更强。假如你有20个朋友,你是喜欢直接用0-19个数字来代表他们,还是用名字表示他们,那个更好呢?
枚举最佳的使用方式就是作为case结构中的选择开关。因为它更直观的显示出目前的状态和下个迭代的目标。这里引入枚举的目的就是想通过简单的文字来表示现在所处的状态,以及下一个将要进入的状态。
补充说一点,在我的“虚拟仪器设计方法之一”一文中的信号发生器的频率调节就是利用Ring控件来实现的,而Ring控件的工作方式就是“枚举”的方式。
现在我们还是要回到状态机的讨论上,与事件结构相同是每个状态都有一个case结构,在这里可进行该状态的处理程序。不同的是状态机要不停的查询是否有事件发生。还有状态机的程序要比事件结构的程序显得复杂的多,很不容易看懂。
的确,即便引入的枚举,状态机的代码还是不好读懂,到现在我也不想去读懂它,因为LabVIEW状态机工具包可以代替我们做这方面的工作,我们只需能够将程序抽象为状态图就可以了。LabVIEW状态机工具包可以通过状态图自动生成LabVIEW状态机程序构架。正是基于这样的原因,本单元讲得就相对简单些。
下一小节我们将介绍LabVIEW状态机工具包的使用方法,并通过一个应用例子了解如何应用及应用中要注意的地方。
三、LabVIEW 状态机工具包及使用方法
LabVIEW状态机工具包提供了一个状态图编辑器(State Diagram Editor),用于设计、编辑程序的状态图(状态图是根据程序的要求抽象出来的,称“程序流程图”可能更好理解些),编辑结束后关闭状态图编辑器便会在程序框图上自动地生成LabVIEW状态机构架(或者说是LabVIEW状态机代码)。
安装好LabVIEW状态机工具包后,在函数控件板上找到它的图标,双击图标“State Diagram”,然后移动鼠标将这个vi放在在程序框图上,这个操作与放置while 循环时的操作相同。如图2(参见上一单元)所示。同时,还将提供一个状态图编辑器如图3 所示。
图3 状态图编辑器
图4 up down SM的前面板(使用原来的图片)
用状态图编辑器可以方便的生成程序的状态图,但现在我们还不能使用它,为什么呢?因为现在我们还不知道程序的具体要求。下面看一个例子,也就是我们在“虚拟仪器设计方法之一”一文中的例子,不过现在是用状态机来实现(原例子是使用的事件结构)。
首先新建一个名字为:up down SM的vi,并在其的前面板中放入以下控件如图4所示。
Numeric——数字指示器,指示up、down操作的结果,但无论up down 如何操作,需保持Numeric的数值仅在0-9之间变化。
Up 按键——当“鼠标”每点击一次up键,Numeric值+1,并发出点击音提示操作者。当Numeric值加到9时,“鼠标”每点击一次,发出警告音,并保持Numeric值是9不变。
Down按键——当“鼠标”每点击一次down键,Numeric值-1,并发出点击音提示操作者。当Numeric值减到0时,“鼠标”每点击一次,发出警告音,并保持Numeric值是0不变。
Stop按键——当“鼠标”点击该键退出程序。
在这个程序中有三个输入控件:up、down、stop。他们的动作应该是引起状态改变的原因(鼠标点击事件发生)。
现在可以根据上述条件使用图3所示的状态机编辑器来编制状态图了。下面将一边编辑状态图一边了解状态图编辑器的使用方法。
在图3 所示状态图编辑器中,仅仅包含一个Init(初始化)状态,我们讲过状态机必须有一个初始化状态,下面就从初始化状态开始编辑状态图。
<!--[if !supportLists]-->1、 <!--[endif]-->初始化状态
通常的测控程序开始工作时都要有一个初始化,避免发生瞬变或混乱。状态机也有一个初始化状态,就是图3中的那个绿椭圆。
用鼠标双击绿椭圆中的“Init” 就可以修改绿椭圆的状态名,键入“初始化状态”然后用鼠标点击任何空白处,我们看到绿椭圆的名字已经变成“初始化状态”。
在绿椭圆的上边还有一个名为“default(默认)”的线段,用鼠标点击它后,它将呈现红色此时用鼠标可以将它拖拽到绿椭圆边缘的任何地方。
那么up down SM程序在“初始化状态”作什么呢?只有一个任务:将“0”这个值赋给 Numeric 指示器(或者是它的本地变量)。注:此操作将来在程序框图中完成。
“初始化状态”基本搞定,下一步该如何做?分析一下,本程序是用up、down、stop作为输入事件,那么初始化完成后,如果没有任何的输入事件发生时,程序一定会处于某个状态,那就命名为“基本状态”吧!
2、 基本状态
在图3的绿椭圆下方约2cm处,单击鼠标的右键将弹出一个“New State”指示框,单击该框将创建一个状态椭圆,在命名处键入“基本状态”后用鼠标点击任何空白处,将看到一个名字为“基本状态”的黄椭圆。
因为初始化完成后,程序自动进入该状态,所以将绿椭圆上的“default”线段拖拽到与该状态连接。
“基本状态”已搞定,下一步该如何做?当然,下步是与up、down、stop有关了。
3、 输入事件响应状态
这次三个状态一起搞定,在上面创建的黄椭圆下方约2cm处,创建三个新的状态椭圆分别命名为:“加处理”、“减处理”和“退出”。
用鼠标右键点击“基本状态”将弹出一个快捷菜单:选择“Create New Transition”后,将生成一个标号为“1”的连接线段,将该线段与“加处理”状态相连,双击“1”后键入“up键按下”;同时将“加处理”状态的“default”线与“基本状态”相连,表明该操作完成后返回将返回“基本状态”。
用鼠标右键点击“基本状态”将弹出一个快捷菜单:选择“Create New Transition”后,将生成一个标号为“1”的连接线段,将该线段与“减处理”状态相连,双击“1”后键入“down键按下”;同时将“减处理”状态的“default”线与“基本状态”相连,表明该操作完成后返回将返回“基本状态”。
用鼠标右键点击“基本状态”将弹出一个快捷菜单:选择“Create New Transition”后,将生成一个标号为“1”的连接线段,将该线段与“退出”状态相连;双击“1”后键入“stop键按下”;用鼠标右键点击“退出状态”将弹出一个快捷菜单:选择“Make Terminal” 用鼠标点击任何空白处,将看到“退出状态”变成一个红椭圆。这是程序结束的标志。
“default”表示默认的意思,表示当该状态中的程序处理完成后将自动转入下一个状态,它是每个状态椭圆自带的它既不能改变名称又不能删除。不用时可按“基本状态”椭圆中的自闭合方式处理(见图5)。
此时,按程序设计要求已用状态图实现。关闭状态编辑器的窗口(只有关闭状态图编辑器才能在程序框图上看到正确的状态机构架),将在程序框图上看到按状态图生成的状态机的构架,分别如图5、图6所示。图5 up down SM 的状态图
图6 up down SM 的状态机构架
从图6 中的case指示器可以看到又多了一个“Quit”case,这也是LabVIEW状态机所必须要求的并自动生成的,它在状态图上是不出现的。
注意:此时LabVIEW开发环境中的“运行”按钮是折断的,提示我们不能运行。因为状态机构架中没有任何程序代码。
当把程序代码填入up down SM程序中,(代码与up down event一样,放入不同的状态case中 )程序才算真正的完成了,具体形式见图7-图11。图8 up down SM程序“基本状态”代码(空,没有添加任何代码) 图9 up down SM程序“加处理”代码
图10 up down SM程序“减处理”代码
图11 up down SM程序“退出”代码
图12 up down SM程序“Quit”代码
运行up down SM程序,按程序要求可以up down Numeric指示器中的数字并伴有不同提示音。像我们在“之一”所做的那样,还要完成最后的程序up down SM Frequency,也就是把Numeric作为一个仿真信号源的频率调节器。仿真信号源放在那个状态中呢?当然是放在“基本状态”里了。因为我们讲过,在没有事件发生时程序是处于基本状态下。好了,图13、图14给出了完整的程序框图和程序前面板图。
图13 up down SM Frequency程序框图
图14 up down SM Frequency程序前面板图
细心的朋友可能会发现,图13与图8 相比不仅仅多了程序代码,还在while 循环与case中加入了一个100mS的定时器。如不加入这个定时器,在程序运行时,从Windows任务管理器中可看到CPU的使用率为:100%。这是很危险的事情,很容易导致系统崩溃。在我作一个数据采集(包含AO和AI)项目中,当程序运行时,CPU的使用率达到100%,这时只要移动一下鼠标程序就会停下来(非常可怕)。为什么CPU的使用率会达到100%呢?我们讲过状态机是以“轮询(polling)”的方式工作的,它会占用CPU的资源。这是与事件结构有区别的地方。当我们加入定时器后降低了“轮询”的速率,使CPU的使用率大大下降(不同的机器会有些差别,目前是30%)。
如果我们要在上述程序中,修改状态图,可在图13所示while 循环上用鼠标点击右键,从弹出的快捷菜单中选择:Edit State Diagram 选项,则会弹出State Diagram Editor界面,修改后关闭State Diagram Editor新的状态机构架就出现了。
在图13所示while 循环上用鼠标点击右键,从弹出的快捷菜单中还可以选择:Unlock Code From State Diagram 选项,选择该项后就不能在编辑状态图了(千万要小心),建议程序打包前都不要选择此项。
程序确定后,状态图可以打印下来存档。
至此,通过例子已经将LabVIEW状态机工具包和基本使用方法介绍完了。还是做个小结吧!
四、小结
两篇文章使用不同的程序构架,完成了同一个需求。这里面的差异和不同,朋友们自己去总结吧!什么时候选择事件结构、什么时候使用状态机?从实践中慢慢去体会吧。
使用状态机架构对解决有序控制问题及降低软件的复杂度方面是最有效的方法。但很多初学者在这里遇到麻烦了,其实他们对状态机编辑器的使用、操作的非常正确,但就是不会设计程序。问题出在哪里呢?问题出现在:他们搞不清什么是状态,什么是状态的改变及如何改变或由谁来改变等问题上,所以他们画不出状态图。希望本文对这些朋友会有一些帮助。
下面是一些忠告:
1.在编程序前,一定要搞清楚应用程序的要求。然后,根据要求抽象出状态,分清事件来源,再着手制作状态图。
2.对已经编辑完并生成代码的状态图(或者说从新打开状态图编辑器)需要从新编辑时,特别是要删除原有的某个状态时,一定要先将该状态内的程序代码移出到while 循环外(如果还想使用这些代码的话),否则这些代码也会被删除。特别是,如果在其它状态case中引用了这些部件的参考、本地变量、属性和方法时,会导致整个程序的混乱。
3.状态机的构架(while 循环和case等)只有在选择了:Unlock Code From State Diagram (在while 循环上用鼠标点击右键,从弹出的快捷菜单中选择:Unlock Code From State Diagram 选项)选项后才能被删除。所以在此操作之前最好保存(打印)状态图。
4.由于引入了枚举类型,所以状态椭圆名称和线段名称最好用概念明确的名字或字符串表示。这样可大大增强程序的可读性。
5.编制这样的程序或数据采集程序时,最好把Windows的任务管理器打开,随时查看CPU的使用率,这也是至关重要的。
NI Developer Suite(开发者套件)中包含一个很实用的工具包——状态机工具包(State Machine Toolkit)。当然,如果你还没有的话,也可单独购买。
状态机工具包提供了在LabVIEW开发环境下,根据程序的需求设计状态图,并将设计好的状态图转换成LabVIEW状态机架构。
“状态机”的概念大约出现在上个世纪30-40年代,在学习数字电路设计时也涉及到了状态机,在数字逻辑设计中,“状态机”是一个系统设计的规范方法。在程序设计中引入状态机的概念,可使复杂的程序看起来更清晰,程序修改起来更容易。由此可见,状态机是降低软件复杂度的最佳方法。
状态机的定义:状态机是一种具有指定数目的状态的概念机,它在某个指定的时刻仅处于一个状态。状态的改变是由输入事件引起的状态变化。作为对输入事件的响应,系统可能转变到相同或不同的状态,而输出事件可能是任意产生的。
——摘自A software engineering Approach to LabVIEW 的中文译本
“状态图”是用图示的方法来描述状态及状态之间的联系,有点类似于通常程序设计中的流程图。
“LabVIEW状态机架构”是指LabVIEW开发环境下的状态机机制的LabVIEW程序代码。
本文主要讨论LabVIEW中“状态机”的概念、原理和架构以及如何使用状态机工具包实现程序设计。
“状态机”在LabVIEW那个版本中开始出现我无从考证,从检索到的资料看好像应该包含在LabVIEW 5中。现在发布的状态机工具包适用于LabVIEW 7以后的版本。
在我的“虚拟仪器设计方法之一”中提到的“轮询(polling)”的方式就是指“状态机”在GUI设计中的应用。当然,在较大的开发项目中,如果使用状态机工具包会进行设计,将会提供方便、简洁、灵活的应用程序架构。
由于状态机工具包是一个附加的工具包,只有NI Developer Suite中包含了这个工具包,所以绝大部分介绍LabVIEW的中文书籍中都没有包含这部分内容。考虑到它的实用性,觉得还是有必要将它简要的介绍给大家,正是基于这样的原因,这部分可能讲解得要相对细一些。另外,LabVIEW State Diagram Toolkit User Guide中会有更精确的描述,鉴于我的英文水平不高,所以没办法将其内容完整的表述给大家。本文的内容仅仅是我在使用中的体会,并且对它的应用也仅仅体现在程序架构设计中。其实在仪器控制(GPIB)等方面它应该更具有使用价值。
<!--[if !supportLists]-->一、 <!--[endif]-->“程序”与“状态机”间的关系
“程序”是指挥计算机并通过计算机来表达或实现我们意念(想法、要求)的一堆代码;“状态机”是用来抽象地表示这堆代码的一种机制(方法)。
实际上,我们可以将程序看作是有许多种状态的机器,这些状态相互连接,状态之间的转换是通过某些事件发生或状态结束来触发。
对于上面这种说教式的表述方式,很多初学者还是很难理解,那么就用一个生活事例来做进一步的说明吧。比如,我们早上起床后要把被子叠好,穿好衣服,拉开窗帘打开窗户置换室内的空气;然后跑到卫生间方便完,开始刮胡须、洗脸、刷牙;之后到餐厅边吃早餐边听广播;早餐后穿好外衣出门上班。基本上就是这么个程序(也可能比这个还复杂随你想象:比如把手机、钱包、带好等等)。
问题是:有没有这样的时候,连洗脸、刷牙都顾不上了,吃点东西赶紧出门上班,可能会有。
:有没有这样的时候,连吃点东西都顾不上了,赶紧出门上班,可能会有。
你看到了吧,这套程序有多复杂!其实把它抽象化后很简单,就这么几件事:起床、洗漱、早餐、上班。图1给出了用状态机表述的状态图。
图1 生活事例——状态图
图1 中:绿色椭圆——初始化,每个状态机都要求必须有一个初始化状态,也是程序的开始,用绿色表示也说明与其它的状态不同。通常的测控程序开始工作时都要有一个初始化状态,避免发生瞬变或混乱。如:做系统复位操作,程序也是从初始化开始从新运行。本例中是由“闹钟响起”导致起床的,其实还可以是恶梦惊醒、生物钟的作用等等。
在此,为了使读者有更清晰的理解,有必要再重申一下:状态机不是程序,是从程序中抽象出来的程序构架,真正的程序应该在存在于状态椭圆中。如:在初始化状态椭圆中,导致起床的事件可能有:“闹钟响起”、“恶梦惊醒”、“生物钟的作用”、“内急”、“意外的响动”等等,可以有N个。所有这些事件源都应该包含在初始化状态中,它们中任何一个事件的发生都导致“起床”(从一个状态到另一个状态)。
而引导这一过程的是图1中两个状态圆间标记:default的线段。
黄色椭圆——黄色椭圆有多少取决于满足程序要求的前提下对程序抽象的程度。如:可以把洗漱包含在起床这个状态中,那就缺少了“来不及洗脸、刷牙、吃早餐”这个事件。避免遗漏事件,这是抽象的基本原则。
红色椭圆——也是我们抽象出的一个状态,是程序的终点,所以用红色的椭圆表示。这是LabVIEW状态机(图)中所要求的。
Default ——是默认的意思,或理解为程序的正常流程。
上面我们通过生活中的事例了解了程序与状态机间的关系,同时也了解了LabVIEW状态图的结构。下面还要了解一下LabVIEW状态机的代码。
二、LabVIEW状态机程序代码
图2表示的就是LabVIEW状态机的代码。从图2可知LabVIEW中的状态机是由 while 循环与移位寄存器, case 结构及case 选择器组成。
图2 LabVIEW状态机代码
图2中的带有左右箭头和下拉箭头含有字符的小蓝色框我们称为“枚举常数”。这里还要先介绍一下“枚举”的概念。
枚举类型(Enums)
枚举类型是从C语言中借用的概念,在枚举类型中可将每个连续的整数值从零开始按顺序分配给每一个名称(或字符串)。例如:红、黄、蓝、绿就分别对应于0、1、2、3。显然,使用文字或字符串要比使用数字更直观、方便、概念性更强。假如你有20个朋友,你是喜欢直接用0-19个数字来代表他们,还是用名字表示他们,那个更好呢?
枚举最佳的使用方式就是作为case结构中的选择开关。因为它更直观的显示出目前的状态和下个迭代的目标。这里引入枚举的目的就是想通过简单的文字来表示现在所处的状态,以及下一个将要进入的状态。
补充说一点,在我的“虚拟仪器设计方法之一”一文中的信号发生器的频率调节就是利用Ring控件来实现的,而Ring控件的工作方式就是“枚举”的方式。
现在我们还是要回到状态机的讨论上,与事件结构相同是每个状态都有一个case结构,在这里可进行该状态的处理程序。不同的是状态机要不停的查询是否有事件发生。还有状态机的程序要比事件结构的程序显得复杂的多,很不容易看懂。
的确,即便引入的枚举,状态机的代码还是不好读懂,到现在我也不想去读懂它,因为LabVIEW状态机工具包可以代替我们做这方面的工作,我们只需能够将程序抽象为状态图就可以了。LabVIEW状态机工具包可以通过状态图自动生成LabVIEW状态机程序构架。正是基于这样的原因,本单元讲得就相对简单些。
下一小节我们将介绍LabVIEW状态机工具包的使用方法,并通过一个应用例子了解如何应用及应用中要注意的地方。
三、LabVIEW 状态机工具包及使用方法
LabVIEW状态机工具包提供了一个状态图编辑器(State Diagram Editor),用于设计、编辑程序的状态图(状态图是根据程序的要求抽象出来的,称“程序流程图”可能更好理解些),编辑结束后关闭状态图编辑器便会在程序框图上自动地生成LabVIEW状态机构架(或者说是LabVIEW状态机代码)。
安装好LabVIEW状态机工具包后,在函数控件板上找到它的图标,双击图标“State Diagram”,然后移动鼠标将这个vi放在在程序框图上,这个操作与放置while 循环时的操作相同。如图2(参见上一单元)所示。同时,还将提供一个状态图编辑器如图3 所示。
图3 状态图编辑器
图4 up down SM的前面板(使用原来的图片)
用状态图编辑器可以方便的生成程序的状态图,但现在我们还不能使用它,为什么呢?因为现在我们还不知道程序的具体要求。下面看一个例子,也就是我们在“虚拟仪器设计方法之一”一文中的例子,不过现在是用状态机来实现(原例子是使用的事件结构)。
首先新建一个名字为:up down SM的vi,并在其的前面板中放入以下控件如图4所示。
Numeric——数字指示器,指示up、down操作的结果,但无论up down 如何操作,需保持Numeric的数值仅在0-9之间变化。
Up 按键——当“鼠标”每点击一次up键,Numeric值+1,并发出点击音提示操作者。当Numeric值加到9时,“鼠标”每点击一次,发出警告音,并保持Numeric值是9不变。
Down按键——当“鼠标”每点击一次down键,Numeric值-1,并发出点击音提示操作者。当Numeric值减到0时,“鼠标”每点击一次,发出警告音,并保持Numeric值是0不变。
Stop按键——当“鼠标”点击该键退出程序。
在这个程序中有三个输入控件:up、down、stop。他们的动作应该是引起状态改变的原因(鼠标点击事件发生)。
现在可以根据上述条件使用图3所示的状态机编辑器来编制状态图了。下面将一边编辑状态图一边了解状态图编辑器的使用方法。
在图3 所示状态图编辑器中,仅仅包含一个Init(初始化)状态,我们讲过状态机必须有一个初始化状态,下面就从初始化状态开始编辑状态图。
<!--[if !supportLists]-->1、 <!--[endif]-->初始化状态
通常的测控程序开始工作时都要有一个初始化,避免发生瞬变或混乱。状态机也有一个初始化状态,就是图3中的那个绿椭圆。
用鼠标双击绿椭圆中的“Init” 就可以修改绿椭圆的状态名,键入“初始化状态”然后用鼠标点击任何空白处,我们看到绿椭圆的名字已经变成“初始化状态”。
在绿椭圆的上边还有一个名为“default(默认)”的线段,用鼠标点击它后,它将呈现红色此时用鼠标可以将它拖拽到绿椭圆边缘的任何地方。
那么up down SM程序在“初始化状态”作什么呢?只有一个任务:将“0”这个值赋给 Numeric 指示器(或者是它的本地变量)。注:此操作将来在程序框图中完成。
“初始化状态”基本搞定,下一步该如何做?分析一下,本程序是用up、down、stop作为输入事件,那么初始化完成后,如果没有任何的输入事件发生时,程序一定会处于某个状态,那就命名为“基本状态”吧!
2、 基本状态
在图3的绿椭圆下方约2cm处,单击鼠标的右键将弹出一个“New State”指示框,单击该框将创建一个状态椭圆,在命名处键入“基本状态”后用鼠标点击任何空白处,将看到一个名字为“基本状态”的黄椭圆。
因为初始化完成后,程序自动进入该状态,所以将绿椭圆上的“default”线段拖拽到与该状态连接。
“基本状态”已搞定,下一步该如何做?当然,下步是与up、down、stop有关了。
3、 输入事件响应状态
这次三个状态一起搞定,在上面创建的黄椭圆下方约2cm处,创建三个新的状态椭圆分别命名为:“加处理”、“减处理”和“退出”。
用鼠标右键点击“基本状态”将弹出一个快捷菜单:选择“Create New Transition”后,将生成一个标号为“1”的连接线段,将该线段与“加处理”状态相连,双击“1”后键入“up键按下”;同时将“加处理”状态的“default”线与“基本状态”相连,表明该操作完成后返回将返回“基本状态”。
用鼠标右键点击“基本状态”将弹出一个快捷菜单:选择“Create New Transition”后,将生成一个标号为“1”的连接线段,将该线段与“减处理”状态相连,双击“1”后键入“down键按下”;同时将“减处理”状态的“default”线与“基本状态”相连,表明该操作完成后返回将返回“基本状态”。
用鼠标右键点击“基本状态”将弹出一个快捷菜单:选择“Create New Transition”后,将生成一个标号为“1”的连接线段,将该线段与“退出”状态相连;双击“1”后键入“stop键按下”;用鼠标右键点击“退出状态”将弹出一个快捷菜单:选择“Make Terminal” 用鼠标点击任何空白处,将看到“退出状态”变成一个红椭圆。这是程序结束的标志。
“default”表示默认的意思,表示当该状态中的程序处理完成后将自动转入下一个状态,它是每个状态椭圆自带的它既不能改变名称又不能删除。不用时可按“基本状态”椭圆中的自闭合方式处理(见图5)。
此时,按程序设计要求已用状态图实现。关闭状态编辑器的窗口(只有关闭状态图编辑器才能在程序框图上看到正确的状态机构架),将在程序框图上看到按状态图生成的状态机的构架,分别如图5、图6所示。
图5 up down SM 的状态图
图6 up down SM 的状态机构架
从图6 中的case指示器可以看到又多了一个“Quit”case,这也是LabVIEW状态机所必须要求的并自动生成的,它在状态图上是不出现的。
注意:此时LabVIEW开发环境中的“运行”按钮是折断的,提示我们不能运行。因为状态机构架中没有任何程序代码。
当把程序代码填入up down SM程序中,(代码与up down event一样,放入不同的状态case中 )程序才算真正的完成了,具体形式见图7-图11。
图7 up down SM程序“初始化状态”代码
图8 up down SM程序“基本状态”代码(空,没有添加任何代码) 图9 up down SM程序“加处理”代码
图10 up down SM程序“减处理”代码
图11 up down SM程序“退出”代码
图12 up down SM程序“Quit”代码
运行up down SM程序,按程序要求可以up down Numeric指示器中的数字并伴有不同提示音。像我们在“之一”所做的那样,还要完成最后的程序up down SM Frequency,也就是把Numeric作为一个仿真信号源的频率调节器。仿真信号源放在那个状态中呢?当然是放在“基本状态”里了。因为我们讲过,在没有事件发生时程序是处于基本状态下。好了,图13、图14给出了完整的程序框图和程序前面板图。
图13 up down SM Frequency程序框图
图14 up down SM Frequency程序前面板图
细心的朋友可能会发现,图13与图8 相比不仅仅多了程序代码,还在while 循环与case中加入了一个100mS的定时器。如不加入这个定时器,在程序运行时,从Windows任务管理器中可看到CPU的使用率为:100%。这是很危险的事情,很容易导致系统崩溃。在我作一个数据采集(包含AO和AI)项目中,当程序运行时,CPU的使用率达到100%,这时只要移动一下鼠标程序就会停下来(非常可怕)。为什么CPU的使用率会达到100%呢?我们讲过状态机是以“轮询(polling)”的方式工作的,它会占用CPU的资源。这是与事件结构有区别的地方。当我们加入定时器后降低了“轮询”的速率,使CPU的使用率大大下降(不同的机器会有些差别,目前是30%)。
如果我们要在上述程序中,修改状态图,可在图13所示while 循环上用鼠标点击右键,从弹出的快捷菜单中选择:Edit State Diagram 选项,则会弹出State Diagram Editor界面,修改后关闭State Diagram Editor新的状态机构架就出现了。
在图13所示while 循环上用鼠标点击右键,从弹出的快捷菜单中还可以选择:Unlock Code From State Diagram 选项,选择该项后就不能在编辑状态图了(千万要小心),建议程序打包前都不要选择此项。
程序确定后,状态图可以打印下来存档。
至此,通过例子已经将LabVIEW状态机工具包和基本使用方法介绍完了。还是做个小结吧!
四、小结
两篇文章使用不同的程序构架,完成了同一个需求。这里面的差异和不同,朋友们自己去总结吧!什么时候选择事件结构、什么时候使用状态机?从实践中慢慢去体会吧。
使用状态机架构对解决有序控制问题及降低软件的复杂度方面是最有效的方法。但很多初学者在这里遇到麻烦了,其实他们对状态机编辑器的使用、操作的非常正确,但就是不会设计程序。问题出在哪里呢?问题出现在:他们搞不清什么是状态,什么是状态的改变及如何改变或由谁来改变等问题上,所以他们画不出状态图。希望本文对这些朋友会有一些帮助。
下面是一些忠告:
1.在编程序前,一定要搞清楚应用程序的要求。然后,根据要求抽象出状态,分清事件来源,再着手制作状态图。
2.对已经编辑完并生成代码的状态图(或者说从新打开状态图编辑器)需要从新编辑时,特别是要删除原有的某个状态时,一定要先将该状态内的程序代码移出到while 循环外(如果还想使用这些代码的话),否则这些代码也会被删除。特别是,如果在其它状态case中引用了这些部件的参考、本地变量、属性和方法时,会导致整个程序的混乱。
3.状态机的构架(while 循环和case等)只有在选择了:Unlock Code From State Diagram (在while 循环上用鼠标点击右键,从弹出的快捷菜单中选择:Unlock Code From State Diagram 选项)选项后才能被删除。所以在此操作之前最好保存(打印)状态图。
4.由于引入了枚举类型,所以状态椭圆名称和线段名称最好用概念明确的名字或字符串表示。这样可大大增强程序的可读性。
5.编制这样的程序或数据采集程序时,最好把Windows的任务管理器打开,随时查看CPU的使用率,这也是至关重要的。
文章评论(0条评论)
登录后参与讨论