第8章 USB接口HID设备 211 ValueCaps(0), _ Capabilities.NumberInputValueCaps, _ PreparsedData ) (3) 输出报表到设备
当应用程序取得HID设备的句柄,并且知道输出报表的字节数目后,它就可以传送输出报表给此设备。应用程序先将要传送的数据复制到一个缓冲区内,然后调用WriteFile函数。缓冲区的大小等于HidP_GetCaps函数返回的HIDP_CAPS结构的OutputReportByte Length属性值。这个大小值等于报表的字节大小,再加上一个字节的Report ID。Report ID是缓冲区的第一个字节。
HlD驱动程序用来确定输出报表的传输类型,根据Windows的版本以及HID接口有无中断输出端点而定。应用程序不需要干预,低阶的驱动程序会自动处理。
? 函数声明
Public Declare Function WriteFile Lib \ ByVal hFile As Long, _ ByRef lpBuffer As Byte, _ ByVal nNumberOfBytesToWrite As Long, _ ByRef lpNumberOfBytesWritten As Long, _ ByVal lpOverlapped As Long _ ) As Long
? 变量定义
Dim SendBuffer() As Byte
Dim OutputReportData(7) As Byte Dim Count as Long
? 调用
ReDim SendBuffer(Capabilities.OutputReportByteLength - 1) SendBuffer(0) = 0 ? Report ID
? 将准备的数据从OutputReportData复制到SendBuffer
For Count = 1 To Capabilities.OutputReportByteLength - 1 SendBuffer(Count) = OutputReportData(Count - 1) Next Count
NumberOfBytesWritten = 0 Result = WriteFile( _ HidDevice, _ ? 由CreateFile函数返回的设备句柄 SendBuffer(0), _ ? 输出报表缓存 CLng(Capabilities.OutputReportByteLength), _ ? 要输出字节数 NumberOfBytesWritten, _ ? 实际输出的字节数
0 )
如果函数返回的Result数值不等于零,表示函数成功执行。
如果接口只支持数值为0的Report ID,这个Report ID并不传送,但需要出现在应用程序传给WriteFile函数的缓冲区内。
WriteFile函数在HID通信中最常发生的错误是CRC error。此错误表示主机控制器试图要传送报表,但是没有从设备收到预期的响应。通常该错误不是发生在CRC计算时所检测到的错误,而是因为主机没有收到固件预期的响应。
(4) 从设备输入出报表
当应用程序取得HID设备的句柄,并且知道输入报表的字节数目后,就可以从此设备读取输入报表。应用程序先声明一个缓冲区来储存数据,然后调用ReadFile函数。用来储
212 计算机高级接口实践
存数据的缓冲区大小等于HidP_GetCaps函数所返回的HIDP_CAPS结构的InputReport
ByteLength属性值。
? 函数声明
Public Declare Function ReadFile Lib \ ByVal hFile As Long, _ ByRef lpBuffer As Byte, _ ByVal nNumberOfBytesToRead As Long, _ ByRef lpNumberOfBytesRead As Long, _ ByVal lpOverlapped As Long _ ) As Long
? 变量定义 Dim Count
Dim NumberOfBytesRead As Long Dim ReadBuffer() As Byte ? 输入缓冲区,字节0为Report ID ReDim ReadBuffer(Capabilities.InputReportByteLength - 1)
? 调用
Result = ReadFile( _ HidDevice, _ ? 由CreateFile函数返回的设备句柄 ReadBuffer(0), _ ? 输入缓冲区首地址 CLng(Capabilities.InputReportByteLength), _ ? 要读取的字节数 NumberOfBytesRead, _ ? 读到的字节数
0 )
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来响应输入令牌信息包。如果设备有新的数据要传送,固件可以设置端点来传回该数据。否则设备会在主机轮询端点时继续传送相同的报表,应用程序也会重复地读取相同的报表。
第8章 USB接口HID设备 213
上面程序中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为单位的指定时间间隔。在读取到数据或到达该时间间隔时,此函数才返回。
? 函数声明
Public Declare Function ReadFile Lib \ ByVal hFile As Long, _ ByRef lpBuffer As Byte, _ ByVal nNumberOfBytesToRead As Long, _ ByRef lpNumberOfBytesRead As Long, _ ByVal lpOverlapped As Long _ ) As Long
Public Declare Function CreateEvent Lib \ ByVal SecurityAttributes As Long, _ ByVal bManualReset As Long, _ ByVal bInitialState As Long, _ ByVal lpName As String ) As Long
Public Declare Function WaitForSingleObject Lib “kernal32.dll” (_ ByVal hHandle as Long, _ ByVal dwMilliseconds as Long ) as Long
? 重叠结构声明
Public Type OVERLAPPED Internal as Long InternalHigh as Long Offset as Long OffsetHigh as Long hEvent as Long
214 计算机高级接口实践 End Type
? 常量定义
Public Const FILE_FLAG_OVERLAPPED = &H40000000
? 变量定义
Dim EventObject as Long
Dim HIDOverlapped as OVERLAPPED
? 部分代码
EventObject = CreatEvent (0&, True, True, “”) HIDOverlapped.hEvent = EventObject HIDOverlapped.Offset = 0
HIDOverlapped.OffsetHigh = 0
HidDevice = CreateFile( _ DevicePathName, _
GENERIC_READ Or GENERIC_WRITE, _
(FILE_SHARE_READ Or FILE_SHARE_WRITE), _ 0, _
OPEN_EXISTING, _ FILE_FLAG_OVERLAPPED, _ 0
) ? 获得HID设备句柄
Result = ReadFile( _ HidDevice, _ ? 由CreateFile函数返回的设备句柄 ReadBuffer(0), _ ? 输入缓冲区首地址 CLng(Capabilities.InputReportByteLength), _ ? 要读取的字节数 NumberOfBytesRead, _ ? 读到的字节数
HIDOverlapped
) ? 读取报表,同时传送一个指针到重叠结构
Result = WaitForSingleObject (EventObject, 5000) ? 等待ReadFile完成,超时间隔设为5秒
(5) 特征报表的传送
应用程序调用HidD_SetFeature函数传送一个特征报表到设备。
? 函数声明 Public Declare Function HidD_SetFeature Lib \ ByVal HidDeviceObject As Long, _ ByRef ReportBuffer As Byte, _ ByVal ReportBufferLength As Long ) As Long ? 调用 Result = HidD_SetFeature( _ HidDevice, _ ? 由CreateFile函数返回的设备句柄 SendBuffer(0), _ ? 输出缓冲区首地址 CLng(Capabilities.FeatureReportByteLength) ? 特征报表长度字节数 ) 第8章 USB接口HID设备 215
API函数HidD_GetFeature用于从设备读取特征报表,通过对该API函数的调用,主机控制器以控制传输送出Get_Feature请求,并在数据阶段,设备回传特征报表。
? 函数声明 Public Declare Function HidD_GetFeature Lib \ ByVal HidDeviceObject As Long, _ ByRef ReportBuffer As Byte, _ ByVal ReportBufferLength As Long ) As Long ? 调用 Result = HidD_GetFeature( _ HidDevice, _ ? 由CreateFile函数返回的设备句柄 ReadBuffer(0), _ ? 输出缓冲区首地址 CLng(Capabilities.FeatureReportByteLength) ? 特征报表长度字节数 ) (6) 关闭设备
当结束与HID的通信,需要调用CloseHandle函数关闭通信
? 函数声明 Public Declare Function CloseHandle Lib \ ByVal hObject As Long _ ) As Long ? 调用 Result = CloseHandle(HidDevice) 8.6 HID实验
本节介绍通过高级接口实验台进行的一个HID编程实验。
在USB设备软件的开发过程中,借助于一些工具软件的测试会对USB设备的信息获取和通信过程又更深入的理解。USB测试软件有很多,如USBView、BusHound等,下面针对一个具体的HID设备的API通信软件的开发,对这两个工具软件作一简单介绍。
高级接口实验台中,有一个HID实验,在该实验中,实验台通过固件软件设计了一个简单的HID仿真设备,在PC的API通信软件的开发过程中,可以借助USB工具软件对实验台的HID设备进行测试。
8.6.1 获得描述符
将高级接口实验台通过USB电缆与PC连接后,在实验台上通过菜单选择HID实验,实验台显示器显示器上会显示HID实验界面。
实验台的HID实验是一个为了学习HID编程而专门设计的一个简单的HID仿真设备,设备中设计了8个寄存器(R1~R8),可以通过USB接口与主机交换数据。其中R1和R2两个寄存器只是数据存储单元,主机可以对这两个寄存器进行附值,也可以读取寄存器的值。寄存器R3~R8构成了一个日期和时钟,6个寄存器的值分别表示年、月、日、时、分、秒。时钟在当前值的基础上运行,可以通过主机对时钟进行设置,也可以读取当前的时钟值。
在实验台上设计了自动回传功能,如果自动回传打开,时钟每一秒向主机传送一次报