1 引言 USB是目前发展应用非常广泛的一项技术。它是一种计算机系统连接外围设备的标准输入/输出接口。根据外围设备的不同的类型USB协议将其分类,每个设备类型都定义了类似功能设备的共同行为和协议。相同类型的设备都由一组标准定义的功能模块组成。这样主机与USB设备之间的通信就可以通过一些标准格式的数据包来完成。USB开发者论坛发布了一系列USB设备的类型定义,并配以相应的使用说明。下面表格显示出 USB的设备类型(DEVEICE CLASS): 虽然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设备通讯,寻找与识别设备,交换数据的作用。 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设备应用的场合也必须根据该特点灵活使用。 |