基于网络的虚拟仪器测试系统

引 言
著名科学家门捷列夫说:“没有测量,就没有科学”。测量科学的先驱凯尔文又说,一个事物你如果能够测量它,并且能用数字来表达它,你对它就有了深刻的了解;但如果你不知道如何测量它,且不能用数字表达它,那么你的知识可能就是贫瘠的,是不令人满意的。测量是知识的起点,也是你进入科学殿堂的开端。

1 基于网络的虚拟仪器测试系统
1.1 概述
随着计算机技术、通信技术与仪器技术的不断发展,使得虚拟仪器得到了进一步发展,虚拟仪器将计算机资源与仪器硬件、数字信号处理技术结合,把厂家定义仪器功能的方式转变为由用户自己定义仪器功能。用户可根据测试的需要,自己设计所需要的仪器系统,利用一种或多种功能的通用模块,调用不同功能的软件模块,组成不同的仪器功能。在虚拟仪器中,计算机成为仪器的一部分,使得计算机可以得到充分发挥。除了仪器的输入、输出、数据处理分析、结果显示由计算机完成外,还可组成计算机网络。一个大的复杂的测试系统往往系统的测量、输入、输出、结果分析分布在不同的地理位置,仅用一台计算机并不能胜任测试任务,需要由分布在不同地理位置的若干计算机共同完成整个测试任务。计算机网络技术、总线技术的发展,乃至Internet网的发展拓展了虚拟仪器测试系统的应用范围。利用网络技术将分散在不同地理位置不同功能的测试设备联系在一起,使昂贵的硬件设备、软件在网络内得以共享,减少了设备重复投资。一台计算机采集的数据可以立即传输到另一台处理分析机上进行处理分析,分析后的结果可被执行机构、设计师查询使用,使数据采集、传输、处理分析成为一体,容易实现实时采集、实时监测。重要的数据实行多机备份,提高了系统的可靠性。对于有些危险的、环境恶劣的不适合人员操作的数据采集工作可实行远程采集,将采集的数据放在服务器中供用户使用。
1.2 虚拟仪器网络测试系统设计
虚拟仪器网络测试系统的设计需从虚拟仪器和网络技术两方面考虑。
1.2.1虚拟仪器部分
分析测试系统的要求和系统的功能,根据系统功能的要求,在软件和硬件之间进行优化选择,从而确定仪器所必需的硬件模块,以便用最少的模块实现仪器的最佳功能。根据应用情况与实际的条件选择基于计算机的虚拟仪器的仪器模块(VXI总线、PCI总线、PXI总线、GPIB仪器),包括示波器、信号调理电路、万用表、信号源等模块。
  硬件确定以后,主要确定软件功能模块,哪些仪器功能由软件实现,将软件功能划分为相对独立的模块。然后选择易于编写的图形化的软件平台。
Virsual C++6.0是一种面向对象的通用的功能强大的程序设计语言,提供集成性及可视化用户界面,采用面向对象的程序设计方法,它具有底层操作功能,也具有数据库开发功能,编程灵活,因此也是编程人员常选用的编程语言,我也用VC++编写了虚拟仪器平台,可以组建虚拟信号发生器、虚拟示波器、数据采集。
1.2.2网络结构设计
根据测试系统各部分所处的地理位置和覆盖的范围不同可构建局域网、城域网、广域网。一个大的复杂的测试系统由各个子系统组成,每个子系统一般在一个单位的小范围内,因此可建立局域网,然后将每个局域网互联,形成企业测量系统。由于Internet网的发展,一些公用的数据还可以通过Internet网将测量数据发布到网上供网上用户使用,可建立测量发布系统。对于有些危险的、环境恶劣的不适合人员操作的数据采集工作可实行远程采集。
  由于网络测试中每个测试点担任不同的测试任务,为了减少不必要的重复工作,通过网络实现资源共享,同时要减轻服务器与各节点的数据传输,提高网络系统性能,因此服务器和各个节点以及各节点之间协同工作显得尤为重要。基于Client/Server模式的分布式计算、分布式处理系统是解决这个问题的最好选择。基于C/S模式,将系统功能分解到各个节点,各个节点有机配合,用户在自己的终端上就可以观察到从服务器中获取的数据和处理结果。在C/S中,分客户机(Client)程序和服务器(Sewer)程序。客户机程序和服务器程序可以运行在一台计算机中,也可运行在两台或多台计算机中,Client程序与Server程序相互协同处理,一个测试系统由一个或承担不同任务的多个客户机与一个或多个服务器组成。客户机是用户与系统的交互接口,提供一个用户界面,完成用户命令与数据的输入,显示服务器送回的结果。服务器接受客户机提出的申请,完成所要求的操作并将结果传送给用户。在一个测试系统中,根据任务不同,每个服务器和客户机承担的任务也不同,例如可划分为采集、数据处理分析、输出、监控。一台计算机采集外部数据,将采集的数据存储并传输给另一台计算机,它就是服务器,另外它又需要得到远地计算机的数据,这时它又成为客户机。C/S模式是一种开放式系统的协同处理工作模式。
  设计C/S模式时应首先分析系统所要达到的指标和功能要求,在此基础上,把系统划分为各个相对独立的模块,分配到各个计算机上,每个计算机执行不同的功能,能在客户机上完成的独立任务就不要放在服务器上,以减低服务器的工作量。第二,要保证数据在服务器和客户机、客户机间可靠的传输。设计时需考虑网络的带宽要满足传输要求,并减少网络数据传输量。
Agilent IO库允许通过LAN进行仪器的控制操作。使用标准的LAN接口,计算机可以不要特殊的仪器接口就可以实现对仪器的控制。LAN软件使用计算机的C/S模式,这种模式使得应用程序(客户)可以不用执行所有的工作,相反,客户机可以向另一台仪器(服务器)发出某种请求。与可控仪器或器件相连的LAN服务器,一旦完成仪器或器件的请求,LAN服务器会反馈信息给客户机。反馈的信息包括数据和状态等表明操作是否成功的信息。例如网关在LAN软件(客户机支持)和器件支持的仪器接口之间扮演一个服务器的角色。基本结构如图:

图1-1
LAN软件结构:该结构中,包含了LAN客户软件和LAN软件(TCP/IP)(用于访问服务器—gateway)。网关包含了LAN服务器软件,LAN(TCP/IP)软件以及用于和客户机通讯和控制仪器的仪器驱动软件。

图1-2
1.2.3软件设计
与硬件结构相对应,软件结构可分为网络测控系统控制主模块,是系统控制的中枢,连接和控制各子模块;虚拟信号源、虚拟万用表和虚拟示波器等模块,是控制相应台式仪器,使其完全能处于远程虚拟控制状态,给测控对象提供激励信号,并监视受控对象输出特性变化;虚拟VXI仪器和信号调理模块是控制SCXI和VXI硬件模块仪器进行模拟量数据采集与开关量对象控制,包括数据处理;图象采集模块,包括采集现场景物视频图象和有形对象的图象采集.与图象处理;网络通信模块承担不同总线模块与主模块之间的数据传输与通信,也是实现本测控系统与远程用户之间的控制与数据传送。每个虚拟仪器模块内部都封装了对应仪器状态控制命令集、数据采集命令集、数据处理模型以及通信协议等处理子模块。
下面的第三章至第五章介绍的就是函数信号发生器、示波器以及万用表模块的设计和具体的编程。下面让我们先来了解一下VC++6.0开发工具和Agilent VISA库。
2 开发工具和Agilent VISA库介绍
Visual C++6.0是一套功能强大面向对象的且具有集成编辑器及编译器的编程语言开发平台。今天我们所使用的许多应用软件,大部分是利用C语言来进行编写的。Visual C++6.0没有很复杂的开发界面,给用户的开发以很大的灵活性。
2.1 Visual C++6.0的特点
在众多的程序语言中,Visual C++6.0是具有编辑和编译的集成开发环境。通过 Visual C++6.0的编辑功能,可以方便的设计程序。它的编辑功能可以随时让我们了解到程序的结构、流程以及对象的成员,而完成程序设计之后,也不必经过复杂的编译程序,而且给出的错误警告可以很方便的进行定位操作,这给程序设计人员以方便。
2.2 IDE的集成开发环境界面
IDE就是Integrated Development Enviroment的缩写,是将程序代码的编写(editor)、编译(Compiler)、执行(execution)以及纠错(debug)等功能汇集一身的单一整合性的界面,提供程序开发过程所需要的相关或扩展服务,让用户只需要通过此环境便可以轻松完成程序工程。
2.3 Agilent VISA纵览
VISA是Agilent IO库的一个部分。Agilent IO库由两个部分组成:VISA和SICL。Agilent VISA是根据VXIpp系统联盟的规定设计的,它允许由不同的厂商开发的软件能在同一个系统中运行。
如果你想在你的应用程序中使用VXIpp仪器驱动或者是想是自己开发的IO应用软件或仪器驱动能符合VXIpp的标准,就需要使用VISA。如果你使用新的仪器或开发IO应用程序以及仪器驱动的话,也应该使用VISA。
Agilent VISA 是一个IO库,可以用来开发符合VXIpp标准的IO应用软件和仪器的驱动。使用VISA开发的应用软件和仪器驱动可以在使用VISA IO库的VXIpp主机上运行。因此,不同厂商开发的应用软件可以在同一个系统中运行。
32位版本的VISA能在Windows 95 98 Me以及NT和2000中使用。C,C++和Visual Basic在这些系统中都被支持。
对与Windows,VISA支持GPIB、VXI、GPIB-VXI、Serial(RS-232)以及局域网接口。在Windows NT中,对VXI接口支持的VISA仅能运行在Agilent 嵌入式的VXI控制器产品中。一台网络计算机能够通过网络访问配置VISA地址譬如GPIB接口地址上的仪器。
2.4 VISA编程
在VISA的编程中需要了解VISA的一些机制: VISA的资源和属性、会话使用、
发送I/O命令、使用事件与句柄、捕抓错误等等。
VISA资源:在VISA中,任何VISA可以提供通讯的器件(如电压表)都可以定义为一种资源。对于一个完善的VISA系统,VISA定义完全符合VXI即插即用联盟的规范的六种资源类可以被执行。
VISA属性: 属性是和各种资源或会话相关联的。你可以使用属性来决定资源或会话的状态或设置资源或会话为指定的状态。
譬如,你可以通过viGetAttribute函数来读取指定会话、事件、查找列表的属性的状态。有只读和读写的属性。通过使用viSetAttribute函数来修改指定的会话、事件或查找列表的读写属性。
传递给viGetAttribute函数的指针必须指向那个属性的准确类型:ViUInt16,ViInt32,等等。譬如,当读取一个返回ViUInt16值的属性的状态时,你必须声明那个属性的变量和使用它来存储返回的数值。如果ViString被返回,你必须分配一个数组和传递那个数组的指针给那个返回的数值。
对于C和C++程序,你必须在每一个文件的开始处添加包含VISA调用函数visa.h头文件:#include “visa.h”这个头文件包含了VISA函数的原型的声明和所有VISA的常量和错误代码的定义。Visa.h头文件也包含了visatype.h头文件。
该visatype.h头文件对VISA的所有类型进行了定义。VISA的各种类型贯穿了整个VISA,对函数中用到的数据进行类型定义。
而关于会话,会话其实是一个通讯通道。会话必须在默认的资源管理器中被首先打开,接着是你将会使用的每一种资源。首先,你必须通过默认资源管理器的viOpenDefaultRM函数来建立一个会话。这个函数的第一次调用用来初始化默认的资源管理器并返回该资源管理器会话的会话。你仅需要打开默认资源管理器一次就可以了。然而,后来的对viOpenDefaultRM函数的调用则返回同样的默认资源管理器资源的唯一的会话。
接下来,你利用viOpen函数对指定的资源打开一个会话。而在退出程序的时候需要多打开的会话进行关闭操作,在关闭每一个会话时,必须调用viClose函数以释放掉分配给该会话的数据资源。你如果关闭默认的资源管理器会话,则所有被该会话打开的会话将会被关闭。
当你为一个器件建立一个通讯会话时,通过使用VISA的I/O程序来启动与该器件的通讯。VISA提供了格式化和非格式化的程序。
格式化I/O将对格式化字符下的混合数据类型进行转换。数据被存在数据缓冲器中,因此是接口的通讯得到最大的优化。
非格式化I/O口向一个器件发送或接收原始数据。通过非格式化I/O,数据并不进行格式化或相应的转换。因此,如果要求格式化数据,它必须通过用户自己来定义。
事件是那些需要你的应用程序关注的特指事件。事件类型包括服务请求,中断和
硬件触发。事件不会被传递,除非相应的事件被启用。
注意:VISA不能回调VB函数。因此,你只能使用函数ViEnableEvent来使用队列机制。在VB中不能安装VISA事件句柄。
当事件发生时,有两种方式来接收通知消息:函数回调法和队列法
当一个事件需要立即相应时,可以使用回调函数法。使用回调函数法来接收事
发生时的通知消息。当使能事件发生时,安装的事件句柄会被调用。可以使用函数viInstallHandler安装事件句柄,而使用函数viEnableEvent来启动一个或多个事件。VISA允许应用程序在同一个会话中对一种事件类型安装多个句柄。多个句柄可以通过多次调用函数viInstallHandler来安装。每次调用添加到前面的句柄列表中。
如果一个事件类型被安装了多个句柄,当指定的事件发生时,每一个句柄会被调用。VISA指出,句柄的调用的顺序是后进先出的顺序。当需要安装句柄时,使用下面的函数:
viInstallHandler(vi, eventType, handler, userHandle);
当你的应用程序捕需要及时相应时,队列法通常被使用。为了使用队列法来接收事件发生时的通知消息,步骤如下:
使用函数viEnableEvent来使能一个或多个事件。
当准备查询时,使用函数viWaitOnEvent来检查队列事件。
如果指定的事件发生,事件信息将会被取得,程序立即返回。如果指定的事件没有发生,应用程序挂起直至指定的事件发生或指定的超时时间到来。

3 函数信号发生器
3.1 Agilent 33250A 80 MHz 函数/ 任意波形发生器介绍
Agilent Technologies 33250A 是高性能的 80 MHz 复合函数发生器,其具有内置任意波形和脉冲功能函数发生器,其80MHz带宽能适应各种各样的应用要求,它具有各种内置信号、自定义的任意波形和脉冲能力,能帮助您验证设计,检验新的构想。直接数字合成技术能获得稳定、精确和低失真的输出信号。
Agilent 33250A能提供您需要的所有波形,其输出频率更高达80MHz。标准输出包括正弦波、方波、斜波、噪声波、sin(x)/x、真流和其它波形。当标准信号还不能满足您的要求时,33250A可简化生成任意波形的过程,并赠强这些自定义信号的性能。现在通过达200MSa/s的采样率,12bit分辨率和64K点的存储器深度,您可建立无限多种信号,以精确适应您的应用要求。同时具备工作台特性和系统特性是满足您目前及未来测试需求的多用途解决方案
Agilent Technologies 33250A 的特点如下:
• 10 个标准波形
• 内置的 12 位 200 MSa/s 任意波形功能
• 具有可调边沿时间的精确脉冲波形功能
• LCD 彩色显示器可提供数字和图形视图
• 易用的旋钮和数字小键盘
• 仪器状态存储器用户可自定义名称
• 带有防滑支脚的便携式耐用机箱灵活的系统特性
• 四个可下载的 64K 点任意波形存储器
• GPIB (IEEE-488) 接口和 RS-232 接口为标准配置
• SCPI 可编程仪器的标准命令兼容
Agilent Technologies 33250A 函数/ 任意波形发生器的技术参数如下:
波形
标准波形 正弦波、方波、斜波、脉冲、噪声、SIN(x)/x,
指数上升和下降、心律波、真流电压
任意波形 波形长度 1~64K点
非易失性存储器 4个波形(每一个汉形1~64K点)
幅度分辨率 12bit
采样率 200MSa/s
频率特性
正弦波 1μHz~80MHz 白噪声 50MHz带宽
方波 1μHz~80MHz 分辨率 1μHz,除脉冲为5个字
三角波 1μHz~1MHz 准确度 0.3ppm,(18℃~℃)
斜波 500μHz~50MHz THD(dc~20kHz) <0.2%+1mVrms
其它特性
幅度(至50Ω) 10mVpp~10Vpp 准确度(1kHz) ±1%设置值±1mVpp
调制
AM FSK
调制 任何内部波形 内部速率 2mHz~1MHz
频率 2mHz~20kHz 频率范围 1μHz~80MHz
深度 0%~120%    
FM 脉冲列
调制 任何内部波形 波形频率 1μHz~80MHz
频率 2mHz~20kHz 计数 1~1,000,000或无穷多个周期
偏移 dc~80MHz 起始/停止相位 -360°~+360°
    内部周期 1μ~500s
扫描
类型 线性或对数 扫描时间 1ms~500s
方向 上或下 标记 同步信号下降沿
起动F/停止F 100μHz至80MHz   (可编程)
时钟基准
外部锁定范围 10MHz±kHz 内部频率 10MHz
3.2 对函数/任意波形发生器的远端编程控制
3.2.1 编程的准备工作
了解函数信号发生器的功能特点:因为我要操作的函数信号发生器的型号是Agilent Technologies 33250A。所以,上网去搜索关于该型号的资源。在安捷伦-中国的网站上查找到了相关的文件资源。并通过阅读了解了远程控制仪器的方法(通过VISA I/O库对输入输出操作的支持,并使用该仪器的SCPI-standard command for programmable instrument命令来对仪器进行相关的操作。
通过查看“自动测试系统”课本上的例子,对自己编写函数信号发生器的界面有了一定的模型和认识。接下来就是去了解VISA I/O库。通过殷老师提供给我的资料,并通过阅读,对VISA库中的操作函数有了一定的认识。如下给出的程序:
这个例子程序的功能是查询GPIB器件的型号,并输出结果。
#include <visa.h>
#include <stdio.h>//这里是头文件,因为需要使用VISA的数据类型和函数
void main () {
ViSession defaultRM, vi;//变量的声明
char buf [256] = {0};
viOpenDefaultRM(&defaultRM);//打开地址为22的GPIB器件
viOpen(defaultRM, “GPIB0::22::INSTR”,VI_NULL,VI_NULL,&vi);
viPrintf(vi, “*RST\n”);//初始化器件
viPrintf(vi, “*IDN?\n”);//查询器件的型号
viScanf(vi, “%t”, buf);//读取结果
printf(“Instrument identification string: %s\n”, buf);//打印结果
viClose(vi);//关闭会话
viClose(defaultRM);}
这里涉及到了5个VISA函数:viOpenDefaultRM,viOpen,viPrintf,viScanf,viClose。在我后来的编程过程中,也基本上就用到了这几个函数的功能。viOpen函数的功能是建立一个指定器件的会话的通道,而会话具有一个标识,该标识可以被VISA库的其他函数进行调用。这个函数很重要,因为你如果需要跟一个器件通讯,就必须通过该函数来进行会话的建立。函数viOpenDefaultRM用来打开默认资源管理器的会话,必须是第一个打开。这个函数初始化资源管理器,并返回该会话的指针。以供其他函数使用。而函数viPrintf和viScanf这两个函数在用的编程中的比例是最多的,我发送SCPI指令就是通过这两个函数来进行的。函数viClose是用于关闭每一个会话的。当你关闭一个会话时,所有为会话分配的数据结构都会被释放掉。当你使用这个函数关闭默认资源管理器时,所有通过该会话打开的其他会话将会被关闭。
通过阅读VISA手册,懂得如何配置VC参数从而可以正常的进行操作。利用VISA来进行编程,需要链接VISA库-VISA32.LIB。对VC编译器配置如下:选择Project->Settings并选择C/C++按钮,从类型列表中选择Code Generation,从运行库列表中选择Multi-Threaded using DLL。(VISA在WIN32环境下需要这些配置)选择Project->Settings点击link按钮并添加visa32.lib库到Object/Library Modules列表中。换一种方法是,直接添加库到你的工程文件夹中。头文件和库文件的搜索路径也可以进行更改:选择Tools->Options点击Directories按钮,就可以对头文件和库文件的路径进行更改了。
在VISA中也学习了几个接口的编程。VISA支持三种接口来访问GPIB和VXI仪器:GPIB,VXI和GPIB-VXI。通过这三种接口可以对GPIB和VXI器件进行编程。当你使用GPIB-VXI或VXI接口直接访问VXI主机时。你必须知道你所要进行的编程的仪器时基于消息基还是寄存器基的VXI器件。消息基的VXI器件拥有自己的处理器来识别高级的命令,譬如,SCPI(可编程仪器标准命令)。当使用VISA时,你利用VISA的输出函数来发送SCPI命令。接着,消息基器件就会解释SCPI命令。在这种情况下,就可以使用VISA的格式化IO或非格式化IO含糊来对消息基器件进行编程。如果消息基器件有共享内存,可以通过对寄存器的PEEK和POKE操作来访问器件的共享内存。VISA提供两个不同的方法对寄存器进行编程:高级内存函数或低级内存函数。而寄存器基的VXI器件没有处理器来处理高级的命令。因此,只有通过寄存器的PEEK和POKE命令直接对器件的寄存器进行编程。VISA提供了两种不同的方法来对寄存器基的器件进行编程:高级内存函数低级内存函数。下面是一个关于GPIB接口的示意图:一台装有多个GPIB卡(PCI和ISA)的PC机和通过GPIB电缆与GPIB卡相连的多台GPIB仪器组成。PC机和仪器之间的通讯是通过GPIB卡和GPIB电缆来实现的。如下所示:

图3-1
如下面的例子:GPIB接口系统由装由两个82350GPIB卡的PC机和三台GPIB仪器通过GPIB电缆进行连接。通过IO组件对GPIB卡#1和#2分别配置一个VISA名字GPIB0和GPIB1。这样VISA库就可以对其进行寻址操作了,如下图:

图3-2

而其他的接口类型的配置与连接也是大同小异。
对仪器进行编程之前还需要熟悉一下Agilent IO库的相关知识。从Agilent IO 的安装向导PDF电子资料中了解到了相关的知识点,也知道了该库的重要作用。Agilent IO库软件由两个库和四个IO配置组建加上一个IO库控制组成。两个库为Agilent VISA 和Agilent SICL。Agilent VISA――Agilent Virtual Instrument Software Architecture(VISA)是一个开发符合VXIplug&play标准的I/O应用程序和仪器驱动。而Agilent SICL—Agilent Standard Instrument Control Library(SICL)是一个Agilent开发的适合许多IO接口的IO库。四个IO配置组建中的IO配置是被Agilent IO库用来对仪器的IO硬件接口进行配置。一个硬件接口在被Agilent IO库使用之前必须被IO配置组建进行配置之后才能使用。而VISA助手则是一个应用程序,可以控制和与VXI,GPIB以及Serial仪器进行通讯。这是一个比较好的帮助软件,可以了解到仪器的地址,在编程之前,对相关指令测试操作,以熟悉操作指令。也可以通过它来了解到连接的相关情况。我在三个模块的编程过程中,经常通过该组建来了解相关的信息。其他两个组件则用的不多。VXI 资源管理器是用来显示你的系统是否进行适当的配置,它需要你的系统安装有E8491 IEEE-1394 PC Link to VXI 接口;而LAN Server是通过IO Config对系统进行LAN Server的配置。了解了IO库的介绍性知识,随后又对如何利用IO库对接口进行配置的章节进行的阅读。
一个IO接口可以被定义为硬件接口和软件接口。IO Config组件使用的目的是将一个唯一的软件接口名字和一个硬件接口联系起来。IO库使用接口名字或逻辑单元数字来对接口进行标识。这个信息在VISA程序中被函数viOpen作为一个参数进行调用或在SICL程序中被函数iopen进行调用。IO Config分配一个接口名字和一个逻辑单元数字给一个硬件接口,就象其他接口配置参数一样。当IO接口配置完之后,就可以使用Agilent VISA或Agilent SICL库对分配的仪器进行编程了。比如,下图中显示的是对通过GPIB接口和PC进行连接的GPIB仪器进行VISA和SICL寻址操作。

图3-3
譬如对GPIB(82357USB)接口进行配置,为了对82357USB/GPIB接口进行配置,启动IO Config组件,并在左边的有效接口类型中选中GPIB 82357USB to GPIB如下图:

图3-4
如果在你的系统中有多个82357接口,则在配置窗口中会显示出来。选择你想进行配置的接口的Serial Number并单击OK会显示82357 USB to GPIB配置窗口。在出现的窗口中设置VISA 接口名字,SICL接口名字,逻辑单元以及总线地址。如果你的系统中有多个82357接口的话,重复配置。

图3-5
对PCI接口82350和82341ISA接口的配置也是类似。
对VISA和IO库以及其相关的组件有了一定的认识之后,通过网上和书本上的资料了解了函数信号发生器的基本功能和相关的参数,我便开始了界面的设计以及编程的构思。
3.2.2 函数信号发生器的界面以及编程构思
因为我要完成三个模块的设计:33250A函数信号发生器,54622D示波器以及E1412A万用表的设计工作,所以最初的设计是设计一个主界面,通过三个按钮的点击来分别进入不同的模块如下图所示:

图3-6
后来通过考虑,感觉这样的设计不够人性化和可操作性,也不太符合实际情况。所以通过反复的修改最终是如下所示:

图3-7
这样的设计自己的感觉是比较满意的。从主界面上可以一目了然的知道软件的功能。三个功能模块,通过按钮来进行操作,而左边的列表是通过下面的按钮来进行查找的。开始是,建立会话是处于屏蔽状态,通过查找资源按钮来进行资源的查找工作,并将查找到的资源一一的列在列表框中。用户可以通过双击任一个资源地址来激化建立会话按钮并进行会话的建立工作。会话是否成功的建立会给出提示信息,以确保用户的正确操作和使用。建立会话后,将会把建立会话按钮变为关闭会话按钮,来进行会话的关闭操作。也可以通过进入相应的模块操作之后来对该按钮进行屏蔽操作。进入相关的模块的时候,无需用的对资源地址的记忆,程序会对用户的操作进行判断,以确保用户进入正确的模块中。函数信号发生器模块的界面如下:

图3-8

在该模块中可以完成几种基本波形的产生以及各种基本的调试功能:正弦波,三角波,方波,锯齿波,任意波等;调幅,调频,脉冲调制,频移键控;可以修改频率,幅度,偏置以及占空比;可以配置输出阻抗何同步信号。在该界面中还存在复位按钮以及硬件连接按钮,方便用户的测试。从整体上看,符合人们的操作习惯。自己认为也还可以,也不失有点人性化吧!
其实,在界面的设计过程中,也一边开始程序的构思。基本上界面是根据信号发生器的信号的产生过程及顺序以及编程的思路来进行安排的,界面的定型也就意味着自己的编程思路已经基本成型了!下面是自己的编程思路结构:

以上就是我的函数信号发生器的编程思想和设计思路。这是一个最基本的函数信号发生器模块,具备基本的功能参数。有了界面和思路之后就可以进行底下的编程操作了。
3.2.3 函数信号发生器的编程
首先,需要能使函数信号发生器根据用户的输入来产生相应的波形。我于是参看了
33250A 80MHz的函数/任意波形发生器的电子资料以及网上关于该仪器的操作的SCPI指令。
就像前面所说的那样,函数信号发生器是基于消息基,也就是说它具有自己的处理器来对用户输入的高级指令进行解释和判断以及进行执行操作。譬如,如果希望产生一个正弦波,则输入下面的语句既可:viPrintf(funcgen,“function :shape sin\n”);就可以产生一个采用默认频率和幅值的正弦波了!而viPrintf()就是VISA库中的格式化输入输出操作函数,用来向仪器发送SCPI指令。在函数信号发生器的编程过程中,经常需要用到这个函数,还有格式化输入函数:viScanf()。在指令的测试过程中,也遇到了一些问题:如viPrintf(funcgen,“func:shape tri;freq 2000;volt 2\n”);这条指令就有问题,只能显示三角波,但是后面的参数就没有根据我的指定来进行显示。后来才发现,原来每一个指令后面加上回车符就可以正常的执行了。譬如,下面一条语句:viQueryf(funcgen,“AM:SOUR?”,buf);该语句的功能是查询AM的调制源的类型并将查询到的类型字符以%t的格式给buf。注意:该语句是没有错误,但是好像就没有起到预期的效果。而当我在"AM:SOUR?\n"加上一个回车符后,就可以执行正确了。可以看出格式化命令是以回车符作为触发命令的,只有\n才能使命令有效,舍去将视为字符串处理。(但程序本身并不提示错误)又如,产生象使用APPL命令的波形:“Appl:sin freq,volt,dcoffset\n”,当使用其他函数产生时,如下:
"FUNC SIN\nFREQ freq\nVOLT volt\n…\n"或是"FUNC SIN\n;FREQ freq\n;VOLT volt\n;…\n"可以看出执行指令都加上了回车符(对于象这样的一串执行指令,也可以在末尾加\n来执行――只是需要使用分号来进行隔开,用逗号和空格均不行)
下面是复位按钮的执行代码:

图3-9
原理是通过向仪器发送IDN的SCPI指令根据函数返回值来进行判断,并给出相关的信息提示用户。除了SCPI命令外,还有IEEE 488.2命令,两种命令之间用分号隔开如下所示:
RST;OUTP:LOAD INF\n 又如:TRIG:SOUR BUS;TRG\n连接多个SCPI命令时,使用分号和冒号(冒号表示不同的命令系统)如:OUTP:LOAD 50;:APPL:RAMP
注意:函数viPrintf()和viScanf()都是单向的传送命令和数据,而函数viQueryf()则是双向的传送数据和命令。在格式化命令中不允许有两条如似的命令"FREQ?\n VOLT?\n"。在我的测试命令以及组合发送命令过程中,我遇到这样的问题:
Cstring m_sFreq,m_sVolt; viQueryf(vi,“Freq?\n”,“%t”,m_sFreq);
viQueryf(vi,“Volt?\n”,“%t”,m_sVolt);
SetDlgItemText(ID0,m_sFreq);
SetDlgItemText(ID1,m_sVolt);如果以这种方式执行的话,执行的结果是m_sFreq和m_sVolt是同一个值,当把语句改为如下的形式的时候就正常了:
Cstring m_sFreq,m_sVolt; viQueryf(vi,“Freq?\n”,“%t”,m_sFreq);SetDlgItemText(ID0,m_sFreq);
viQueryf(vi,“Volt?\n”,“%t”,m_sVolt);SetDlgItemText(ID1,m_sVolt);
这也表示SCPI查询命令不允许执行多个,只能一条一条的执行,否则,最后的结果会是以最后一条查询指令的结果。
通过反复的测试之后,理解了SCPI命令的执行的一些格式及语法之后,就开始我的编程。下面列出了函数信号发生器的部分执行代码:
//这是一个switch语句,对用户的调制方式的判断,其中nID_Select_Modul保存的是用//户选择的调制方式的ID。通过GetCheckedRadioButton()函数获得。
switch(nID_Select_Modul)//对用户通过单选按钮选择的调制参数的判断
{ case IDC_RADIO_BW://无调制情况
viPrintf(/funcgen/theApp.m_Session,“OUTPut:LOAD %s\n”,m_nResistance);//输出端的设置
viPrintf(/funcgen/theApp.m_Session,“OUTPut:SYNC %s\n”,m_nSignal);//设置同步信号
CheckWaveRadioButton();//这是一个自己定义的函数,对用户的波形的选择进行判断-将波形的字符串存储在m_nBaseShape中,该函数的具体代码将会在附录中给出。
//判断用户选择的波形是否是方波,并通过函数viPrintf来进行各种SCPI命令的执行
if(GetCheckedRadioButton(IDC_RADIO_SIN,IDC_RADIO_DC)==IDC_RADIO_FWAVE)
{ viPrintf(/funcgen/theApp.m_Session,“FUNC %s\n”,m_nBaseShape);
viPrintf(/funcgen/theApp.m_Session,“FREQ %f\n”,m_nFreq);
viPrintf(/funcgen/theApp.m_Session,“VOLT %f\n”,m_nAmpl);
viPrintf(/funcgen/theApp.m_Session,“VOLT:OFFS %f\n”,m_nOffs);
viPrintf(/funcgen/theApp.m_Session,“FUNC:SQU:DCYC %f\n”,m_nDcycle);
}//判断用户选择的是否是任意波
else if(GetCheckedRadioButton(IDC_RADIO_SIN,IDC_RADIO_DC)==IDC_RADIO_ARBI)
{error=viPrintf(/funcgen/theApp.m_Session,“APPL:USER %f,%f,%f\n”,m_nFreq,m_nAmpl,m_nOffs);
if(error!=VI_SUCCESS)
{error_handler(/funcgen/theApp.m_Session,error);
break;}
viPrintf(/funcgen/theApp.m_Session,“FUNC:USER%s\n”,WaveForm.m_Arbitrary);
}
else
{error=viPrintf(/funcgen/theApp.m_Session,“APPL:%s %f,%f,%f\n”,m_nBaseShape,m_nFreq,m_nAmpl,m_nOffs);//这里的参数需要用户自己添加
if(error!=VI_SUCCESS)
{ error_handler(/funcgen/theApp.m_Session,error);
break;}}
MessageBox(“Successfully!”);//给一个提示框--明示用户的操作
break;
以上只是无调制情况下的输出代码,而调幅、调频等的实现代码将会在附录给给出。
对仪器的编程中,首先是先打开跟资源管理器的会话,然后通过资源管理器会话来进一步打开与某一台仪器的会话(会话也就是与仪器进行通讯,所有被使用的仪器都需要建立连接,并且都要调用VISA函数viOpenDefaultRM来打开资源管理器,这个函数会初始化VISA系统,包括资源管理系统;如果在没有器件连接的情况下调用该函数,则在有器件连接上时又进行一次操作,则连接的器件是不被认识的;你必须关闭所有的资源管理器会话,并重新的打开才行);当结束访问之后需要对会话进行清除操作,调用函数viClose对资源会话和其他会话进行关闭。该操作会把所有分配给指定会话的数据结构资源进行释放操作。上面给出的是在用户配置好各种参数后的执行代码。然而,在自己的界面的设计过程中也给出了一些配置信息,比如,频率,幅值,偏移,占空比(其中对占空比也做了特别的处理,因为占空比参数只适合于方波,所以在选中其他波形的时候需要对该参数进行屏蔽,以防止用户的输入)。当用户选择调制方式的时候,会弹出对话框以提供用户的输入操作:

图3-10
可以看出在该界面中共有四种调制方式,也是最基本的调制方式。在弹出时,各个参数都有其初始值以方便用户。对于其中的数据的处理比较简单:通过VC的ClassWizard对界面中的各个控件进行变量的定义,以接收用户的数据的输入:

图3-11
在程序的编写过程中也遇到了一些问题:譬如,多个单选按钮如何组合在一起,实现真正的单选;当用户选中一个按钮之后,如何对用户的选择进行判断,以正确的执行用户所需要的执行指令;各个功能模块之间如何的进行协调的工作等等。就拿单选按钮的实现来说,因为我的基本波形的选择设想是通过用户的选择(单选)来进行的,这样的设计直观且美观,符合人们的操作习惯。但是应该如何去实现呢?通过查找书本上介绍的例子,首先进行一些测试例子。测试通过之后,再在程序中进行运用。但是也会遇上这样那样的一些问题:例子中能进行正确的执行,但是移植到自己的程序中就不行了。(不过,我想说的一点是有许多书籍的内容是很垃圾的,程序不完整不说,而且还误人子弟)。我现在遇到的问题就是,已经将单选按钮组合在一起了,也能实现单选了,可是,单击的波形,与设计的并不一样,也就是各个单选按钮之间的功能出现了紊乱。而且还出现了跟另外一组的单选按钮出现了冲突。当时我就有点懵了。怎么会出现这样的情况呢??思索了蛮久,使自己对VC中的resource.h文件产生了怀疑:因为该文件中都是一些关于控件ID资源的定义。是不是ID资源冲突了呢?!通过自己的仔细的研究和思索,发现了另外一个问题:在该文件中有许多自己并没有使用到的空闲ID,怎么会这样呢?原来是自己在设计界面的过程中,经常的添加和删除控件,使得有些控件ID并没有随着自己对控件的删除而删除。故而遗留了下来!!于是我就将其全部的进行删除。感觉ID就干净了许多。但是,前面的问题还没有解决啊!于是我对单选按钮的ID进行的排序和分析,发现冲突的按钮的ID的顺序也是交叉的。并没有顺序的进行ID的定义。我怀疑是这个原因,于是对混乱的ID进行的重新的排序。并将不同的两组ID进行了分离。重新运行程序,结果一切OK!

图3-12

象这样的问题,如果没有碰上过,也是有点不知所措的。这是我的小小的心得哦!!
3.2.4函数信号发生器的程序的调试
在调试前,先进行IO Config的配置,并通过VISA 助手进行仪器的连接的测试,通过之后,开始调试程序。
我进行分块的调试:首先是调试产生不同波形的功能块是否正常。

图3-13
相应的程序如下:
//******以下是对单选按钮的处理函数,通过单击按钮来进行相应的操作
void CGENERATER::OnRadioSin()
{ m_nEdit_Dcycle.EnableWindow(FALSE);}
void CGENERATER::OnRadioTriangle()
{ m_nEdit_Dcycle.EnableWindow(FALSE);}
void CGENERATER::OnRadioFwave()
{ m_nEdit_Dcycle.EnableWindow(TRUE);}
void CGENERATER::OnRadioRamp()
{ m_nEdit_Dcycle.EnableWindow(FALSE);}等等//********************************************以下是对基本波形选择处理函数
void CGENERATER::CheckWaveRadioButton()
{ int nID_SelectWaveF;
nID_SelectWaveF=GetCheckedRadioButton(IDC_RADIO_SIN,IDC_RADIO_DC);
switch(nID_SelectWaveF)
{//添加处理代码--即是对波形参数赋值
case IDC_RADIO_SIN:m_nBaseShape=“SIN”;break;
case IDC_RADIO_TRIANGLE:m_nBaseShape=“TRI”;break;
case IDC_RADIO_FWAVE:m_nBaseShape=“SQU”;break;
case IDC_RADIO_RAMP:m_nBaseShape=“RAMP”;break;
case IDC_RADIO_ARBI:m_nBaseShape=m_nArbdlg.m_nSet_Arbitrary;break;
case IDC_RADIO_NOISE:m_nBaseShape=“NOISE”;break;
case IDC_RADIO_PULSE:m_nBaseShape=“PULSE”;break;
case IDC_RADIO_DC:m_nBaseShape=“DC”;break;
default:return;}}
上面是对用户的选择进行的变量的赋值以备后用。
下面就是产生各种函数的部分(部分代码):
viPrintf(/funcgen/m_Session,“OUTPut:LOAD %s\n”,m_nResistance);//输出端的设置
viPrintf(/funcgen/m_Session,“OUTPut:SYNC %s\n”,m_nSignal);//设置同步信号
注意:下面这个是一个子程序,用来对用户的选择进行判断,并将用户选择的波形的相应的参数传递给一个字符串变量中。
CheckWaveRadioButton();//波形选择的判断--将波形的字符串存储在m_nBaseShape中
//**********************************判断用户选择的波形是否是方波
if(GetCheckedRadioButton(IDC_RADIO_SIN,IDC_RADIO_DC)==IDC_RADIO_FWAVE) { viPrintf(/funcgen/m_Session,“FUNC %s\n”,m_nBaseShape);
viPrintf(/funcgen/m_Session,“FREQ %f\n”,m_nFreq);
viPrintf(/funcgen/m_Session,“VOLT %f\n”,m_nAmpl);
viPrintf(/funcgen/m_Session,“VOLT:OFFS %f\n”,m_nOffs);
viPrintf(/funcgen/m_Session,“FUNC:SQU:DCYC %f\n”,m_nDcycle);
}//******************************判断用户选择的是否是任意波
Else
if(GetCheckedRadioButton(IDC_RADIO_SIN,IDC_RADIO_DC)==IDC_RADIO_ARBI)
{
error=viPrintf(/funcgen/m_Session,“APPL:USER %f,%f,%f\n”,m_nFreq,m_nAmpl,m_nOffs);
if(error!=VI_SUCCESS)
{error_handler(/funcgen/m_Session,error);
break;
}
viPrintf(/funcgen/m_Session,“FUNC:USER %s\n”,WaveForm.m_Arbitrary);
}
else
{error=viPrintf(/funcgen/m_Session,“APPL:%s %f,%f,%f\n”,m_nBaseShape,m_nFreq,m_nAmpl,m_nOffs);//这里的参数需要用户自己添加
if(error!=VI_SUCCESS)
{
error_handler(/funcgen/m_Session,error);
break;
}
}
MessageBox(“Successfully!”);//给一个提示框--明示用户的操作
break;
上面的程序中对方波进行了特别的处理,因为占空比只适合方波,所以对方波进行另行的处理。其中下面的语句给我带来了一定的问题:
nID_Select_Modul=GetCheckedRadioButton(IDC_RADIO_BW,IDC_RADIO_FM);
//问题的关键就出现在这里--该函数没有执行--2005、4、28 //问题解决了!!原来是单选按钮的ID的值的大小问题!! //在该函数中,小的在前,大的在后!!!–2005/4/29
问题就向注释的文字所说的那样,单选按钮的ID在这个函数中是有大小区分的,当时却没有进行留意,结果把我给郁闷了!点击按钮却没有执行相应的功能。不过我利用设置断点进行分段调试,被我给发现了这个函数并没有执行。于是我就思索着是否是ID的原因,就像我前面所说的那样去查看resource.h文件中的ID的关系。最后,对自己的猜测进行试验,结果一切OK了!!“试验是检验真理的唯一标准”是一个不变的真理。
基本波形在测试成功之后,需要将其与调制方式结合起来。因为调制需要一个调制波和一个载波。我就利用基本波作为调制波,而通过另外一组单选按钮来进行载波的配置。这样就比较的清晰。而为了方便用户的操作,我调制方式的单选按钮中加入了一个无调制的按钮,并作为一个默认的设置,如下图:

图3-14
这样的处理就可以把基波的产生和最为载波进行很好区别。不至于使用户分辨不清哪个是载波与调制波。而当用户单击调制方式中的一个的时候就会弹出另外一个对话框:

图3-15

该面板上的信息足以提示用户最正确的选择。将各种调制方式分列在不同的组中并列有标题,清晰可见。而各种参数的数值的传递,我是通过两组变量进行的。一组为在主窗口中进行定义,而另外一组是在相应的面板中通过ClassWizard进行定义,这样方便数据的交换,也方便自己的数据的处理工作。
在我的函数信号发生器的主面板中还有复位按钮和测试硬件连接情况的按钮,一切从用户的方便操作来进行考虑。

4 示波器模块
4.1 Agilent 54622D 100MHz混合信号示波器介绍
Agilent 54622D混合信号示波器(MSO)具有100MHz 的带宽,200MS/S采样率, 2MB通道的MegaZoom深存储器以及2+16通道有2个模拟通道和16个数字通道,组合了示波器对信号的详尽分析和逻辑分析仪的多通道定时测量能力。
其中MegaZoom 技术把常规深存储器数字示波器的优点与传统模拟示波器的响应能力和更新率结合在一起常规深存储器数字式示波器,能够捕获长的时间间隔同时保持高取样率MegaZoom 技术保持了深存储器的优点而并没有在通常深存储器中常见的迟钝用户界面在对数据作平移和缩放时和很差的信号更新率;MegaZoom 技术在示波器探头与示波器显示间的信号数据通路中融入多处理器体系结构, 因此在查看采集数据时能迅速响应平移和缩放控制MegaZoom 技术把更新率即时控制响应和创新的深存储器融于一体使您能以高取样率捕获长时间周期并且不管是在运行中还是在停止后都能立即放大所关注的信号细节在一台示波器中组合了深存储器前面板响应能力和显示屏更新率因此就不需要选择特定的工作模式或存储器深度。以下是54620系列的数字示波器的参数对照表:
 54620系列 54621A 54621D 54622A 54622D 54624A
示波器通道数 2 2 2 2 4
带宽 60MHZ 60MHZ 100MHZ 100MHZ 100MHZ
采样率 400MSa/s
最大输入(dc+峰值ac) 400V
分辨率 8bit
量程(每格) 1mV-5V
逻辑通道 N/A 16 N/A 16 N/A
采样率 8通道有效 400MSa/s 400MSa/s
16通道有效 200MSa/s 200MSa/s
输入电平 500mVppmin 500mVppmin
+40Vmax +40Vmax
存储器 示波器通道 2MB/ch 2MB/ch 2MB/ch 2MB/ch 2MB/ch
逻辑通道 单接口夹 8MB/ch 8MB/ch
双接口夹 4MB/ch 4MB/ch
时基(每格) 5ns-50s
触发 边沿,脉冲宽度,模式,序列,持续时间和新的I2C
峰检测 5ns
测量 峰直,平均直,有效值,频率,周期,脉冲宽度,上升/下降时间
波形运算 相减,相乘,FFT,积分,微分
存储 内装的软盘
连接性 标准配置的RS-232和并行接口,可选GPIB
内装帮助 9种语言的快速帮助

4.2 Agilent 54622D 100MHz混合信号示波器界面设计和编程
在进行界面设计之前,我通过安捷伦网站下载了其相关的资料: 54620/40-series Oscilloscopes Programmer’s Guide通过该资料我了解了该数字示波器的工作原理:

图4-2
上面的结构框图是对于每一个程序的基本构架。示波器初始化是很重要的,没有进行正确的初始化,你的程序也许在这台机子上能正确的运行,但换台机子就可能不行了。程序的初始化包括定义和初始化变量、分配内存或测试系统的配置;控制器的初始化确保连接示波器的接口(GPIB/RS-232)能正确的建立以及准备好数据的收发。示波器的初始化建立通道配置和标签,起始电压,触发配置以及时间轴与采集类型。
一旦初始化正确,就可以进行数据的采集工作了。注意:当示波器响应从控制器发过来的指令的时候,不能执行采集工作。还有,当你改变示波器的配置时,所有采集回来的数据将会被放弃。采集数据是通过执行:Digitize指令来启动的。该指令清空波形缓冲区,启动采集直至采集内存已经存满数据,并停止。采集回来的数据要么通过示波器进行显示或测量,要么就是发送给计算机来进行进一步的分析。而当指令:digitize正在工作的时候,其他的指令将会被放入缓冲区直至该指令执行完毕。
你可以置示波器为运行模式,并在你的程序中使用一个等待循环来确保在你进行测量之前示波器至少完成了一个采集工作。安捷伦并不推荐这种方式,因为循环等待的时间是会变化的,促使你的程序的崩溃。另外,:digitize当完成采集时,会停止采集工作以至于所有测量是针对所显示的数据而不是针对改变了的数据。
数据采集回来之后,你可以通过示波器进行数据的测量或传送数据给编写的程序进行处理。测量包括频率、占空比、周期、正负脉宽。你可以使用指令:waveform来进行数据的读取并返回给计算机,并通过自己编写的程序进行数据的分析处理。
大体了解了示波器的工作过程之后,我开始自己的程序的架构(参看资料上的架构),下面是我的程序的结构(详细):

有了大体的框架之后,开始了界面的设计,因为是示波器,所以一定要有一个显示窗口来对采集回来的数据进行处理显示;除了波形的显示外,还需要对一些参数进行测量操作。固然需要一些执行按钮和一些显示用的控件。而用户有时需要对示波器的参数进行更改,所以需要提供给用户一个设置参数的对话框。而对于测量的结果的显示,我是通过一个弹出式信息对话框的形式来进行的,这样做比较的简明清楚。下面是我的大体界面:

图4-3
这里的界面和函数信号发生器的类似:有复位按钮、信息按钮等。测量时可以对测量源进行选择,测量参数进行选择等。而示波器的参数的设置窗口中对各种参数进行了默认的设置,方便用户的设置。
在示波器的编程过程中,有两个难题:数据的采集和数据的显示问题。波形数据包括引导数据和波形数据:引导数据通过指令👋pre来对选择的波形进行引导数据的查询,该引导数据包含了关于当前响应的通道的水平测量和垂直测量的相关信息如下所示:format、type、points、count、xincrement、xoringin、xreference、yincrement、yorigin、yreference
其中,详细信息如下:
formant:从示波器传送给计算机的数据的格式化字符;0表示BYTE,1表示WORD格式,2表示ASCII格式,用一个整数表示(由指令:waveform:format进行设置)
type:数据采集模式;0表示正常模式,1表示peak模式,2表示平均模式,用一个整数表示(有指令:acquire:type来进行设置)
points:数据传送的个数
count:该参数常为1
xincrement:该参数是指当前的通道源的时间增益(在两个连续数据之间)
xorigin:在内存中的第一个数据值
xreference:与x-origin相联系的数据点的索引值,该值经常是0
yincrement: 该参数是指当前的通道源的电压增益(在两个连续数据之间)
yorigin: 该参数是指当前的通道源的y的原点值
yreference:该参数指的是y-origin起点的数据值
只有正确的读回引导数据才能结合下面的波形数据进行计算出真正的实际波形数据值。
通过指令:waveform:data?来查询采样回来的二进制数据块(使用ieee488.2任意数据块格式)。通过指令:waveform:unsigned,:waveform:byteorder,:waveform:format 中的任意一个指令来对二进制数据进行格式化操作。而该数据点的个数是由指令:waveform:points来进行设定的。该二进制数据块由两个部分组成:


举个例子:
=#800002048 ”#8”会从头部去掉,而留下数值部分,表示波形数据块的字节的个数。该参数是会变的(根据指令:waveform:points来设置数据点的采样的个数)。从示波器中读取数据直至读回结束符为止。下面给出了读取引导数据和波形数据的部分代码:
ViStatus error;
viPrintf(/ oscillograph/m_Session,“:ACQ:COMPLETE 100\n”);
viPrintf(/ oscillograph/m_Session,“👋points 2000\n”);
viPrintf(/ oscillograph/m_Session,“:DIGITIZE %s\n”,osciset.m_Channel);
// preamble的采集***
viPrintf(/ oscillograph/m_Session,“:waveform:preamble?\n”); error=viScanf(/ oscillograph/m_Session,“%f,%f,%f,%f,%f,%f,%f,%f,%f,%f\n”,&preamble[0],&preamble[1],&preamble[2],&preamble[3],&preamble[4],&preamble[5],&preamble[6],&preamble[7],&preamble[8],&preamble[9]);
if(error!=VI_SUCCESS)
{error_handler(/ oscillograph/m_Session,error);return;}
// 数据点的采集部分**
//利用字符转换符和viScanf函数来进行读取数据并定义一个无符号的字符数组来对读//取的数据进行存储--随后进行数据
//************************************ 的转换和数据的处理—2005/5/17
viPrintf(/oscillograph/m_Session,“:waveform:data?\n”);
unsigned char temp[2000];
for(int i=0;i<2000;i++)
{ error=viScanf(/oscillograph/m_Session,“%c”,&temp[i]);
if(error!=VI_SUCCESS)
{error_handler(/oscillograph/m_Session,error); return;}
}
//数据的转换
int j=0;
for(i=18;i<2000;i+=20,j++)
{ temp[j]=(int)temp[i];
n++;//数据的个数 }
数据采集这一块我花了不少的时间,不断的对指令进行测试,但是以为只要通过:waveform:data?指令然后进行读取就OK了!!然而,查看资料才晓得示波器采集回来的数据是想前面所示的那样,有头部数据以及二进制数据块,这就把我给难住了!二进制数据该怎么读呢??从安捷伦网站上下载下来的资料中虽然提供了怎样进行波形数据的读取的方法,但它并没有给出在VISA情况下的数据的读取的方法。但是它也给了我一定的提示:头部给出了数据块的字节的个数,既然是字节,也就是一个字符。那么这样的话就可以读取数据了。然后经过我的测试,一切OK!我好高兴~~~~~
读回数据之后,就是数据的显示问题。至于这个我采用了别人给我的一个类,通过该类我可以比较轻松的进行数据的显示工作。我只要将读回的波形数据进行处理,并将数据传递给给类的一个数组就可以了!!
//数据的转换
int j=0;
for(i=18;i<2000;i+=20,j++)
{ temp[j]=(int)temp[i];
n++;//数据的个数 }
//数据存储

for(int k=0;k<n;k++)
{ m_Static.wave_data[k]=(((float)temp[k]-preamble[9])*preamble[7]+preamble[8])*m_Y;//这里使用Y
} Invalidate();//这个用于屏幕画面的刷新
两大问题的解决,示波器也就基本上可以告一个段落了!!其他的功能的实现相对来说要容易的些!
下面是示波器显示窗口部分初始化代码:
//***** 下面是另外一种方法—更方便控制示波器的显示的尺度,也就是在对话框模板上//添加一个静态控件,并调节它的尺度到你需要的范围,并最好是更改一下它的ID,然//后通过下列语句来进行创建显示
CRect m_nRect;
CStatic
pStatic=(CStatic
)GetDlgItem(IDC_STATIC_SHOW);
pStatic->GetClientRect(&m_nRect);
m_Static.SubclassDlgItem(IDC_STATIC_SHOW,this);

m_Static.m_rect=m_nRect;
m_Static.m_nStaticLeft=m_nRect.left+20;
m_Static.m_nStaticTop=m_nRect.top+15;
m_Static.m_nStaticRight=m_nRect.right-3;
m_Static.m_nStaticBottom=m_nRect.bottom-16;

在基本的问题解决之后,希望对于显示在示波器上的波形具有缩放功能,使自己的示波器更完美。于是就添加了两个Slider控件来控制波形的拉伸,起初的设计就是刷新窗口的问题:因为通过滑块来改变XY的值,但需要它能及时的在窗口上得到响应,这个问题使我有点头痛。起初的设计比较的粗糙,通过鼠标的点击消息来进行对整个窗口的刷新。不过,这样的设计实在是太难看了,后来,通过上网咨询,论坛的朋友建议我使用线程函数进行对特定的窗口进行刷新。于是,我开始接触线程,并去了解线程,并通过书本上的例子的练习,加深了解以及和同学进行探讨,终于将问题得以解决。具体代码见附录,下面是线程函数的代码:
static CEvent g_End;//事件对象的建立
struct data //需要传递的数据
{ CRect rect;
COSCILLOGRAPH* dlg;
CStatic* m_pCS;
}m_Data;
//*************************关键的线程函数
UINT ThreadProc(LPVOID param)
{ if(::WaitForSingleObject(g_End,0)==WAIT_OBJECT_0)//该语句用来判断是否挂起或结束线程
{ data
p=(data
)param;
//这里是针对指定控件如静态控件的刷新,相对对话框的刷新,这种刷新不会出现闪屏问题
p->m_pCS->InvalidateRect(p->rect,TRUE);
g_End.ResetEvent();
return 0; }
return 0;
}
5万用表模块
5.1 Agilent E1412A 6.5高精度万用表介绍
安捷伦的E1412A 6位半高精度万用表是一个规格为C,具有一个槽,基于消息基的VXI模块,它与E1312A模块只是规格上的不同。它具有更多的功能和更高的执行效率,而在价格上却与5位半的万用表相当。该万用表提供了广泛的测量功能包括电压、幅值、电阻、和频率的高级测量以及对测试TTL输出和直流电压比测量。标准的测量包括ac/dc电压、ac/dc电流、2-4线电阻和频率与周期的测量。测量直流电压时,该万用表可以提供每秒65次范围变化和每秒30次函数变化。
5.2 Agilent E1412A 6.5高精度万用表界面设计和编程
通过阅读Agilent E1412A的用户使用手册,了解到Agilent E1412A能进行高精度的测量。然而,为了到达最好的测量效果,必须做一些准备工作来消除一些潜在的测量误差。在使用手册中给出了一些常见的错误,并提供了一些解决的办法。譬如在DC电压测量过程中,就会遇上如下的一些常见的错误:热电动势、阻抗错误、电流泄漏错误、不合格电源的噪声、共模以及磁环路和地环路引起的噪声等等,都提供了一些解决的思路。
而在进行测量之前也需要对万用表进行适当的配置:比如说AC信号的过虑(设置三种不同的AC过滤器,可以提高低频测量的精度或是在进行AC电压以及电流测量时提高读取速率。又如DC输入阻抗(在正常的情况下,所有的DC电压范围的输入阻抗是10兆欧姆来最小化噪声。你可以为100mVdc, 1Vdc and 10Vdc 范围的直流电压设置输入阻抗高于10G欧姆以利于减少测量负载的错误。)还有许多配置,用户手册中都给出了说明。
最简单的测量是通过使用MEASURE命令来进行的,该命令用来配置函数,启动测量和置数据于缓冲区以便计算机利用IO命令的读取。一条MEASURE命令将会启动多个测量(如果触发数或采样数大于1);如果读回数据的速度不够快以至于模块的输出缓冲区满时,测量将会停止直至有空间来存储数据时才重新启动测量。譬如:
*RST //复位万用表
MEAS:VOLT:DC?//配置dc电压测量,并从万用表返回数据
ENTER statement//将数据传送给计算机
而指令read?也是可以用来启动测量的,在使用read?指令之前,需要使用所需的测量函数对万用表进行配置。该命令启动测量并将数据直接放入输出缓冲区,接着由用户提供IO函数来取回数据并发送给计算机。一条read?指令可以启动多个测量(如果触发数或采样数大于1时);如果读回数据的速度不够快以至于模块的输出缓冲区满时,测量将会停止直至有空间来存储数据时才重新启动测量。read?指令被分解为两个命令:init和fetc?.指令init置万用表于等待触发状态,外部触发发生来启动测量,测量值被存储于万用表内部存储器中。而指令fetc?将万用表的内存中的数据输出到输出缓冲区中并由计算机取回数据。注意:在使用init和fetc?命令时应当小心,万用表的内部存储器的容量只有有512个字节,超过该范围的最先的数据将会被丢弃。
每个测量以LF(换行符)结束。HP-IB End or Indentify信号将会在最后一个字节被发送。如果多个测量被返回,测量值会以逗号分隔,EOI也会在最后一个字节被发送。
例如:+1.23456E+LF,+1.234567E+12LF,+1.23456E+12LFEOI。
做外部触发测量时,必须提供TTL外部触发信号到BNC(同轴电缆插接件);测量将会被这个信号低脉冲触发。注意:Configure命令用于指定函数配置万用表,该命令并不启动测量。
对万用表的测量过程有了一定的了解之后,心中也有了一定的思路,如下图:

对于万用表的初始化,我将其设置为多选按钮来进行配置,设计的思想是这样的:采用多选按钮,也就意味着可以不选,采用默认的参数值;而用户如果需要设置的话,则单击选中按钮,会弹出一个对话框提供用户选择如下所示:

图5-1
上面就是我的万用表的基本的框图了。而测量结果的显示部分则是通过弹出式信息框的形式来对测量结果进行显示。这里给出了程序的部分代码如下:
//这里对万用表的输入输出以及采样进行设置,而这些参数的值是通过用户界面中的//控件进行传递的
viPrintf(/multimeter/theApp.m_Session,“SAMPLE:COUNT%s\n”,m_nStr_Sample);
viPrintf(/multimeter/theApp.m_Session,“INPUT:IMPEDANCE:AUTO %s\n”,m_nStr_Input);
viPrintf(/multimeter/theApp.m_Session,“OUTPUT:%s\n”,m_nStr_Output);
//触发器设置按钮的点击-这里是一个大的循环,对用户是否对触发器进行了设置操作//通过变量m_btrigger进行判断
if(m_btrigger1)
{viPrintf(/multimeter/theApp.m_Session,“TRIG:SOURCE %s\n”,SetParam.m_nTrigger_Source);
viPrintf(/multimeter/theApp.m_Session,“TRIG:COUNT %s\n”,SetParam.m_nTrigger_Count);
viPrintf(/multimeter/theApp.m_Session,“TRIG:DELAY %s\n”,SetParam.m_nTrigger_Delay);//这里要对延时做处理判断
//参数是否已经被选择的判断2005/5/22**
int m_nID;
m_nID=GetCheckedRadioButton(IDC_RADIO_VOLTAGE,IDC_RADIO_PERIOD);
if(m_nID
0)
{ MessageBox(“请先选择测量参数!”,“提示信息”,MB_OK);
return;}
switch(m_nID)//通过这个switch语句对用户选择的测量参数的判断
{case IDC_RADIO_VOLTAGE://电压测量
viPrintf(/multimeter/theApp.m_Session,“CONF:VOLT:%s %s,%s\n”,SetParam.m_nParam_Type,SetParam.m_nParam_Range,SetParam.m_nParam_Resolution);
if(“DC”==SetParam.m_nParam_Type) {viPrintf(/multimeter/theApp.m_Session,“VOLT:DC:%s\n”,SetParam.m_nParam_Aper); viPrintf(/multimeter/theApp.m_Session,“VOLT:DC:%s\n”,SetParam.m_nParam_NPLC);} break; ………………
//通过如下的SCPI命令启动测量
viPrintf(/multimeter/theApp.m_Session,“INIT;FETC?\n”);
//通过函数viScanf()来读取读取数据,并通过一个弹出式信息框对测量结果进行显示
char buff[100];
viScanf(/multimeter/theApp.m_Session,“%s”,buff);
CString temp;
temp.Format(“Result is %s”,buff);
MessageBox(temp,“测量结果显示”,MB_OK);
在万用表的设计和编程过程中,由于有了函数信号发生器以及示波器的经验,所以工作进度就相对来的快一些,关键的的问题还在于程序的调试上。
6测试系统的测试举例
6.1 利用函数信号发生器调频波和调制波的实现
首先,运行主程序如下:
如左图所示,通过单击查找资源按钮来搜索仪器
并通过一个列表控件将有效的资源列于表中,通
过双击列表框中的资源地址来激活建立会话按钮
从而允许用户建立会话(会话建立成功与否会提
示用户相关的信息)。在三个模块的按钮的处理函
数中,对地址进行有效的判断,从而无需用户来
记忆仪器的地址,即使单击错误也没有什么关

图6-1	               系,重新进行建立即可。

因为是调频波,所以需要对载波和调制信号进行设置,这里只给出了设置窗口的一部分:

图6-2
可以发现,基本参数中的占空比是被屏蔽掉的,因为占空比指对方波有效,所以为了避免用户的误操作,进行了屏蔽处理,方便了用户。参数设置之后,单击执行按钮,就可以看到函数信号发生器按照用户的设置进行了调频波形的产生。通过自己的示波器软件对波形进行读取操作,得到如下的波形图 :

图6-3
以上是调频波的实现过程。下面是调幅波的实现:

图6-4
调幅波的实现过程如图所示,和调频波类似,其结果如下图:

图6-5
函数信号发生器可以实现基本的功能,不过在某些细节方面也存在一定的问题,如上面的各种参数的设置,并没有对用户输入的数据进行相应的判断(范围大小、数字或字符等)以及对参数单位处理操作。这些都是需要改进的地方。
6.2 利用示波器实现对波形的显示
通过自己编写的函数信号发生器产生一个三角波形(其操作过程如上一小节所描述的那样),发送给54622D混合示波器进行显示。接着,结束与函数信号发生器的会话,并建立与该示波器的会话来进行数据的测量以及波形的显示操作。需要注意的是,当进入示波器的显示界面时,如果先进行波形的显示操作,则参数的测量将会无法进行下去或者进行过一次显示之后,也一样不能在继续进行采样显示了,需要重新的退出再建立会话才行(这个问题是在调试的过程中发现的),不过在示波器章节中也提到了关于示波器执行采集数据的时候的一些注意事项。下面是软面板示波器对采集回来的数据的处理以及显示部分:

对一个三角波的显示。通过示波器下面的水平的调节滑块可以实现对X轴的波形的缩放操作,Y轴的缩放功能
没有被实现(当设法去实现Y轴的缩放
功能的时候,出现一些问题,这也许跟主次线程有关,因为我的示波器的界面是通过建立一个单独的CMyStatic类来实现的,将采集来的数据输入该类中的数组即可,所以在Y轴缩放的处理上就出现了问题,而相对来说,X轴就比较的容易实现,通过一个辅助线程来对特定的界面(示波器界面)进行刷新即可,并且也已经实现了该功能。(2005/6/9)同样,调节滑块也能实现Y轴的缩放功能。
下面给出了利用示波器进行数据的测量的例子,如图:
频率的测量:(首先,选择通道源和测量参数,然后,点击测量按钮来进行测量)

图6-6
电压的测量:

图6-7
以上的测量只需用户选择好测量源和测量参数,并点击测量按钮就可以实现参数的测量;而测量结果的给出就向上面给出的图片一样,通过弹出式的信息框来进行显示,这种显示方式直观方便。
6.3 利用万用表模块实现测量
万用表模块的启动和上述的仪器一样,通过主界面的会话的建立并点击相应的按钮进入万用表模块,当会话没有打开的时候,会提示用户相关的信息,如下图: 可以看到在资源列表中,有许多资源的地址。用户只要选中一个地址,并相应的双击就可以打开会话(同样会提示用户会话的打开是成功或失败)就象左图那样。
图6-8

万用表的主界面如下图:

图6-9
万用表可以对电压、电流、电阻、频率和周期进行测量,通过右边框的参数可以实现对输入和输出以及触发器的参数的设置(对于触发器提供了默认值,用户可以不用设置而直接的使用),而对于输入的设置,起初是进行屏蔽的,只有当用户选择测量的是DC电压时才激活。
下面是电压的测量示意图:

图6-10
通过单选按钮来选择测量参数,并弹出参数设置对话框提供用户设置;设置对话框的设置参数分为几个块:电压/电流、2线/4线、周期/频率以及公共参数的设置(各个参数都提供了默认值),清晰的设置使用户的设置比较的快捷方便。设置好参数后,就可以通过测量按钮来执行测量操作,如下图:

图6-11
测量的结果和前面的显示的方式是一样的。而对于触发器的参数的设置,如果用户对自己的设置想进行修改的话,可以重新设置并会提示相应的信息来提醒用户是否真的放弃过去参数的配置,如下图:

图6-12
以上就是对万用表模块实现简单的测量操作以及一些人性化的设计。在万用表的许多方面都本着人性化的设计考虑,尽量的使用户的操作显得简单和明了。

7开发的心得和体会
通过这个课题的开发制作,首先,让我认识了虚拟仪器,这种比传统仪器更为先进的仪器结合了现代计算机技术、测量技术、显示技术/控制技术、传感器技术以及网络技术等当今先进的科学技术,给测量领域带来了一场新的革命;自动测试系统就是利用上面的技术进行各种测量参数的自动化测试,并通过计算机强大的数据处理能力和图形绘制能力对数据进行处理和显示操作,提高了测量的精度和速度以及改变了人们的测量环境和人为的测量的误差。人们只需要在计算机的虚拟面板上输入各种配置参数和测量参数,点击按钮就可以实现快速准确的测量,而无需人们深入到测量现场。人们可以利用虚拟仪器系统来组建适合自己的任何测试系统,摆脱传统仪器带来的功能固定、缺乏灵活性以及高昂的价格的烦恼。虚拟仪器将会给科技和工业生产产生不可估量的影响。
其次,通过这次的制作,让自己也有幸接触价格不菲的可程控的仪器,并通过自己的编程对其进行远程的控制操作。我所接触的是一些消息基的仪器,本身具有自己的CPU,能对用户发送指令进行解释并执行。这些仪器通过前面的面板操作更便捷和人性化,由于具有CPU和应用软件,功能更强大,速度越快,精度越高。价格也不菲,一台就是5~6万,顶上十几台电脑了。
再者,收获最大应该就是软件的编写了。我是利用VC开发工具开发一个简单的虚拟仪器。刚开始接触的时候,也是模模糊糊的概念。后来通过资料的阅读,才明确自己的工作。在这个过程中,资料的搜索和阅读是比较的重要。这会加快自己认识。在软件的编写过程中,经常会碰上这样那样的小问题,问题虽小,可也是烦人啊!就拿一个这样的问题,我在头文件中定义了一个函数,在执行文件中进行具体的编写。按理说,在编写的过程中,”::”时会有提示的,却奇怪的却没有给出提示,如果没有碰到过这样问题,还真的有点烦人哦!这对于不熟悉函数的人来说是一种困扰。这个其实是VC的一种记忆功能而已,如果遇到这样的问题,只要退出程序,把NCB为后缀的文件删了就OK了!还有,在装过NI的LABVIEW的机子上运行自己的程序(不管是MFC还是WIN32)都会出现这样的问题:The type of interface is valid but the specified interface number is not configured.后来解决了,原来是一个NI-VISA和Agilent VISA的设置的问题,只要通过NI-VISA中的设置一下就行了。还有数据传递问题,因为我是想通过设置一个全局的变量进行传递总的资源会话和某个仪器的会话的值给三个模块,这样的设计不仅使自己软件的功能上更符合实际,而且对资源的管理更合理,省得在每一个模块中进行独立的会话的建立和关闭,而且,用户没有选择的余地,资源的地址是固定的,当外部的仪器的地址改变的时候,需要修改地址,重新的编译连接。在软件设计的过程中,应该竭力使自己的软件符合人们的使用习惯,便于操作和界面的简洁,一切的设计都要从方便人们的操作和使用为前提。这是软件设计过程中一个比较重要的方面。在软件的设计过程中遇到的其他问题在前面的各个模块的章节中做了一些说明,这里就不说了。
8 结论
本课题是基于LAN的VXI自动测试系统,通过局域网实现对仪器的远程控制和操作。其原理是通过Agilent 的IO库之一visa实现SCPI指令的发送和数据的接收,而基于消息基的各种仪器接收计算机发送过来的SCPI指令,并通过仪器本身的CPU进行指令的翻译执行来响应用户指令,并将数据送到总线、缓冲区或内存以供计算机的读取做进一步的处理。虚拟仪器自动测试系统具有广阔的发展前景。
该虚拟仪器软件通过不断地进行实验和调试,基本能实现函数信号发生器、示波器和万用表简单的功能。在函数信号发生器模块上,能实现几种基本波形和几种调制方式的实现;示波器能实现简单的显示操作和数据的测量;万用表同样能实现基本参数的测量,并且三个模块可以相互的利用资源。比如,通过函数信号发生器模块来发出一个特定的信号,接着通过示波器进行读取和显示测量或通过万用表进行相关参数更精确的测量,三个模块在测试系统中是比较常用的:函数信号发生器提供各种信号供使用、示波器能对外部信号进行显示和简单的测量以及万用表来实现对各种参数的较为准确的测量。
在三个模块的调试过程中,遇到了许多问题:数据如何的传递问题、窗口图像的如何的进行及时的刷新问题、三个模块在进入之前,如何来识别问题、如何使用全局的变量来实现统一的资源会话的建立和关闭问题、示波器的波形的绘画问题以及数据的读取和处理问题等等,还有许许多多的问题在毕业设计之前,没有遇到过。不过在老师同学还有自己的努力下(还有论坛朋友的建议与帮助),一个一个的将问题解决。
由于时间比较的紧,需要完成的模块比较的多,所以在模块设计的许多地方,都没有考虑周全,比如,对用户的输入没有进行合理的判断处理,用户可以输出任意的字符,这个显然是不合理的;又如对于一个频率很高的波形的产生,需要用户自己输入以Hz为单位的数字,所以会输入比较大的数据,如果能够改变频率的单位,从而可以减少用户的输入和一些不必要的错误,所有这些都是需要进一步完善的地方。希望各位老师能给予谅解!

参考文献(黑体四号、顶格)
[1] 张毅刚,彭喜元,姜守达等.自动测试系统.哈尔滨:哈尔滨工业大学出版社,2001,9:1~258.
[2] 胡晓军,邓波,高宏伟.Visual C++高级开发范例解析.北京:电子工业出版社,2002,1:1~326.
[3] 陈尚松,雷加,郭庆.电子测量与仪器.北京:电子工业出版社,2005,1:1~435.
[4] 张毅,周绍磊,杨秀霞.虚拟仪器技术分析与应用.北京:机械工业出版社,2004,2:1~301.
[5] 辛长安,梅林.VC++编程技术与难点剖析.北京:清华大学出版社,2002,4:1~514.
[6] Agilent VISA User’guide Manual Part Number: E2090-90040 Printed in U.S.A. E0801
[7] Agilent 54621D/22D/41D/42D 混合信号示波器. 出版号 54622-97032 2002 年 3 月
[8] Agilent 33250A 80 MHz 函数/ 任意波形发生器. 出版号 33250-90418 2000 年 4 月
[9] Agilent E5810A LAN/GPIB Gateway for Windows User’s guide

附 录 一
函数信号发生器重点的编写函数
void CGENERATER::OnBtnOk()
{ ViStatus error;//对错误信息的存储
//获得调制方式的ID
int nID_Select_Modul;
nID_Select_Modul=GetCheckedRadioButton(IDC_RADIO_BW,IDC_RADIO_FM);//问题的关键就出现在这里--该函数没有执行--2005、4、28
//问题解决了!!原来是单选按钮的ID的值的大小问题!!
//在该函数中,小的在前,大的在后!!!–2005/4/29
UpdateData(TRUE);
//测试代码—成功—2005/24/29
// sprintf(buffer,“%d”,nID_Select_Modul);
// MessageBox(buffer);
//

//进行判断处理
switch(nID_Select_Modul)
{ case IDC_RADIO_BW:
viPrintf(/funcgen/theApp.m_Session,“OUTPut:LOAD %s\n”,m_nResistance);//输出端的设置
viPrintf(/funcgen/theApp.m_Session,“OUTPut:SYNC %s\n”,m_nSignal);//设置同步信号
CheckWaveRadioButton();//波形选择的判断--将波形的字符串存储在m_nBaseShape中
//**********************************判断用户选择的波形是否是方波
if(GetCheckedRadioButton(IDC_RADIO_SIN,IDC_RADIO_DC)==IDC_RADIO_FWAVE)
{
viPrintf(/funcgen/theApp.m_Session,“FUNC %s\n”,m_nBaseShape);
viPrintf(/funcgen/theApp.m_Session,“FREQ %f\n”,m_nFreq);
viPrintf(/funcgen/theApp.m_Session,“VOLT %f\n”,m_nAmpl);
viPrintf(/funcgen/theApp.m_Session,“VOLT:OFFS %f\n”,m_nOffs);
viPrintf(/funcgen/theApp.m_Session,“FUNC:SQU:DCYC %f\n”,m_nDcycle);
}//******************************判断用户选择的是否是任意波
else if(GetCheckedRadioButton(IDC_RADIO_SIN,IDC_RADIO_DC)==IDC_RADIO_ARBI)
{error=viPrintf(/funcgen/theApp.m_Session,“APPL:USER %f,%f,%f\n”,m_nFreq,m_nAmpl,m_nOffs);
if(error!=VI_SUCCESS)
{ error_handler(/funcgen/theApp.m_Session,error);
break;
} viPrintf(/funcgen/theApp.m_Session,“FUNC:USER %s\n”,WaveForm.m_Arbitrary); }
else
{ error=viPrintf(/funcgen/theApp.m_Session,“APPL:%s %f,%f,%f\n”,m_nBaseShape,m_nFreq,m_nAmpl,m_nOffs);//这里的参数需要用户自己添加 if(error!=VI_SUCCESS)
{ error_handler(/funcgen/theApp.m_Session,error);
break; } }
MessageBox(“Successfully!”);//给一个提示框--明示用户的操作
break;
case IDC_RADIO_AM:
viPrintf(/funcgen/theApp.m_Session,“OUTPut:LOAD %s\n”,m_nResistance);//输出端的设置
viPrintf(/funcgen/theApp.m_Session,“OUTPut:SYNC %s\n”,m_nSignal);//设置同步信号
viPrintf(/funcgen/theApp.m_Session,“OUTPut OFF\n”);
CheckWaveRadioButton();//波形选择的判断--将波形的字符串存储在m_nBaseShape中
error=viPrintf(/funcgen/theApp.m_Session,“APPL:%s %f,%f,%f\n”,m_nBaseShape,m_nFreq,m_nAmpl,m_nOffs);//这里的参数和基波一样--即是载波
if(error!=VI_SUCCESS)
{ error_handler(/funcgen/theApp.m_Session,error);
break; }
if(“EXT”!=WaveForm.m_Source) {
viPrintf(/funcgen/theApp.m_Session,“AM:SOUR %s\n”,WaveForm.m_Source);
viPrintf(/funcgen/theApp.m_Session,“AM:INT:FUNC %s\n”,WaveForm.m_WaveF);//这里输入调制波的波形
viPrintf(/funcgen/theApp.m_Session,“AM:INT:FREQ %f\n”,WaveForm.m_Freq);//这里输入调制波的频率
viPrintf(/funcgen/theApp.m_Session,“AM:DEPT %f\n”,WaveForm.m_A_Dept);//这里输入调制波的调制深度
viPrintf(/funcgen/theApp.m_Session,“AM:STAT ON\n”);//启动调制
viPrintf(/funcgen/theApp.m_Session,“OUTPut ON\n”);
MessageBox(“Successfully!”);
break; }
else
{ break; }
case IDC_RADIO_FM:
viPrintf(/funcgen/theApp.m_Session,“OUTPut:LOAD %s\n”,m_nResistance);//输出端的设置
viPrintf(/funcgen/theApp.m_Session,“OUTPut:SYNC %s\n”,m_nSignal);//设置同步信号
viPrintf(/funcgen/theApp.m_Session,“OUTPut OFF\n”);//是否必要???---有待核实---2005、4、28
CheckWaveRadioButton();//波形选择的判断--将波形的字符串存储在m_nBaseShape中
error=viPrintf(/funcgen/theApp.m_Session,“APPL:%s %f,%f,%f\n”,m_nBaseShape,m_nFreq,m_nAmpl,m_nOffs);//这里的参数和基波一样--即是载波
if(error!=VI_SUCCESS)
{ error_handler(/funcgen/theApp.m_Session,error);
break; }
if(“EXT”!=WaveForm.m_Source)
{ viPrintf(/funcgen/theApp.m_Session,“FM:SOUR %s\n”,WaveForm.m_Source);
viPrintf(/funcgen/theApp.m_Session,“FM:INT:FUNC %s\n”,WaveForm.m_WaveF);//这里输入调制波的波形
viPrintf(/funcgen/theApp.m_Session,“FM:INT:FREQ %f\n”,WaveForm.m_Freq);//这里输入调制波的频率
viPrintf(/funcgen/theApp.m_Session,“FM:DEV %f\n”,WaveForm.m_F_Offset);//这里输入调制波的频偏
viPrintf(/funcgen/theApp.m_Session,“FM:STAT ON\n”);
viPrintf(/funcgen/theApp.m_Session,“OUTPut ON\n”);
MessageBox(“Successfully!”);
break; }
else
{ break; }
case IDC_RADIO_FSK:
viPrintf(/funcgen/theApp.m_Session,“OUTPut:LOAD %s\n”,m_nResistance);//输出端的设置
viPrintf(/funcgen/theApp.m_Session,“OUTPut:SYNC %s\n”,m_nSignal);//设置同步信号
viPrintf(/funcgen/theApp.m_Session,“OUTPut OFF\n”);//是否必要???---有待核实---2005、4、28
CheckWaveRadioButton();//波形选择的判断--将波形的字符串存储在m_nBaseShape中
error=viPrintf(/funcgen/theApp.m_Session,“APPL:%s %f,%f,%f\n”,m_nBaseShape,m_nFreq,m_nAmpl,m_nOffs);//这里的参数和基波一样--即是载波
if(error!=VI_SUCCESS)
{ error_handler(/funcgen/theApp.m_Session,error);
break; }
if(“EXT”!=WaveForm.m_Source)
{ viPrintf(/funcgen/theApp.m_Session,“FSK:SOUR %s\n”,WaveForm.m_Source);
viPrintf(/funcgen/theApp.m_Session,“FSK:FREQ %f\n”,WaveForm.m_Freq);//这里输入调制波的频率
viPrintf(/funcgen/theApp.m_Session,“FSK:INT:RATE %f\n”,WaveForm.m_FSK_Rate);
viPrintf(/funcgen/theApp.m_Session,“FM:STAT ON\n”);
viPrintf(/funcgen/theApp.m_Session,“OUTPut ON\n”);
MessageBox(“Successfully!”);
break; }
else
{ MessageBox(“The FSK’Source is external!”);
break;
}
case IDC_RADIO_BM:
viPrintf(/funcgen/theApp.m_Session,“OUTPut:LOAD %s\n”,m_nResistance);//输出端的设置
viPrintf(/funcgen/theApp.m_Session,“OUTPut:SYNC %s\n”,m_nSignal);//设置同步信号
viPrintf(/funcgen/theApp.m_Session,“OUTPut OFF\n”);//是否必要???---有待核实---2005、4、28
CheckWaveRadioButton();//波形选择的判断--将波形的字符串存储在m_nBaseShape中
error=viPrintf(/funcgen/theApp.m_Session,“APPL:%s %f,%f,%f\n”,m_nBaseShape,m_nFreq,m_nAmpl,m_nOffs);//这里的参数和基波一样--即是载波
if(error!=VI_SUCCESS)
{ error_handler(/funcgen/theApp.m_Session,error);
break;
}
if(“EXT”!=WaveForm.m_Source)
{ error=viPrintf(/funcgen/theApp.m_Session,“BM:MODE %s\n”,WaveForm.m_BM_Mode);
if(error!=VI_SUCCESS)
{ error_handler(/funcgen/theApp.m_Session,error);
break;
} error=viPrintf(/funcgen/theApp.m_Session,“BM:NCYC %d\n”,WaveForm.m_BM_Ncycle);
if(error!=VI_SUCCESS)
{ error_handler(/funcgen/theApp.m_Session,error);
break;
} error=viPrintf(/funcgen/theApp.m_Session,“BM:INT:PER %f\n”,WaveForm.m_BM_Per);
if(error!=VI_SUCCESS)
{ error_handler(/funcgen/theApp.m_Session,error);
break;
}
error=viPrintf(/funcgen/theApp.m_Session,“BM:PHAS %f\n”,WaveForm.m_BM_Phase);
if(error!=VI_SUCCESS)
{ error_handler(/funcgen/theApp.m_Session,error);
break;
} error=viPrintf(/funcgen/theApp.m_Session,“TRIG:SOUR %s\n”,WaveForm.m_Source);
if(error!=VI_SUCCESS)
{ error_handler(/funcgen/theApp.m_Session,error);
break;
} error=viPrintf(/funcgen/theApp.m_Session,“BM:STAT ON\n”);
if(error!=VI_SUCCESS)
{ error_handler(/funcgen/theApp.m_Session,error);
break;
} error=viPrintf(/funcgen/theApp.m_Session,“OUTPut ON\n”);
if(error!=VI_SUCCESS)
{
error_handler(/funcgen/theApp.m_Session,error);
break;
}
MessageBox(“Successfully!”);
break;
} else
{ MessageBox(“The FSK’Source is external!”);
break;
}
break;
default: MessageBox(“Sorry!some error happen!!”);break;
} return;

}

附 录 二
//线程函数的代码
static CEvent g_End;
struct data
{ CRect rect;
COSCILLOGRAPH* dlg;
CStatic* m_pCS;
}m_Data;
//********************线程0
UINT ThreadProc(LPVOID param){
if(::WaitForSingleObject(g_End,0)==WAIT_OBJECT_0)
{ data
p=(data
)param;
// p->dlg->InvalidateRect(p->rect,TRUE);//这里是针对对话框的的窗体进行刷新 p->m_pCS->InvalidateRect(p->rect,TRUE);//这里是针对指定控件如静态控件的刷新,相对对话框的刷新,这种刷新不会出现闪屏问题
g_End.ResetEvent();
return 0; }
return 0
//主函数的代码
//示波器采集数据操作
void COSCILLOGRAPH::OnBtnOsciRead()
{
//

if(m_count!=0||m_setup!=0)
{ if(m_setup!=0)
{ManulSet();
m_setup=0;}
else
{ m_count=0; }}
viSetAttribute(/oscillograph/theApp.m_Session,VI_ATTR_TMO_VALUE,m_time);//

Capture();
//

return;
}
/***********************************数据的采集
void COSCILLOGRAPH::Capture()
{// m_Static.m_XShow=m_nCtrl_Slide_X.GetPos();
// m_Y=m_nCtrl_Slide_Y.GetPos();

ViStatus error;

// viPrintf(/oscillograph/m_Session,“:RUN\n”);
viPrintf(/oscillograph/theApp.m_Session,“:ACQ:COMPLETE 100\n”);
viPrintf(/oscillograph/theApp.m_Session,“👋points 2000\n”);
viPrintf(/oscillograph/theApp.m_Session,“:DIGITIZE %s\n”,osciset.m_Channel);
//******************************************preamble的采集
viPrintf(/oscillograph/theApp.m_Session,“:waveform:preamble?\n”); error=viScanf(/oscillograph/theApp.m_Session,“%f,%f,%f,%f,%f,%f,%f,%f,%f,%f\n”,&preamble[0],&preamble[1],&preamble[2],&preamble[3],&preamble[4],&preamble[5],&preamble[6],&preamble[7],&preamble[8],&preamble[9]);
if(error!=VI_SUCCESS)
{error_handler(/oscillograph/theApp.m_Session,error);
return;
}
//***************************************数据点的采集部分--利用字符转换符何viScanf函数来进行读取数据
//***************************************并定义一个无符号的字符数组来对读取的数据进行存储--随后进行数据
//************************************的转换和数据的处理—2005/5/17
viPrintf(/oscillograph/theApp.m_Session,“:waveform:data?\n”);
// int num=::atoi((TCHAR
)(osciset.m_Ret_Count.LockBuffer()));//数据的转换
// unsigned char
buff=new unsigned char[2000];//动态的分配内存
unsigned char temp[2000];
for(int i=0;i<2000;i++)
{ error=viScanf(/oscillograph/theApp.m_Session,“%c”,&temp[i]);
if(error!=VI_SUCCESS)
{
error_handler(/oscillograph/theApp.m_Session,error);
return;
} }
//****************************数据的转换
int j=0;
for(i=18;i<2000;i+=20,j++)
{ temp[j]=(int)temp[i];
n++;//数据的个数
} m_Static.data_counter=n;
// m_Data.m_n=n;
// m_Static.m_XShow=m_X;
//****************************数据存储
// time=(((float)1-preamble[6])*preamble[4]+preamble[5])*1000;//时间有问题--负值!2005/5/17
for(int k=0;k<n;k++)
{ m_Static.wave_data[k]=(((float)temp[k]-preamble[9])*preamble[7]+preamble[8])*5;
} Invalidate();
return; }

//**********************************下面是测量的线程
struct MEASURE
{
CComboBox
m_CB_Source;
CComboBox
m_CB_Param;
CDialog
m_dlg;
}m_measure;
//***********************************线程函数如下
UINT ThreadMeasure(LPVOID param)
{
MEASURE
m_meas=(MEASURE
)param;

int m_nSource;
CString m_nStr_Source;
m_nSource=m_meas->m_CB_Source->GetCurSel();
CString m_Source;
m_meas->m_CB_Source->GetWindowText(m_Source);

switch(m_nSource)
{
case 0:m_nStr_Source="CHAN1";break;
case 1:m_nStr_Source="CHAN2";break;
case 2:m_nStr_Source="DIGI0";break;
case 3:m_nStr_Source="DIGI1";break;
case 4:m_nStr_Source="DIGI2";break;
case 5:m_nStr_Source="DIGI3";break;
case 6:m_nStr_Source="FUNC";break;
case 7:m_nStr_Source="MATH";break;
}

CString m_nStr_Param;
m_nSource=m_meas->m_CB_Param->GetCurSel();
CString m_Param;
m_meas->m_CB_Param->GetWindowText(m_Param);
switch(m_nSource)
{
case 0:m_nStr_Param="VAMP";break;
case 1:m_nStr_Param="VPP";break;
case 2:m_nStr_Param="VAVE";break;
case 3:m_nStr_Param="VRMS";break;
case 4:m_nStr_Param="FREQ";break;
case 5:m_nStr_Param="PHAS";break;
case 6:m_nStr_Param="PERI";break;
case 7:m_nStr_Param="NWID";break;
case 8:m_nStr_Param="PWID";break;
case 9:m_nStr_Param="RISE";break;
case 10:m_nStr_Param="FALL";break;
case 11:m_nStr_Param="DELA";break;
case 12:m_nStr_Param="DUTY";break;
}

CString  m_Result;

viPrintf(theApp.m_Session,":MEAS:SOUR %s\n",m_nStr_Source);
viPrintf(/*oscillograph*/theApp.m_Session,":MEAS:%s?\n",m_nStr_Param);//这里的通道源的赋值会会结果好像有影响//%s\n",m_nStr_Param,m_nStr_Source);
float buff;
viScanf(/*oscillograph*/theApp.m_Session,"%f",&buff);
m_Result.Format("%s's %s value is %f!",m_Source,m_Param,buff);
m_meas->m_dlg->MessageBox(m_Result,"测量结果",MB_OK); 

return 0;

}
//******************************************测量函数
void COSCILLOGRAPH::OnBtnMeasure()
{ UpdateData(TRUE);
m_measure.m_CB_Param=&m_nCtrl_Param;
m_measure.m_CB_Source=&m_nCtrl_Source;
m_measure.m_dlg=this;
AfxBeginThread(ThreadMeasure,&m_measure);
return;

}//************************************X Y的处理函数
void COSCILLOGRAPH::OnCustomdrawSliderX(NMHDR
pNMHDR, LRESULT
pResult)
{ m_Static.m_XShow=m_nCtrl_Slide_X.GetPos();
AfxBeginThread(ThreadProc,&m_Data);//采用线程的方式进行界面的刷新
g_End.SetEvent();
pResult = 0;
}void COSCILLOGRAPH::OnCustomdrawSliderY(NMHDR
pNMHDR, LRESULT
pResult)
{ m_Static.m_YShow=m_nCtrl_Slide_Y.GetPos();
// m_Y=m_nCtrl_Slide_Y.GetPos();
AfxBeginThread(ThreadProc,&m_Data);
g_End.SetEvent();
*pResult = 0;
}

附 录 三
//万用表的测量函数的代码
void CMULTIMETER::OnBtnMultimeterMeas()
{//******************************************参数是否已经被选择的判断2005/5/22
int m_nID;
m_nID=GetCheckedRadioButton(IDC_RADIO_VOLTAGE,IDC_RADIO_PERIOD);
if(m_nID0)
{ MessageBox(“请先选择测量参数!”,“提示信息”,MB_OK);
return; }
//******************************************加上超时时间
viSetAttribute(/multimeter/theApp.m_Session,VI_ATTR_TMO_VALUE,10000);
//*输入输出的设置以及采样的设置
viPrintf(/multimeter/theApp.m_Session,"RST\n");
viPrintf(/multimeter/theApp.m_Session,“SAMP:COUN %s\n”,m_nStr_Sample);
// viPrintf(/multimeter/theApp.m_Session,“INP:IMP:AUTO %s\n”,m_nStr_Input);
viPrintf(/multimeter/theApp.m_Session,“OUTPUT:%s %s\n”,m_nStr_Output,m_nStr_Output_Mode);
//
//触发器设置按钮的点击--这里是一个大的循环
if(m_bTrig
TRUE)
{ viPrintf(/multimeter/theApp.m_Session,“TRIG:SOUR %s\n”,SetParam.m_nTrigger_Source);
viPrintf(/multimeter/theApp.m_Session,“TRIG:COUN %s\n”,SetParam.m_nTrigger_Count);

	viPrintf(/*multimeter*/theApp.m_Session,"TRIG:DEL %s\n",SetParam.m_nTrigger_Delay);//这里要对延时做处理判断
	m_bTrig=FALSE;
}		CButton*pRB;
switch(m_nID)
{    case IDC_RADIO_VOLTAGE:
	viPrintf(/*multimeter*/theApp.m_Session,"CONF:VOLT:%s %s,%s\n",SetParam.m_nParam_Type,SetParam.m_nParam_Range,SetParam.m_nParam_Resolution);
	if("DC"==SetParam.m_nParam_Type)
	{			viPrintf(/*multimeter*/theApp.m_Session,"INP:IMP:AUTO %s\n",m_nStr_Input);
viPrintf(/*multimeter*/theApp.m_Session,"VOLT:DC:%s\n",SetParam.m_nParam_Aper);
viPrintf(/*multimeter*/theApp.m_Session,"VOLT:DC:%s\n",SetParam.m_nParam_NPLC);		}
	pRB=(CButton*)GetDlgItem(IDC_RADIO_VOLTAGE);
	pRB->SetCheck(0);
	break;
case IDC_RADIO_CURRENT:
	viPrintf(/*multimeter*/theApp.m_Session,"CONF:CURR:%s %s,%s\n",SetParam.m_nParam_Type,SetParam.m_nParam_Range,SetParam.m_nParam_Resolution);
	if("DC"==SetParam.m_nParam_Type)
	{		viPrintf(/*multimeter*/theApp.m_Session,"CURR:DC:%s\n",SetParam.m_nParam_Aper);
viPrintf(/*multimeter*/theApp.m_Session,"CURR:DC:%s\n",SetParam.m_nParam_NPLC);		}
	pRB=(CButton*)GetDlgItem(IDC_RADIO_CURRENT);
	pRB->SetCheck(0);
	break;
case IDC_RADIO_RESISTANCE:
	if("FRES"==SetParam.m_nParam_Resistance_Type)
	{			viPrintf(/*multimeter*/theApp.m_Session,"CONF:FRES %s,%s\n",SetParam.m_nParam_Resistance_Range,SetParam.m_nParam_Resistance_Resolution);
			viPrintf(/*multimeter*/theApp.m_Session,"FRES:APER %s\n",SetParam.m_nParam_Aper);
		viPrintf(/*multimeter*/theApp.m_Session,"FRES:NPLC %s\n",SetParam.m_nParam_NPLC);
	}
	else
	{		viPrintf(/*multimeter*/theApp.m_Session,"CONF:RES %s,%s\n",SetParam.m_nParam_Resistance_Range,SetParam.m_nParam_Resistance_Resolution);
		viPrintf(/*multimeter*/theApp.m_Session,"RES:APER %s\n",SetParam.m_nParam_Aper);
		viPrintf(/*multimeter*/theApp.m_Session,"RES:NPLC %s\n",SetParam.m_nParam_NPLC);
	}
	pRB=(CButton*)GetDlgItem(IDC_RADIO_RESISTANCE);
	pRB->SetCheck(0);
	break;
case IDC_RADIO_FREQUENCY:
	viPrintf(/*multimeter*/theApp.m_Session,"CONF:FREQ %s,%s\n",SetParam.m_nParam_Peri_Freq_Range,SetParam.m_nParam_Peri_Freq_Resolution);
	viPrintf(/*multimeter*/theApp.m_Session,"FREQ:APER %s\n",SetParam.m_nParam_Peri_Freq_Aper);	
	pRB=(CButton*)GetDlgItem(IDC_RADIO_FREQUENCY);
	pRB->SetCheck(0);
	break;
case IDC_RADIO_PERIOD:
	viPrintf(/*multimeter*/theApp.m_Session,"CONF:PER %s,%s\n",SetParam.m_nParam_Peri_Freq_Range,SetParam.m_nParam_Peri_Freq_Resolution);	
	viPrintf(/*multimeter*/theApp.m_Session,"PER:APER %s\n",SetParam.m_nParam_Peri_Freq_Aper);		
	pRB=(CButton*)GetDlgItem(IDC_RADIO_PERIOD);
	pRB->SetCheck(0);
	break;
}//	viPrintf(theApp.m_Session,"CONF:FREQ AUTO,MIN\n");

//****************************************启动测量
viPrintf(/multimeter/theApp.m_Session,“READ?\n”);
//****************************************读取数据
float temp=0;

// ZeroMemory(buff,100);//初始化数据为零
ViStatus error;
error=viScanf(/multimeter/theApp.m_Session,“%f”,&temp);
if(error!=VI_SUCCESS)
{ error_handler(/multimeter/theApp.m_Session,error);
return;}

CString str;
str.Format("Result is %f",temp);
MessageBox(str,"测量结果显示",MB_OK);}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三少爷的剑!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值