2

我正在尝试使用 Arudino 板和 CH376 模块为 USB (1.1) HID 设备创建原型。我设法编写了将 CH376 设置为设备模式并处理所有 USB 事务的 Arduino 代码。

只要我将设备声明为属于“供应商特定”类,Windows 10 就会将该设备识别为“未知设备”,并在我在控制面板中询问设备详细信息时正确显示其 VID 和 PID(设备属性 - 详细信息- 硬件 ID)。到目前为止,一切都很好。然后我可以使用Zadig为设备分配一个 libusb 驱动程序,然后它将显示在控制面板的“libusb 设备”类别下;一切都很好,我可以使用 libusb 功能与设备进行通信。

现在问题来了。如果我尝试使用 WinUSB 而不是 libusb 来管理设备,则在插入或重置设备时会发生以下情况:

  1. Windows 执行通常的设备初始化序列:它请求设备描述符(64 字节),然后为设备分配地址,然后再次请求设备描述符(这次只有 18 字节),然后请求配置描述符。

  2. 五秒钟后,Windows 再次请求设备描述符。这就是设备出现在设备管理器中的位置,状态为“此设备工作正常”。

  3. 再过五秒钟后,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

(设备现在显示感叹号)

顺便说一句,我通过写入串行端口来获得这些跟踪,然后由计算机读取。考虑到这可能会减慢整个处理速度,我尝试禁用任何串行端口通信,结果是一样的。

所以我的问题是:

  1. 难道我做错了什么?还是Windows有问题?
  2. 如果是后者,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 在对给定设备的请求之间稍等片刻?

4

2 回答 2

4

我检查了你的代码,发现:

#define USB_REQ_SET_CONFIGURATION 是 9 而不是 8

在通过 SPI 驱动 CH376 的 Teensy2 上,它在我的 Mac 上正确显示,如下所示:

         NestorDevice:

              Product ID: 0x0002
              Vendor ID: 0x1209
              Version: 1.00
              Speed: Up to 12 Mb/s
              Location ID: 0x14223000 / 48
              Current Available (mA): 500
              Current Required (mA): 100
              Extra Operating Current (mA): 0

我还没有尝试与它交流。

也知道时机就是一切。当我在 Mac 上运行您的代码时,通过串行将所有命令转发给 Teensy,我错过了某些事件。Mac 正在非常迅速地启动所有命令,并且通过串行的延迟太多了。

编辑

有两种读法:

CMD01_RD_USB_DATA0  EQU         027H             ; / * Read the data block from the current USB interrupt endpoint buffer or the host endpoint receive buffer * /
; / * Output: length, data stream * /

CMD01_RD_USB_DATA   EQU         028H             ; / * Device mode: Read the data block from the current USB interrupt endpoint buffer and release the buffer, equivalent to CMD01_RD_USB_DATA0 + CMD00_UNLOCK_USB * /
; / * Output: length, data stream * /

如果我们使用后一个函数呢?它会更早地释放缓冲区并为下一个事件做好准备吗?我会在这里尝试一下,看看是否有意义。

于 2021-06-27T14:55:57.887 回答
1

我一直在进一步调查,并在 Github 上找到了更多中文示例。令我印象深刻的是,他们在 EP0_IN 处添加了数量可变的 UNLOCK_USB 命令。

一旦我这样做了,我就更幸运地注册了串行设备。否则它会处于一个循环中,每 5 秒重试一次。

所以在 USB_INT_EP0_IN 在处理任何需要的处理之后添加一个额外的 CMD_UNLOCK_USB 。在这里查看更多。

于 2021-07-10T15:04:43.933 回答