美化程序 – 隐藏程序框图上的大个 Cluster

 
    在编写某些程序的时候可能会遇到如图1 所示的情形:即用到了一个极为复杂的数据类型常量。 这个常量由于体积巨大,使得在程序框图无论怎么摆放都让人看起来不太舒服。如何才能把这个程序改造得美观一些呢?

图1:体积巨大的常量会有碍观瞻

 
   要解决这个问题,只有设法把这个常量在主程序框图上隐藏起来。通常可以用以下两种方法。
    第一种方法:把这个常数变换成控件,再把控件隐藏起来。这种方法比较简单,但是也有弊病。①容易引起误解:控件一般表示有值传入,其他人读程序读到这里就可能搞不清楚这个值是从哪里传来的了;②如果要修改常量 Cluster 中某一个元素的值,操作起来比较麻烦。
 
    第二种方法,也就是我向大家推荐的:把它隐藏到更深层的子 VI 中去。具体操作方法如下:
 
    如图2 先给这个复杂数据类型建立一个 Strict Type Def。我的建议是为所有程序中用到的 Cluster 都建立一个 Strict Type Def。这样可以为以后的程序维护省去很多麻烦。

图2:Strict Type Def.

 
    然后然后再建立一个新的 VI,把我们要隐藏的这个个头巨大的常量摆放在这个 VI 中,并且连接一个 Indicator ,以把它的值传出来。VI 的接线板采用 4-2-2-4 格式的,最下层第 3 个接线端用于传出 VI 中唯一的数据,如图3 所示。

图3:用于隐藏个头巨大常量的 VI

    这个 VI 的图标要做得小巧漂亮,如图4,图标不一定非要做成正方形。只要 B&W 和 256 Colors 中的图标形状一样,我们就可以画出不规则图标了。详细方法可以参考《制作不规则图形的子VI图标》

图4:常量数据 VI 的图标

 
    把这个新造出来的常量数据 VI 拖到程序框图上,把它的输出链接到刚才链接常量的地方,再把位置摆放好。现在我们的程序是不是漂亮多了

图5:改造后的程序框图

 
 
 相关文章:
 

 
 
 

Caption 和 Label 的书写规范

    LabVIEW控件的 Caption 和 Label 的特性和用途很相似,都是给了控件一个有意义的名字。因此,在很多场合没有必要刻意区分他们。
     Caption 和 Label 的最主要区别在于,Caption 可以在程序运行的时候改变;而 Label 则不可以,一旦程序运行,就固定不变了。鉴于这一点,Caption 和 Label 的用途也略有区别。Label 应该是给程序自己用的,比如在程序中需要根据控件的名字找到它,那就得跟据  Label 来找,而不能用Caption来找;Caption 是为了给用户看的,有时控件的名字在运行到不同状态下需要发生改变,此时显示在界面上的就应该是 Caption。
 
    推荐大家按照下面的规范使用  Caption 和 Label。
 
    先给 VI 分一下类:
    1. 底层 VI:用户不会直接使用到的 VI,作为 subVI 随程序一起发布。
    2. 用户界面 VI:VI 前面板是给用户看的程序界面的一部分。
    3. 程序接口 VI:VI 是提供给用户,在他们编程时,当作 API 被调用。
 
    对于  Caption 和 Label 一个共同的书写规范是:使用有意义的文字,在使用英语短语命名时,单词之间用空格分隔,不应该有重名。
    不同点列于下表:
Label Caption
底层 VI 显示出来 使用 LabVIEW 的默认状态,即 Caption 为空。
用户界面 VI 隐藏
多语言版本中,只使用英语
显示
多语言版本中,使用本地化语言
程序接口 VI 隐藏
多语言版本中,只使用英语
不用标注控件的默认值
显示
多语言版本中,使用本地化语言
在后面加一括号,括号内标注控件的默认值和数据单位

 
 
 

LabVIEW 程序中的线程 1 – LabVIEW 是自动多线程语言

一. LabVIEW 是自动多线程语言

一般情况下,运行一个 VI,LabVIEW 至少会在两个线程内运行它:一个界面线程(UI Thread),用于处理界面刷新,用户对控件的操作等等;还有一个执行线程,负责 VI 除界面操作之外的其它工作。LabVIEW 是自动多线程的编程语言,只要 VI 的代码可以并行执行,LabVIEW 就会将它们分配在多个执行线程内同时运行。
图1 是一个正在运行的简单 VI,它由单独一个一直在运行的循环组成。在此情况下,这个执行循环的线程运算负担特别重,其它线程则基本空闲。在单 CPU 计算机上,这个线程将会占用几乎 100% 的 CPU 时间。图1 中的任务管理器是在一个双核 CPU 计算机上截取的。这个循环虽然在每一个时刻只能运行在一个线程上,但这并不表示他始终不变的就固定在一个线程上。他可能在这个时刻运行在这个线程上,另一时刻又被调度到其他线程上去运行了。(关于这一段,在看完本文第二章:LabVIEW 的执行系统,会有更深刻的理解。)
因此,图1 这个程序最多只能占用两个 CPU 内核 50% 的总 CPU 时间,两个 CPU 内核各被占用一些。
图1:双核 CPU 计算机执行一个计算繁重的任务
图2 是当程序有两个并行的繁重计算任务时的情况,这时 LabVIEW 会自动把两个任务分配到两个线程中去。这时即便是双核 CPU 也会被 100% 占用。

图2:双核 CPU 计算机执行两个计算繁重的任务

从上面的例子,我们可以得出如下两个结论。
1. 在 LabVIEW 上编写多线程程序非常方便,我们应该充分利用这个优势。一般情况下,编写程序时应当遵循这样的原则:可以同时运行的模块就并排摆放,千万不要用连线,顺序框等方式强制它们依次执行。在并行执行时, LabVIEW 会自动地把它们安排在在不同线程下同时运行,以提高程序的执行速度,节省程序的运行时间。今后多核计算机将成为主流配置,多线程的优势会更为明显。
特殊的情况也是有的,即用多线程时,运行速度反而慢。 以后我们再来详细介绍此类特殊情况。
2. 假如有一个或某几个线程占用了 100% 的 CPU,此时系统对其他线程就会反应迟钝。例如,程序的执行线程占用了100% 的 CPU,那么用户对界面的操作就会迟迟得不到响应,甚至于用户会误认为程序死锁了。所以在程序中要尽量避免出现 100% 占用 CPU 的情况。 目前大多数的计算机还是单核单个 CPU 的,因此要避免任何一个线程试图 100% 占用 CPU 的情况(如图1、图2 所示的程序)。
此类问题最简单的解决方法就是在循环内加一个延时。在图1、图2 的例子中,如果在每个循环内加上 100 毫秒的延时,CPU 占用率就会接近为 0。
对于总运行时间较短的循环(假如CPU 占用总时间不足 100毫秒)就没有必要再加延时了。
在很多情况下,运行时间很长的循环往往都只是为了等待某一个任务的完成,在此类循环体的内部几乎没有耗时较多的、又有意义的运算,所以必须在循环框内加延时。
对于那些确实非常耗费 CPU资源 的运算(如需要 100% 地占用 CPU 几秒钟甚至更长的时间),最好也在循环内插入少量延时,从而让 CPU 至少 空出 10% 的时间给其它线程或进程。你的程序会因此而多运行 10% 的时间。 但是由于 CPU 可以及时处理其他线程的需求,比如界面操作等,其他后台程序也不会被打断,用户反而会感觉到程序似乎运行得更加流畅。反之,假如你的程序太霸道了,CPU长期被某些运算所霸占,而别的什么都不能做,这样的程序,用户是不可能满意的。
还有这样一种情况,比如某些运算可能需要程序循环 1,000,000次,每执行一次仅需要 0.1 毫秒。此时如果在每次循环里都插入延时,即使是 1 毫秒的延时,也会令程序速度减慢 10 倍。 这当然是不能容忍的。这种情况下,就不能在每次循环都加延时了,但可以采用每一千次循环后加上 10 毫秒延时的策略。此时,程序仅减慢 10% 左右,而 CPU 也有处理其他工作的时间了。
在处理界面操作的 VI 中,常常会使用到 While 循环内套一个 Event Structure 这种结构形式。在这种情况下,就没有必要再在循环内添加延时了。因为程序在执行到 Event Structure 时,如果没有事件产生,程序不再继续执行下去,而是等待某一事件的发生。这是,运行这段代码的线程会暂时休眠,不占用任何 CPU 资源,一直等到有事件发生,这个线程才会重新被唤醒,继续工作。

相关文章:

《致加西亚的一封信》读后感

    在封建社会里,统治者们(包括君王、地主、家长、帮会头领等等)及其御用文人们,搞出了一大套理论,来要求被管理者对自己效忠。并且把对自己的忠诚提升到了道德和法律的层面,让被统治者不得不忠于自己、为自己卖命。封建制度垮台后,这些理论也遭到了摒弃。
    现在在企业里,雇员们都很清楚:雇主发给自己的钱是自己应得的,无需对雇主感激。雇主不过是利用自己当赚钱,他们不会去真正关心一个员工的个人发展,而员工也就没必要自作多情地把企业当成是自己的家。企业效益不好时,雇主会毫不犹豫把多余的员工一脚踢出去;反过来,员工跳槽,也不会背负任何道义上的压力。
    在这样的环境下,大多数雇员对待工作仅仅是应付了事,能够做到不对老板布置的任务讨价还价,已经很不错了; 而想让员工发挥主动性,为企业分忧简直是痴人说梦。没有了法律和道德上的约束,雇主们为如何笼络住员工们的心,让他们对企业尽忠职守而苦恼不已。除了许以厚禄要职,雇主们还必须不厌其烦,转弯抹角地劝说自己的员工。当然荣誉也好,道德也好,都不那么容易获得雇员的赞同。到最后,行之有效的说辞可以归结为只有一条“作为雇员,你现在表面上是在为公司工作,其实你是在为自己工作,做得好,会对你自己有如何如何的好处…….”确实,要想让别人帮助自己,最好的方法就是要让他知道,他这么做是对他自己是有好处的。然而, 即便是这一条,雇主们翻来覆去说得天花乱坠,也未必打动得了雇员。 更何况很多雇员已经认定雇主和自己是对立的,认定雇主不可能真心替自己着想,认定他们多半是在骗人。
 
    《致加西亚的一封信》一文发表以后,让所有的雇主眼前一亮。虽然文章的中心思想还是那些陈词滥调,但《致加西亚的一封信》无疑说得更加婉转动听。 并且它把道理融入传奇故事,娓娓道来,对雇员们更加受用。加之这是篇短文,连同其他文章编成小册子,正好可以让雇员们带在身边随时激励自己。于是雇主们开始大量采购这本小册子发放给自己的员工,从而造就了一本书的销售神话。
 
    刚才说了半天,都是我对于为什么这本书如此畅销的想法。与其说它深得读者的心,不如说它深得老板们的心。下面说一说我对书中观点的看法。
    这本书的观点还是我说过的“陈词滥调”——敬业,这是为雇员自己好,而不是为老板好。总的来说,我还是非常赞成这个观点的。对于我等没有背景的小人物来说,做事不认真,不主动是很难成功的。打工也一样,不做一个优秀的员工,就没有机会升职加薪,更谈不上将来成为老板了。从这一点而言,雇员个人对公司的付出不会白费。再有在很多情况下,雇员和雇主的共同利益会大于他们之间的矛盾,所以,虽然雇主对雇员苦口婆心的劝说,本质虽是为了自己多赚钱,但并非是在骗人。老板与员工的双赢,对双方都是最好的结局。
 
    我这个人抽象思维能力比较差,所以凡事喜欢举例说明。举个例子说明什么是优秀员工:
    老板一早叫你去菜场看看今天的土豆是什么价钱。次等员工会找借口说自己在忙,或者身体不行什么的,反正偷懒不肯去; 自作聪明的就会建议:为什么要跑一趟呢,打个电话,找个快递什么的行不行啊;当然,如果你二话不说,立马跑到菜市场,然会回来向老板汇报:土豆两毛五一斤,老板也不见得会真的对你满意;优秀的员工是这样的,在菜市场考察一圈,然后回来向老板建议,土豆两毛五一斤是近期的最低价,值得买进。 还有,哪家土豆最好,还有其他商品价格大致如何,是否也值得买,和其他菜场比较又如何如何等等。还有,商贩的电话也要来了,打个电话会立刻有人送货上门。
 
    下一篇读后感将讨论《突破停滞》
 
 
老爸的读后感:
    我也浏览过《致加西亚的信》,给我留下深刻印象的只有一句话:“一个人应该永远同时从事两件工作:一件是目前所从事的工作;另一件则 是真正想做的工作。如果你能将该做的工作做得和想做的工作一样认真,那么你一定会成功 ,因为你在为未来做准备,你正在学习一些足以超越目前职位,甚至成为老板或老板的老板 的技巧。当时机成熟,你已准备就绪了。”
   你现在是雇员,那么也不要只站在雇员的角度看待一切。应该说,雇员的忠诚和敬业,对老板和对雇员本人都是有利的。当你欣赏和赞美周围的人的时候,别忘了把同样的赞美恩施给你的老板、上司、长辈和师长。
 
 
 
 

工作之余,玩个小游戏

这个游戏非常简单,是 Windows 自带的,用来测试你点击鼠标的速度。
 
按住键盘上 [Win]+[F] 三秒钟,游戏就开始了。
 
在此声明,游戏若造成不良后果(比如被吓倒了,打翻了茶杯,惹怒了老板等)请找微软,本人概不负责

如何使用 VI 的重入属性(Reentrant)

    在 VI Properties -> Execution 中可以选择 VI 的Reentrant Execution属性(中文译为:可重入执行)。 我们在《LabVIEW 程序的内存优化》一文中讨论过,尽量不要把 VI 设置为重入属性,因为这样就多占用了内存,降低了运行效率。此外,如果不加注意的话,还可能引发多线程不安全的问题。 尽管可重入 VI 在 LabVIEW 中不是必须的,但是在某些情况下使用可重入 VI 可以简化我们的程序。那么在什么情况下可以使用 Reentrant VI 呢?

    首先看一下图 1 所示的程序,程序中调用的两个子 VI 是同一个 VI,并且不是可重入的 VI。LabVIEW 是自动多线程的语言,那么图中的两个子VI会不会同时执行呢。一定不会的。如果程序中调用的是两个不同的子 VI,LabVIEW 有可能会同时在不同的线程执行它们,但对于两次调用相同的子 VI,LabVIEW 一定要等一个执行完,再执行另一个。


图1:并行的两个相同子 VI

    其原因是,LabVIEW 会为每个 VI 都开辟一块内存用于数据存储。作为子 VI,每次被调用,它的局部变量的数据都是被存在同一地址的。与 C 语言相对照,在默认情况下,VI 是不可重入的,VI 中所有的局部变量都是静态变量。如果 LabVIEW 在不同的线程下执行同一 VI,那么两个线程就会同时对这一块数据地址进行读写,就会导致这一块地址内数据的混乱。为避免此类不安全情况的出现,LabVIEW 必须等待一个子 VI 执行结束,再执行另一个子 VI。
    如果需要图1 中的两个子 VI 同时运行,比如子 VI 所做的工作是读取文件这样一类耗时多、但CPU占用不大的操作,则并行执行可以大大提高效率。这时,就需要把子 VI 设置为可重入了。LabVIEW 在不同的地方调用一个可重入 VI 时,会给它另外分配一个独立的数据地址空间。这样就做到了线程安全。在两个线程执行的子 VI 使用两份在不同的地址存储的数据,也就不会造成混乱。但是千万要注意, 这个“在不同的地方”调用:不可重入的 VI 的局部变量与 C 语言中非静态变量的含义是不同的。在后面提到的计数器的例子可以验证这一点。

     我觉得我说得挺清楚了,出道题目给大家测试一下:


图2:延时子 VI


图3:计算延时的主 VI

    图2 是一个子 VI 的代码,功能是延时 1000 毫秒。图3 是主 VI 的代码,并行调用同一子 VI 两次,并计算程序的执行时间。运行主 VI,total time 的值是多少?
    答案在文章最后。

    这是可重入 VI 的一种用途,即希望在不同的线程里同时执行同一个子 VI。
    另外还有一种情况下,也可以用到可重入 VI:即需要使用到子 VI 中局部变量保存的数据,而在不同的调用处,这些数据是独立不同的。这句话可能解释得不那么清楚,看下面例子就会比较容易理解些。


图4:计数子 VI

 
图5:测试计数的主 VI

    图 4 是一个可重入子 VI 的代码,功能是计算这个VI被运行的次数,每运行一次,输出的 count 值就增加1。图5 是调用它的主VI,用于演示这个计数器。执行主VI一次,output 1 和 output 2 的值分别是 10 和 20,表示这个子 VI 在两处分别被调用了 10 次和 20 次。
    如果把图 4 中的 VI 改为不可重入,则 output 1 和 output 2 的输出值是不确定的。大家可以自己试一试,再想一下原因。

    当使用递归结构时,参与了递归调用的 VI 是需要被同时调用多次的。因此这些 VI 中的变量必须是局部的,也就是说参与了递归调用的 VI 必须都被设置为可重入。参考:在 LabVIEW 中实现 VI 的递归调用

 

    测试题目答案:如果图2的子 VI 没有设置为可重入,则 total time = 2000;如果设置为可重入则 total time = 1000。

下载示例:

     计算两次调用可重用 VI 的延时
     利用可重用 VI 计数

相关文章:

    我和 LabVIEW
    LabVIEW 程序的内存优化
    LabVIEW 程序中的线程 1 

 

编辑

 

 

 

 

LabVIEW 程序的内存优化 1

全书: https://labview.qizhen.xyz/

一. VI 在内存中的结构

打开一个VI的属性面板(VI Properties),其中的“内存使用”(Memory Usage)是用来查看这个VI内存占用情况的。它显示了一个VI内存占用所包含的四个主要部分:前面板、框图、代码和数据,以及这四个部分的总和。但在打开一个VI时,这四段内容并不是同时都会被LabVIEW调入内存的。
当我们打开一个主VI时,主VI连同它的所有子VI的代码和数据段都会被调入内存。由于主VI的前面板一般情况下是打开的,它的前面板也就同时被调入内存。但是此时主VI的框图和子VI的前面板、框图并没有被调入内存。只有当主动查看主VI的框图或是打开子VI的前面板和框图时,它们才会被调入。
基于LabVIEW的这种内存管理的特性,我们在编写VI的时候可以通过以下方法来优化LabVIEW程序的内存使用。
第一,把一个复杂VI分解为数个子VI。子VI的使用会增添额外的前面板和框图的空间,但并不增添额外的代码和数据空间。由于程序运行时只有代码和数据被调入内存,因此使用子VI不会占用额外的内存。使用子VI的好处还在于当子VI运行结束时,LabVIEW可以及时收回子VI的数据空间,从而改善了内存的使用效率。
第二,在没有必要时不要设置子VI的重入(Reentrant)属性。重入型VI每次运行时都会对自己使用的数据生成一个副本,这增加了内存开销。
第三,主VI的面板通常就是用户界面,需要显示给用户。但是要尽量避免开启子VI前面板。比如,在子VI中使用与其前面板控件有关的属性节点(Property Node)会导致它的前面板被调入内存中,增加了内存开销,所以要尽量避免在子VI中使用主面板控件的属性节点来设置控件的值,而可以用局部变量等方法来替代。
第四,我们可以放心地在 VI 的前面板(对于非界面VI)和框图里添加图片,注释等信息来帮助你编写、维护LabVIEW程序,这些帮助信息不会在VI运行时占用内存。

二. 内存泄漏。

LabVIEW与C语言不同,它没有任何分配或释放内存的语句,LabVIEW可以自动管理内存,在适当的时候分配或收回内存资源[1]。这样就避免了C语言中常见的因为内存管理语句使用不当而引起的内存泄漏。
在LabVIEW中一般只有一种情况能够引起内存泄漏,即你打开了某些资源,却忘记了关闭它们。比如,在对文件操作时,我们需要先打开这个文件,返回它的句柄。随后如果忘记了关闭这个句柄,它所占用的内存就始终不会被释放,从而产生内存泄漏。LabVIEW中其它带有打开句柄的函数或VI也会引起同样的问题。
由于内存泄漏是动态产生的,我们无法通过VI的属性面板来查看,但可以通过Windows自带的任务管理工具来查看LabVIEW程序内存是否有泄漏。也可以使用LabVIEW的Profile (Tools>>Advanced>>Profile VIs)工具来查看某个VI运行时内存的分配情况。

三. 缓存重用

LabVIEW程序主要是数据流驱动型的。数据传递到不同节点时往往需要复制一个副本。这是LabVIEW为了防止数据被节点改变引起错误所做的一种数据保护措施。只有当目标节点为只读节点,不可能对输入数据作任何更改时,才不在这些节点处做备份。例如,数组索引节点(Index)是不会改变数组值的,LabVIEW在这里就不为输入数组做备份。对于加减法运算等肯定改变输入数据的节点,LabVIEW往往需要对输入或输出数据作备份。有些LabVIEW程序,比如涉及到大数组运算的程序,内存消耗极大。其主要原因就是LabVIEW在运算时为数组数据生成了过多的副本。
实际上很多LabVIEW节点是允许使用缓存重用的,这类似C语言调用子函数所使用的地址传递。通过合理设计和使用缓存重用节点,可以大大优化LabVIEW程序的内存使用。使用LabVIEW 7.1的Tool>>Advanced>>Show Buffer Allocations (LabVIEW 8.0 之后使用 Tool>>Profile>>Show Buffer Allocations)工具可以在VI框图中查看缓存的分配情况。打开该工具,凡是在框图中有缓存分配的地方,都会显示出一个黑点。
下面是几个最常用节点的试验结果。LabVIEW节点众多,不可能一一列举,文中未提及的节点读者在编程时自己可以尝试。

1. 一般顺序执行VI中的运算节点

图1:简单的顺序执行程序
如图1所示,程序对一个常量加1,然后将结果输出。
“+1”节点输出端有一个黑点,表示LabVIEW在此处开辟了一个缓存用于保存运算结果。
其实完全可以利用输入数据的内存空间来保存这个运算结果。我们可以通过如下的方法来告知LabVIEW编译器,在此运算节点处重用输入数据的内存空间。
首先,用一个控制型数值控件代替图中的数值常量,然后分别将VI中的两个控件与VI的接线器(Connector Pane)相连。

图2:实现缓存重用

图2是经过我们优化后的VI,LabVIEW在“+1”节点处没有开辟新的缓存。LabVIEW中其它运算节点也有类似的性质。

2. 移位寄存器(Shift Register in the Loop Structure)

移位寄存器是LabVIEW内存优化中最为重要的一个节点,因为移位寄存器在循环结构两端的接线端是强制使用同一内存的。这一特性可以被用来通知LabVIEW在编译循环内代码时,重用输入输出缓存。

图3: 对数组进行数值运算的顺序执行程序

让我们分析一下图3所示的程序:它首先构造了一个数组,然后对这个数组进行了几次数学运算。每一步运算,LabVIEW都要开辟一块缓存用以保存运算结果的副本。打开VI属性面板上的内存使用,可以看到这个VI大约会占用2.7M的内存空间。其实这些副本都是不必要的,每一步运算的结果都可以被保存到输入数据的内存空间。我们可以把所用的运算节点都放到一个子VI中,然后利用上一段提到的方法,使子VI中的代码缓存重用。还有一种方法,利用移位寄存器也可以实现缓存重用。

图4: 利用移位寄存器实现缓存重用

如图4,我们可以将运算代码放在一个只运行一次的循环结构内,由于运算部分的输入和输出都与移位寄存器相连,这就相当于通知了LabVIEW,在运算的输入输出需要使用同一块缓存。因而,LabVIEW 不再为每一步运算开辟新的缓存而是直接利用输入数据的缓存保存结果。打开VI属性面板上的内存使用,可以查看到这个VI的内存占用已经减少到了原来的六分之一。
在 LabVIEW 8.5 中,有了一个新的结构——缓存重用结构,专门用于优化代码的内存使用。可以不必再使用移位寄存器来完成这项工作了。

3. 库函数调用节点(Call Library Node)

以传递整型参数为例:在参数配置面板,我们可以选择值传递(Pass Value)或选择指针传递(Pass Pointer to Value)。
当选择了值传递时,库函数调用节点是不会改变该参数的内容的。如果我们在该库函数调用节点参数的左侧接线端引入输入数据,在输出端引出输出参数,那么输出数据其实是直接由输入数据引出的,LabVIEW不会在这个节点处开辟缓存。
在指针传递方式时,LabVIEW则认为传入的数据会被改变。如果输入数据同时还要发往其它节点,LabVIEW会在此处开辟缓存,为输入数据作一个副本。选用指针传递方式,库函数调用节点的每一对接线端也同样是缓存重用的。就是说,库函数调用节点的输出值是直接存放在输入值的缓存空间的。
如果一个参数只用作输出,我们通常会在库函数调用节点的输入接线端为它建立一个输入常数,这个常数的地址空间并不能直接被利用,它只是为库函数调用节点开辟的缓存而设置的初始值。不接输入常数,LabVIEW也会为此参数开辟一块缓存。但是,这样每次传入的参数值都会有变化。例如图5,库函数调用节点调用的函数功能是为把输入的值加1,然后输出。图5-a中的输出值永远都是1,而图5-b,每次运行输出结果都会比前次增加1。这是因为库函数调用节点每个指针传递的参数的输入输出用的是同一块缓存,即每次运行输入值是上回的输出值。

图5: 库函数调用节点

我们可以利用图5-c的例子证明LabVIEW某些节点是缓存重用的。每次运行5-c的例子,输出结果都会比前次增加2。这是因为示例中的参数接线端以及“+1”节点的输入输出端所使用的都是同一缓存。
如果,库函数调用节点中某个参数只有输入链进去,没有输出。那么,LabVIEW 是假设你调用的函数不会修改这个参数的。LabVIEW 不会为这个数据做拷贝,它会重用这个数据的缓存。但如果你调用的函数修改的这个数据,你的程序就会面临这样一个潜在的危险:这个数据可能被程序其它部分的代码使用了,在那里,你看不出这个数据有任何被改动的地方,但它在运行时却不是你期望的数值。因为这个数据所在的缓存,被程序其它一个地方的一个库函数调用节点给重用了,而这个节点又偷偷摸摸的修改了它。
在图5中的示例中,如果库函数调用节点输出的参数是个数组或者字符串,那么就必须为它相对应的输入端联入一个与输出数据大小一致的数组或字符串。否则,LabVIEW无法知道输出数据的大小,而使用默认分配的缓存空间很容易出现数组越界错误。

四. 小结

缓存重用是LabVIEW内存优化的最重要的一个环节。精心设计的LabVIEW程序可以大大节约内存的占用,提高运行效率。但是,在编写完程序后再按照程序优化的技巧回头去优化一段已有的程序,这并不是一个好的编程方法。我们应该先熟悉理解优化的方法,在以后的开发过程中自然而然地将它们应用在编程中。

办理招行信用卡的痛苦经历

    我本来是有个招行普通信用卡的。大概两个月前,突然出现一笔 9 美元的消费,这不是我消费的,而且我当时又不在美国。所以我提出了申诉,招行处理申诉还是比较快的。两三天后就把钱退给了我。而且他们说为了防止类似情况再出现,会帮我把现在的卡销掉,给我免费换一个。我一直觉得我的信用卡额度太少了,用起来不方便,于是就要求他们直接把现在的卡销掉,不用给我新的了。我准备申请金卡。
 
    几天之后,路过一家招行营业部,就顺便申请了一个金卡,结果没批准,也不知道理由。集体申请会容易被批准些,于是我打算走这条道路。
 
    几天前,我开始联系招行来我们公司集体办理信用卡。公司里总共有20多个人申请,基本都通过了。但是我的申请居然没有被批准。我费了半天劲,虽然帮助了别人,自己却白忙了。     
    我想我已经工作7年了,我的信用怎么也不会比公司的新员工低吧。 于是打电话给那个和我联的代理理论。结果他居然说我还款记录不好。我是设定的自动还款,哪里可能还款记录不好呢。后来他说也许是那 9 美元的争议引起的。既然我已经注销了信用卡,三个月之后重新申请应该就没什么问题了。
 
    只好再等三个月了。幸好这段时间里我还有个建行的信用卡作备用。

一个 IVI 驱动程序的开发过程

这篇文章是我本科的毕业论文,它只是对我实习工作的记录,描述了开发一个IVI驱动程序的全过程,谈不上什么技术含量。
可惜的年头已久,我竟然找不到它的最终版本了,我只从备份光盘里翻出了一个未完成的版本。
以前的很多东西我没有注意保存下来,真是可惜啊!今天想找出一份我以前写的代码,也找不到了。以后我就会把写的东西都放到网上来,以免这为数不多的东西再被遗失了。
 


 

[摘要]

在虚拟仪器系统中,仪器驱动程序是连接仪器与用户界面的桥梁,是系统设计的一个关键。IVI驱动程序(Interchangeable Virtual Instrument Driver 可互换性虚拟仪器驱动程序),虽然刚出现不久,但因其性能卓越,IVI驱动程序已成为了驱动程序的发展方向。本文论述了IVI驱动程序的工作原理。并结合实例,讲述了用LabWindows/CVI语言为YOKOGAWA FG200/FG300 系列信号发生器编写IVI驱动程序的全过程。

关键词:虚拟仪器、可互换虚拟仪器驱动程序、信号发生器、FG200/FG300、VXI即插即用规范、LabWindows/CVI。

[Abstract]

In virtual instrument system, the instrument drivers are the bridges between instruments and user interface. It is also the key technique of system design. IVI driver is a new technology in industry science. Because of its excellent performance, IVI already became the direction of driver development. The principle of IVI driver is discussed. And we paid more attention in the implementation of IVI driver for FG200/FG300 series function generators with LabWindows/CVI.

Key words: virtual instrument, IVI, interchangeable virtual instrument driver, function generator, FG200/FG300, VXI plug&play specification, LabWindows/CVI.

第一章 驱动程序工作原理

一、 驱动程序的发展背景:

1、 测试系统的工作方式:

在工业生产中,测试过程需要使用到各种各样的仪器。计算机被用来控制这些仪器及生成用户界面。现代的大规模生产线需要用到成百上千的测试仪器,要实现自动化测试必须使用计算机控制这些仪器。

在计算机控制仪器发展的早期,仪器与仪器之间、仪器与计算机之间的接口大多通过GPIB接口总线;编程采用的是BASIC语言的I/O操作语句。每一台仪器都有自己的一套ASCII命令集。测试程序的编写者在编写测试程序前需要熟悉硬件设备和其一大套命令,这对于一般的软件人员来说较为困难。

编程常常成为开发自动检测系统中最耗费时间的意向工作,当检测系统使用到多种不同型号的仪器时,情况会更遭。并且,变成人员还会发现当他们在为一台仪器编写新的应用程序时,需要做大量的重复性工作,浪费严重。

2、 解决办法:

显而易见,如果编程人员在编写程序时,只调用一些例行程序的高层函数,而不需要直接面对低层的函数命令,则开发费用和开发时间都会大大下降。这些可被重复利用的理性函数就是仪器驱动程序。

二、 设备驱动程序的发展历史:

1、GPIB接口总线标准。计算机与仪器之间的接口总线以GPIB(通用并接口总线)最为常见,其它还有RS232、485、VXI等。GPIB总线的雏形是惠普公司(Hewlett Packard)在60年代制定的公司内部总线标准HP-IB。在此基础上,美国国家工程师协会于1975年制定了意在统一接口总线结构的IEEE488接口总线标准。1978年,又对IEEE标准在编码、格式、传输协议方面作了修订,这就是IEEE488.2标准。我们现在开发的GPIB设备驱动程序都必须遵循以上标准。此类标准中的最新版本是于1990年制定的SCPI(Standard Commands for Programmable Instruments)标准。

2、VXI plug&play标准的出现。VXI是一种仪器工业中常见的总线结构,通常我们把采用这种接口总线进行信息传送的集成卡式仪器也称为VXI。由于这种卡式仪器没有用户界面,通常被用来与计算机配合使用。VXI plug&play 标准的产生是为给VXI板卡制造商们提供一个统一的接口标准。后来VXI plug&play 标准被引用到整个仪器及计算机板卡制造业。现在大多数仪器制造商的产品都支持VXI plug&play 标准。

VXI plug&play 标准也为驱动程序的编写提供了固定格式。如驱动程序向仪器发送命令时必须遵守以下步骤:1〉初始化仪器。2〉设置变量参数。3〉设置测量命令。4〉进一步数据分析。5〉关闭进程。

由于使用诸如LabWindows/CVI一类的编程语言可以方便的产生满足VXI plug&play结构框架的驱动程序,VXI plug&play标准的出现极大的推动了现有仪器的发展。

3、VISA标准。 VXI plug&play 标准制定了驱动程序的结构框架,但这还不够完全。现在,许多驱动程序的数据类型都采用的是VISA(Virtual Instrument Software Architecture)标准。VISA标准还统一了驱动程序中一部分用户接口函数,这就为设备的互换奠定了基础。

4、IVI标准。VXI标准并不是最新的设备驱动程序标准。IVI(Interchangeable Virtual Instrument)驱动程序标准在此基础上又向前迈进了一步,VIV标准产生的时间是1998年7月。

三、 IVI驱动程序的特点和优点:

1、可互换性: IVI驱动程序完全符合VXI plug&play和VISA标准,是对以上两种标准的发展和完善。“IVI协会”为五大类仪器(数字万用标、示波器、信号发生器、开关、电源)定义了标准属性(Classic attribute)和标准用户接口函数(Classic function)。因此,使用IVI驱动程序的仪器设备在标纯特性范围内可以实现互换。对于每种型号仪器的特独特性能,可以通过非标准属性和非标准用户接口实现。

2、智能化与状态缓存功能:IVI驱动程序引入了状态缓存(State Cache)功能。现代设备控制中,影响系统速度提升的主要瓶颈是仪器与计算机间的信息传输速率。VXI p&p标准的驱动程序不存在状态缓存功能,因此,每执行一条测量函数都要对仪器进行一次设置,即便仪器已经设置正常也是如此。而IVI的驱动程序可以自动将仪器状态放入缓冲区,并根据此次状态截断向仪器发送的冗余设置命令。以此优化命令结构,提高系统效率。

3、模拟功能:模拟状态指在缺少真实仪器的情况下,IVI驱动程序可以产生一个模拟的输出值,以保证高层程序正常工作。使用模拟功能可以帮助我们自在没有仪器的情况下开发驱动程序、编写、检查测试程序,以及检测新的仪器是否与测试程序匹配。

4、安全的多线程工作方式:这使得测试工程师可以充分利用多线程程序的优点。

5、数据范围检测和状态检测功能:这些工具减轻了测试程序编写人员的工作量并提高了程序的可靠性。

四、 IVI驱动程序的结构和工作原理:

1、 IVI驱动程序是建立在由“国际IVI协会”制定的仪器属性模型的基础上的。我们对五大类仪器(数字万用表类、示波器类、信号发生器类、开关类、电源类)分别定义了其属性模型,使其具有可互换性,状态缓存等功能。

仪器设备上的每一个参数设置都对应一条属性。如信号发生器,我么可以把它的输出波形,频率电压等都定义为其属性。IVI驱动程序的“SetAttribute”和“GetAttribute”两个低层函数时对用户开放的。高级用户可以直接在他们的测试程序中设置仪器的某个独立属性。但是,以为在大多数情况下,仪器的各个属性之间是有联系的,IVI驱动程序提供了高层函数来一次性设置一组相关联的几个属性。这些高层函数减轻了用户的任务。

2、 IVI驱动程序的工作过程:

IVI结构的核心是IVI引擎对仪器属性的读写。

IVI驱动程序利用CallBack函数(回调函数)对仪器设置进行读写操作,利用RangTable(取值范围表)核查仪器属性的取值是否合法。IVI引擎会自动选择实际访问RangeTable和CallBack函数。

例如:我们要使用FG300_ConfigureTriangleSymmetry函数来设置仪器产生的三角波的对称度,并假设其对称度为30(30%)。

当用户调用FG300_ConfigureTriangleSymmetry函数后,驱动程序和IVI引擎需要作以下工作:

1〉 驱动程序调用Ivi_SetAttributeViReal64()函数,对属性FG300_ATTR_FUNC_TRIANGLE_SYMMETRY的值进行设置。同时,SetAttribute函数将会激发IVI引擎。

 
  clip_image001

2〉 如果继承属性(Ingerent attribute)FG300_ATTR_RANGE_CHECK的值为VI_TRUE(值为真,允许值范围检查),IVI引擎则自动调用属性FG300_ATTR_FUNC_TRIANGLE_SYMMETRY的RangeCheckCallBack函数。检查值30是否在属性的RangTable的允许范围内。如果取值超出范围,SetAttribute函数会结束任务,并将出错代码作为返回值返回。有时,IVI引擎还利用RangeTable把输入值强行转换至某一范围内。

3〉 如购继承属性FG300_ATTR_CACHE的值为VI_TRUE(允许状态缓存),IVI引擎会检查属性FG300_ATTR_FUNC_TRIANGLE_SYMMETRY的当前值是否就是30。如果是这样,SetAttribute函数立即结束任务,并正常返回。

4〉 如果继承属性FG300_ATTR_SIMULATE的职位VI_TRUE(允许模拟状态),IVI引擎将值30赋给属性FG300_ATTR_FUNC_TRIANGLE_SYMMETRY后即正常返回,不进行任何I/O操作。

5〉 如果以上情况都不成立,IVI引擎会调用属性FG300_ATTR_FUNC_TRIANGLE_SYMMETRY的CallBack函数,通过CallBack函数中的I/O操作命令设置值输出至仪器。

6〉 如果继承属性FG300_ATTR_QUERY_INSTR_STATUS的值为VI_TRUE(允许状态检查),IVI引擎会调用CheckStatusCallBack函数读取仪器的状态。

第二章 FG300信号发生器

一、 信号发生器的工作原理:

1、 信号发生器的分类:

根据信号波形产生的原理可将信号发生器分为以下三类:

1〉 模拟发生器(Analog generation)模拟发生器利用积分运算电路和比较运算电路来产生三角波和矩形波信号。正弦波由三角波信号通过一个由二极管和电阻组成的电路来产生。模拟发生器的价格便宜,大多数信号发生器都是采用这种方式工作的。但是模拟发生器的频率精度比较低,而且低频工作状态不稳定。

2〉 PLL发生器(Phase-locked loop generation 锁相环路发生器)。PLL发生器电路包括:(a)一个电压可调的振荡器,用输入电压来调节输出频率;(b)一个可编程可任意设置的分频器;(c)一个基准振荡器;(d)一个低通滤波器;(e)一个相位比较器,用于比较基准振荡器发出的信号波于滤波器输出波形之间的相位差异。

工作时,PLL发生器用相位差异来调节控制电压,使输出频率与基准振荡器经分频器产生的频率保持一致。因此它的频率精度非常高。PLL发生器的缺点是价格高、频率变化缓慢,低频工作状态不稳定。

3〉 DDS发生器(Direct digital synthesis generation 直接数字合成发生器)。DDS发生器把各种样式的波形的数字信息纪录于内存。当用户选定波形与频率,发生器则按照选定的时钟频率将波形数据读出。取出的数据通过D/A转换器转变为模拟信号。由于采用了全数字的方法,DDS发生器已经克服了传统信号发生器的缺点。

2、 DDS信号发生器的工作原理:

1〉 原理图:

clip_image002

其电路中包括:晶体振荡器,用来产生基准时钟信号;相位运算部分;波形存储区;数/模转换器;低通滤波器。波形存储器存储了样本信号在一个完整周期内的数据。存储单元的地址与相位相对应。

2〉 工作过程:

首先,根据用户选择的频率确定N值。锁存器的输出在第一个时钟周期内也是N,这就是第一次读波形存储器时的地址。锁存器的输出值反馈给加法器,以后每个时钟周期,锁存器的输出都会增加N(2N、3N、4N……),波形存储器会根据这些地址信号送出波形数据,再由D/A转换器转换成模拟信号。你可以通过改变N值来修改输出频率。

clip_image004

二、 FG300的主要性能指标:

生产厂家: YOKOGAWA

波形输出:

输出通道数: 2

输出波形: 正弦波、矩形波、三角波、脉冲波、任意波形

输出信号: 连续输出、触发输出、门控输出、直流输出

频率:

频率范围: 正弦波、矩形波: 1μHz – 15 MHz

三角波、脉冲波、任意波形: 1μHz – 200 kHz

分辨率: 1μHz ;九位数字

精度: ±20 PPM

稳定性: ±20 PPM

基准频率: 40.2107 MHz

输出特性:

最大输出电压: ±10 V

幅值调节范围: ±20 Vpp (分辨率:1 mVpp)

幅值精度: ±(0.8%×幅值+14 mVpp)

幅值频率特性:

正弦波: ≤100 kHz ±0.1 dB

≤1 MHz ±0.2 dB

≤10 MHz ±0.5 dB

≤15 MHz ±1 dB

矩形波、脉冲波(50% duty cycle):

≤10 kHz ±2%

三角波: ≤10 kHz ±3%

偏移电压范围: ±10V

输出阻抗: 50Ω±1%

输出衰减: 1/1、1/10、1/100

调制特性:

调制方式: 调幅(AM)、双边调幅(DSBAM)、调频(FM)、调相(PM)、偏移调制(Offset Modulation)、脉宽调制(PWM)

调制波波形: 正弦波、矩形波、三角波、脉冲波、任意波形

调制波频率: 1 mHz – 50 Hz

通用特性:

预热时间: 30分钟

工作环境温度: 5C – 40C

工作环境湿度: 20% – 80%

电源电压: 100 – 240 V Ac

电源频率: 50 Hz – 60 Hz

功率: 125 W

外形尺寸: 350×213×132 mm (长×宽×高)

重量: 5 kg

详细资料参见附录。

三、 FG300的工作原理:

1、 FG300是DDS类的信号发生器。

2、 信号流程:

1〉 它将信号的波形数据存储在波形存储器中。

2〉 相位的递增计算由一个48bit的相位计算器完成。波形存储器的地址输入至于相位计算器的输出值相对应。

3〉 波形存储器的数字输出信号经过一个12位的D/A转换器,转换为模拟信号。

4〉 模拟信号首先要经过滤波器滤除高频谐波。

5〉 FG300中的方波是由正弦波通入比较锁存器后产生的。

6〉 经滤波后的模拟信号由多路转换器选择输出通道。

7〉 幅值调整和偏移量调整。

8〉 通入放大器和衰减器。

9〉 信号由FG300的连接器输出。

四、 编程准备:

FG300信号发生器采用的是消息型通讯方式(message-based)。就是说,FG300与计算机或控制器之间的通讯采用字符串的方式,我们称这些标准字符为消息(message, MSG)。从控制器发送至仪器的消息成为程序消息(program message);从仪器发送至控制器的消息成为反馈消息(response message)。

每条程序消息可以由数个程序消息单元组成,

第三章 LabWindows/CVI开发语言

一、 LabWindows/CVI开发环境简介

LabWindows/CVI是一种C语言开发系统。与其它开发系统相比,CVI增添了一个交互式程序开发环境、数据采集函数库、仪器控制工具等工具。同时,LabWindows/CVI包含一整套用来进行数据采集、分析、显示等处理的软件工具。

clip_image006

你可以利用CVI的交互式环境编写调试ANSI C语言(美国国家标准C语言),还可以使用LabWindows/CVI自带的库函数以及调用其它的C语言模块、动态链接库函数、C语言库函数、仪器驱动程序等。

CVI最大的优势在于功能强的的函数库。它的函数库包含了对数据处理的每一过程及仪器进行控制的各种函数。具体分类如下:

1〉 数据采集类包括以下函数库:GPIB/IEEE 488.2函数库、数据采集函数库、DAQ卡的IO操作函数库、RS-232函数库、VISA库、VXI库。

2〉 数据分析方面:数据格式转换和标准化函数库、数据分析函数库、高级数据分析函数库。

3〉 数据显示方面有用户界面函数库。

4〉 在网络操作和信息交换方面有四个函数库:动态数据交换(DDE)函数库、信息交换控制协议(TCP)函数库、X Property控件函数库和Active X控件函数库。

仪器控制函数库是CVI特有的,它包括了各种采用GPIB、VXI、和RS-232接口的仪器的驱动程序,例如示波器、万用表等。用户还可以利用CVI全套的驱动程序开发软件工具开发自己的驱动程序。

二、 LabWindows/CVI程序开发过程简介:

1〉 通开发其它语言的程序一样,在书写程序源代码前,你先要设计好程序个部分的功能,并以文件的形式记录下来。

2〉 LabWindows/CVI是一种在一起操作方面大大增强了的C语言。在使用CVI之前,你一定要熟悉C语言。

3〉 CVI程序的结构。通常,一个CVI程序由以下几部分组成:

Ø 用户界面

Ø 主控程序

Ø 数据采集

Ø 数据分析

4〉 用户界面:使用LabWindows/CVI的用户界面编辑工具可以轻易的制作出精美的交互式用户界面。使用图形化用户界面可以使你的程序更加友好,更加确切的表达出你的程序的功能和用途。因此,我们编写一个CVI程序往往是从事及用户界面开始的。

5〉 程序框架和代码生成:LabWindows/CVI会自动根据设计好的用户界面生成程序框架。这个程序框架内包括用户界面的调用函数(Callback Function)和装载用户界面的主函数(Main Function)。CVI的代码生成器可以大大节约用户编写Windows程序的时间。

6〉 主控程序部分:主控程序用来协调数据采集、数据分析、和用户界面部分的工作,控制程序执行的流程。大部分主控程序部分的程序代码要由用户自己书写,你可以参考LabWindows/CVI软件包中的范例程序。

7〉 数据采集:LabWindows/CVI已经包含有控制GPIB、RS-232和VXI设备的驱动程序和接口函数,用户可直接调用这些函数。

8〉 数据分析。

第四章 驱动程序开发步骤

第一节 整理仪器属性和用户界面函数

为了确保仪器的可互换性,国际IVI协会为仪器驱动程序的开发制定了一系列的标准。其中一个重要部分就是制定了标准的通用仪器属性和通用高层接口函数。针对信号发生器部分的文件是《IviFgen Class Specification》。

《IviFgen Class Specification》文件包含了信号发生器的最常用功能,绝大多数的信号发生器都支持这些功能。但每台仪器实现这些功能的方法可能是不同的;仪器与计算机之间的通讯方式和指令也存在较大差别。

驱动程序中,基本上每条属性(Attribute)对应一条仪器设置命令。在开始工作前,要熟悉仪器的命令体系和IVI通用属性(Classic Attribute)及用户界面函数(Classic Function)的定义。

首先,要满足文件《IviFgen Class Specification》 中定义的属性和界面函数。

第二节 使用仪器驱动程序开发向导创建驱动程序文件:

clip_image008

一、 打开LabWindows/CVI的主工作窗口,在“Tools”菜单下选取“Create IVI Instrument Driver”,开始进入驱动程序开发向导。

二、 在“Select an Instrument Driver”对话框中,选取显见驱动程序的信息:

clip_image010

1〉 新建一个驱动程序(Create a new driver)。

2〉 接口中线类型为:GPIB。

3〉 仪器类型为信号发生器(Function Generator)。

按下“Next”键,进行下一步。

三、 在“General Information”对话框中,填写以下信息:

1> 仪器的名称:“TOKOGAWA FG300 Function Generator”;

clip_image012

2> 仪器前缀名:“FG300”;

3> 作者姓名及公司等;

4> 程序方在何目录下。

按下Next键,进行下一步。

四、 在General Command Strings 对话框中填写以下信息:

clip_image014

1〉 填写默认的设置命令,FG300可以不需要默认的设置命令。

2〉 列出仪器的通道数,FG300共有两个通道。

按下Next键,进行下一步。

五、 在Stand Operation对话框种选择仪器支持的基本操作。

clip_image016

六、 在ID Query对话框中填入仪器用来询问ID号的命令,以及希望得到的返回值。

clip_image018

七、 在Reset对话框中填入仪器用来进行重设置的命令。

clip_image020

八、 在Self Test对话框中填入以下内容:

clip_image022

1〉 在自检命令控制栏中填入自检命令:“*RST?”;

2〉 FG300的自检返回信息中只有状态码;

3〉 选择“%hd”通用符作为格式化模式。

clip_image024

九、 在Error Query对话框中填入以下信息:

1〉 FG300的错误查询命令为:“:STAT:ERR?”;

2〉 FG300的错误查询返回值中即包括错误代码,也包含错误信息。

3〉 选择通配符“%ld,\”%256[”\”]”作为格式化模式。

clip_image026

十、 在Revision对话框中填入FG300的版本询问命令:“*IDN?”;并选用通配符“%x[^,],%x[^,],%x[^,],%256[^\n]”作为返回信息的格式化模式。

clip_image028

十一、 Test对话框:

如果这台仪器已经连在计算机上,你可以在建立驱动程序前,先测试以下你在前面几个对话框中输入的命令是否正确。

1〉 填入仪器的GPIB地址;

2〉 填入仪器的复位时间和自检时间。

3〉 按下Run Tests键,CVI开始检查仪器。

十二、 按下Next键,CVI就会按前面输入的信息生成该仪器驱动程序的程序框架。

第三节 编辑仪器的属性:

clip_image030

当时用“仪器驱动程序开发向导”生成了驱动程序的框架后,可以直接装入属性编辑器,也可以在将来的任何时候,通过选择Tools菜单中的Edit Instrument Attributes项来调出属性编辑器编辑仪器属性。

一、 制定仪器属性:

clip_image032

选定你要编辑的属性,按Edit键,或按下Add Attribute添加新的属性,就会进入Edit Attribute对话框。在此对话框内,需要填写或修改以下内容:

1〉 属性的名称;

2〉 属性的描述性名称;

3〉 属性的数据类型;

4〉 属性的取值范围表;

5〉 属性的默认值;

6〉 属性需要达到的精度;

7〉 该实行的简要说明;

8〉 属性的一些特殊标志;

9〉 该属性与其它属性或函数之间的关系。

二、 编写或修改属性的Callback函数。

一个仪器的属性可能会用到六个Callback函数。通常,每个Callback函数的作用如下:

1〉 Read Callback函数,用来读取仪器当前的设置值或数据信息,该函数一般由几个询问仪器设置或数据的命令组成。

2〉 Write Callback函数,用来设置仪器的值或把数据传递给仪器,该函数常由一组设置命令组成。

3〉 Compare Callback函数,用来比较属性的值是否改变。

4〉 Range Check Callback函数,察看属性取值范围表,检查赋给属性的值是否在允许的范围内。

5〉 Coerce Callback函数,把赋给属性的值,按规律转换值一定的范围内。

6〉 Range Table Callback函数,用来为仪器的当前状态选择一个合适的取值范围表。

在“Edit Driver Attribute”对话框中,选择要编辑的函数,按下“Go To Callback Source”键,即可找到Callback函数的源代码进行编辑。

三、 删除无用的属性。

由开发向导生成的驱动程序中,有一些属性在FG300中用不到。在编写驱动程序的过程中,应该删除这些属性。

第四节 编辑高层函数。

1、编辑或创立一个高层函数。

clip_image034

clip_image036

用户在使用仪器时,为完成某一任务,通常需要一次设置一组相关的几个属性;很少需要对某个单个的属性进行设置。因此,IVI驱动程序为用户提供了高层函数,以方便操作。一个高层函数常常由几个设置仪器属性的自函数组成。选择Tools菜单中的“Edit function tree”项,或打开“ykfgxx0.fp”函数面板文件,就会看到驱动程序的函数树列表。我们把函数树中的每一项成为一个结点,每一个结点都对应一个高层函数。修改一个已有的函数或创建一个新的函数可以按以下步骤进行:

1〉 用鼠标右键点击需要修改的结点,或选择Greate菜单中的“Function Panel Window”项创建一个新的结点,就会进入“Edit Node”对话框,此对话框用来编辑结点名及其对应的函数名。

2〉 编辑函数面板。为了方便用户了解和数用一个函数,CVI为每一个函数都设置了一个函数面板。面板中列出了函数的全部参数,并提供了函数及其参数的简要说明。我们同样需要为自己的函数设计一个函数面板,以方便驱动程序的使用者。

3〉 如果选中的结点是新建的,先要用“Generate Source For Function Node”项为函数创建源代码。若源代码已经存在,可直接选择“Go To Definition”想找到函数源代码。

4〉 编写源代码。

第五节 建立驱动程序的文档。

为了更清楚的向用户介绍你的驱动程序,你可以直接利用CVI为你的程序生成两种说明文档,供用户参考。

 

用科学的方法追女朋友 之 奢侈论

    有人问我信仰什么,我说我信仰科学。信仰不是随便说说的,要事事以科学为指导才够虔诚,包括追女朋友。前些天我看了一档科技节目,讲的是吸引异性中的一些科学道理。现在听我添油加醋地把其中与奢侈相关的话题总结一番。

 
    许多动物身上都有一些器官是需要消耗它大量资源,而又对生存没有任何帮助,甚者是个累赘的。比如公孔雀的大尾巴。这条大尾巴要消耗大量的能源,而其在躲避捕食者的时候是个严重负担。我们把这种开销巨大,却又无益生存的东西叫做奢侈品。

    虽然孔雀的大尾巴对生存无益,但对于吸引异性至关重要。母孔雀就是根据公孔雀尾巴的漂亮程度来确定他是不是一个合格的好老公的。这种选择方式的科学依据是,一个好老公不但能自己吃得饱,还要有余粮来养活小孔雀。咋看得出有没有余粮呢,凭的就是花尾巴这个奢侈品了。动物和人一样,营养不足的时候,先要保证最重要的与生存相关的器官的供应。比如心脏,只有一份能量也要先给他。而花尾巴这样的奢侈品只好牺牲掉了。所以只有食物丰富,心肝脾肺肾都补给充足了的孔雀才有能力养一条漂亮的花尾巴。尾巴越漂亮,说明余粮越多,越养得起小孔雀。

 
    人类跟其他动物的择偶标准在本质上其实是相同的,只不过表现形式更复杂多样一些。奢侈品在吸引异性中的作用仍然至关重要。比如,你月薪一千,但是平时不吃不喝,睡自行车棚,省的钱购置一身名牌,穿出去绝对比那些月薪一万,却衣着老土,脚蹬布鞋,捧着本 C++ 的人更能吸引小姑娘。当然,如果月薪一万的人能勒紧裤腰带,买辆二手宝马,就算只穿拖鞋,也会有一群小姑娘在后边追着不放的。

 
    这就是奢侈品的功效。不过上面说的这些都太肤浅了,人类最奢侈的东西并非名车名表,而是人的大脑。大脑只占人体重量的2%,但是却要消耗人类摄入资源的1/4。大脑自然需要做一些与人生存相关的工作,但他的主要精力都被看电视、打游戏、聊天、做白日梦等等浪费掉了。这个才是人身上最大的奢侈品,也是最吸引异性的地方。

    越会用脑的人,越显示得出大脑的奢侈。比如,会甜言蜜语,讲个笑话什么的,都很能吸引异性。不过这些都是小聪明,创造力才是大智慧,尤其是在艺术领域的创造力最为奢侈。艺术创作极其耗费脑力,却既不能吃也不能穿,最没用,因而也就最能吸引异性。追星族最能体会这一点了。

    然而艺术和创造力还不是最奢侈的。一个人能够达到的最奢侈的层次,也是他最具魅力的品性叫做善良。一般来说,人总是先满足自己,再满足别人。善举越多越能证明自己的能力。前些天听说世界首富和二富分别准备捐出自己100%和85%的财产,这真是太奢侈了–自己的财产,自己甚至自己的亲属都不许用,全留给素不相识的人。相信他们的魅力值会随着他们财产的减少而上窜n个数量级。

    这奢侈论推而广之,不但可以指导爱情,更可以帮助你在人生其他方面取得成功。以工作为例,仅仅做好本职工作,还不足以体现你的能力,如果你还能抽空打听打听别人都在干啥,甚至培训一下新员工,隔三岔五做个 presentation 什么的,就离升职加薪不远了。作为软件工程师,写的软件仅仅完成功能,那你只能算个初级程序员;如果打开你的代码,满篇都是注释,看不见几条语句,那你就是高级程序员了;如果还能像我这样,有时间写写 blog 啥的,就离专家不远了。

 

《生活随笔》

LabVIEW 是编译型语言还是解释型语言

    LabVIEW 和常用的 VC++、VB 一样,是编译型语言。LabVIEW 的语法定义比较严格,在程序运行之前会检查所有语句的语法,一旦查出有差错,程序会报错,不能运行。
在LabVIEW是否是编译型语言的问题上容易引起混淆的原因,一是用户看不到编译时生成的目标文件(在 LabVIEW 的环境中,可以直接运行一个 VI,并不生成任何其他可执行文件);二是 LabVIEW 没有编译这个按钮。此外,VI 运行前似乎也没有占用编译时间。

我们可以把 LabVIEW 和 C 语言的存储与编译方法作一比较:C 语言的原文件存储在 .c 文件中。需要编译时,要显式地告知编译器进行编译。在耗费一段编译时间后,可以看到编译后生成的含有可执行二进制代码的 .obj 文件。而LabVIEW 的原代码是存储在 .vi 文件中的。
一个 .c 文件中通常保存了多个函数,一个由几十个函数构成的 C 语言工程,也许只由两三个 .c 文件组成。而通常情况下,一个 .vi 文件只存储一个 VI,即相当于 C 语言中的一个函数。所以,一个小型 LabVIEW 工程也可能由几十个 .vi 文件组成。
但在某些情况下,一个 .vi 文件也可能包含了某些子 VI(子函数),即这些子函数没有他们自己的 .vi 文件。这样的子 VI 被称为实例 VI(Instance VI)。LabVIEW 7版本中出现的、目前很常用的 Express VI就是这种 Instance VI。他们都是被存储在调用他们的 VI 中的。

.c 文件只保存程序的原代码;而 .vi 文件不仅保存了 LabVIEW 程序的原代码,也保存了程序编译之后生成的目标代码。在 LabVIEW 的工程中看不到类似 .obj 这样的文件,就是因为编译后的代码也已经被保存在了 .vi 中的缘故。

LabVIEW 在运行VI 之前无需编译,是因为 LabVIEW 在把 VI 装入内存的时候、以及在编辑 VI 的同时进行了编译。

当把一个 VI 装入内存时,LabVIEW 先要判断一下这个 VI 是否需要被编译。一般情况下,如果不对VI的代码做改动,是不需要重新编译的。但是在两种情况下需要重新编译。第一种,是在高版本 LabVIEW 中打开一个用低版本LabVIEW 保存的 VI;第二种,是在不同的操作系统下装入和打开了同一个 VI。
比如,要在 LabVIEW 8.0 中打开一个原来用 LabVIEW 7.0 编写保存的 VI,则被装入的 VI 需要被重新编译,因为不同版本的 LabVIEW 生成的目标代码会稍有不同。如果你的工程包含有上百个 VI,在新版本的 LabVIEW 中打开顶层 VI,就会明显地察觉到编译所占用的时间。第二种情况的例子是,在 Linux 中打开一个原来是在 Windows XP 下编写保存的 VI,LabVIEW 也需要重新编译。LabVIEW 为不同操作系统生成的目标代码也是不同的。
在以上两种情况下,打开一个 VI 后,会发现 VI 窗口的标题栏中的标题后面出现一个星号,这表示需要重新保存 VI。此时,虽然 VI 中的程序原代码没有改变,但是编译生成的目标代码已经变了,所以需要重新保存。
在LabVIEW 安装了升级补丁之后(比如从8.0升级到8.01),程序会提示你是否需要把 LabVIEW 自带的 VI 全部批量编译(mass compile)。如果你选择“是”,则可能需要占用几个小时的时间才能完成编译。

LabVIEW 在你编辑程序原代码的同时,就会对它进行编译。LabVIEW 只编译你当前正在编辑的这个 VI,它的子 VI 已经保存有已编译好的目标代码,所以不需要重新编译了。因为每个 .vi 只相当于一个函数,代码量不会很大,编译速度就相当快,用户基本上是察觉不到的。 你在编写一个LabVIEW程序时,假如你把两个类型不同的接线端联在一起,会看到程序的运行按钮立即断裂,它表示程序已经编译了,并且编译后的代码不可执行。程序编写完毕,所有 VI也都已是被编译好了,程序直接运行即可。

有时会出现这种情况:打开一个 VI,VI 左上方运行按钮上的箭头是断裂的,表示 VI 不能运行。但是点击断裂的箭头,在错误列表里却没有列出任何错误信息。此时箭头断裂是由于 VI 保存的编译后的代码不能执行引起的。例如在上一次打开这个 VI 时,有一个被此VI 调用的 DLL 文件没有找到,编译后的代码自然不能执行。而后关闭 VI 再把缺失的 DLL 文件放回去。下次打开始 VI 时,理论上 VI 应当可以运行了,但是这时 LabVIEW 没有重新编译这个 VI,VI 中保存的是上一次不可执行的代码,所以运行按钮的箭头仍然断裂。而程序原代码没有任何错误,所以错误列表中什么都看不到。
修复箭头状态的方法是按住 Ctrl + Shift 键,再用鼠标左键点击运行按钮(断裂的箭头)。在 LabVIEW 中按住 Ctrl + Shift 键 + 鼠标左键点击运行按钮表示编译,但不运行,这相当于其他语言的 Compile 按钮。

    LabVIEW 采用的把可执行代码与源程序保存在同一文件,分散编译的方式,与其它语言相比是相当特殊的。它既有优点也有缺点。
它最大的缺点是不利于代码管理。比较正规的做法,程序代码需要每天都上传至代码管理服务器。因此,源代码管理需要占用大量的硬盘空间。如果只是程序代码还好,把编译好的执行代码也存在同一个文件里,这就大大加重了代码管理的负担。程序开发的时候,经常需要回头查看过去的修改历史。如果某个文件发生了变化,代码管理软件就会意识到这是代码作了修改。但是VI中有时只是它包含的执行代码发生的变化,因此代码管理软件无法正确的判断出是否代码有变化。
    它的优点主要有两条:1. 运行子 VI 极为方便。其它语言要运行,只能从主入口进入,不能够单独运行某一个函数。而 LabVIEW 则可以直接运行任何一个VI;2. 分散了编译时间。大型的C++程序,编译起来很花时间,有时要用几天。LabVIEW 把编译时间分散到了写代码的同时,因此用户基本感觉不到 LabVIEW 编译占用的时间。