您现在的位置: USB开发网 > USB技术文档 > USB专题 > USB HID协议中文版
USB HID协议中文版

5、HID主机程序设计(VB)

------分隔线----------------------------

   HID设备是Windows系统提供了完善支持的一类,应用程序可以通过标准的API函数调用,实现与HID设备的通信。Windows系统提供了几千个API函数,作为应用程序与操作系统的接口,与HID相关的API函数被封装在hid.dll、setupapi.dll和kernal32.dll文件中。

1.1 HID访问使用的API函数

  文件hid.dll中提供了很多个API,因为与HID设备通信有一定的复杂性。首先,在应用程序与HID传输数据之前,应用程序必须先识别该设备并且读取它的报表信息,这些动作需要调用多个API函数才可以实现。应用程序需要寻找连接到系统上的是哪些HID设备,然后读取每个设备的信息直到查找到所需的属性。如果是客户化的设备,应用程序可以寻找特定的厂商与产品ID,或者应用程序可以寻找特定类型的设备,例如键盘或鼠标。

表5-1  用于HID设备的API函数

用于了解HID设备情况的API函数(hid.dll)

HidD_GetAttributes

请求获得HID设备的厂商ID、产品ID和版本号

HidD_FreePreparsedData

释放函数HidD_GetPreparsedData所使用的资源

HidD_GetHidGuid

请求获得HID设备的GUID

HidD_GetIndexString

请求获得由索引识别的字符串

HidD_GetManufactureString

请求获得设备制造商字符串

HidD_GetPhysicalDescriptor

请求获得设备实体字符串

HidD_GetPreparsedData

请求获得与设备能力信息相关的缓冲区的代号

HidD_GetProductString

请求获得产品字符串

HidD_GetSerialNumberString

请求获得产品序列号字符串

HidP_GetButtonCaps

请求获得HID报表中所有按钮的能力

HidP_GetCaps

请求获得用于描述设备能力的结构的指针

HidP_GetLinkCollectionNotes

请求获得描述在顶层集合中的连接集合(Link Collection)关系的结构的数组

HidP_GetSpecificButtonCaps

请求获得报表中按钮的能力,该请求可以设定一个Usage Page、Usage或是Link Collection

HidP_GetSpecificValueCaps

请求获得报表中数值的能力,该请求可以设定一个Usage Page、Usage或是Link Collection

HidP_GetValueCaps

请求获得HID报表中所有数值的能力

HidP_MaxUsageListLength

请求获得HID报表中可以回传的按钮的最大数目,该请求可以设定一个Usage Page

HidP_UsageListDifference

比较两个按钮列表,并且求出在一个列表中设定而在另一个列表中没有设定的按钮

用于从设备读取、向设备传送报表的API函数(hid.dll)

HidD_GetFeature

从设备读取一个特征报表

HidD_SetFeature

向设备传送一个特征报表

HidP_GetButtons

从设备读取包含每个按下的按钮的用法(Usage)的缓冲区的指针,该请求可以设定一个Usage Page

HidP_GetButtonEx

从设备读取包含每个按下的按钮的Usage和Usage Page的缓冲区的指针

HidP_GetScaledUsageValue

从设备读取一个已经经过比例因子调整的有符号数值

HidP_GetUsageValue

从设备读取一个指向数值的指针

HidP_GetUsageValueArray

从设备读取包含多个数据项的Usage的数据

HidP_SetButtons

向设备传送设置按钮的数据

HidP_SetScaledUsageValue

将一个实际数值转换成设备使用的逻辑数值,并将其插入到报表中

HidP_SetUsageValue

向设备传送数据

HidP_SetUsageValueArray

向设备传送包含多个数据项的Usage的数据

HidD_FlushQueue

清空输入缓冲区

HidD_GetNumInputBuffer

获得驱动程序用于存储输入报表的环形缓冲区的大小,默认值是8

HidD_SetNumInputBuffer

设置驱动程序用于存储输入报表的环形缓冲区的大小

用于查找和识别设备的API函数(setupapi.dll)

SetupDiGetClassDevs

获得HID的信息,针对已安装的设备,回传一个指向其信息集的代码

SetupDiEnumDeviceInterfaces

请求获得设备信息群内的一个设备的信息

SetupDiGetDeviceInterfaceDetail

请求获得设备的路径

SetupDiDestroyDeviceInfoList

释放SetupDiGetClassDevs使用的资源

用于打开、关闭设备和实现数据传送的API函数(kernal32.dll)

CreatFile

取得设备的路径后,调用该函数获得设备代号

WriteFile

向设备传送输出报表

ReadFile

从设备读取输入报表

CloseHandle

关闭设备,释放CreateFile所使用的资源

 5.2 查找HID的过程

  在实现HID的访问之前,首先要查找指定(根据设备的厂商ID、产品ID和产品序列号)的HID。查找指定设备的过程如下:

• 调用函数HidD_GetHidGuid获得USB设备的GUID;
• 调用函数SetupDiGetClassDevs,获得一个包含全部HID信息的结构数组的指针,下面根据此数组逐项查找指定的HID;
• 调用函数SetupDiEnumDeviceInterfaces,填写SP_DEVICE_INTERFACE_DATA结构的数据项,该结构用于识别一个HID设备接口;
• 调用函数SetupDiGetDeviceInterfaceDetail,获得一个指向该设备的路径名;
• 调用函数CreateFile,获得设备句柄;
• 调用函数HidD_GetAttributes,填写HIDD_ATTRIBUTES结构的数据项,该结构包含设备的厂商ID、产品ID和产品序列号,比照这些数值确定该设备是否是查找的设备。

  查找HID的流程如下图:

 

USB主 查找设备的流程

图5-1  查找设备的流程

  下面介绍VB实现的查找过程。

  (1)获得GUID

  应用程序要与HID设备通信之前,必须先获得HID类别的GUID(Globally Unique Indentifer)。GUID是一个128位的数值,每个对象都有惟一的GUID。HID类别的GUID包含在hidclass.h文档内,可以接引用,或是使用 HidD_GetHidGuid函数来取得HID类别的 GUID。

 

  1. ‘ 函数声明  
  2. Public Declare Function HidD_GetHidGuid Lib "hid.dll" (ByRef HidGuid As GUID  
  3. )  As Long  
  4.   
  5. ‘ GUID结构说明  
  6. Public Type GUID  
  7.     Data1 As Long  
  8.     Data2 As Integer  
  9.     Data3 As Integer  
  10.     Data4(7) As Byte  
  11. End Type  
  12.   
  13. ‘ 变量说明  
  14. Dim HidGuid as GUID  
  15.   
  16. ‘ 调用  
  17. Call HidD_GetHidGuid(HidGuid)  

(2) 获得HID的结构数组

  得到GUID后调用SetupDiGetClassDevs函数传回所有已经连接并且检测过的HID包含其信息的结构数组的地址。

  1. ‘ 函数声明 
  2. Public Declare Function SetupDiGetClassDevs Lib "setupapi.dll" Alias "SetupDiGetClassDevsA"( _ 
  3.     ByRef ClassGuid As GUID, _       
  4.         ByVal Enumerator As String, _ 
  5.         ByVal hwndParent As Long, _ 
  6.         ByVal Flags As Long) _ 
  7. As Long 
  8. ‘ 常量说明 
  9. Public Const DIGCF_PRESENT = &H2 
  10. Public Const DIGCF_DEVICEINTERFACE = &H10 
  11.  
  12. ‘ 调用 
  13. hDevInfoSet = SetupDiGetClassDevs( 
  14.     HidGuid, _          ‘通过HidD_GetHidGuid函数获得GUID 
  15.     vbNullString, _ 
  16.     0, _ 
  17.     (DIFCG_PRESENT or DIFCG_DEVICEINTERFACE) _ 

  该函数的ClassGuid参数值为通过HidD_GetHidGuid函数获得GUID。Enumerator与hwndParent参数没有用到,Flags参数是两个定义在setupapi.h文档内的系统常数。代码中的Flags常数告诉SetupDiGetClassDevs函数只寻找目前存在(连接并且检测过)的设备接口,并且是HID类别的成员。

  返回值hDevlnfoSet是包含所有连接并且检测到的全部HID的信息的结构数组的地址。在程序中并不需要访问hDevInfoSet的元素,只需要将hDevlnfSet值传给SetupDiEnumDeviceInterfaces函数即可。

  当程序不再需要hDevInfoSet指向的数组时,应该调用 SetupDiDestroyDeviceInfo List函数来释放资源。

  下面在hDevInfoSet指向的结构数组中查找。

(3) 识别HID接口
接下来调用SetupDiEnumDeviceInterfaces,填写SP_DEVICE_INTERFACE_DATA结构的数据项,该结构用于识别一个HID设备接口。

 

  1. ‘ 函数声明 
  2. Public Declare Function SetupDiEnumDeviceInterfaces Lib "setupapi.dll" ( 
  3.     ByVal DeviceInfoSet As Long, _           
  4.     ByVal DeviceInfoData As Long, _ 
  5.     ByRef InterfaceClassGuid As GUID, _ 
  6.     ByVal MemberIndex As Long, _ 
  7.     ByRef DeviceInterfaceData As SP_DEVICE_INTERFACE_DATA _ 
  8. )As Long 
  9.  
  10. ‘ 结构定义 
  11. Public Type SP_DEVICE_INTERFACE_DATA 
  12.    cbSize As Long 
  13.    InterfaceClassGuid As GUID 
  14.    Flags As Long 
  15.    Reserved As Long 
  16. End Type 
  17.  
  18. ‘ 变量说明 
  19. Dim Result as Long 
  20. Dim MemberIndex as Long 
  21. Dim MyDeviceInterfaceData as SP_DEVICE_INTERFACE_DATA 
  22.  
  23. ‘ 调用 
  24. MyDeviceInterfaceData.cbSize = LenB(MyDeviceInterfaceData) 
  25. MemberIndex = 0 
  26. Result = SetupDiEnumDeviceInterfaces( 
  27.     DeviceInfoSet, _        ‘SetupDiGetClassDevs的返回值 
  28.     0, _ 
  29.     HidGuid, _              ‘通过HidD_GetHidGuid函数获得的GUID 
  30.     MemberIndex, _          ‘第一次调用设置为0 
  31.     MyDeviceInterfaceData _ 

  参数cbSize是SP_DEVICE_INTERFACE_DATA结构的大小,以字节为单位。在调用SetupDiEnumDeviceInterfaces函数之前,cbSize必须储存在结构内来当做传递的参数。

  参数HidGuid和DeviceInfoSet是函数之前的传回值。

  DeviceInfoData是SP_DEVICE_INTERFACE_DATA结构的指针,用来限制检测特定设备的接口。MemberIndex是DeviceInfoSet数组的索引值,在遍历整个数组的循环中MemberIndex递增。MyDeviceInterfaceData是回传的结构,用来识别HID的一个接口。

(4) 获得设备路径

  下面通过调用SetupDiGetDeviceInterfaceDetail函数用来获得另外一个结构:SP_DEVICE_INTERFACE_DETAIL_DATA。此结构与前一个函数SetupDiEnumDeviceInterfaces所识别的设备接口有关。结构的DevicePath成员是一个设备路径,应用程序可以用此路径来实现与该设备的通信。

  1. ‘ 函数声明: 
  2. Public Declare Function SetupDiGetDeviceInterfaceDetail Lib "setupapi.dll" _ 
  3.    Alias "SetupDiGetDeviceInterfaceDetailA" (_ 
  4.    ByVal DeviceInfoSet As Long, _ 
  5.    ByRef DeviceInterfaceData As SP_DEVICE_INTERFACE_DATA, _ 
  6.    ByVal DeviceInterfaceDetailData As Long, _ 
  7.    ByVal DeviceInterfaceDetailDataSize As Long, _ 
  8.    ByRef RequiredSize As Long, _ 
  9.    ByVal DeviceInfoData As Long _ 
  10. As Long 
  11.      
  12. ‘ 结构声明 
  13. Public Type SP_DEVICE_INTERFACE_DETAIL_DATA 
  14.     cbSize As Long 
  15.     DevicePath As Byte 
  16. End Type 
  17.  
  18. ‘ 变量定义 
  19. Dim Needed as Long, DetailData as long 
  20. Dim MyDeviceInterfaceDetailData As SP_DEVICE_INTERFACE_DETAIL_DATA 
  21. Dim DetailDataBuffer() as Byte 
  22. Dim DevicePathName As String 
  23.  
  24. ‘ 调用 
  25. MyDeviceInterfaceData.cbSize = LenB(MyDeviceInterfaceData) 
  26.  
  27. Result = SetupDiGetDeviceInterfaceDetailA( 
  28.     DeviceInfoSet, _ 
  29.     DeviceInterfaceData, _ 
  30.     0, _ 
  31.     0, _ 
  32.     Needed, _ 
  33.     0 _ 
  34.  
  35. DetailData = needed 
  36. MyDeviceInterfaceDetailData.cbSize = Len(MyDeviceInterfaceDetailData) 
  37. ReDim DetailDataBuffer(Needed) 
  38.  
  39. Call RtlMoveMemory(DetailDataBuffer(0), MyDeviceInterfaceDetailData, 4) 
  40. Result = SetupDiGetDeviceInterfaceDetailA( 
  41.     DeviceInfoSet, _ 
  42.     DeviceInterfaceData, _ 
  43.     VarPtr(DetailDataBuffer(0)), _ 
  44.     DetailData, _ 
  45.     Needed, _ 
  46.     0 _ 
  47.  
  48. ‘ 转换成字符串,再转换成Unicode并从中删除4个字符得到设备路径 
  49. DevicePathName = Cstr(DetailDataBuffer())    
  50. DevicePathName = StrCovn(DevicePathName, vbUnicode)  
  51. DevicePathName = Right$(DevicePathName, Len(DevicePathName)-4)   

  DeviceInterfaceDetailDataSize包含DeviceInterfaceData结构的大小,以字节为单位。但是,在调用SetupDiGetDeviceInterfaceDetail函数之前无法知道此数值的大小,如果没有DeviceInterfaceDetailDataSize函数,SetupDiGetDeviceInterfaceDetail函数不会传回所需的结构。

  这个问题的解决方式是两次调用SetupDiGetDeviceInterfaceDetail函数。第一次调用时GetLastError函数会传回错误信息,即:The data erea passed to a system call is too small,但是RequireSize参数会包含正确的DeviceInterfaceDetailDataSize数值。再次调用时传递此传回值,函数就会执行成功。

(5) 获得设备句柄

  取得设备的路径以后,就可以准备开始与设备通信。使用CreateFile来打开一个HID设备,并且取得此设备的句柄,使用设备的句柄来与设备交换数据。当不再需要访问此设备时,应该调用CIoseHandle函数来关闭设备并释放系统资源。

  1. ‘ 函数声明: 
  2. Public Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" ( _ 
  3.     ByVal lpFileName As String, _ 
  4.     ByVal dwDesiredAccess As Long, _ 
  5.     ByVal dwShareMode As Long, _ 
  6.     ByRef lpSecurityAttributes As Long, _ 
  7.     ByVal dwCreationDisposition As Long, _ 
  8.     ByVal dwFlagsAndAttributes As Long, _ 
  9.     ByVal hTemplateFile As Long _ 
  10. As Long 
  11.  
  12. ‘ 常量定义 
  13. Public Const GENERIC_READ = &H80000000 
  14. Public Const GENERIC_WRITE = &H40000000 
  15. Public Const FILE_SHARE_READ = &H1 
  16. Public Const FILE_SHARE_WRITE = &H2 
  17. Public Const OPEN_EXISTING = 3 
  18.  
  19. ‘ 调用 
  20. HidDevice = CreateFile( _ 
  21.     DevicePathName, _ 
  22.      GENERIC_READ Or GENERIC_WRITE, _ 
  23.      (FILE_SHARE_READ Or FILE_SHARE_WRITE), _ 
  24.      0, _ 
  25.      OPEN_EXISTING, _ 
  26.         0, _ 
  27.         0 

(6) 获得设备的厂商ID、产品ID和产品序列号

  要识别一个设备是否是所要的设备,只要比较此设备的厂商与产品ID 即可。HidD_GetAttributes函数用来取得一个包含厂商与产品ID以及产品的版本号码的结构。

  1. ‘ 函数声明 
  2. Public Declare Function HidD_GetAttributes Lib "hid.dll" ( _ 
  3. ByVal HidDeviceObject As Long, _ 
  4. ByRef Attributes As HIDD_ATTRIBUTES _ 
  5. As Long 
  6.  
  7. ‘ 结构说明 
  8. Public Type HIDD_ATTRIBUTES 
  9.     Size As Long 
  10.     VendorID As Integer         ‘厂商ID 
  11.     ProductID As Integer        ‘产品ID 
  12.     VersionNumber As Integer    ‘产品版本号 
  13. End Type 
  14.  
  15. ‘ 变量定义 
  16. Dim DeviceAttributes As HIDD_ATTRIBUTES 
  17.  
  18. ‘ 调用 
  19. DeviceAttributes.Size = LenB(DeviceAttributes) 
  20. Result = HidD_GetAttributes( _ 
  21.     HidDevice, _                ‘由CreateFile函数返回 
  22.         DeviceAttributes 

  HidDevice是由 CreateFile函数所传回的设备句柄。如果CreateFile函数传回的是非零值,DeviceAttributes结构就会填写正确值。应用程序可以由DeviceAttributes结构取得厂商ID、产品ID以及产品的版本号码。

  如果厂商与产品ID不是想查找的,应用程序应该使用CloseHandle函数来关闭该设备,然后移到下一个SetupDiEnumDeviceInterfaces所检测到的下一个HlD继续查找。当应用程序检测到指定的设备或是检测完全部HID,它应该调用SetupDiDestroyDeviceInfoList函数来释放SetupDiGetClassDevs函数所保留的资源。

5.3 获得HID的能力

  获得设备的能力是可以进一步了解HID的手段,但不是必须的。如果在查找设备时,如果通过厂商ID、产品ID和产品序列号可以确定特定的设备,一般是已知设备的细节信息了。如果不通过厂商ID、产品ID和产品序列号确定设备,另一个方法是通过获得设备能力来确定设备。

获得HID的能力的过程主要经过以下几个步骤:

● 先使用HidD_GetPreparsedData函数获得一个包含设备能力信息的缓冲区的指针,调用HidP_GetCaps获得描述HID的能力的数据结构;
● 调用HidP_GetValueCaps取得描述能力的数值。

  (1) 获得描述HID能力的数据结构

  1. ‘ 函数声明 
  2. Public Declare Function HidD_GetPreparsedData Lib "hid.dll" (_ 
  3.         ByVal HidDeviceObject As Long, _ 
  4.         ByRef PreparsedData As Long 
  5. As Long 
  6.  
  7. ‘ 变量定义 
  8. Dim PreparsedData As Long 
  9.  
  10. ‘ 调用 
  11. Result = HidD_GetPreparsedData (HidDevice, _    ‘ 由CreateFile获得的设备句柄 
  12.          PreparsedData _ 

  PreparsedData是一个包含数据的缓冲区的指针。程序并不需要访问缓冲区内的数据,只需要将缓冲区的开始地址传递给其他的API函数。当应用程序不再需要PreparsedData时,它应该调用HidD_FreePreparsedData函数来释放系统资源。

  接下来调用HidP_GetCaps,该函数传回一个结构,里面包含设备能力的信息,包括设备的Usage Page、Usage、报表长度以及按钮能力和数值能力等的数目。如果不使用厂商与产品ID来识另设备,HidP_GetCaps函数传回的设备能力信息可以帮助决定是否继续与此设备通信。
 

  1. ‘ 函数声明 
  2. Public Declare Function HidP_GetCaps Lib "hid.dll" ( _ 
  3.         ByVal PreparsedData As Long, _ 
  4.         ByRef Capabilities As HIDP_CAPS 
  5. As Long 
  6.  
  7. ‘ 结构定义 
  8. Public Type HIDP_CAPS 
  9.     Usage As Integer                            ‘ 用法 
  10.     UsagePage As Integer                    ‘ 用法页 
  11.     InputReportByteLength As Integer        ‘ 输入报表长度 
  12.     OutputReportByteLength As Integer       ‘ 输出报表长度 
  13.     FeatureReportByteLength As Integer      ‘ 特征报表长度 
  14.     Reserved(16) As Integer 
  15. NumberLinkCollectionNodes As Integer    ‘ 由函数HidP_GetLinkCollectionNodes 
  16.                                             ‘ 返回的顶层集合连接数目 
  17.     NumberInputButtonCaps As Integer        ‘ 由函数HidP_GetButtonCaps返回的 
  18.                                             ‘ 包含输入按钮能力的结构HIDP_BUTTON_  
  19.                                             ‘ CAPS的数目 
  20.     NumberInputValueCaps As Integer         ‘ 由函数HidP_GetValueCaps返回的 
  21.                                             ‘ 包含输入数值能力的结构HIDP_VALUE_  
  22.                                             ‘ CAPS的数目 
  23.     NumberInputDataIndices As Integer       ‘ 在输入报表中有关按键和数值的数据 
  24.                                             ‘ 指示器(Data Indices)的数目  
  25.     NumberOutputButtonCaps As Integer       ‘ 由函数HidP_GetButtonCaps返回的 
  26.                                             ‘ 包含输出按钮能力的结构HIDP_BUTTON_  
  27.                                             ‘ CAPS的数目 
  28.     NumberOutputValueCaps As Integer        ‘ 由函数HidP_GetValueCaps返回的 
  29.                                             ‘ 包含输出数值能力的结构HIDP_VALUE_  
  30.                                             ‘ CAPS的数目 
  31.     NumberOutputDataIndices As Integer      ‘ 在输出报表中有关按键和数值的数据 
  32.                                                 ‘ 指示器(Data Indices)的数目 
  33.     NumberFeatureButtonCaps As Integer      ‘ 由函数HidP_GetButtonCaps返回的 
  34.                                             ‘ 包含特征按钮能力的结构HIDP_BUTTON_  
  35.                                             ‘ CAPS的数目 
  36.     NumberFeatureValueCaps As Integer       ‘ 由函数HidP_GetValueCaps返回的 
  37.                                             ‘ 包含特征数值能力的结构HIDP_VALUE_  
  38.                                             ‘ CAPS的数目 
  39.     NumberFeatureDataIndices As Integer     ‘ 在特征报表中有关按键和数值的数据 
  40.                                                 ‘ 指示器(Data Indices)的数目 
  41. End Type 
  42.  
  43. ‘ 变量定义 
  44. Dim Capabilities As HIDP_CAPS 
  45.  
  46. ‘ 调用 
  47. Result = HidP_GetCaps (_ 
  48.     PreparsedData, _        ‘ 由函数HidD_GetPreparsedData定义 
  49.         Capabilities 

  HidP_GetCaps函数填写Capabilities结构中的数据项,Capabilities结构成员说明了HID的基本信息。这些信息包括:

● 用法页和用法:UsagePage、Usage;

● 输入、输出和特征报表长度:InputReportByteLength、 OutputReportByteLength和 FeatureReportByteLength;

● 由函数HidP_GetLinkCollectionNodes返回的顶层集合连接数目NumberLinkCollectionNodes

● 在输入、输出和特征报表的按钮、数值和数据指示器的的数目。

(2) 获得描述HID数值能力的数据结构

  通过HidP_GetCaps获得的HID的基本能力信息不是能从设备得到全部信息,还可以调用另一个函数HidP_GetValueCap,该函数可以获得报表描述符中的数值和按钮的能力。HidP_GetValueCap返回的是包含了报表描述符中全部数值信息的结构的指针。

  1. ‘ 函数声明 
  2. Public Declare Function HidP_GetValueCaps Lib "hid.dll" (_ 
  3.         ByVal ReportType As Integer, _ 
  4.         ByRef ValueCaps As Byte, _ 
  5.         ByRef ValueCapsLength As Integer, _ 
  6.         ByVal PreparsedData As Long _ 
  7. As Long 
  8.  
  9. ‘ ReportType常量定义 
  10. Public Const HidP_Input = 0 
  11. Public Const HidP_Output = 1 
  12. Public Const HidP_Feature = 2 
  13.  
  14. Public Type HidP_Value_Caps 
  15.         UsagePage As Integer 
  16.         ReportID As Byte 
  17.         IsAlias As Long 
  18.         BitField As Integer 
  19.         LinkCollection As Integer 
  20.         LinkUsage As Integer 
  21.         LinkUsagePage As Integer 
  22.         IsRange As Long 
  23.         IsStringRange As Long 
  24.         IsDesignatorRange As Long 
  25.         IsAbsolute As Long 
  26.         HasNull As Long 
  27.         Reserved As Byte 
  28.         BitSize As Integer 
  29.         ReportCount As Integer 
  30.         Reserved2 As Integer 
  31.         Reserved3 As Integer 
  32.         Reserved4 As Integer 
  33.         Reserved5 As Integer 
  34.         Reserved6 As Integer 
  35.         LogicalMin As Long 
  36.         LogicalMax As Long 
  37.         PhysicalMin As Long 
  38.         PhysicalMax As Long 
  39.         UsageMin As Integer 
  40.         UsageMax As Integer 
  41.         StringMin As Integer 
  42.         StringMax As Integer 
  43.         DesignatorMin As Integer 
  44.         DesignatorMax As Integer 
  45.         DataIndexMin As Integer 
  46.         DataIndexMax As Integer 
  47. End Type 
  48.  
  49.  
  50. ‘ 变量定义 
  51. Dim ValueCaps(1023) As Byte 
  52. ‘ 调用 
  53. Result = HidP_GetValueCaps (_ 
  54.     HidP_Input, _ 
  55.         ValueCaps(0), _ 
  56.         Capabilities.NumberInputValueCaps, _ 
  57.         PreparsedData 

(3) 输出报表到设备

  当应用程序取得HID设备的句柄,并且知道输出报表的字节数目后,它就可以传送输出报表给此设备。应用程序先将要传送的数据复制到一个缓冲区内,然后调用WriteFile函数。缓冲区的大小等于HidP_GetCaps函数返回的HIDP_CAPS结构的OutputReportByte Length属性值。这个大小值等于报表的字节大小,再加上一个字节的Report ID。Report ID是缓冲区的第一个字节。

  HlD驱动程序用来确定输出报表的传输类型,根据Windows的版本以及HID接口有无中断输出端点而定。应用程序不需要干预,低阶的驱动程序会自动处理。

  1. ‘ 函数声明 
  2. Public Declare Function WriteFile Lib "kernel32" (_ 
  3.         ByVal hFile As Long, _ 
  4.         ByRef lpBuffer As Byte, _ 
  5.         ByVal nNumberOfBytesToWrite As Long, _ 
  6.         ByRef lpNumberOfBytesWritten As Long, _ 
  7.         ByVal lpOverlapped As Long _ 
  8. As Long  
  9.  
  10. ‘ 变量定义 
  11. Dim SendBuffer() As Byte  
  12. Dim OutputReportData(7) As Byte 
  13. Dim Count as Long 
  14.  
  15. ‘ 调用 
  16. ReDim SendBuffer(Capabilities.OutputReportByteLength - 1) 
  17. SendBuffer(0) = 0           ‘ Report ID 
  18. ‘ 将准备的数据从OutputReportData复制到SendBuffer 
  19. For Count = 1 To Capabilities.OutputReportByteLength - 1 
  20.     SendBuffer(Count) = OutputReportData(Count - 1) 
  21. Next Count 
  22. NumberOfBytesWritten = 0 
  23. Result = WriteFile( _ 
  24.         HidDevice, _            ‘ 由CreateFile函数返回的设备句柄 
  25.         SendBuffer(0), _        ‘ 输出报表缓存 
  26.         CLng(Capabilities.OutputReportByteLength), _        ‘ 要输出字节数 
  27.         NumberOfBytesWritten, _                             ‘ 实际输出的字节数 
  28.     0 


如果函数返回的Result数值不等于零,表示函数成功执行。

  如果接口只支持数值为0的Report ID,这个Report ID并不传送,但需要出现在应用程序传给WriteFile函数的缓冲区内。

  WriteFile函数在HID通信中最常发生的错误是CRC error。此错误表示主机控制器试图要传送报表,但是没有从设备收到预期的响应。通常该错误不是发生在CRC计算时所检测到的错误,而是因为主机没有收到固件预期的响应。

(4) 从设备输入出报表

  当应用程序取得HID设备的句柄,并且知道输入报表的字节数目后,就可以从此设备读取输入报表。应用程序先声明一个缓冲区来储存数据,然后调用ReadFile函数。用来储存数据的缓冲区大小等于HidP_GetCaps函数所返回的HIDP_CAPS结构的InputReportByteLength属性值。

 

  1. ‘ 函数声明 
  2. Public Declare Function ReadFile Lib "kernel32" ( _ 
  3.         ByVal hFile As Long, _ 
  4.         ByRef lpBuffer As Byte, _ 
  5.         ByVal nNumberOfBytesToRead As Long, _ 
  6.         ByRef lpNumberOfBytesRead As Long, _ 
  7.         ByVal lpOverlapped As Long _ 
  8. As Long 
  9.  
  10. ‘ 变量定义 
  11. Dim Count 
  12. Dim NumberOfBytesRead As Long 
  13. Dim ReadBuffer() As Byte            ‘ 输入缓冲区,字节0为Report ID 
  14. ReDim ReadBuffer(Capabilities.InputReportByteLength - 1) 
  15.  
  16. ‘ 调用 
  17. Result = ReadFile( _ 
  18.     HidDevice, _                        ‘ 由CreateFile函数返回的设备句柄 
  19.     ReadBuffer(0), _                    ‘ 输入缓冲区首地址 
  20.     CLng(Capabilities.InputReportByteLength), _     ‘ 要读取的字节数 
  21.     NumberOfBytesRead, _            ‘ 读到的字节数 

  ReadBuffer字节数组包含报表的数据。如果函数返回的Result数值不等于零,表示函数成功执行。

  通过ReadFile读取的缓冲区的第一个字节是Report ID,后续是从设备读取的报表数据。如果接口只支持一个Report ID,此Report ID不在总线上传输,但会出现在ReadBuffer缓冲区内。

  调用ReadFile函数不会立刻开始总线上的传输,只是主机在定时的中断输入传输中读取一个报表。如果没有未读取的报表,就等待下一个传输完成。主机在检测设备后开始请求报表,当HlD驱动程序加载后,驱动程序将报表储存在环状缓冲区内。当缓冲区已经填满并有新的报表到达时,旧的报表会被覆盖。调用ReadFile函数会读取缓冲区内最旧的报表。

  在Windows 98 SE以及后来的版本中,默认的环状缓冲区尺寸是8个报表。应用程序可以使用HidD_SetNumInputBuffers函数来设置缓冲区的大小。如果应用程序没有频繁的请求报表,有些报表就会丢失。如果不想要丢失报表,就应该改用特征报表。

  如果报表的数据从上一次传输后就没有改变,闲置速率决定设备是否要传送报表。在检测设备时HlD驱动程序会试图将设备的闲置速率设置为0,这表示除非报表的数据有改变否则HID不会传送报表。没有可以改变闲置速率的API函数。

  如果设备拒绝将闲置速率设置为0,可以传回Stall来响应Set_Idle请求来通知主机设备不支持该请求。

  如果设备不支持Set_Idle请求,而且应用程序只要读取一次报表,固件可以设置成只传送一次报表。在送报表后,固件可以设置端点传回NAK来响应输入令牌信息包。如果设备有新的数据要传送,固件可以设置端点来传回该数据。否则设备会在主机轮询端点时继续传送相同的报表,应用程序也会重复地读取相同的报表。

  上面程序中ReadFile的最后一个参数为0,表示ReadFile调用是阻塞的。当应用程序在环形缓冲区为空时调用ReadFile,应用程序将会被挂起,直到有输入报表为止,否则只能按下Ctrl+Alt+Del来关闭应用程序,或是从总线上移除设备。

  采用多线程方式编程可以较好的解决这个问题,在另一个线程中调用ReadFile可以避免主线程被挂起,在使用Visual Basic编写多线程应用程序会遇到困难,这是因为Visual Basic本身不支持多线程。而在Visual C++编写API方式通信程序时可以采用多线程方式,ReadFile函数调用发生在一个独立的线程,这样可以实现重叠I/O操作。

  解决的办法之一是保证设备永远有数据传送,可以将固件设计为输入端点永远启用并且准备响应要求。如果没有新的数据传送,设备可以传送上一次的数据,或是传回一个特定代码来指示没有新的数据。

  也可以采用这样的方法,在调用ReadFile之前,应用程序先调用WriteFile来传送一个报表,报表内可以包含一个特定代码来告诉固件准备传送数据,这样当应用程序调用ReadFile时,设备的端点就会启用并且有数据准备传送。

  比较好的方法是使用ReadFile的重叠选项。在重叠的读取时ReadFile函数会立即返回(即使没有可读数据),然后应用程序调用 WaitForSingleObject函数来读取数据。WaitForSingleObject函数可以设置暂停,如果数据在指定时间内尚未抵达,此函数会传回一个码来指示此情况,然后应用程序可以使用CancelIo函数来取消读取动作。

  要使用重叠I/O,CreateFile函数必须在dwFlagsAndAttributes参数中传递一个重叠的结构。应用程序调用CreateEvent函数建立一个事件对象,在ReadFile完成后此事件对象会被设置成信号状态。当应用程序调用ReadFile时,它传递一个重叠结构的指针,重叠结构的hEvent参数是一个事件对象的代号。应用程序调用 WaitForSingleObject函数来传递此事件代号,以及一个以ms为单位的指定时间间隔。在读取到数据或到达该时间间隔时,此函数才返回。

 


  1. ‘ 函数声明 
  2. Public Declare Function ReadFile Lib "kernel32" ( _ 
  3.         ByVal hFile As Long, _ 
  4.         ByRef lpBuffer As Byte, _ 
  5.         ByVal nNumberOfBytesToRead As Long, _ 
  6.         ByRef lpNumberOfBytesRead As Long, _ 
  7.         ByVal lpOverlapped As Long _ 
  8. As Long 
  9.  
  10. Public Declare Function CreateEvent Lib "kernal32.dll" Alias CreatEventA( _ 
  11.         ByVal SecurityAttributes As Long, _ 
  12.         ByVal bManualReset As Long, _ 
  13.         ByVal bInitialState As Long, _ 
  14.         ByVal lpName As String 
  15. As Long 
  16.  
  17. Public Declare Function WaitForSingleObject Lib “kernal32.dll” (_ 
  18.     ByVal hHandle as Long, _ 
  19.     ByVal dwMilliseconds as Long 
  20. ) as Long 
  21.  
  22. ‘ 重叠结构声明 
  23. Public Type OVERLAPPED 
  24.     Internal as Long 
  25.     InternalHigh as Long 
  26.     Offset as Long 
  27.     OffsetHigh as Long 
  28.     hEvent as Long 
  29. End Type 
  30.  
  31. ‘ 常量定义 
  32. Public Const FILE_FLAG_OVERLAPPED = &H40000000 
  33.  
  34. ‘ 变量定义 
  35. Dim EventObject as Long 
  36. Dim HIDOverlapped as OVERLAPPED 
  37.  
  38. ‘ 部分代码 
  39. EventObject = CreatEvent (0&, TrueTrue, “”) 
  40. HIDOverlapped.hEvent = EventObject 
  41. HIDOverlapped.Offset = 0 
  42. HIDOverlapped.OffsetHigh = 0 
  43.  
  44. HidDevice = CreateFile( _ 
  45.     DevicePathName, _ 
  46.      GENERIC_READ Or GENERIC_WRITE, _ 
  47.      (FILE_SHARE_READ Or FILE_SHARE_WRITE), _ 
  48.      0, _ 
  49.      OPEN_EXISTING, _ 
  50.         FILE_FLAG_OVERLAPPED, _ 
  51.         0 
  52. )   ‘ 获得HID设备句柄 
  53.  
  54. Result = ReadFile( _ 
  55.     HidDevice, _                        ‘ 由CreateFile函数返回的设备句柄 
  56.     ReadBuffer(0), _                    ‘ 输入缓冲区首地址 
  57.     CLng(Capabilities.InputReportByteLength), _     ‘ 要读取的字节数 
  58.     NumberOfBytesRead, _            ‘ 读到的字节数 
  59. HIDOverlapped 
  60. )   ‘ 读取报表,同时传送一个指针到重叠结构 
  61.  
  62. Result = WaitForSingleObject (EventObject, 5000) 
  63.     ‘ 等待ReadFile完成,超时间隔设为5秒 

(5) 特征报表的传送
应用程序调用HidD_SetFeature函数传送一个特征报表到设备。

  1. ‘ 函数声明 
  2. Public Declare Function HidD_SetFeature Lib "hid.dll" ( _ 
  3.         ByVal HidDeviceObject As Long, _ 
  4.         ByRef ReportBuffer As Byte, _ 
  5.         ByVal ReportBufferLength As Long 
  6. As Long 
  7.  
  8. ‘ 调用 
  9. Result = HidD_SetFeature( _ 
  10.     HidDevice, _                        ‘ 由CreateFile函数返回的设备句柄 
  11.     SendBuffer(0), _                    ‘ 输出缓冲区首地址 
  12.     CLng(Capabilities.FeatureReportByteLength)  ‘ 特征报表长度字节数 

  API函数HidD_GetFeature用于从设备读取特征报表,通过对该API函数的调用,主机控制器以控制传输送出Get_Feature请求,并在数据阶段,设备回传特征报表。

  1. ‘ 函数声明 
  2. Public Declare Function HidD_GetFeature Lib "hid.dll" ( _ 
  3.         ByVal HidDeviceObject As Long, _ 
  4.         ByRef ReportBuffer As Byte, _ 
  5.         ByVal ReportBufferLength As Long 
  6. As Long 
  7.  
  8. ‘ 调用 
  9. Result = HidD_GetFeature( _ 
  10.     HidDevice, _                        ‘ 由CreateFile函数返回的设备句柄 
  11.     ReadBuffer(0), _                    ‘ 输出缓冲区首地址 
  12.     CLng(Capabilities.FeatureReportByteLength)  ‘ 特征报表长度字节数 


(6) 关闭设备
当结束与HID的通信,需要调用CloseHandle函数关闭通信。

 

  1. ‘ 函数声明 
  2. Public Declare Function CloseHandle Lib "kernel32" ( _ 
  3.         ByVal hObject As Long _ 
  4.  ) As Long 
  5.  
  6. ‘ 调用 
  7. Result = CloseHandle(HidDevice) 

 

------分隔线----------------------------
USB开源项目
联系我们
  • Q Q: 1148374829 点击这里给我发消息
  • 旺旺:jhoneqhsieh 点击这里给我发消息
  • 电话:(0)15923141204
  • 淘宝网店