Writing apps for USB devices (Windows Store apps using C#/VB/C++)

[This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation]

Windows Runtime in Windows 8.1 provides a new namespace: Windows.Devices.Usb. By using the namespace, you can write a Windows Store app that talks to a custom USB device. "Custom" in this context means, a peripheral device for which Microsoft does not provide an in-box class driver.

The official USB specification is the industry standard for hardware manufacturers to make USB peripherals designed for PCs. Windows includes in-box class drivers for most of those devices. For devices that do not have an in-box class drivers, users can install the generic in-box Winusb.sys driver provided by Microsoft. If the driver is Winusb.sys, you can easily write accompanying apps that the user can use to talk to the device. In earlier versions of Windows, such apps were desktop apps that were written by using WinUSB Functions. In Windows 8.1, Windows Store apps can be written by using the new Windows.Devices.Usb namespace.

You can use Windows.Devices.Usb, if...

  • The device driver is the Microsoft-provided Winusb.sys driver. The namespace does not support vendor-supplied device drivers. When you plug in the device, Windows may or may not install that driver automatically, depending on the design of the device. If the driver is not installed automatically, you must do so manually in Device Manager.

    1. Right-click the device and select Update Driver Software....
    2. In the wizard, select Browse my computer for driver software.
    3. On the next page, select Let me pick from a list of device drivers on my computer.
    4. On the next page, from the list, select Universal Serial Bus devices.
    5. You should see WinUsb Device. Select it and click Next to install the driver.
  • You provide the information about your device as device capability declarations in the App Manifest. This allows the app to get associated with the device.

    For more information, see How to add USB device capabilities to the app manifest.

  • The device belongs to one of device classes supported by the namespace. Note that a custom device can belong to a pre-defined USB device class or its functionality could be defined by the vendor.

    Use the namespace for these USB device class, subclass, and protocol codes:

    • CDC control class (class code: 0x02, subclass code: any, protocol code: any)
    • Physical class (class code: 0x05, subclass code: any, protocol code: any)
    • PersonalHealthcare class (class code: 0x0f, subclass code: 0x00, protocol code: 0x00)
    • ActiveSync class (class code: 0xef, subclass code: 0x01, protocol code: 0x01)
    • PalmSync class (class code: 0xef, subclass code: 0x01, protocol code: 0x02)
    • DeviceFirmwareUpdate class (class code: 0xfe, subclass code: 0x01, protocol code: 0x01)
    • IrDA class (class code: 0, subclass code: 0x02, protocol code: 0x00)
    • Measurement class (class code: 0xfe, subclass code: 0x03, protocol code: any)
    • Vendor-specific class (class code: 0xff, subclass code: any, protocol code: any)

    Do not use the namespace for these USB device classes:

    Note  Instead, use other relevant APIs. These USB device classes are blocked by the namespace to prevent conflict with other APIs. For example, if your device conforms to HID protocol, use Windows.Devices.HumanInterfaceDevice.

     

    • Audio class (0x01)
    • HID class(0x03)
    • Image class (0x06)
    • Printer class (0x07)
    • Mass storage class (0x08)
    • Smart card class (0x0B)
    • Audio/video class (0x10)
    • Wireless controller (such as, wireless USB host/hub) (0xE0)

You should not use Windows.Devices.Usb, if...

  • Your app wants to access internal devices. Windows.Devices.Usb is designed for accessing peripheral devices. A Windows Store app can access internal USB devices only if it is a privileged app that is explicitly declared by the OEM for that system.
  • Your app is a Control Panel app. Apps using the namespace must be per-user apps. The app can communicate with the device but cannot save setting data outside its scope, a functionality required by many Control Panel apps.

The code examples in this topic show common tasks that your app can perform by using the namespace. The examples are in C#.

  • Connect to the USB device
  • Send control transfers
  • Get device information
  • Send or receive interrupt data
  • Send or receive bulk data
  • Change the interface alternate setting of the device
  • Step-by-step tutorial
  • Windows Store app sample for accessing USB devices

For more information about features and limitations, see these frequently asked questions.

Connect to the USB device

In your app, the first mandatory task is to search the device in the system by providing information that identifies the device, such as the hardware Id, a device interface GUID, or if you are searching by device class information, by providing device subclass or protocol codes. The search can return more than one device. The namespace has been designed to refine your search query with extra information so that the result set is smaller. From that set, you must select the device you want, obtain a reference to it, and open the device for communication.

This code example demonstrates important calls to search the device and connect to it.

 
    var deviceQueryString = UsbDevice.GetDeviceSelector(deviceVid, devicePid, deviceInterfaceClass);

    var myDevices = await Windows.Devices.Enumeration.DeviceInformation.FindAllAsync(deviceQueryString, null);
    
    UsbDevice device = await UsbDevice.FromIdAsync(myDevices[0].Id);

    // Device is in use.
    if (device != null)
    {
        MainPage.Current.NotifyUser("Device " + id + " opened", NotifyType.StatusMessage);
    }
    else
    {
        MainPage.Current.NotifyUser("Unable to open device : " + id, NotifyType.ErrorMessage);
    }

Send control transfers

An app sends several control transfers requests that can read (IN transfer) or write (OUT transfer) configuration information or perform device-specific functions defined by the hardware vendor. If the transfer performs a write operation, it's an OUT transfer; a read operation, it's an IN transfer. Regardless of the direction, your app always builds and initiates the request for the transfer.

This code example shows how to send control transfer that reads information from the device.

    async Task<IBuffer> SendVendorControlTransferInToDeviceRecipientAsync(Byte vendorCommand, UInt32 dataPacketLength)
    {
        // Data will be written to this buffer when we receive it

        var buffer = new Windows.Storage.Streams.Buffer(dataPacketLength);
        
        UsbSetupPacket setupPacket = new UsbSetupPacket
        {
            RequestType = new UsbControlRequestType
            {
                Direction = UsbTransferDirection.In,
                Recipient = UsbControlRecipient.Device.DefaultInterface,
                ControlTransferType = UsbControlTransferType.Vendor,
            },
            Request = vendorCommand,
            Length = dataPacketLength
        };

        return await device.SendControlInTransferAsync(initSetupPacket, buffer);
    }

Get device information

A USB device provides information about itself in data structures called USB descriptors. The namespace provides classes that you can use to get various USB descriptors by simply accessing property values.

This code example shows how to get the USB device descriptor.

public String GetDeviceDescriptorAsString()
{
    String content = null;
        
    var deviceDescriptor = DeviceList.Current.CurrentDevice.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;
}

Send or receive interrupt data

Your app can write or read data from interrupt endpoints. This involves registering an event handler. Each time the device generates an interrupt, data associated with the interrupt is read into the interrupt endpoint. At that time, the event handler is invoked and your app can access data.

This code example shows how to get interrupt data.

    void RegisterForInterruptEvent(UsbDevice CurrentDevice, 
                       UInt32 pipeIndex)
    {
        if (!registeredInterrupt)
        {
        // Search for the correct pipe that has the specified endpoint number
        var interruptInPipe = CurrentDevice.DefaultInterface.InterruptInPipes[(int) pipeIndex];

        registeredInterrupt = true;
        registeredInterruptPipeIndex = pipeIndex;

        TypedEventHandler<UsbInterruptInPipe, UsbInterruptInEventArgs> interruptEventHandler = 
            new TypedEventHandler<UsbInterruptInPipe, UsbInterruptInEventArgs>(this.OnGeneralInterruptEvent);

        interruptInPipe.DataReceived += interruptEventHandler;

        }
    }

    void UnregisterFromInterruptEvent(UsbDevice CurrentDevice)
    {
        if (registeredInterrupt)
        {
        // Search for the correct pipe that we know we used to register events
        var interruptInPipe =CurrentDevice.DefaultInterface.InterruptInPipes[(int)registeredInterruptPipeIndex];
        interruptInPipe.DataReceived -= interruptEventHandler;

        registeredInterrupt = false;
        }
    }

    async void OnGeneralInterruptEvent(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 rootPage.Dispatcher.RunAsync(
        CoreDispatcherPriority.Normal,
        new DispatchedHandler(() =>
        {
            MainPage.Current.NotifyUser(
            "Number of interrupt events received: " + numInterruptsReceived.ToString()
            + "\nReceived " + buffer.Length.ToString() + " bytes",
            NotifyType.StatusMessage);
        }));

    }

Send or receive bulk data

Your app can send or receive large amounts of data through bulk transfers. Bulk data can take long time to complete depending on the traffic on the bus. However, data delivery is guaranteed.

Your app can initiate these transfers and even modify the way the data buffer is sent or received by setting various policy properties.

By using the cancellationTokenSource, you can cancel pending requests. After those requests are canceled, the app receives an OperationCanceled exception. For more information, see Cancellation in managed threads.

This code example shows how to write data to the device by using bulk transfer.

    async void BulkWriteAsync(UsbDevice CurrentDevice, UInt32 bulkPipeIndex, UInt32 bytesToWrite)
    {
        var arrayBuffer = new Byte[bytesToWrite];

        var stream = CurrentDevice.DefaultInterface.BulkOutPipes[(int) bulkPipeIndex].OutputStream;

        //Initialize the buffer. Not shown.

        var writer = new DataWriter(stream);
        writer.WriteBytes(arrayBuffer);

        runningWriteTask = true;

        UInt32 bytesWritten = await writer.StoreAsync().AsTask(cancellationTokenSource.Token);

        runningWriteTask = false;

        totalBytesWritten += bytesWritten;
    }

Change the interface alternate setting of the device

USB devices are configured such that the endpoint buffers (that hold transfer data) are grouped in alternate settings. The device can have many settings but only one can be active at a time. Data transfers can take place to or from the endpoints of the active setting. If the app wants to use other endpoints, it can change the setting and endpoints of that setting become available for transfers.

This code example shows how to get the active setting and select an alternate setting.

    async void SetInterfaceSetting(UsbDevice CurrentDevice, Byte settingNumber)
    {

        var interfaceSetting = CurrentDevice.DefaultInterface.InterfaceSettings[settingNumber];

        await interfaceSetting.SelectSettingAsync();

        MainPage.Current.NotifyUser("Interface Setting is set to " + settingNumber, NotifyType.StatusMessage);

    }

    void GetInterfaceSetting(UsbDevice CurrentDevice)
    {
        var interfaceSettings = CurrentDevice.DefaultInterface.InterfaceSettings;

        foreach(UsbInterfaceSetting interfaceSetting in interfaceSettings)
        {
            if (interfaceSetting.Selected)
            {
                Byte interfaceSettingNumber = interfaceSetting.InterfaceDescriptor.AlternateSettingNumber;

                MainPage.Current.NotifyUser("Interface setting:" + interfaceSettingNumber.ToString("D", NumberFormatInfo.InvariantInfo), 
                NotifyType.StatusMessage);

                break;
            }
        }
    }

Step-by-step tutorial

For step-by-step instructions about using these APIs to perform common tasks for communicating with a USB device, see Talking to USB devices, start to finish (Windows Store app).

Windows Store app sample for accessing USB devices

Get started on writing a Windows Store app by studying these Windows Store app samples for USB.

.