4.4 事件结构
在实际应用中,经常要实现类似下面的功能。VI运行后,当用户操作前面板上的输入控件,比如将输入控件的值改变后,计算结果也会随之更新(使用测量仪器时,改变输入的大小,输出就会有相应的改变)。对这个功能需求,该如何利用LabVIEW实现呢?下面将通过例4.13做详细介绍。
【例4.13】 编写一个简易加法器VI,其输入参数有“加数1”和“加数2”,输出结果是“和”。要求一旦VI运行后,在前面板上改变“加数1”或“加数2”的值,结果“和”的值便会跟着做相应改变。
这个VI的算法部分很简单,编写好的VI程序框图如图4.45所示。为了实现VI运行后,计算结果会随着输入参数的变化而改变,可以将图4.45所示的程序代码放入一个While循环中,并在前面板上创建一个开关按钮,在程序框图上,用连线将该开关按钮与While循环的条件端子连接起来,如图4.46所示。
图4.45 两个数的求和
图4.46 循环查询(轮询)
运行此VI,在前面板上改变输入参数“加数1”和“加数2”的值,可以看到输出控件“和”的值就会跟着改变。此种实现方法被称为“轮询”。很容易理解,即该VI运行后,每一次While循环,都会去读输入控件的值,然后进行加法运算;最后,将新的计算结果输出给“和”显示控件。这样的编程思路,可以确保当输入控件的值发生改变时,输出结果一定跟着更新。但是细心的读者会发现,以这样的思路编程,其实也做了无用功。因为当输入控件的值未发生变化时,其实无须做相加运算,但是该VI还是照例又做了一次加法运算。所以,采用“轮询”方式来判断控件的值有否改变,会消耗较多CPU的使用时间,不利于处理复杂、多线程的问题。
那么,如何实现只有当输入控件的值改变时才进行加法运算呢?可以利用本节将要介绍的事件结构。
图4.47 事件结构中的超时分支
同前面介绍的其他程序结构一样,事件结构位于“函数”选板→“编程”→“结构”子选板上。在LabVIEW环境下,选中一个事件结构,将其拖曳到程序框图面板上,如图4.47所示,其默认的是超时分支。图4.48给出了一个事件结构的例子。
从外形上看,事件结构与条件结构相类似。两者的区别在于:条件结构要执行哪个分支,由“条件选择器”输入数据来决定;而事件结构则是根据发生的事件来决定随后执行哪个分支。条件结构的“选择器标签”可以直接写入;而事件结构的“事件标签”,则是要通过编辑事件对话框进行设置。
图4.48 事件结构(其中某个分支的界面)
在事件结构的边框上右击,在弹出的快捷菜单上,选择“编辑本分支所处理的事件”,可以弹出编辑事件对话框,在此对话框内,可以删除、添加和编辑本分支内处理的事件。
事件结构中的事件,分为两种类型:通知事件和过滤事件。通知事件不带问号,是在LabVIEW处理用户操作之后发出的,表明某个用户的操作已经发生;而带问号的为过滤事件,是在LabVIEW处理用户的操作之前发出的,允许用户确认事件或改变事件数据后再进行相应的操作。
下面通过例4.14,来加深理解通知事件与过滤事件的区别。
【例4.14】 通知事件与过滤事件的区别
此例VI的程序框图如图4.49和图4.50所示,其中,一个While循环结构里套了一个事件结构。对事件结构要编写两个分支,一个分支是“停止”控件的值改变,在此分支中,将“停止”控件通过连线与While循环结构的条件端子连接起来;另一个分支是“前面板关闭?”,在此分支中,要将一个true的布尔型常量赋给此分支右边框上的“放弃?”端子。“停止”控件的值改变这个事件的分支没有带问号,即它是通知事件;而“前面板关闭?”有问号,是过滤事件。
运行此VI,当用鼠标单击前面板右上角的“关闭”按钮时,可以发现此VI并没有响应,这是因为VI在“前面板关闭?”事件分支中,放弃了此次事件行为。这样的程序设计,可以防止误操作的发生。而单击前面板上的“停止”控件,可以看到该VI退出运行状态。
图4.49 通知事件示例
图4.50 过滤事件示例
在学习了事件结构的基础知识后,再学习如何利用事件结构实现例4.13的功能,为此,编写好的VI的程序框图如图4.51所示。其中,在While循环结构内嵌套了一个事件结构。事件结构要编写两个分支,一个分支是“停止”控件的值改变,在此分支中,将“停止”控件通过连线与While循环结构的条件端子连接起来;另一个分支是“加数1”“加数2”值改变,将求和的程序代码放在此分支中。
图4.51 利用循环事件结构实现加法运算
事件结构在使用时,常会被放在一个While循环内,此种实现方式也被称为循环事件结构。利用循环事件结构,可以实现只有当“加数1”或者“加数2”的值发生改变时,才会进行求和运算。可见,使用事件结构的优点,是在不牺牲与用户的交互前提下,可将CPU的占用降低到最少。
可以通过查看Windows的任务管理器,比较一下轮询与循环事件结构的程序性能。在相同的计算机条件下,两者的比较结果如图4.52和图4.53所示。可以看出,采用轮询的方式,CPU的占用率为24%;而改为使用循环事件结构,CPU的占用率仅为1%。
图4.52 利用循环事件结构时的CPU占用情况
例4.13展示了合理利用事件结构进行编程的优点,并介绍了事件结构的基本使用方法。另外,还有一些其他应用需求必须使用事件结构来实现,比如在前面板上绘图,要捕获鼠标的移动、释放等动作。本教材会在第14章具体介绍如何利用事件结构实现在前面板上绘图。
图4.53 利用轮询时的CPU占用情况
为事件结构配置事件前,应先阅读LabVIEW帮助中的“使用事件的说明与建议”。选中事件结构,右击,在弹出的快捷菜单中选择帮助,即可查看到有关事件结构的相关说明和建议等具体内容。下面归纳出事件结构使用时的常见问题,供大家参考。
常见问题7:前面板无法响应其他事件。
在使用事件结构时,首先要注意理顺程序各个结构之间的逻辑关系。如果出现前面板无法响应其他事件的问题,可以尝试将事件编辑框下方的一个选择框“锁定前面板直至本事件分支完成”取消勾选。
常见问题8:如何避免过多占用CPU资源?
从上述介绍可以看出,使用轮询方式会过多占用CPU资源。那么,针对轮询结构,有没有改善的办法呢?回答是肯定的。
图4.54 在While循环里加定时
在VI运行能够接受的时间允许范围内,可以通过在While循环内添加定时函数的办法降低CPU的占用率。如图4.54所示,在While循环内增加一个定时函数,设置每两次循环之间相隔500ms。运行此VI,会发现CPU的占用率会大幅度下降。即虽然两次循环之间相隔了500ms,但在前面板上改变输入控件的值,结果“和”的值也会及时跟着改变。
这种实现方式,虽然不是严格意义上的及时更新输入控件的值,但是从用户操作及响应的快慢上讲,是完全可以接受的,也是一种可行的解决方案,可以有效降低对CPU资源的占用。
当然,实际中经常需要定时执行一段代码,为此,也可以在While循环里加上适当的定时来实现。
常见问题9:关于“停止”按钮的处理。
针对图4.51所示的VI,一个错误的编写结果如图4.55所示。停止按钮位于事件结构外,运行该VI时,发现停止按钮不能正确响应。分析发现,出错的原因在于对事件结构的使用上。在图4.55所示的VI中,事件结构只有一个分支,此分支是判断前面板的控件x和y的值是否发生改变,程序会等待此事件;而由于没有响应“停止:值改变”的事件分支,所以前面板上的停止按钮控件不能被正确执行。
针对上述问题,一种解决思路是将停止按钮放入到事件结构中,再为事件结构增加一个分支“停止:值改变”,如图4.51所示。另一种解决办法是利用超时分支,如图4.56所示,即增加一个超时分支,将超时接线端接入一个常量10,如此,再运行该VI,发现停止按钮可以正确响应了。前一种思路是较为严谨的解决办法,所以建议使用前一种方法。
图4.55 使用事件结构编程存在错误的VI的程序框图
图4.56 增加超时分支
常见问题10:事件结构中超时分支的作用。
在创建事件分支时,可以看到默认生成的第0分支就是超时分支。超时接线端子默认设置为-1,表示永不执行超时事件,循环处于空闲等待状态。
这里,试着将求和的程序代码放到超时分支中,编写的程序框图如图4.57所示,其中,超时接线端子接入一个常量500,表示超过500ms无任何事件触发时,则执行超时事件。运行此VI,改变超时接线端子接入的常量值,在前面板上观察其运行的效果。图4.58给出了利用超时分支的另外一种思路,与图4.54相比,此情况下,事件结构的超时分支的功能,与定时函数的相类似。
可以看出,利用事件结构中的超时分支,也可以实现定时执行一段程序代码的功能。在VI处理的事件不多的情况下,可以采用此种方式;而如果事件较多,则不建议采用此种方式实现VI的定时执行功能。
另外,需要补充的是,并不是所有的事件结构都必须有超时分支。如果事件分支可以覆盖所有可能情况,就不需要超时分支。例如,图4.51所示的利用“循环事件结构”实现加法运算,只有两个分支,分别是“停止控件的值改变”和“加数1、加数2值改变”,并没有超时分支。但如果事件分支并不能包含所有的可能情况,则可以利用超时分支使VI能响应别的事件,例如对常见问题9的处理。
图4.57 事件结构的超时分支
图4.58 事件结构的超时分支
常见问题11:如何实现定时?
定时执行某个任务,这在实际应用中会经常遇到。那如何实现定时功能呢?在例4.5、常见问题8和10中,已经包含着定时的功能。这里再总结一下定时功能的程序实现方式。
实现定时功能,主要有3种方式,分别是在循环结构里添加定时函数,利用事件超时分支,或者利用专门的定时结构实现。其中,前两种方式的计时器的精度由系统确定,依据使用的平台,定时器的精度可能低于1毫秒。定时结构子选板如图4.59所示。定时结构主要应用于实时系统和FPGA,也可以在Windows系统下使用,精度要比前两种方式高,但会占用更多的资源。
图4.59 定时结构子选板
常见问题12:LabVIEW中的定时函数有哪些?
LabVIEW中的定时函数共有3个,分别是“等待(ms)”“等待下一个整数倍毫秒”和“时间延迟”,如图4.60所示。
“时间延迟”是一个快速VI,可以通过弹出对话框的方式对具体的延迟时间进行设置,单位是秒(s)。
“等待(ms)”函数的功能是等待指定长度的毫秒数,比如在一个While循环内放入一个“等待(ms)”函数,毫秒等待设为10毫秒,如果此时毫秒计时值为12毫秒,则当毫秒计时值为22毫秒时,此次循环结束、进入下一次循环。
“等待下一个整数倍毫秒”函数的功能,是先等待,直至毫秒计时器的值为毫秒倍数中指定值的整数倍。例如,在一个While循环内放入一个“等待下一个整数倍毫秒”函数,毫秒倍数设为10毫秒,如果当前毫秒计时值为12毫秒,则While循环将会等待8毫秒,直到毫秒计时值为10毫秒的整数倍即20毫秒时,才会结束本次循环、进入到下一次循环。
图4.60 LabVIEW中的定时函数
最后,再对事件结构的使用做一个小结。①事件结构的优点是在不牺牲与用户的交互前提下,将CPU资源的占用降低到最小;②一些应用需求必须使用事件结构来实现,比如在前面板上绘图,要捕获鼠标的移动、释放等动作;③停止按钮控件最好放在“值改变”的分支中;④默认分支“超时分支”不是每个VI都会用到,而是要根据实际需求进行选择;⑤一个事件分支里,不要处理多个事件;⑥若前面板无法响应时,可以尝试将事件编辑框下方的一个选择框“锁定前面板直至本事件分支完成”取消勾选。