How to send a USB interrupt transfer request (UWP app)

Interrupt transfers occur when the host polls the device. This article demonstrates how to:

Important APIs

A USB device can support interrupt endpoints so that it can send or receive data at regular intervals. To accomplish that, the host polls the device at regular intervals and data is transmitted each time the host polls the device. Interrupt transfers are mostly used for getting interrupt data from the device. This topic describes how a UWP app can get continuous interrupt data from the device.

Interrupt endpoint information

For interrupt endpoints, the descriptor exposes these properties. Those values are for information only and should not affect how you manage the buffer transfer buffer.

  • How often can data be transmitted?

    Get that information by getting the Interval value of the endpoint descriptor (see UsbInterruptOutEndpointDescriptor.Interval or UsbInterruptInEndpointDescriptor.Interval). That value indicates how often data is sent to or received from the device in each frame on the bus.

    The Interval property is not the bInterval value (defined in the USB specification).

    That value indicates how often data is transmitted to or from the device. For example, for a high speed device, if Interval is 125 microseconds, data is transmitted every 125 microseconds. If Interval is 1000 microseconds, then data is transmitted every millisecond.

  • How much data can be transmitted in each service interval?

    Get the number of bytes that can transmitted by getting the maximum packet size supported by the endpoint descriptor (see UsbInterruptOutEndpointDescriptor.MaxPacketSize or UsbInterruptInEndpointDescriptor.MaxPacketSize). The maximum packet size constrained on the speed of the device. For low-speed devices up to 8 bytes. For full-speed devices, up to 64 bytes. For high-speed, high-bandwidth devices, the app can send or receive more than maximum packet size up to 3072 bytes per microframe.

    Interrupt endpoints on SuperSpeed devices are capable of transmitting even more number of bytes. That value is indicated by the wBytesPerInterval of the USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR. To retrieve the descriptor, get the descriptor buffer by using the UsbEndpointDescriptor.AsByte property and then parse that buffer by using DataReader methods.

Interrupt OUT transfers

A USB device can support interrupt OUT endpoints that receive data from the host at regular intervals. Each time the host polls the device, the host sends data. A UWP app can initiate an interrupt OUT transfer request that specifies the data to send. That request is completed when the device acknowledges the data from the host. A UWP app can write data to the UsbInterruptOutPipe.

Interrupt IN transfers

Conversely, a USB device can support interrupt IN endpoints as a way to inform the host about hardware interrupts generated by the device. Typically USB Human Interface Devices (HID) such as keyboards and pointing devices support interrupt OUT endpoints. When an interrupt occurs, the endpoint stores interrupt data but that data does not reach the host immediately. The endpoint must wait for the host controller to poll the device. Because there must be minimal delay between the time data is generated and reaches the host, it polls the device at regular intervals. A UWP app can get data received in the UsbInterruptInPipe. The request that completes when data from the device is received by the host.

Before you start

Writing to the interrupt OUT endpoint

The way the app sends an interrupt OUT transfer request is identical to bulk OUT transfers, except the target is an interrupt OUT pipe, represented by UsbInterruptOutPipe. For more information, see How to send a USB bulk transfer request (UWP app).

Step 1: Implement the interrupt event handler (Interrupt IN)

When data is received from the device into the interrupt pipe, it raises the DataReceived event. To get the interrupt data, the app must implement an event handler. The eventArgs parameter of the handler, points to the data buffer.

This example code shows a simple implementation of the event handler. The handler maintains the count of interrupts received. Each time the handler is invoked, it increments the count. The handler gets the data buffer from eventArgs parameter and displays the count of interrupts and the length of bytes received.

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 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

    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);
        }));
}

Step 2: Get the interrupt pipe object (Interrupt IN)

To register the event handler for the DataReceived event, obtain a reference to the UsbInterruptInPipe by using any these properties:

Note Avoid getting the pipe object by enumerating interrupt endpoints of an interface setting that is not currently selected. To transfer data, pipes must be associated with endpoints in the active setting.

Step 3: Register the event handler to start receiving data (Interrupt IN)

Next, you must register the event handler on the UsbInterruptInPipe object that raises the DataReceived event.

This example code shows how to register the event handler. In this example, the class keeps track of the event handler, pipe for which the event handler is registered, and whether the pipe is currently receiving data. All that information is used for unregistering the event handler, shown in the next step.

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;    

}

After the event handler is registered, it is invoked each time data is received in the associated interrupt pipe.

Step 4: Unregister the event handler to stop receiving data (Interrupt IN)

After you are finished receiving data, unregister the event handler.

This example code shows how to unregister the event handler. In this example, if the app has an previously registered event handler, the method gets the tracked event handler, and unregisters it on the interrupt pipe.

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

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

        registeredInterrupt = false;
    }
}

After the event handler is unregistered, the app stops receiving data from the interrupt pipe because the event handler is not invoked on interrupt events. This does not mean that the interrupt pipe stops getting data.