通常情况下,在Windows下开发设备驱动采用WDM(Win32 Driver Model)的方法,是Microsoft力推的全新驱动程序模式。WDM可以对PCI、USB、IEEE1394、SCSI、PCMCIA、ISA、EISA、EIDE、ECP和串行口等设备开发驱动。
而在本章中,我们将通过CVI下的VISA开发设备的驱动程序,跟使用WDM相比,VISA开发的驱动程序的步骤更容易、与操作系统无关、集成性强且可以利用CVI软件更方便的实现仪器设备的控制功能。CVI同样也支持GPIB、GPIB-VXI、VXI、PXI/PCI、串行口、LAN、USB以及IEEE1394等接口驱动的开发工作。
VISA是虚拟仪器软件结构(Virtual Instrument Software Architecture)的缩写,实质是一个I/O接口软件及其规范的总称。
随着仪器的发展,目前市面上大部分的测控仪器已经具有了可以与计算机进行通信的功能。使用计算机可以对仪器设备进行更快更强大更灵活的控制,并且为自动化测量提供了巨大的便利。现有的基于计算机的自动化测量的软件很容易开发,然而基于总线通讯的驱动级的软件编写却存在工作量巨大、移植性差、对程序员知识水平要求较高等问题。
VXI 总线自 1987 年推出以来,已成为仪器测试领域的标准。它的硬件规范和字串行协议保证了不同厂家 VXI 总线仪器模块的互换性。 为了使 VXI 总线更易于使用, 实现 VXI系统的互换性,并在系统级上使 VXI 总线系统成为一个真正的开放的系统结构,1993 年NI 等著名仪器公司成立了 VXIplug&play 联盟并制定了 VXIplug&play 规范,简称 VPP 规范。联盟成立的目标是提高 VXI 技术最终用户的使用简便性。通过制定规范,使 VXI 技术和产品获得更好的实用性、兼容性、互换性,使系统的调试、维护、开发等更加方便易行。
VPP 规范是对 VXI 总线标准的补充和发展,主要解决了 VXI 总线系统的软件级标准问题。它制定了标准的系统软件结构框架,对操作系统、编程语言、I/O 程序库、仪器驱动程序和高级应用软件工具作了原则性规定,从而真正实现了 VXI 总线系统的开放性、兼容性、互换性。
然而,VPP规范却只解决了硬件的统一规范,却缺乏一个统一的软件规范。VPP联盟考虑到多种仪器所具有的通用的接口以及模型之后,提出了一种自底向上的I/O接口软件模型,即VISA。与自顶向下的方法不同的是,VISA的实现首先定义了管理所有资源的资源(VISA资源管理器),用于管理、控制与分配VISA资源的操作功能;然后在VISA资源管理器基础上列出了仪器各自的操作功能并实现了操作功能的合并;然后在顶层上为用户提供了控制所有VISA仪器控制资源的方法。
正是因为VISA的自底向上的设计方法,使得VISA可以让不同厂商的软件可以在同一平台上协调运行,大大减少了软件的重复开发,缩短了测试应用程序的开发周期。
图 5‑1 VISA结构模型
在我们深入了解VISA之前,我们有必要了解VISA体系中涉及的一些基本概念。
资源(Resource):
VISA的资源是对具有某种功能的设备的统称。
VISA的资源由三个要素组成:属性集、事件集和操作集,属性集包括字符串、超时值及协议等,事件集包括用户要求服务事件等,操作集包括各种端口读写操作。
资源的属性(Attributes)描述了一个资源或者一个会话所具有的某种状态。资源的属性可以通过viGetAttribute()函数或者viSetAttribute()函数进行读取或设置。
资源的事件(Events)是独立于系统正常运行状态的由资源发出的异步请求。可以通过viEnableEvent ()、viDisableEvent()或者viInstallHandler ()、viUninstallHandler ()、viWaitOnEvent ()、viDiscardEvents()等函数进行事件相关的操作。
资源的操作(Operations)是指由资源定义的可对资源起到控制作用的行为。每种资源都可以定义一系列的操作。上述提到的VISA函数均是对资源的操作。此外常用的对资源的操作函数还有viClose()、viLock()、viUnlock()等等。
如同USB设备存在音频类、人体工学输入设备类、大容量存储设备类、打印机类、扫描仪类等诸多类一样,VISA中的资源也存在INSTR(仪器控制)、MEMACC(内存访问)、INTFC(GPIB总线接口)、BACKPLANE(背板)、SERVANT(伺服)、SOCKET(以太网套接字)、RAW(原始设备)等类。其中最重要的类是INSTR类。INSTR类封装了所有的通过资源接口进行通信的函数并且满足大部分情况下的要求。
会话(Session):
会话是指从一个资源被打开到被关闭的过程中进行的操作的集合。在VISA中,一个会话可以唯一的描述一条通讯通道。在VISA中,除串口之外的很多接口都可以同一个或多个应用程序建立一个或多个会话。正是因为如此,VISA将资源的属性具有全局属性与本地属性之分。
会话可以通过viOpen()函数建立。
资源模板(Resource Template):
资源模板是所有的VISA具有的公共属性、公共事件与公共操作的集合。在VISA中,所有的资源具有的属性、事件与操作都从资源模板继承,并根据资源本身特点增加特有的属性、事件与操作。
如VI_ATTR_RSRC_NAME(资源名)属性为每个资源都会具有的属性,即被定义在资源模板中,某个特点的资源无需额外定义即拥有该属性。
资源管理器(Resource Manager):
在VISA中,资源管理器帮助扫描计算机上连接的所有VISA设备并且帮助我们建立同某个资源的会话。需要注意的是,资源管理器一般只起到跟踪连接到计算机上的资源并且帮助创建会话的作用,并不是任何会话的任何操作都需要资源管理器的协助。
资源管理器由VISA的软件包内置的驱动提供。我们可以通过viOpenDefaultRM()函数获取资源管理器的操作句柄。
在VISA中,一般情况下建立一个会话均需要使用资源管理器的功能。
基于消息的会话/基于寄存器的会话(Message-Based Communication/Register-Based Communication):
当计算机跟资源之间的通信基于“指令-相应”的工作模式时,计算机跟资源之间的会话被称为“基于消息的会话”。
当计算机跟资源之间的通信基于“写寄存器-读寄存器”的工作模式时,计算机跟资源之间的会话被称为“基于寄存器的会话”。
基于消息的会话跟基于寄存器的会话是计算机跟其他设备进行通信的两种基本模式。
基于消息的会话常见于串口、GPIB接口、以太网、VXI接口设备中。如用户想通过计算机访问某个网页,则是通过多次的“请求命令-接收数据”的过程完成。
基于寄存器的会话常见于VXI与PXI接口设备中。计算机与VXI或者PXI设备之间的通信通过往设备指定地址的寄存器中写入指定的数据来完成。
事件(Event):
当计算机跟设备进行通信时,设备经常需要向计算机主动发出一些请求,以通知应用程序一个行为的发生。一般情况下,请求可能因为服务请求信号有效、资源行为的起始与结束、资源进行了不正常的操作等情况导致。这些主动发出的请求通常被称为异步请求,或者简称为“事件”。
事件类型包括服务请求(SRQ)、中断和硬件触发。
处理时间常用的方法有回调函数法以及排队法。具体内容参见5.3.4 事件处理类函数。
锁定(Locking):
有时候我们通过某会话访问一个设备的时候,不希望其他会话再来访问这个设备。VISA有一套叫做“锁定”(Locking)的机制,以防止其他会话获取该设备的访问权限。
在VISA中锁定或解锁常常使用viLock()或viUnlock()函数。
使用VISA对资源进行操作时,通常按照以下步骤完成:
(1) 打开资源管理器句柄
(2) 打开资源句柄,建立一个会话
(3) 设置会话属性,发送消息或者设置资源中的寄存器值
(4) 释放资源句柄
(5) 释放VISA资源管理器句柄
而VISA的函数根据功能可以分为三类:资源管理类、IO操作类、事件处理类。在本章节中,我们将会对VISA函数库中每一类的主要函数做简要介绍。
需要额外注意的是,因为VISA库作为一个单独的组件存在,需要单独安装。因此下面章节中介绍的函数的具体信息在CVI的帮助文档中并不存在。若大家安装了VISA库,则VISA函数的帮助文档可以在“开始菜单-National Instruments-VISA-Documentation-NI-VISA Help”中找到。
VISA的数据类型是不依赖于硬件设备、编程语言以及编程环境的。因此VISA中使用专门定义的数据类型进行操作,以增强平台的兼容性。
若系统已安装VISA库,则打开CVI安装目录下的CVI80/include文件夹则可以找到visatype.h头文件。在该头文件中定义了VISA中的专属数据类型。
常见的VISA数据类型如下表所示。
表 5‑1 VISA数据类型
数据类型 |
类型描述 |
数据类型 |
类型描述 |
ViChar |
字符类型 |
ViInt32 |
32位整型 |
ViPChar |
字符指针类型 |
ViPInt32 |
32位整型指针类型 |
ViByte |
字节类型 |
ViInt64 |
64位整型 |
ViPByte |
字节指针类型 |
ViPInt64 |
64位整型指针类型 |
ViString |
字符串类型 |
ViUInt8 |
8位无符号整型 |
ViPString |
字符串指针类型 |
ViPUInt8 |
8位无符号整型指针类型 |
ViBoolean |
布尔类型 |
ViUInt16 |
16位无符号整型 |
ViPBoolean |
布尔指针类型 |
ViPUInt16 |
16位无符号整型指针类型 |
ViInt8 |
8位整型 |
ViUInt32 |
32位无符号整型 |
ViPInt8 |
8位整型指针类型 |
ViPUInt32 |
32位无符号整型指针类型 |
ViInt16 |
16位整型 |
ViUInt64 |
64位无符号整型 |
ViPInt16 |
16位整型指针类型 |
ViPUInt64 |
64位无符号整型指针类型 |
ViReal32 |
32位实型 |
ViReal64 |
64位实型 |
ViPReal32 |
32位实型指针类型 |
ViPReal64 |
64位实型指针类型 |
ViRsrc |
资源标识类型 |
ViHndlr |
操作句柄类型 |
ViPRsrc |
ViRsrc指针类型 |
ViPHndlr |
ViHndlr指针类型 |
ViStatus |
返回状态值类型 |
ViVAList |
参数列类型 |
ViPStatus |
ViStatus指针类型 |
ViJobId |
操作请求类型 |
ViBuf |
数据块类型 |
ViPJobId |
ViJobId指针类型 |
ViPBuf |
ViBuf指针类型 |
ViJobStatus |
操作请求状态类型 |
ViAddr |
逻辑地址类型 |
ViPJobStatus |
ViJobStatus指针类型 |
ViPAddr |
ViAddr指针类型 |
ViSpaceInfo |
内存映射类型 |
ViSession |
资源对话通道类型 |
ViPhysAddr |
物理地址类型 |
ViPSession |
ViSession指针类型 |
ViSigMask |
信号过滤类型 |
ViVersion |
资源版本类型 |
ViIntrMask |
中断屏蔽类型 |
ViPVersion |
ViVersion指针类型 |
ViBusAddress |
总线地址类型 |
ViObject |
资源对象类型 |
ViBusSize |
地址长度类型 |
ViPObject |
ViObject指针类型 |
ViAttr |
资源属性类型 |
ViRsrcList |
资源标识列类型 |
ViAttrState |
资源属性值类型 |
ViClass |
资源类类型 |
ViLock |
资源进程/线程管理类型 |
ViEvent |
资源事件类型 |
ViAccessMode |
控制存取机制类型 |
ViPEvent |
ViEvent指针类型 |
ViEventType |
资源事件类型类型 |
ViPLock |
ViLock指针类型 |
ViPEventType |
ViEventType指针类型 |
在WIN32下,以ViInt32为例,在CVI软件中选中ViInt32,右击并“Go To Defination”,可以看到,ViInt32的数据类型其实就是signed long。所以,没有必要对VISA的数据类型保持神秘感。VISA定义了种类如此丰富的数据类型,只是为了增强VISA的平台兼容性而已。
资源管理类函数是VISA函数库中与仪器打开关闭、获取/设置状态有关的函数。VISA中常见的函数如下:
(1)viOpenDefaultRM 打开默认VISA资源管理器
(2)viFindRsrc 从VISA资源管理器中查找符合条件的资源
(3)viFindNext 查找下一个符合条件的资源
(4)viOpen 从资源管理器中打开指定会话
(5)viClose 关闭特定的会话通道
(6)viGetAttribute 获取资源属性状态值
(7)viSetAttribute 设置资源属性状态值
由于设备的不同,会话分为基于消息的会话与基于寄存器的会话(Message-Based Communication/Register-Based Communication),因此IO操作函数也分为基于消息的IO操作函数与基于寄存器的IO操作函数。
(1)viIn8 从指定地址的寄存器读取8位数据
(2)viOut8 向指定地址的寄存器写入8位数据
(3)viIn16从指定地址的寄存器读取16位数据
(4)viOut16向指定地址的寄存器写入16位数据
(5)viIn32从指定地址的寄存器读取32位数据
(6)viOut32向指定地址的寄存器写入32位数据
(7)viPrintf 按指定格式发送消息到资源中
(8)viScanf 按指定格式从资源中读取消息
(9)viQueryf 按指定格式向资源中发送消息,随即按指定格式从资源中读消息
常用的处理事件的方法有回调函数法与排队法。回调函数法是为指定的事件设定回调函数(本质上是指定的函数指针),当事件发生后指定的函数即被执行的方法。排队法是使能事件后程序暂停并等待指定的事件发生,事件发生后程序继续执行的方法。
(1)viInstallHandler 安装回调函数句柄
(2)viUninstallHandler 撤销回调函数句柄
(3)viEnableEvent 使能事件
(4)viDisableEvent 禁止事件
(5)viWaitOnEvent 等待某一特定事件的发生
在下面一节里,我们将通过VISA库为一基于CH430芯片的USB转串口开发基于VISA的驱动程序,并在CVI下开发基于VISA的测试程序。
为了方便测试,我们需要事先将USB串口的TXD引脚与RXD引脚短接,以方便做“回环测试”。
传统的仪器驱动开发往往是整个项目过程中最复杂、难度最高的环节之一,但是在CVI下开发基于VISA的驱动却简单的“令人发指”。
将USB串口连接至计算机后,我们点击计算机开始菜单-程序- National Instruments-VISA-Driver Wizard,我们可以看到如下图所示的界面。
图 5‑2 NI-VISA Driver Wizard
在上面的界面中,我们可以注意到,利用此向导可以产生基于PCI/PXI(PCIe/PXIe)以及基于USB的驱动程序。
在此,我们选择USB,点击下一步。
图 5‑3 USB-Device Selection
在上面的USB-Device Selection界面的Device List中选择我们计算机上已经连接的USB串口设备。选择后,下方的USB Manufacturer ID(Verdor ID)以及USB Model Code(Product ID)已经自动填好。点击下一步。
图 5‑4 Output Files Generation
在Output Files Generation界面中,我们选择生成的驱动文件(inf文件)的保存位置,在这里我们选择默认的位置。点击下一步。
图 5‑5 Installation Options
在上面的界面中,我们需要选择立即安装程序,还是让向导帮我们打开生成的驱动程序所在的文件夹,或者直接退出向导。在此,我们选择直接安装生成的驱动程序。点击完成,此时向导退出,驱动程序安装完成。
由于生成的驱动文件为.inf文件,因此我们可以右击inf文件-选择“安装”,也可以达到同样的安装驱动程序的目的。
安装完成之后,打开设备管理器,我们可以看到,USB串口的驱动程序已经成功安装。
图 5‑6 USB串口程序已经成功安装
驱动生成并安装完毕之后,我们可以通过NI-VISA提供的VISA Interactive Control工具对安装的VISA资源进行简单的测试与调试。
打开VISA交互控制台:“开始菜单--程序- National Instruments-VISA-VISA Interactive Control”。我们可以看到,程序中已经枚举出连接到计算机的所有资源。
图 5‑7 VISA Interactive Control界面
在该界面中,我们可以看到所有VISA资源以及资源的设备描述符。以设备描述符“USB0::0x1A86::0x7523::NI-VISA-0::RAW”为例,该描述符具有下面的格式:
USB[board]::manufacturer ID::model code::serial number[::USB interface number][::INSTR]
因此,设备描述符“USB0::0x1A86::0x7523::NI-VISA-0::RAW”表示了该设备具有USB接口,位于板卡0上,厂家ID为0x1A86,产品ID为0x7523,序列号为NI-VISA-0,设备类型为RAW。
我们找到刚刚安装VISA驱动的USB串口,并双击。此时VISA交互控制台会帮我们建立与该资源的会话。如下图所示。
图 5‑8 VISA交互控制台会话界面
在该会话界面的Template标签中,我们可以获取并设置资源的属性、锁定或解锁资源、使能或禁止事件,并可以查看或等待某事件。
Basic I/O、Interface I/O两个标签页分别是基于消息的会话测试界面与USB接口的消息会话测试界面。
在此,我们可以给设备设定延时时间(VI_ATTR_TMO_VALUE)为500(超时时间为500ms),然后切换到Basic I/O标签。
图 5‑9 会话的Basic I/O界面
在Basic I/O标签的viWrite界面中,我们可以尝试着向设备写入数据。在Buffer文本框中,我们输入“Hello,world”,点击Execute。
由于我们已经将USB串口的TXD、RXD引脚短接,因此切换到viRead,点击Execute接收缓冲区的字符串,我们可以看到,刚发送的Hello,world字符已经被成功接收。
图 5‑10 成功接收Hello,world字符
通过上一小节交互式控制台的模拟,我们应该已经大致上了解了使用VISA进行设备读写操作的基本步骤。下面我们将在CVI环境下使用C语言将对设备的操作实现出来。
如前所述,使用VISA对资源进行操作时,通常按照以下步骤完成:
(1) 打开资源管理器句柄
(2) 打开资源句柄,建立一个会话
(3) 设置会话属性,发送消息或者设置资源中的寄存器值
(4) 释放资源句柄
(5) 释放VISA资源管理器句柄
上面五步需要我们调用几个VISA常用的库函数。其声明与含义如下:
(1)viOpenDefaultRM(ViPSession sesn) 打开默认资源管理器资源对话通道
(2)viOpen(ViSession sesn, ViRsrc rsrcName, ViAccessMode accessMode, ViUInt32 openTimeout, ViPSession vi) 打开特定资源的对话通道
(3)viSetAttribute(ViObject vi, ViAttr attribute, ViAttrState attrState) 设置资源属性状态值
(4)viWrite(ViSession vi, ViBuf buf, ViUInt32 count, ViPUInt32 retCount) 将数据写入到器件中
(5)viRead(ViSession vi, ViPBuf buf, ViUInt32 count, ViPUInt32 retCount) 从器件读取数据
(6)viClose(ViObject vi) 关闭指定的会话
以上函数的返回值都是ViStatus类型。
因此,我们建立一个内容如下的C语言文件,并添加到新建的工程中。
需要注意的是,VISA发送的数据可能接收一次并不能接收完全,所以我们在上面代码中采取了尝试3次接收的方法。
若上述代码运行成功,则会在界面上显示“Hello,world!”字样。
图 5‑11 VISA测试程序运行成功
USB具有控制传输、批量传输、中断传输、同步传输四种传输类型。NI-VISA支持最常用的前三种类型。这就是说,除了USB声卡之外,其他常见的USB设备大部分都可以使用VISA开发驱动。
为USB键盘开发VISA驱动,并且通过程序获取键盘按下的情况。
提示:
USB键盘采用的是事件触发的机制。我们在VISA Interactive Control中开启VI_EVENT_USB_INSTR事件的Handler:
图 5‑12 开启USB键盘的事件Handler
此时切换到viEventHandler界面。当USB键盘被按下时,此时该文本框中会出现相应的事件的提示信息。
图 5‑13 空格键被按下时的事件
我们可以通过viInstallHandler()函数安装回调函数,获取键盘按键的情况。
请大家通过自己开发的基于VISA的驱动,将常见的USB键盘的QWERTY键值映射为ABCDEF键值。
图 5‑14 ABCDEF键盘