主机轮询设备时发生中断传输。 本文演示如何实现以下内容:
- 为 UsbInterruptInPipe.DataReceived 实现事件处理程序
- 注册和注销事件处理程序
重要 API
USB 设备可以支持中断终结点,以便它可以定期发送或接收数据。 为此,主机会定期轮询设备,每次主机轮询设备时都会传输数据。 中断传输主要用于从设备上接收中断数据。 本主题介绍 UWP 应用如何从设备获取连续中断数据。
中断终结点信息
对于中断终结点,描述符公开这些属性。 这些值仅供参考,不应影响您管理缓冲传输的方式。
数据的传输频率是多少?
获取该信息,方法是获取终结点描述符的 Interval 值(请参阅 UsbInterruptOutEndpointDescriptor.Interval 或 UsbInterruptInEndpointDescriptor.Interval)。 该值指示在总线上每个帧中向设备发送或接收数据的频率。
Interval 属性不是 bInterval 值(在 USB 规范中定义)。
该值指示将数据传输到设备或从设备传输的频率。 例如,对于高速设备,如果 Interval 为 125 微秒,则每隔 125 微秒传输一次数据。 如果 Interval 为 1000 微秒,则每毫秒传输一次数据。
每个服务间隔可以传输多少数据?
获取可以通过获取终结点描述符支持的最大数据包大小来传输的字节数(请参阅 UsbInterruptOutEndpointDescriptor.MaxPacketSize 或 UsbInterruptInEndpointDescriptor.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),例如键盘和指针设备,支持中断 IN 终结点。 发生中断时,终结点会存储中断数据,但该数据不会立即到达主机。 终结点必须等待主机控制器轮询设备。 由于生成数据并到达主机的时间之间必须有最小的延迟,因此它会定期轮询设备。 UWP 应用可以获取 UsbInterruptInPipe 中接收的数据。 数据从设备传输至主机时请求完成。
在您开始之前
- 必须已打开设备并获取 UsbDevice 对象。 阅读如何连接到 USB 设备(UWP 应用)。
- 可以在 CustomUsbDeviceAccess 示例中的 Scenario3_InterruptPipes 文件里查看本主题中展示的完整代码。
写入中断 OUT 端点
应用发送中断 OUT 传输请求的方式与批量 OUT 传输相同,但目标是 UsbInterruptOutPipe 表示的中断 OUT 管道。 有关详细信息,请参阅如何发送 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 的引用:
- 如果第一个 USB 接口中存在中断终结点,那么 UsbDevice.DefaultInterface.InterruptInPipes[n]。
- UsbDevice.Configuration.UsbInterfaces[m].InterruptInPipes[n] 用于枚举设备支持的每个接口的所有中断 IN 管道。
- UsbInterface.InterfaceSettings[m].InterruptInEndpoints [n].Pipe 用于枚举由接口设置定义的中断 IN 管道。
- UsbEndpointDescriptor.AsInterruptInEndpointDescriptor.Pipe ,用于从中断 IN 终结点的描述符中获取管道对象。
注意 通过枚举当前未选择的接口设置的中断终结点来避免获取管道对象。 若要传输数据,管道必须与活动设置中的终结点相关联。
步骤 3:注册事件处理程序以开始接收数据(中断输入)
接下来,必须在引发 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;
}
}
取消注册事件处理程序后,应用停止从中断管道接收数据,因为未在中断事件上调用事件处理程序。 这并不意味着中断管道停止获取数据。