如何获取 USB 描述符(Windows 应用商店应用)

与 USB 设备交互的一个主要任务是获取相关信息。所有 USB 设备均以几种数据结构(称为描述符)提供信息。本主题介绍了 Windows 应用商店应用如何在端点、接口、配置和设备层从设备获取描述符。

USB 描述符

USB 设备会以两个主要描述符描述其功能:一个是设备描述符,一个是配置描述符。

USB 设备必须提供一个包含 USB 设备整体相关信息的设备描述符。如果设备不提供该描述符或提供的描述符格式不正确,那么 Windows 就无法加载设备驱动程序。描述符中最重要的信息是设备的硬件 IDvendor IDproduct ID 字段的组合)。Windows 根据该信息才能为设备匹配内置驱动程序。另外一条重要的信息是默认端点的最大数据包大小 (MaxPacketSize0)。默认端点是主机向设备发送用于配置的所有控制请求的目标。

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

USB 设备还必须提供一个完整的配置描述符。此描述符的开始部分长度为固定的 9 个字节,其他部分为可变长度,取决于接口数以及这些接口支持的端点数。固定长度的部分提供有关 USB 配置的信息:其支持的接口数以及该配置下设备的能耗。开始的这 9 个字节后是描述符的可变部分,介绍关于所有 USB 接口的信息。每个接口包含一个或多个接口设置,并且每个接口设置由一组端点组成。接口描述符、备用设置和端点均包含在此可变部分中。

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

开始之前...

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

如何获取设备描述符

Windows 应用商店应用可以通过获取 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 对象均显示了一个名为 Descriptors 的属性。该属性值检索 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;
}

 

 

显示:
© 2014 Microsoft