1 引言
USB是目前发展应用非常广泛的一项技术。它是一种计算机系统连接外围设备的标准输入/输出接口。根据外围设备的不同的类型USB协议将其分类,每个设备类型都定义了类似功能设备的共同行为和协议。相同类型的设备都由一组标准定义的功能模块组成。这样主机与USB设备之间的通信就可以通过一些标准格式的数据包来完成。USB开发者论坛发布了一系列USB设备的类型定义,并配以相应的使用说明。下面表格显示出 USB的设备类型(DEVEICE CLASS):
表1 USB的设备类型
虽然Windows已经提供了底层总线操作的驱动程序,但与此类底层驱动程序接口的是i/o请求包的IRPs的结构,而Windows为应用程序提供的接口是API函数。因此必须在其间建立一个驱动程序,在USB底层驱动与Windows应用程序之间传递消息。VB、 C/C++、Delphi等通用编程语言编写的应用程序都可以在设备驱动程序的支持下,调用Readfile、WriteFile、DeviceIoControl等API函数。而编写底层总线的驱动程序是非常复杂的一项工程。为了消除编写设备驱动程序的问题,可对于一些具有相似功能的设备可以组成一类,分享共有的特性,便于使用Windows提供共同的类驱动程序。
2 HID类型概述
第一个被windows支持的usb外围设备类是人机接口设备。hid是human interface device人机接口设备的英文缩写。是指直接和人进行互动的设备。如鼠标、键盘等。运行在WINDOWS98或其他更高的版本的操作系统的PC机,系统除了提供通用的USB设备的底层驱动以外,还单独提供了一些HID设备的完整驱动,应用程序可以很容易的与操作系统内部的hid通讯。这样使得符合hid类的USB设备很容易开发与运行。也就是说,我们如果想实现一个USB的HID类设备,是不需要在Windows下开发自己的驱动程序。HID不一定要是标准的外设类型,唯一的要求是交换的数据存储在报文的结构内,设备固件必须支持报文的格式。任何工作在该限制之内的设备都可以成为一个hid,例如温度计,电压计,读卡机等。
hid类设备只能使用控制传输与中断传输两种方式。HID的交换的数据格式称为报文。报文形式灵活,能处理任何类型的数据。HID特有的请求,Set_Report和Get_Report为主机和设备之间的任何类型数据块传输提供了一种方法。主机发出Get_Report请求,设备响应向主机传送数据块;主机发出Set_Report请求,设备响应准备接收主机发出的数据块。对于一个全速设备,中断传输方式下每笔事务能够传送的最大数据量是64字节,全速设备每毫秒不能有超过一笔事务,所以每秒最多传送64000字节。高速设备,每笔事务能够传送的最大数据量是1024字节。对于不能一次传输完毕的数据,接收和发送报文可以采用多笔事务。
表2列举出了与HID类设备通信过程中使用到的大量函数,这些函数的用法在DDK的帮助文档中均有详细地解释。这些函数包含在Hid.dll、Setupapi.dll、Kernel32.dll三个动态链接库中,分别起到与HID设备通讯,寻找与识别设备,交换数据的作用。
表2 HID设备通信相关API函数
Visual Basic编程语言,语法简单用法灵活易于掌握。用它来开发通信程序是一种非常快捷的途径。但是需要指出的是,API函数的声明格式在DDK的文档内全部是采用C语言的格式声明,而VB与C语言的在变量的声明、存储格式上都有很大的区别。所以首先需要进行类型转换,例如CHAR转换为Byte类型,USHORT、ULONG、BOOLEAN、LP_(长指针前缀)、P_(指针前缀)转换为Long 类型,PCTSTR转换为String类型。其次,C语言的结构体可以转换为VB中的自定义数据类型,并且特别值得注意,在涉及到自定义数据类型中子类存储位置必须注意到VB与C语言对应转换类型所占内存空间不同的问题。最后C语言中有指针的概念,而VB中没有,所以需用Byref按地址传递,VarPtr取变量地址等方法进行相应的操作。
3 VB中调用API函数详述
第一步需要获得GUID(global unique identifier),需要调用函数 HidD_GetHidGuid ,他在可以如下定义:
Public Declare Function HidD_GetHidGuid Lib_
"hid.dll" _
(ByRef HidGuid As GUID)
As Long
通过调用它可以得到HID 类设备的 GUID,应用程序在与HID设备通讯之前,必须获取HID类的独特标志符GUID,它是一个128位值,每一位唯一表示了一个对象。通过这个API函数就可以从系统中读取该值,得到 HID 设备句柄。接下来应检测符合设定参数的HID,应使用函数 SetupDiGetClassDevs,转换为VB中的定义是:
Public Declare Function SetupDiGetClassDevs_
Lib "setupapi.dll" _
Alias "SetupDiGetClassDevsA" _
(ByRef ClassGuid As GUID, _
ByVal Enumerator As String, _
ByVal hwndParent As Long, _
ByVal Flags As Long) _
As Long
当调用该函数成功是,他返回一个包含所有的与设定参数匹配如:已连接和列举,的设备信息的结构体数组地址,该值在下一个将要调用的函数SetupDiEnumDeviceInterfaces中将使用到。SetupDiEnumDeviceInterfaces可定义为:
Public Declare Function_
SetupDiEnumDeviceInterfaces Lib_
"setupapi.dll" _
(ByVal DeviceInfoSet As Long, _
ByVal DeviceInfoData As Long, _
ByRef InterfaceClassGuid As GUID, _
ByVal MenberIndex As Long, _
ByRef DeviceInterfaceData As_
SP_DEVICE_INTERFACE_DATA) _
As Long
这样通过上面函数SetupDiGetClassDevs给出的设备信息得到的设备的一个接口的地址,每一次调用必需传递一个数组的索引来指定一个接口。上例中SP_DEVICE_INTERFACE
_DATA包含的结构用来识别每一个HID的接口。要与设备通信还需要一些更详细的信息,其中最重要的是设备路径,它可以通过函数SetupDiGetDeviceInterfaceDetail得到。其定义格式如下:
Public Declare Function_
SetupDiGetDeviceInterfaceDetail Lib_
"setupapi.dll" _
Alias "SetupDiGetDeviceInterfaceDetailA" _
(ByVal DeviceInfoSet As Long, _
ByRef DeviceInterfaceData As_
SP_DEVICE_INTERFACE_DATA, _
ByVal DeviceInterfaceDetailData As Long, _
ByVal DeviceInterfaceDetailDataSize As Long, _
ByRef RequiredSize As Long, _
ByVal DeviceInfoData As Long) _
As Long
其中的DeviceInterfaceData是自定义类型:
Public Type_
SP_DEVICE_INTERFACE_DETAIL_DATA
cbSize As Long
DevicePath As String
End Type
这样函数就可传回与前一函数所识别的接口有关的结构体,其中DevicePath是一个设备路径,应用通过上面函数SetupDiEnumDevice-
Interfaces给出的设备接口数据设定设备接口的详细信息。第一次调用该函数时,其中的DeviceInterfaceDetailDataSize无法预知,故可以两次调用该函数,第一次调用出错,但可以返回正确的DeviceInterfaceDetailDataSize ,第二次调用传递返回值,调用即可成功。通过以上步骤基本可以建立与设备的连接了。
如想获得更多关于设备能力的信息,还可以使用HidD_GetAttributes函数,HidP_Get-
PreparsedData函数,HidP_GetPreparsedData函数,他们都是包含在hid.dll文件中的。分别可以实现获得厂商ID,产品ID,Usage, Usage Page,报文长度等,不再一一赘述。
为了开启一个HID设备需用到函数CreateFile,以取得设备代号,并用此代号与设备交换数据。定义形式如下:
Public Declare Function CreateFile Lib_
"kernel32" Alias "CreateFileA" _
(ByVal lpFilename As Long, _
ByVal dwDesiredAccess As Long, _
ByVal dwShareMode As Long, _
ByRef lpSecurityAttributes As Long, _
ByVal dwCreationDisposition As Long, _
ByVal dwFlagsAndAttributes As Long, _
ByVal hTemplateFile As Long) _
As Long
调用函数CreateFile以A结尾,是因为处理字符串时采用了8位的ANSI码。
当应用程序取得HID设备的代号,此时就可以读写报文,利用通用函数WriteFile与ReadFile
定义如下所示:
Public Declare Function WriteFile Lib_
"kernel32" _
(ByVal hFile As Long, _
ByRef lpBuffer As Byte, _
ByVal nNumberOfBytesToWrite As Long, _
ByRef lpNumberOfBytesWritten As Long, _
ByVal lpOverlapped As Long) _
As Long
Declare Function ReadFile Lib "kernel32" _
(ByVal hFile As Long, _
ByRef lpBuffer As Any, _
ByVal nNumberOfBytesToRead As Long, _
ByRef lpNumberOfBytesRead As Long, _
lpOverlapped As Long) _
As Long
读写报文缓冲区时,第一个字节是Report ID,其后是报文数据。报文缓冲区默认是八个报文,并且环状排列。因为数据读写是发生在主机轮训设备的时候,并不是由设备触发产生硬件中断,所以如不能及时读写,新的数据会覆盖旧的数据,导致生报文丢失。当数据读写频繁时应使用特征报文,它可以保证当报文数据没有变化时,HID不会传送新的数据。
当应用程序结束与HID的通信后,必须释放所有之前保留的资源。所涉及到的几个API函数如下:其中包括HidD_FreePreparsedData ,如下定义:
Public Declare Function_
HidD_FreePreparsedData Lib "hid.dll" _
(ByRef PreparsedData As Long) _
As Long
其作用是清除函数HidP_GetPreparsedData传回的PreparsedData数据所占用的缓冲区;函数SetupDiDestroyDeviceInfoLis定义为:
Public Declare Function_
SetupDiDestroyDeviceInfoList Lib_
"setupapi.dll" _
(ByVal DeviceInfoSet As Long) _
As Long
当不再使用SetupDiGetClassDevs时,应用上述函数释放其返回的数组hDevInfo数组。还有需要使用函数CloseHandle,它是一个非常通用的API函数,可以用于关闭通信。
4 小结
实际编程表明使用VB开发HID类USB设备是一条非常方便快捷的途径,不需要编写底层驱动,涉及到的API函数多是大家熟知的通用用函数。但同时还必须注意到一点,HID类只支持控制传输与中断传输。控制传输通常不用于数据的传输,而中断传输的特点是保证最大延迟,也就是事务之间的时间。他没有保证传输速率,而是保证每笔事务之间的时间不会超过最大延时。所以中断传输适用于数据量不太大,但需要及时快速的传送,实时性要求较高的场合。对于数据量比较大的传输,是不宜使用中断传输的。因此HID设备应用的场合也必须根据该特点灵活使用。