How to work with WinUSB?

cp.engr picture cp.engr · Aug 12, 2016 · Viewed 9k times · Source

This is a follow-up to my previous question, Need to write driver for USB peripheral device?

Background

I'm working on designing a USB peripheral using an STM32 microcontroller (bare metal / no OS). The device will occasionally be connected to a Windows PC, and transfer a few KB of data in each direction. There will be a custom PC application that controls the data transfers, using a proprietary protocol (i.e. for the USB payloads).

The PC will always be the master (initiator) - it'll send commands, and the device will issue responses, with up to a few hundred bytes of data going in either direction in a single command or response. I think I'll want to use USB Bulk Transfer mode.

Option 1 - USB CDC

From what I understand, one option is I could use the USB Communications Device Class (CDC). On the device side, I could use sample code from ST for the USB CDC, e.g. from STM32Cube. On the PC side, the device would present as a Virtual COM Port (VCP). Then in software I'd basically have a raw, bidirectional stream, on top of which I'd have to define my messsage formats, commands, etc.

  1. Have I explained this correctly?

Option 2 - WinUSB

I'm having trouble wrapping my mind around exactly what this is, and how to work with it.

  1. What is the relationship of WinUSB to USB device classes? It seems to function as a "generic" USB class, but I can't find any documentation that spells it out as such.

  2. Does WinUSB provide any built-in message delimiters? e.g. Does WinUsb_WritePipe send the contents of the buffer down to the device as an atomic unit? Or do I just get raw streams like a VCP/UART?

  3. How does one implement WinUSB on the device? Is there sample code available? (Preferably for STM32.)

Choosing

  1. What are the relevant considerations in choosing between options 1 and 2 for my application?

Answer

Mark Martin picture Mark Martin · Feb 18, 2018

WinUSB consists of two parts:

  • WinUsb.sys is a kernel-mode driver that can be installed as either a filter or function driver, above the protocol drivers in a USB device’s kernel-mode device stack.
  • WinUsb.dll is a user-mode DLL that exposes the WinUSB API. Applications can use this API to communicate with WinUsb.sys when it is installed as a device’s function driver. WinUSB API—exposed by WinUSB.dll. WinUSB is included in the Windows Driver Kit (WDK) in the form of a co-installer package, WinUSBCoInstaller.dll, located in WinDDK\BuildNumber\Redist\Winusb.

To use the WinUSB API in an application:

  • Include WinUsb.h
  • Add WinUsb.lib to the list of libraries that are linked to your application.
  • Usb100.h contains declarations for some useful macros.
  • Use the device interface GUID to obtain the device path. The correct GUID is the one that you specified in the INF that was used to install WinUsb.sys.
  • Get a handle to the device information set by passing the device interface GUID that you defined in the INF to SetupDiGetClassDevs. The function returns an HDEVINFO handle.
  • Call SetupDiEnumDeviceInterfaces to enumerate the system’s device interfaces and obtain information on your device interface.
  • Call SetupDiGetDeviceInterfaceDetail to get detailed data for the device interface.
  • Call the GetDevicePath function to obtain the device path.
  • Pass the device path to CreateFile to obtain a file handle for the device. Use ReadFile and WriteFile to communicate with device.
  • Pass the file handle to WinUsb_Initialize to initialize WinUSB and obtain a WinUSB handle. You use the device’s WinUSB handle to identify the device when you call WinUSB API functions, not the device’s file handle.

For more advanced solutions - use functions:

  • WinUsb_QueryDeviceInformation to obtain the device’s speed.
  • WinUsb_QueryInterfaceSettings to obtain the corresponding interface descriptors. The WinUSB handle corresponds to the first interface.
  • WinUsb_QueryPipe gets information about each endpoint.
  • WinUsb_WritePipe writes the buffer to the device - default behavior: zero-length writes are forwarded down the stack. If the transfer length is greater than a maximum transfer length, WinUSB divides the request into smaller requests of maximum transfer length and submits them serially.
  • more functions and info: http://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/winusb_howto.docx

For debugging purposes You probably need: winusbtrace_tool https://blogs.msdn.microsoft.com/usbcoreblog/2010/02/05/how-to-generate-and-view-a-winusb-debug-trace-log/; Wireshark https://www.wireshark.org with USBPcap plugin.

Other Example: http://searchingforbit.blogspot.com/2012/04/winusb-communication-with-stm32-part-1.html. Sample template comes with Visual Studio.

You need also have knowledge of writing .inf files.

Another easy way to communicate with USB - libusb-win32 https://sourceforge.net/projects/libusb-win32/

My simple example console application sends small (for keep-alive) chunks of data to device:

#include "stdafx.h"
#include <SetupAPI.h>
#include <Hidsdi.h> 
#include <devguid.h> 
#include <winusb.h>
#include <usb.h>
#pragma comment(lib, "hid.lib")
#pragma comment(lib, "setupapi.lib")
#pragma comment(lib, "winusb.lib")
#include <iUString.h> 


iString<char> DevicePath;
bool                    WinusbHandle_Open=false;
bool                    DeviceHandle_Open = false;
WINUSB_INTERFACE_HANDLE WinusbHandle;
HANDLE                  DeviceHandle;
UCHAR usb_out_buffer[64];
DEFINE_GUID(GUID_DEVCLASS_WINUSB, 0x88bae032L, 0x5a81, 0x49f0, 0xbc, 0x3d, 0xa4, 0xff, 0x13, 0x82, 0x16, 0xd6);
DEFINE_GUID(GUID_DEVCLASS_STL, 0xf177724dL, 0x74d3, 0x430e, 0x86, 0xb5, 0xf0, 0x36, 0x89, 0x10, 0xeb, 0x23);
GUID winusb_guid;
GUID stl_guid;

bool connectusb();
void  disconnectusb();




int main()
{
    DWORD n;
    DWORD   numEvents;
    HANDLE rHnd;    

WinusbHandle_Open = false;
DeviceHandle_Open = false;
winusb_guid = GUID_DEVCLASS_WINUSB;
stl_guid = GUID_DEVCLASS_STL;
usb_out_buffer[0] = 0;
usb_out_buffer[1] = 1;
usb_out_buffer[2] = 2;
usb_out_buffer[3] = 3;

ULONG bytesWritten;
ULONG timeout;
timeout = 100;
rHnd = GetStdHandle(STD_INPUT_HANDLE);

WinUsb_SetPipePolicy(WinusbHandle, 0x01, PIPE_TRANSFER_TIMEOUT, sizeof(ULONG), &timeout);

timeout = TRUE;
WinUsb_SetPipePolicy(WinusbHandle, 0x01, AUTO_CLEAR_STALL, sizeof(ULONG), &timeout);


timeout = TRUE;
WinUsb_SetPipePolicy(WinusbHandle, 0x01, RAW_IO, sizeof(ULONG), &timeout);//Bypasses queuing and error handling to boost performance for multiple read requests.


while (true)
{
if ((!WinusbHandle_Open) || (!WinusbHandle_Open)) { if (!connectusb())Sleep(2000); }
if ((!WinusbHandle_Open) || (!WinusbHandle_Open))continue;

bytesWritten = 0;
if (!WinUsb_WritePipe(WinusbHandle, 0x01, &usb_out_buffer[0], 2, &bytesWritten, NULL))
{
    n = GetLastError();
disconnectusb();
}
Sleep(2000);
}
disconnectusb();
return 0;
}




bool connectusb()
{
    BOOL                             bResult = FALSE;
    HDEVINFO                         deviceInfo;
    SP_DEVICE_INTERFACE_DATA         interfaceData;
    PSP_DEVICE_INTERFACE_DETAIL_DATA detailData = NULL;
    DWORD n;
    SP_DEVINFO_DATA devinfo;
    BYTE devdetailbuffer[4096];
    bool found;

    deviceInfo = SetupDiGetClassDevs(&stl_guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
    if (deviceInfo == INVALID_HANDLE_VALUE) { return false; }

    found = false;
    for (n = 0;; n++)
    {

        interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);

        if (!SetupDiEnumDeviceInterfaces(deviceInfo, NULL, &stl_guid, n, &interfaceData))
        {
            n = GetLastError();
            break;
        }




        detailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)devdetailbuffer;
        detailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
        devinfo.cbSize = sizeof(devinfo);
        if (!SetupDiGetDeviceInterfaceDetail(deviceInfo, &interfaceData, detailData, sizeof(devdetailbuffer), NULL, &devinfo)) { printf("SetupDiGetDeviceInterfaceDetail: %u\n", GetLastError()); break; }
        if (IsEqualGUID(devinfo.ClassGuid, winusb_guid))
        {
            if ((-1 != iStrPos(detailData->DevicePath, "VID_0483")) || (-1 != iStrPos(detailData->DevicePath, "vid_0483")))
            {
                if ((-1 != iStrPos(detailData->DevicePath, "PID_576B")) || (-1 != iStrPos(detailData->DevicePath, "pid_576b")))
                {

                    DevicePath = detailData->DevicePath;
                    found = true;
                    break;
                }
            }
        }
    }



SetupDiDestroyDeviceInfoList(deviceInfo);
if (!found)return false;


DeviceHandle = CreateFile(DevicePath.Buffer() ,
    GENERIC_WRITE | GENERIC_READ,
    FILE_SHARE_WRITE | FILE_SHARE_READ,
    NULL,
    OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
    NULL);

if (INVALID_HANDLE_VALUE == DeviceHandle) {
    n = GetLastError();
}

if (INVALID_HANDLE_VALUE == DeviceHandle) return false;
DeviceHandle_Open = true;



if (!WinUsb_Initialize(DeviceHandle, &WinusbHandle))
 {
     n = GetLastError();
     CloseHandle(DeviceHandle); DeviceHandle_Open = false;
     return false;
 }



WinusbHandle_Open = true;
return true;
}

void  disconnectusb()
{
    if (WinusbHandle_Open) { WinUsb_Free(WinusbHandle); WinusbHandle_Open = false; }
    if (DeviceHandle_Open) { CloseHandle(DeviceHandle); DeviceHandle_Open = false; }
}