我正在尝试使用 Arudino 板和 CH376 模块为 USB (1.1) HID 设备创建原型。我设法编写了将 CH376 设置为设备模式并处理所有 USB 事务的 Arduino 代码。
只要我将设备声明为属于“供应商特定”类,Windows 10 就会将该设备识别为“未知设备”,并在我在控制面板中询问设备详细信息时正确显示其 VID 和 PID(设备属性 - 详细信息- 硬件 ID)。到目前为止,一切都很好。然后我可以使用Zadig为设备分配一个 libusb 驱动程序,然后它将显示在控制面板的“libusb 设备”类别下;一切都很好,我可以使用 libusb 功能与设备进行通信。
现在问题来了。如果我尝试使用 WinUSB 而不是 libusb 来管理设备,则在插入或重置设备时会发生以下情况:
Windows 执行通常的设备初始化序列:它请求设备描述符(64 字节),然后为设备分配地址,然后再次请求设备描述符(这次只有 18 字节),然后请求配置描述符。
五秒钟后,Windows 再次请求设备描述符。这就是设备出现在设备管理器中的位置,状态为“此设备工作正常”。
再过五秒钟后,Windows 再次请求配置描述符,但在仅检索前 8 个字节(即控制端点的大小)之后,它会断开设备连接并在设备管理器中显示一个感叹号。
如果我转到设备属性,“常规”选项卡,它会显示:
This device cannot start. (Code 10)
{Device Timeout}
The specified I/O operation on %hs was not completed before the time-out period expired.
如果我转到“事件”选项卡,最后一个是“设备未启动(WinUSB)”,其中包含以下详细信息:
Device USB\VID_1209&PID_0002\8&339ad878&0&2 had a problem starting.
Driver Name: oem168.inf
Class Guid: {88bae032-5a81-49f0-bc3d-a4ff138216d6}
Service: WinUSB
Lower Filters:
Upper Filters:
Problem: 0xA
Problem Status: 0xC00000B5
现在,如果不是将设备声明为属于“供应商特定”类,而是将其声明为 HID 设备并让 Windows 选择正确的驱动程序,则行为是相同的,但“常规”选项卡中的错误消息是:
This device cannot start. (Code 10)
The I/O request was canceled.
...最后一个事件:
Device USB\VID_1209&PID_0002\8&339ad878&0&2 had a problem starting.
Driver Name: input.inf
Class Guid: {745a17a0-74d3-11d0-b6fe-00a0c90f57da}
Service: HidUsb
Lower Filters:
Upper Filters:
Problem: 0xA
Problem Status: 0xC0000120
我怀疑原型对于 Windows 内置 USB 设备管理库来说可能太慢了,而 libusb 更宽松,但我觉得这很奇怪(Arduino 板中的代码只是检查来自 CH376 的事件并立即处理它们一个循环),我想知道我是否做错了什么。
这是 Arduino 代码生成的跟踪的转储。SETUP/IN/OUT 事件意味着令牌处理已经完成;对于 IN 令牌,我之前写入的任何数据(“写入 X 字节”日志)都已发送。通过使用 libusb,我可以确认记录的数据确实是我发送的数据。
Int: USB_SUSPEND
Int: WAKE_UP
Int: WAKE_UP
Int: EP0_SETUP
0x80 0x06 0x00 0x01 0x00 0x00 0x40 0x00
GET_DESCRIPTOR: DEVICE
Writing 8 bytes: 0x12 0x01 0x10 0x01 0x00 0x00 0x00 0x08
Int: EP0_IN
Writing 8 bytes: 0x09 0x12 0x02 0x00 0x00 0x01 0x01 0x02
Int: EP0_OUT
Int: EP0_OUT
Int: EP0_OUT
Int: EP0_OUT
Int: EP0_OUT
Int: EP0_SETUP
0x00 0x05 0x18 0x00 0x00 0x00 0x00 0x00
SET_ADDRESS: 24
Int: EP0_IN
Setting address: 24
Int: EP0_SETUP
0x80 0x06 0x00 0x01 0x00 0x00 0x12 0x00
GET_DESCRIPTOR: DEVICE
Writing 8 bytes: 0x12 0x01 0x10 0x01 0x00 0x00 0x00 0x08
Int: EP0_IN
Writing 8 bytes: 0x09 0x12 0x02 0x00 0x00 0x01 0x01 0x02
Int: EP0_IN
Writing 2 bytes: 0x00 0x01
Int: EP0_IN
Int: EP0_OUT
Int: EP0_SETUP
0x80 0x06 0x00 0x02 0x00 0x00 0xFF 0x00
GET_DESCRIPTOR: CONFIGURATION
Writing 8 bytes: 0x09 0x02 0x22 0x00 0x01 0x01 0x00 0x80
Int: EP0_IN
Writing 8 bytes: 0x32 0x09 0x04 0x00 0x00 0x01 0x03 0x00
Int: EP0_IN
Writing 8 bytes: 0x00 0x00 0x09 0x21 0x10 0x01 0x00 0x01
Int: EP0_IN
Writing 8 bytes: 0x22 0x29 0x00 0x07 0x05 0x81 0x03 0x03
Int: EP0_IN
Writing 2 bytes: 0x00 0x0A
Int: EP0_IN
Int: EP0_OUT
(暂停 5 秒,设备管理器中还没有条目)
Int: EP0_SETUP
0x80 0x06 0x00 0x01 0x00 0x00 0x12 0x00
GET_DESCRIPTOR: DEVICE
Writing 8 bytes: 0x12 0x01 0x10 0x01 0x00 0x00 0x00 0x08
Int: EP0_IN
Writing 8 bytes: 0x09 0x12 0x02 0x00 0x00 0x01 0x01 0x02
Int: EP0_IN
Writing 2 bytes: 0x00 0x01
Int: EP0_IN
Int: EP0_OUT
(再次暂停5秒,设备在设备管理器中显示为正常工作)
Int: EP0_SETUP
0x80 0x06 0x00 0x02 0x00 0x00 0x09 0x00
GET_DESCRIPTOR: CONFIGURATION
Writing 8 bytes: 0x09 0x02 0x22 0x00 0x01 0x01 0x00 0x80
Int: USB_SUSPEND
(设备现在显示感叹号)
顺便说一句,我通过写入串行端口来获得这些跟踪,然后由计算机读取。考虑到这可能会减慢整个处理速度,我尝试禁用任何串行端口通信,结果是一样的。
所以我的问题是:
- 难道我做错了什么?还是Windows有问题?
- 如果是后者,Windows 10 是否提供任何方法来增加 WinUSB/HID 设备处理的超时时间(如果这确实是问题)?
编辑
如果我使用Microsoft 的 USB 硬件验证器,我会得到以下输出:
Event Message: USBXHCI Device Update
VendorID/ProductID: 0x1209/0x2
PortPath: 0x4, 0x4, 0x4, 0x1, 0x0, 0x0
HWVerify Errors Encountered so far:
#1: (UsbHub3/171): Request for Language ID String Descriptor Failed
#2: (UsbHub3/132): Device Control Transfer Error
...这很奇怪,因为设备似乎根本没有收到任何对字符串描述符的请求!
编辑 2
根据评论中的建议,我将 Wireshark 与 USBPcap 一起使用,我看到的是:
- 对 wLength = 18 的设备描述符的一次请求
- 对 wLength = 9 的配置描述符的一次请求
这与我在自己的跟踪中看到的最后两个请求一致。
编辑 3
为了不使问题文本增长太多,我创建了一个要点,其中包含:
- 我正在使用的 Arduino 代码。
- 详细的 Wireshark 捕获。
这是我正在使用的 CH376 模块的示意图(这是我找到的关于该模块的所有技术信息):
编辑 4
正如评论中所建议的,我已经修改了我的代码,以便设备现在将自己报告为 USB 2.0 设备并提供序列号字符串。现在从设备的角度来看,事件的顺序是:
- 获取设备描述符请求(wLength = 64)
- 设置地址请求
- 获取设备描述符请求(wLength = 18)
- 获取配置描述符请求(wLength = 255)
- 5 秒暂停
- 获取可用语言请求(字符串描述符 0)
- 5 秒暂停
- 获取 Microsoft 特定的描述符请求 (bRequest = 6, wValue = 0x0600) - 不支持,所以我停止它
- 获取设备描述符请求(wLength = 18)
- 5 秒暂停
- 获取配置描述符请求 (wLength = 9) - 在 Wireshark 中显示为 USBD_STATUS_CANCELED 的响应
有趣的是,Wireshark 跟踪是完全相同的:语言请求和 Microsoft 特定的描述符请求不显示!
模式似乎是 5 秒暂停后的请求对 Wireshark 不可见,因此我的代码可能没有正确处理它们,并且主机没有认为它们已完成,但我不明白为什么(因为我'正在执行与第二个获取设备描述符请求相同的处理,该请求成功)。
另外值得注意的是,当第一个设备描述符请求完成时,我得到的不是一个而是六个 OUT 令牌中断。这可能是时间问题的症状吗?
编辑 5
绝对看起来像一个时间问题。
我使用 Zadig 将设备的驱动程序设置为 libusb,然后我尝试在循环中发送一些描述符请求;结果是有二分之一的请求始终失败。但是,如果我在请求之间放置 1 毫秒的延迟,那么它们都可以正常工作!就好像芯片在请求成功后需要一个“冷却期”。失败的请求不会出现在 Wireshark 或我自己的日志中,因此 SETUP 令牌本身似乎丢失了。
有什么方法可以指示 Windows 在对给定设备的请求之间稍等片刻?