如何发送 USB 中断传输请求(UWP 应用)

当主机轮询设备时发生传输中断。 本文演示了以下内容的操作方法:

重要的 API

USB 设备可支持中断终结点,以便定期发送或接收数据。 为此,主机会定期轮询设备,并且每次轮询设备时都会传输数据。 中断传输主要用于从设备获取中断数据。 本主题介绍 UWP 应用如何从设备获取连续中断数据。

中断终结点信息

对于中断终结点,描述符会公开这些属性。 这些值仅供参考,不应影响管理缓冲区传输缓冲区的方式。

  • 多长时间传输一次数据?

    通过获取终结点描述符的 Interval 值来获取该信息(请参阅 UsbInterruptOutEndpointDescriptor.IntervalUsbInterruptInEndpointDescriptor.Interval)。 该值表示总线上每一帧数据发送到设备或从设备接收数据的频率。

    Interval 属性不是 bInterval 值(在 USB 规范中定义)。

    该值表示向设备或从设备传输数据的频率。 例如,对于高速设备,如果 Interval 为 125 微秒,则每 125 微秒传输一次数据。 如果 Interval 为 1000 微秒,则每一毫秒传输一次数据。

  • 每个服务间隔可传输多少数据?

    通过获取终结点描述符支持的最大数据包大小来获取可传输的字节数(请参阅 UsbInterruptOutEndpointDescriptor.MaxPacketSizeUsbInterruptInEndpointDescriptor.MaxPacketSize)。 受设备速度限制的最大数据包大小。 用于低速设备,最多为 8 字节。 用于全速设备,最多为 64 字节。 对于高速、高带宽设备,该应用可发送或接收超过最大数据包大小的数据包,每个微帧可达 3072 字节。

    SuperSpeed 设备上的中断终结点能够传输更多字节。 该值由 USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR 的 wBytesPerInterval 表示。 要检索描述符,请使用 UsbEndpointDescriptor.AsByte 属性来获取描述符缓冲区,然后使用 DataReader 方法来解析该缓冲区。

中断 OUT 传输

USB 设备可支持中断 OUT 终结点,定时从主机接收数据。 每次主机轮询设备时,主机都会发送数据。 UWP 应用可以启动中断 OUT 传输请求,而该请求可指定要发送的数据。 当设备确认来自主机的数据时,该请求便已完成。 UWP 应用以向 UsbInterruptOutPipe 写入数据。

中断 IN 传输

相反,USB 设备可以支持中断 IN 终结点,以此向主机通报设备生成的硬件中断。 键盘和指向设备等 USB 人机接口设备 (HID) 通常支持中断输出终结点。 发生中断时,终结点会存储中断数据,但这些数据不会立即到达主机。 终结点必须等待主机控制器轮询设备。 由于数据从生成到到达主机之间的延迟时间必须尽可能短,因此它会定期轮询设备。 UWP 应用可以获取 UsbInterruptInPipe 中接收到的数据。 当主机接收到来自设备的数据时完成的请求。

准备工作

写入中断 OUT 终结点

应用发送中断 OUT 传输请求的方式与批量 OUT 传输相同,只是目标是中断 OUT 管道,以 UsbInterruptOutPipe 表示。 有关详细信息,请参阅如何发送 USB 批量传输请求(UWP 应用)

步骤 1:实现中断事件处理程序(中断 IN)

将数据从设备接收到中断管道时,会引发 DataReceived 事件。 要获取中断数据,应用必须执行一个事件处理程序。 处理程序的 eventArgs 参数指向数据缓冲区。

本示例代码展示了事件处理程序的简单实现。 处理程序会对收到的中断进行计数。 每次调用处理程序时,计数都会递增。 处理程序从 eventArgs 参数中获取数据缓冲区,并显示中断次数和接收到的字节长度。

private async void OnInterruptDataReceivedEvent(UsbInterruptInPipe sender, UsbInterruptInEventArgs eventArgs)
{
    numInterruptsReceived++;

    // The data from the interrupt
    IBuffer buffer = eventArgs.InterruptData;

    // Create a DispatchedHandler for the because we are interacting with the UI directly and the
    // thread that this function is running on may not be the UI thread; if a non-UI thread modifies
    // the UI, an exception is thrown

    await Dispatcher.RunAsync(
                       CoreDispatcherPriority.Normal,
                       new DispatchedHandler(() =>
    {
        ShowData(
        "Number of interrupt events received: " + numInterruptsReceived.ToString()
        + "\nReceived " + buffer.Length.ToString() + " bytes");
    }));
}
void OnInterruptDataReceivedEvent(UsbInterruptInPipe^ /* sender */, UsbInterruptInEventArgs^  eventArgs )
{
    numInterruptsReceived++;

    // The data from the interrupt
    IBuffer^ buffer = eventArgs->InterruptData;

    // Create a DispatchedHandler for the because we are interracting with the UI directly and the
    // thread that this function is running on may not be the UI thread; if a non-UI thread modifies
    // the UI, an exception is thrown

    MainPage::Current->Dispatcher->RunAsync(
        CoreDispatcherPriority::Normal,
        ref new DispatchedHandler([this, buffer]()
        {
            ShowData(
                "Number of interrupt events received: " + numInterruptsReceived.ToString()
                + "\nReceived " + buffer->Length.ToString() + " bytes",
                NotifyType::StatusMessage);
        }));
}

步骤 2:获取中断管道对象(中断 IN)

要为 DataReceived 事件注册事件处理程序,请通过使用这些属性获取 UsbInterruptInPipe 的引用:

注意 避免通过枚举当前未选择的接口设置的中断终结点来获取管道对象。 要传输数据,管道必须与活动设置中的终结点相关联。

步骤 3:注册事件处理程序以开始接收数据(中断 IN)

接下来,必须在引发 DataReceived 事件的 UsbInterruptInPipe 对象上注册事件处理程序。

本示例代码显示了如何注册事件处理程序。 在此示例中,显示了类跟踪事件处理程序、为事件处理程序所注册的管道,以及管道当前是否正在接收数据。 所有这些信息都将用于注销事件处理程序,下一步将对此进行说明。

private void RegisterForInterruptEvent(TypedEventHandler<UsbInterruptInPipe, UsbInterruptInEventArgs> eventHandler)
{
    // Search for the correct pipe that has the specified endpoint number
    interruptPipe = usbDevice.DefaultInterface.InterruptInPipes[0];

    // Save the interrupt handler so we can use it to unregister
    interruptEventHandler = eventHandler;

    interruptPipe.DataReceived += interruptEventHandler;

    registeredInterruptHandler = true;
}
void RegisterForInterruptEvent(TypedEventHandler<UsbInterruptInPipe, UsbInterruptInEventArgs> eventHandler)
    // Search for the correct pipe that has the specified endpoint number
    interruptInPipe = usbDevice.DefaultInterface.InterruptInPipes.GetAt(pipeIndex);

    // Save the token so we can unregister from the event later
    interruptEventHandler = interruptInPipe.DataReceived += eventHandler;

    registeredInterrupt = true;    

}

注册事件处理程序后,每次相关中断管道接收到数据时都会调用它。

步骤 4:注销事件处理程序以停止接收数据(中断 IN)

完成接收数据后,注销事件处理程序。

本示例代码显示了如何注销事件处理程序。 在此示例中,如果应用先前注册了事件处理程序,则该方法会获取跟踪的事件处理程序,并在中断管道上将其注销。

private void UnregisterInterruptEventHandler()
{
    if (registeredInterruptHandler)
    {
        interruptPipe.DataReceived -= interruptEventHandler;

        registeredInterruptHandler = false;
    }
}
void UnregisterFromInterruptEvent(void)
{
    if (registeredInterrupt)
    {
        interruptInPipe.DataReceived -= eventHandler;

        registeredInterrupt = false;
    }
}

注销事件处理程序后,应用将停止从中断管道接收数据,因为中断事件不会调用事件处理程序。 这并不意味着中断管道将停止获取数据。