如何获取 USB 描述符(UWP 应用)

与 USB 设备交互main任务之一是获取有关它的信息。 所有 USB 设备都以称为描述符的多个数据结构的形式提供信息。 本文介绍了 UWP 应用如何在终结点、接口、配置和设备级别从设备获取描述符。 本文还介绍了:

  • 了解 USB 设备布局
  • 获取标准 USB 描述符
  • 获取自定义描述符

重要的 API

USB 描述符

USB 设备在两个main描述符中描述其功能:设备描述符和配置描述符。

USB 设备必须提供 设备描述符 ,其中包含有关 USB 设备整体的信息。 如果设备未提供该描述符或提供格式不正确的描述符,则 Windows 无法加载设备驱动程序。 描述符中最重要的信息是设备 的硬件 ID , (供应商 ID产品 ID 字段的组合) 。 它基于 Windows 能够匹配设备的内置驱动程序的信息。 另一个关键信息是默认终结点的最大 数据包大小 (MaxPacketSize0) 。 默认终结点是主机发送到设备以对其进行配置的所有控制请求的目标。

设备描述符的长度是固定的。

USB 设备还必须提供完整的 配置描述符。 此描述符的开头部分具有 9 个字节的固定长度,其余部分长度可变,具体取决于这些接口支持的接口和终结点的数量。 固定长度部分提供有关 USB 配置的信息:它支持的接口数以及设备处于该配置时的电源消耗。 这些初始 9 个字节后跟描述符的变量部分,用于提供有关所有 USB 接口的信息。 每个接口由一个或多个接口设置组成,每个设置由一组终结点组成。 接口、备用设置和终结点的描述符包含在变量部分中。

有关设备布局的详细说明,请参阅 标准 USB 描述符

准备工作

  • 必须已打开设备并获取 UsbDevice 对象。 请阅读 如何 (UWP 应用) 连接到 USB 设备
  • 可以在 CustomUsbDeviceAccess 示例中查看本主题中显示的完整代码,Scenario5_UsbDescriptors文件。
  • 获取有关设备布局的信息。 适用于 Windows 8) 的 Windows 软件开发工具包 (SDK) 中包含的Usbview.exe(是一个应用程序,可用于浏览连接到它们的所有 USB 控制器和 USB 设备。 对于每个连接的设备,可以查看设备、配置、接口和终结点描述符,以了解设备的功能。

如何获取设备描述符

UWP 应用可以通过获取 UsbDevice.DeviceDescriptor 属性值,从以前获取的 UsbDevice 对象获取设备描述符

此代码示例演示如何使用设备描述符中的字段值填充字符串。

String GetDeviceDescriptorAsString (UsbDevice device)
{
    String content = null;

    var deviceDescriptor = device.DeviceDescriptor;

    content = "Device Descriptor\n"
            + "\nUsb Spec Number : 0x" + deviceDescriptor.BcdUsb.ToString("X4", NumberFormatInfo.InvariantInfo)
            + "\nMax Packet Size (Endpoint 0) : " + deviceDescriptor.MaxPacketSize0.ToString("D", NumberFormatInfo.InvariantInfo)
            + "\nVendor ID : 0x" + deviceDescriptor.IdVendor.ToString("X4", NumberFormatInfo.InvariantInfo)
            + "\nProduct ID : 0x" + deviceDescriptor.IdProduct.ToString("X4", NumberFormatInfo.InvariantInfo)
            + "\nDevice Revision : 0x" + deviceDescriptor.BcdDeviceRevision.ToString("X4", NumberFormatInfo.InvariantInfo)
            + "\nNumber of Configurations : " + deviceDescriptor.NumberOfConfigurations.ToString("D", NumberFormatInfo.InvariantInfo);

    return content;
}

输出如下所示:

usb 设备描述符。

如何获取配置描述符

若要从以前获取的 UsbDevice 对象获取配置描述符的固定部分,

  1. UsbDevice 获取 UsbConfiguration 对象。 UsbConfiguration 表示设备定义的第一个 USB 配置,默认情况下也由基础设备驱动程序选择。
  2. 获取 UsbConfiguration.ConfigurationDescriptor 属性值。

配置描述符的固定部分指示设备的电源特征。 例如,可以确定设备是从总线还是外部源中取电, (请参阅 UsbConfigurationDescriptor.SelfPowered) 。 如果设备正在从总线供电,则) 消耗的功率 (以毫安为单位 (请参阅 UsbConfigurationDescriptor.MaxPowerMilliamps) 。 此外,还可以通过获取 UsbConfigurationDescriptor.RemoteWakeup 值来确定设备是否能够从低功耗状态唤醒自身或系统。

此代码示例演示如何获取字符串中配置描述符的固定部分。

String GetConfigurationDescriptorAsString(UsbDevice device)
{
    String content = null;

    var usbConfiguration = device.Configuration;
    var configurationDescriptor = usbConfiguration.ConfigurationDescriptor;

    content = "Configuration Descriptor\n"
            + "\nNumber of Interfaces : " + usbConfiguration.UsbInterfaces.Count.ToString("D", NumberFormatInfo.InvariantInfo)
            + "\nConfiguration Value : 0x" + configurationDescriptor.ConfigurationValue.ToString("X2", NumberFormatInfo.InvariantInfo)
            + "\nSelf Powered : " + configurationDescriptor.SelfPowered.ToString()
            + "\nRemote Wakeup : " + configurationDescriptor.RemoteWakeup.ToString()
            + "\nMax Power (milliAmps) : " + configurationDescriptor.MaxPowerMilliamps.ToString("D", NumberFormatInfo.InvariantInfo);

    return content;
}

输出如下所示:

usb 配置描述符。

如何获取接口描述符

接下来,可以获取有关属于配置的 USB 接口的信息。

USB 接口是接口设置的集合。 因此,没有描述整个接口的描述符。 术语 接口描述符 指示描述接口内设置的数据结构。

Windows.Devices.Usb 命名空间公开对象,可用于获取有关每个 USB 接口的信息,以及 (该接口中包含的备用设置) 所有接口描述符的信息。 和配置描述符的可变长度部分的描述符。

若要从 UsbConfiguration 获取接口描述符,

  1. 通过获取 UsbConfiguration.UsbInterfaces 属性,获取配置中的接口数组。
  2. 对于 usbInterface) (每个接口,请获取以下信息:
    • 处于活动状态并可以传输数据的批量和中断管道。
    • 接口中的备用设置数组。
    • 接口描述符数组。

此示例代码获取配置的所有 UsbInterface 对象。 从每个对象中,帮助程序方法获取备用设置和打开的批量和接口管道的数量。 如果设备支持多个接口,则每个接口的设备类、子类和协议代码可能会有所不同。 但是,备用设置的所有接口描述符必须指定相同的代码。 在此示例中,方法从第一个设置的接口描述符获取设备类、子类和协议代码,以确定整个接口的代码。

String GetInterfaceDescriptorsAsString(UsbDevice device)
{
    String content = null;

    var interfaces = device.Configuration.UsbInterfaces;

    content = "Interface Descriptors";

        foreach (UsbInterface usbInterface in interfaces)
        {
            // Class/subclass/protocol values from the first interface setting.

            UsbInterfaceDescriptor usbInterfaceDescriptor = usbInterface.InterfaceSettings[0].InterfaceDescriptor;

            content +="\n\nInterface Number: 0x" +usbInterface.InterfaceNumber.ToString("X2", NumberFormatInfo.InvariantInfo)
                    + "\nClass Code: 0x" +usbInterfaceDescriptor.ClassCode.ToString("X2", NumberFormatInfo.InvariantInfo)
                    + "\nSubclass Code: 0x" +usbInterfaceDescriptor.SubclassCode.ToString("X2", NumberFormatInfo.InvariantInfo)
                    + "\nProtocol Code: 0x" +usbInterfaceDescriptor.ProtocolCode.ToString("X2", NumberFormatInfo.InvariantInfo)
                    + "\nNumber of Interface Settings: "+usbInterface.InterfaceSettings.Count.ToString("D", NumberFormatInfo.InvariantInfo)
                    + "\nNumber of open Bulk In pipes: "+usbInterface.BulkInPipes.Count.ToString("D", NumberFormatInfo.InvariantInfo)
                    + "\nNumber of open Bulk Out pipes: "+usbInterface.BulkOutPipes.Count.ToString("D", NumberFormatInfo.InvariantInfo)
                    + "\nNumber of open Interrupt In pipes: "+usbInterface.InterruptInPipes.Count.ToString("D", NumberFormatInfo.InvariantInfo)
                    + "\nNumber of open Interrupt Out pipes: "+usbInterface.InterruptOutPipes.Count.ToString("D", NumberFormatInfo.InvariantInfo);
       }

    return content;
}

输出如下所示:

usb 接口描述符。

如何获取终结点描述符

除默认控制终结点) 之外, (的所有 USB 终结点都必须具有终结点描述符。 若要获取特定终结点的终结点描述符,必须知道终结点所属的接口和备用设置。

  1. 获取包含终结点的 UsbInterface 对象。

  2. 通过获取 UsbInterface.InterfaceSettings 获取备用设置数组。

  3. 在数组中,找到使用 终结点的 UsbInterfaceSetting) (设置。

  4. 在每个设置中,通过枚举批量和中断描述符数组来查找终结点。

    终结点描述符由以下对象表示:

如果设备只有一个接口,则可以使用 UsbDevice.DefaultInterface 获取接口,如此示例代码所示。 在这里,帮助程序方法使用与活动接口设置的管道关联的终结点描述符填充字符串。

private String GetEndpointDescriptorsAsString(UsbDevice device)
{
    String content = null;

    var usbInterface = device.DefaultInterface;
    var bulkInPipes = usbInterface.BulkInPipes;
    var bulkOutPipes = usbInterface.BulkOutPipes;
    var interruptInPipes = usbInterface.InterruptInPipes;
    var interruptOutPipes = usbInterface.InterruptOutPipes;

    content = "Endpoint Descriptors for open pipes";

    // Print Bulk In Endpoint descriptors
    foreach (UsbBulkInPipe bulkInPipe in bulkInPipes)
    {
        var endpointDescriptor = bulkInPipe.EndpointDescriptor;

        content +="\n\nBulk In Endpoint Descriptor"
                + "\nEndpoint Number : 0x" + endpointDescriptor.EndpointNumber.ToString("X2", NumberFormatInfo.InvariantInfo)
                + "\nMax Packet Size : " + endpointDescriptor.MaxPacketSize.ToString("D", NumberFormatInfo.InvariantInfo);
    }

    // Print Bulk Out Endpoint descriptors
    foreach (UsbBulkOutPipe bulkOutPipe in bulkOutPipes)
    {
        var endpointDescriptor = bulkOutPipe.EndpointDescriptor;

        content +="\n\nBulk Out Endpoint Descriptor"
                + "\nEndpoint Number : 0x" + endpointDescriptor.EndpointNumber.ToString("X2", NumberFormatInfo.InvariantInfo)
                + "\nMax Packet Size : " + endpointDescriptor.MaxPacketSize.ToString("D", NumberFormatInfo.InvariantInfo);
    }

    // Print Interrupt In Endpoint descriptors
    foreach (UsbInterruptInPipe interruptInPipe in interruptInPipes)
    {
        var endpointDescriptor = interruptInPipe.EndpointDescriptor;

        content +="\n\nInterrupt In Endpoint Descriptor"
                + "\nEndpoint Number : 0x" + endpointDescriptor.EndpointNumber.ToString("X2", NumberFormatInfo.InvariantInfo)
                + "\nMax Packet Size : " + endpointDescriptor.MaxPacketSize.ToString("D", NumberFormatInfo.InvariantInfo);
                + "\nInterval : " + endpointDescriptor.Interval.Duration.ToString();
    }

    // Print Interrupt Out Endpoint descriptors
    foreach (UsbInterruptOutPipe interruptOutPipe in interruptOutPipes)
    {
        var endpointDescriptor = interruptOutPipe.EndpointDescriptor;

        content +="\n\nInterrupt Out Endpoint Descriptor"
                + "\nEndpoint Number : 0x" + endpointDescriptor.EndpointNumber.ToString("X2", NumberFormatInfo.InvariantInfo)
                + "\nMax Packet Size : " + endpointDescriptor.MaxPacketSize.ToString("D", NumberFormatInfo.InvariantInfo);
                + "\nInterval : " + endpointDescriptor.Interval.Duration.ToString();
    }

    return content;
}

输出如下所示:

usb 终结点描述符。

如何获取自定义描述符

请注意, UsbConfigurationUsbInterfaceUsbInterfaceSetting 对象都公开一个名为 “描述符”的属性。 该属性值检索 由 UsbDescriptor 对象表示的描述符数组。 UsbDescriptor 对象允许应用在缓冲区中获取描述符数据。 UsbDescriptor.DescriptorTypeUsbDescriptor.Length 属性存储保存描述符所需的缓冲区的类型和长度。

注意 所有描述符缓冲区的前两个字节也指示描述符的类型和长度。

例如, UsbConfiguration.Descriptors 属性获取完整配置描述符的数组, (固定和可变长度部分) 。 该数组中的第一个元素是固定长度的配置描述符, (与 UsbConfigurationDescriptor) 相同,第二个元素是第一个备用设置的接口描述符,依此而至。

同样, UsbInterface.Descriptors 属性获取所有接口描述符和相关终结点描述符的数组。 UsbInterfaceSetting.Descriptors 属性获取该设置的所有描述符的数组,例如终结点描述符。

当应用想要检索自定义描述符或其他描述符(如 SuperSpeed 设备的终结点配套描述符)时,获取描述符的方式非常有用。

此代码示例演示如何从配置描述符获取缓冲区中的描述符数据。 该示例获取配置描述符集并分析该集中包含的所有描述符。 对于每个描述符,它使用 DataReader 对象读取缓冲区并显示描述符长度和类型。 可以获取自定义描述符,如本示例所示。

private String GetCustomDescriptorsAsString(UsbDevice device)
{
    String content = null;
    // Descriptor information will be appended to this string and then printed to UI
    content = "Raw Descriptors";

    var configuration = device.Configuration;
    var allRawDescriptors = configuration.Descriptors;

    // Print first 2 bytes of all descriptors within the configuration descriptor
    // because the first 2 bytes are always length and descriptor type
    // the UsbDescriptor's DescriptorType and Length properties, but we will not use these properties
    // in order to demonstrate ReadDescriptorBuffer() and how to parse it.

    foreach (UsbDescriptor descriptor in allRawDescriptors)
    {
        var descriptorBuffer = new Windows.Storage.Streams.Buffer(descriptor.Length);
        descriptor.ReadDescriptorBuffer(descriptorBuffer);

        DataReader reader = DataReader.FromBuffer(descriptorBuffer);

        // USB data is Little Endian according to the USB spec.
        reader.ByteOrder = ByteOrder.LittleEndian;

        // ReadByte has a side effect where it consumes the current byte, so the next ReadByte will read the next character.
        // Putting multiple ReadByte() on the same line (same variable assignment) may cause the bytes to be read out of order.
        var length = reader.ReadByte().ToString("D", NumberFormatInfo.InvariantInfo);
        var type = "0x" + reader.ReadByte().ToString("X2", NumberFormatInfo.InvariantInfo);

        content += "\n\nDescriptor"
                + "\nLength : " + length
                + "\nDescriptorType : " + type;
    }

    return content;
}