如何将发送 USB 大容量传输请求(UWP 应用)

在本主题中,你将了解 USB 大容量传输,以及如何从与 USB 设备通信的 UWP 应用启动传输请求。

USB 全速、高速和超高速设备可以支持批量终结点。 这些终结点用于传输大量数据,例如将数据传输到 U 盘或从 USB 闪存驱动器传输数据。 批量传输是可靠的,因为它们允许错误检测,并且涉及有限的重试次数,以确保主机或设备接收数据。 批量传输用于非时间关键数据。 仅当总线上存在可用的未使用带宽时,才会传输数据。 因此,当总线忙于其他传输时,批量数据可以无限期等待。

批量终结点是单向的,在一次传输中,数据可以在 IN 或 OUT 方向传输。 若要支持读取和写入批量数据,设备必须支持批量 IN 和批量输出终结点。 大容量 IN 终结点用于将数据从设备读取到主机,而大容量 OUT 终结点用于将数据从主机发送到设备。

若要启动批量传输请求,应用必须具有对表示终结点的 管道 的引用。 管道是配置设备时由设备驱动程序打开的信道。 对于应用,管道是终结点的逻辑表示形式。 若要从终结点读取数据,应用将从关联的批量 IN 管道获取数据。 若要将数据写入终结点,应用会将数据发送到批量 OUT 管道。 对于批量读取和写入管道,请使用 UsbBulkInPipeUsbBulkOutPipe 类。

应用还可以通过设置某些策略标志来修改管道的行为。 例如,对于读取请求,可以设置自动清除管道上的停止条件的标志。 有关这些标志的信息,请参阅 UsbReadOptionsUsbWriteOptions

准备工作

步骤 1:获取大容量管道对象

若要启动传输请求,必须获取对大容量管道对象的引用 (UsbBulkOutPipeUsbBulkInPipe。 可以通过枚举所有接口的所有设置来获取管道。 但是,对于数据传输,只能使用活动设置的管道。 如果关联终结点不在活动设置中,则管道对象为 null。

若希望... 使用此属性值
将数据发送到批量管道,获取对 UsbBulkOutPipe 的引用。 如果设备配置公开一个 USB 接口,则 UsbDevice.DefaultInterface.BulkOutPipes[n]

UsbDevice.Configuration.UsbInterfaces[m]。BulkOutPipes[n] 用于枚举设备支持的多个接口中的批量 OUT 管道。

UsbInterface.InterfaceSettings[m]。BulkOutEndpoints[n]. 用于枚举接口中的设置定义的批量 OUT 管道的管道。

UsbEndpointDescriptor.AsBulkOutEndpointDescriptor.Pipe ,用于从批量 OUT 终结点的终结点描述符获取管道对象。
从批量管道接收数据,可以获取 UsbBulkInPipe 对象。 如果设备配置公开了一个 USB 接口,则 UsbDevice.DefaultInterface.BulkInPipes[n]

UsbDevice.Configuration.UsbInterfaces[m]。BulkInPipes[n] 用于枚举设备支持的多个接口中的大容量 IN 管道。

UsbInterface.InterfaceSettings[m]。BulkInEndpoints[n]。 用于枚举接口中的设置定义的批量 IN 管道的管道。

UsbEndpointDescriptor.AsBulkInEndpointDescriptor.Pipe ,用于从批量 IN 终结点的终结点描述符获取管道对象。

注意

应处于活动设置中,或需要 null 检查。

步骤 2:配置大容量管道 (可选)

可以通过在检索的批量管道上设置某些标志来修改读取或写入操作的行为。

若要从设备读取数据,请将 UsbBulkInPipe.ReadOptions 属性设置为 UsbReadOptions 中定义的值之一。 在写入时,请将 UsbBulkOutPipe.WriteOptions 属性设置为 UsbWriteOptions 中定义的值之一。

若希望... 设置此标志
在不停止数据流的情况下自动清除终结点上的任何错误条件 AutoClearStall

有关详细信息,请参阅 清除停滞条件

此标志适用于读取和写入传输。
以最高效率发送多个读取请求。 通过绕过错误检查来提高性能。 OverrideAutomaticBufferManagement

数据请求可以分为一个或多个传输,其中每个传输都包含一定数量的字节,称为最大传输大小。 对于多次传输,由于驱动程序执行的错误检查,排队两个传输可能会延迟。 此标志绕过该错误检查。 若要获取最大传输大小,请使用 UsbBulkInPipe.MaxTransferSizeBytes 属性。 如果请求大小为 UsbBulkInPipe.MaxTransferSizeBytes,则必须设置此标志。

重要: 如果设置此标志,则必须请求管道最大数据包大小的倍数中的数据。 该信息存储在终结点描述符中。 大小取决于设备的总线速度。 适用于全速、高速和超高速;最大数据包大小分别为 64、512 和 1024 字节。 若要获取该值,请使用 UsbBulkInPipe.EndpointDescriptor.MaxPacketSize 属性。

此标志仅适用于读取传输。
使用零长度数据包终止写入请求 ShortPacketTerminate

发送零长度数据包以指示 OUT 传输的结束。

此标志仅适用于写入传输。
禁用读取短数据包 (小于终结点支持的最大数据包大小) IgnoreShortPacket

默认情况下,如果设备发送的字节数小于最大数据包大小,应用将接收它们。 如果不想接收短数据包,请设置此标志。

此标志仅适用于读取传输。

步骤 3:设置数据流

当设备发送批量数据时,数据将作为大容量管道上的输入流接收。 下面是获取输入流的步骤:

  1. 通过获取 UsbBulkInPipe.InputStream 属性来获取对输入流的引用。
  2. 通过在 DataReader 构造函数中指定输入流来创建 DataReader 对象。

若要将数据写入设备,应用必须写入大容量管道上的输出流。 下面是准备输出流的步骤:

  1. 通过获取 UsbBulkOutPipe.OutputStream 属性来获取对输出流的引用。
  2. 通过在 DataWriter 构造函数中指定输出流来创建 DataWriter 对象。
  3. 填充与输出流关联的数据缓冲区。
  4. 根据数据类型,通过调用 DataWriter 方法(如 WriteBytes)将数据写入输出流。

步骤 4:启动异步传输操作

批量传输通过异步操作启动。

若要读取批量数据,请通过调用 DataReader.LoadAsync 启动异步读取操作。

若要写入批量数据,请通过调用 DataWriter.StoreAsync 启动异步写入操作。

步骤 5:获取读取传输操作的结果

异步数据操作完成后,可以获取从任务对象读取或写入的字节数。 对于读取操作,请调用 DataReader 方法(例如 ReadBytes)从输入流读取数据。

清除停滞条件

有时,应用可能会遇到数据传输失败。 传输失败可能是由于终结点上的停滞条件造成的。 只要终结点已停止,就不能将数据写入或从中读取数据。 为了继续数据传输,应用必须清除关联管道上的停止条件。

应用可以将管道配置为在发生停止条件时自动清除这些条件。 为此,请将 UsbBulkInPipe.ReadOptions 属性设置为 UsbReadOptions.AutoClearStallUsbBulkOutPipe.WriteOptions 属性设置为 UsbWriteOptions.AutoClearStall。 通过该自动配置,应用不会遇到失败的传输,并且数据传输体验是无缝的。

若要手动清除停止条件,请为批量 IN 管道调用 UsbBulkInPipe.ClearStallAsync ;调用 UsbBulkOutPipe.ClearStallAsync 以获取批量 OUT 管道。

注意

停止条件并不表示终结点为空。 如果终结点中没有数据,则传输完成,但长度为零字节。

对于读取操作,可能需要在启动新的传输请求之前清除管道中的挂起数据。 为此,请调用 UsbBulkInPipe.FlushBuffer 方法。

USB 大容量传输代码示例

此代码示例演示如何写入批量管道。 该示例将数据发送到默认接口上的第一个批量 OUT 管道。 它将管道配置为在传输结束时发送长度为零的数据包。 传输完成后,会显示字节数。

    private async void BulkWrite()
    {
        String dataBuffer = "Hello World!";
        UInt32 bytesWritten = 0;

        UsbBulkOutPipe writePipe = usbDevice.DefaultInterface.BulkOutPipes[0];
        writePipe.WriteOptions |= UsbWriteOptions.ShortPacketTerminate;

        var stream = writePipe.OutputStream;

        DataWriter writer = new DataWriter(stream);

        writer.WriteString(dataBuffer);

        try
        {
            bytesWritten = await writer.StoreAsync();
        }
        catch (Exception exception)
        {
            ShowStatus(exception.Message.ToString());
        }
        finally
        {
            ShowStatus("Data written: " + bytesWritten + " bytes.");
        }
    }

此代码示例演示如何从批量管道读取数据。 该示例从默认接口上的第一个大容量 IN 管道中检索数据。 它将管道配置为 以实现最大效率,并接收最大数据包大小的区块中的数据。 传输完成后,会显示字节数。

    private async void BulkRead()
    {
        UInt32 bytesRead = 0;

        UsbBulkInPipe readPipe = usbDevice.DefaultInterface.BulkInPipes[0];

        // Warning: Setting IgnoreShortPacket causes LoadAsync to block until you receive a number of packets >= readPipe.EndpointDescriptor.MaxPacketSize.
        // Remove the following line if you want to see messages that are less than the max transfer size, for example if you are communicating with a USBTMC device. 
        readPipe.ReadOptions |= UsbReadOptions.IgnoreShortPacket;

        var stream = readPipe.InputStream;
        DataReader reader = new DataReader(stream);

        try
        {
            bytesRead = await reader.LoadAsync(readPipe.EndpointDescriptor.MaxPacketSize);
        }
        catch (Exception exception)
        {
            ShowStatus(exception.Message.ToString());
        }
        finally
        {
            ShowStatus("Number of bytes: " + bytesRead);

            IBuffer buffer = reader.ReadBuffer(bytesRead);

            using (var dataReader = Windows.Storage.Streams.DataReader.FromBuffer(buffer))
            {
                dataReader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
                ShowData(dataReader.ReadString(buffer.Length));
            }
        }
    }