1、数值溢出
图1:数值溢出错误
图1 中的 VI 只做了一个简单乘法 300*300 ,答案肯定是 90000,但程序中乘法节点给出的结果却是 24464。乘法节点是不会错的,错误是由于程序中使用的数据类型是 I16。I16 能表示的最大数目只有32767,所以在乘法计算中出现了溢出。
避免此类错误的方法是,在程序中使用短数据类型时,一定要确认程序中的数据绝不会超出该类型可以表示的范围。
2、For 循环的隧道
数据传入传出循环结构可以通过移位寄存器(Shift Register)和隧道(Tunnel)两种方式。隧道又有两种类型:带索引的和不带索引的。
移位寄存器一般用在需要局部变量的情况下,循环运行一次的输出数据要作为下次运行的输入数据使用;循环外的数组数据通过带索引的隧道在循环体内就可以直接得到数组元素;除此之外,简单地在循环内外传递数据,使用一般的隧道就可以了。
值得一提的是,如果一个数据传入循环体,又传出来,那么就应该使用移位寄存器或带索引的隧道来传递这个数据,尽量不要使用不带索引的隧道。因为 For 循环在运行时,循环次数有可能为0。在循环次数为0时,大多数情况,用户还是希望传出循环的数据就是传入值,但使用不带索引隧道时,输入值有时会被丢失的。如果使用移位寄存器,即使循环次数为0,也不会丢失传入的数据。因为移位寄存器在循环上的两个接线柱指向的实际是同一块内存(参考:LabVIEW 程序的内存优化),而输入输出两个隧道指向的是不同的内存,数据不一定相同。
图2:For 循环上的隧道
图2中的程序, vi reference 传入,再传出循环均使用了隧道。如果循环次数为0(Controls数组为空),vi reference 再传出循环时,信息就丢失了。这不但有可能造成后续程序的错误,而且由于 vi reference 的信息丢失,再无法关闭打开的 vi,造成了程序泄漏。
Error 数据线(黄绿色的粗线)在传入传出数组时,一定要使用移位寄存器。原因还不仅是为了防止在循环次数为0时,错误信息丢失。通常一个节点的 Error Out 有错误输出,意味着后续的程序都不应该执行。在错误的情况下继续执行程序代码,风险非常大,可能会引起程序,甚至系统崩溃。只有使用移位寄存器,某次循环产生的错误才会被传递到后续的循环中,从而及时阻止后续循环中的代码被运行。
3、循环次数
与其它语言相比,LabVIEW 的 For 循环有一大特点,在某些情况下它并不要求一定要输入循环次数,而可以根据输入数组的大小自动决定循环次数。通过带索引的隧道,可以把数组分解成元素传递到循环体内,此时不需另行设置循环次数N,循环的次数就是数组的长度。每次循环,带索引的隧道便给出一个元素。
循环体上可以有两个或更多的输入数组使用带索引的隧道,此种情况下容易引起错误。这时,循环的次数等于几个数组中长度最短的那个数组的长度。如果另外又设置了循环次数N,那么循环次数就是N与输入数组长度这两者的最小值。调试时,如果发现一个本该运行多次的循环没有运行,那么很可能就是因为它的一个输入数组是空的。
While 循环同样也可以使用带索引的隧道,但是不建议大家这么用——如果需要用到带索引的隧道,还是使用 For 循环更为适宜。因为 while 循环的循环次数不由数组个数决定,而是由停止条件决定的。如使用了带索引的隧道,你还需要考虑当数组大于、小于循环次数时,程序应该如何处理,所以还是在循环体内作索引比较方便。如果希望循环次数与数组大小保持一致,那自然是用 For 循环的程序更加清晰易懂了。
4、移位寄存器的初始化
图3:没有初始化的移位寄存器
看图3中这个程序,因为它在 while 循环上使用了带索引的隧道,所以可读性不那么好。array out 的运行结果是什么,还要考虑一阵子才能给出答案。实际上这个程序,即使输入不变,每运行一次,array out 的结果都是不一样的,它的长度一直在增加。这个问题就出在没有给程序中的移位寄存器一个初始值。
没有初始化的移位寄存器,总是保存上次运行结束时的数据。这个特点在某些情况下可以被程序员利用,比如用它当作全局变量,随时把数据存入或取出(一个例子是《如何使用 VI 的重入属性》中的图4)。但多数情况下移位寄存器还是被用作为循环内部的局部变量的,这时就一定要对它初始化,以防止潜在的错误。
5、Cluster
图4:Cluster 传递数据出错
图4的程序中有个奇怪的错误,明明应该是 weight 加 1 怎么运行完后的结果变成了high 加 1 了呢?原因是 Cluster 中的元素有个顺序,这个顺序可以和界面上看到的顺序不一致。分别鼠标右击程序中的两个 Cluster,选择“Reorder Controls in Cluster”,就可以看到每个元素在 cluster 中的编号。info out 中的 high 实际上编号是 2,第三个元素。
为了避免 cluster 中用可能出现的错误,以及让 cluster 应用起来更方便,使用 cluster 最好遵循以下原则:
1. 凡是用到 cluster 的地方,就为它造一个类型定义(《在程序中使用类型定义》),在程序所有要用到这个 cluster 的地方,都使用类型定义的实例。这样一是可以保证所有的 cluster 都完全一致,避免图4 这种错误;二是一旦需要变动 cluster 中的元素,只需在类型定义中更新就可以了,不必挨个 VI 修改。
2. 凡是在需要解开(unbundle)或打包(bundle)的地方统统使用 unbundle by name 和 bundle by name 来实现。使用带名字的 bundle,unbundle 可以直观的显示出 bundle 种元素的名字,这样不会因为顺序的不同而导致错误的连线。
6、并行运行
LabVIEW 是自动多线程的编程语言,这一点在方便用户的同时,也会带来一些麻烦。比如最常见的情况,多线程会引起数据或资源的竞争错误(race condition)。
图5:两个并行运行的子 VI
图5是一个简单的两个子 VI 并行运行的例子,在这个例子中就隐藏着一个潜在的问题。并行执行的两部分程序,先后次序是不定的。有可能关闭程序中的引用数据(绿色的线上的数据)的节点在子 VI B 结束前运行。而子 VI B 是要用到这个参考数据的,这是子 VI B 就会因为它所需要的数据失效而产生错误
除此之外,我们还给大家整理了一些LabVIEW编程过程中常用的技巧,希望可以帮助大家提高编程效率。
1、样式(NXG、新式、系统、经典)不同的控件不仅仅是外观上有所差别,其中有些属性并不支持所有样式的控件
2、数值控件可以通过显示格式加入单位。
3、每当不知道应该输入什么类型时,右键创建常量进行参考。
4、为了即时抛出异常,最常用的模式(模版)就是带错误处理的子VI框架。这个模板能够保证在有错误时可以快速通过本VI,进入下一个节点。没有错误发生时,执行本VI的程序代码。
5、浮点数的比较是很严肃的事情,要注意数据精度间的关系。因为一个微小的差别也会导致不相等。比如同为Pi常数,一个是双精度数据(上),另一个是单精度数据(下),比较结果是不相等。
6、布尔转数字是非常有用的,尤其和创建数组函数结合,可以实现类似于3-8译码器的判断部分非常简便。
7、在许多场合下,错误簇就是实现定序数据流的最佳方法。
8、整数只能在选择器标签中显示数字,并且它无法为每个值添加分支,只能一个一个的填写。下拉列表因为使用的也是整数,所以特点与整数相同。组合框控件也无法为每个值添加分支,只能一个一个的填写。列表和选项卡可以通过右键直接为每个值添加分支。
9、可以利用一次while循环来规范图形化代码的分布,避免代码的随意放置(乱丢乱放)。
10、在一个主线程内,while结构必须放在事件结构外,就会一直在等。如果不想因此造成死机等待,有以下方案:
a、设置超时事件,比如设置50,那么在50ms内如果没有触发事件结构,那么将执行超时事件中的程序;
b、(推荐)一个while就是一个线程,如果需要事件结构介入,那么可以把其他服务程序放到另外一个while循环中,事件结构单独一个循环,实现多线程效果(并行)。
11、定时循环不是do---While循环,在当次循环时间内的数据更新都有用。
12、for循环中通过错误簇连接到条件端子,可以及时停止循环,下图错误的传递也可换为移位寄存器。
13、用对话框作为事件的响应并不是一个好的选择,因为对话框需要操作者及时处理,如果没有及时处理,事件一直处于等待状态,这样其它事件源发出的响应就会产生堆积。所以在事件处理程序中应该避免使用对话框。事件处理程序应该尽可能的简洁迅速,避免其它事件被堆积。
14、当需要弹出简单对话框时,可以直接调用应用程序封装的函数。
15、涉及网络编程问题,先保证把windows自带的防火墙关了,然后能ping到。本地可先用localhost测试。
16、附上一些快捷键技巧:
(1)Ctrl + E 切换前后面板
(2)Ctrl + B 在程序框图面板,把错误的断线删除掉
(3)Ctrl + R 运行程序
(4)Ctrl + S 保存程序
(5)Shift + 鼠标放在框边上,可以实现拖动
(6)Ctrl + 鼠标放在框边上,可以实现复制
(7)Ctrtl + Q 退出程序
(8)Ctrl+T 分屏
(9)选中图标,Ctrl + "+"号键 :实现图标放大,或者选中,在控制面板选择“17pt 应用程序字体”,再选择大小