Click here to Skip to main content
13,596,321 members
Click here to Skip to main content
Add your own
alternative version

Stats

7.8K views
255 downloads
27 bookmarked
Posted 17 May 2018
Licenced CPOL

How to Communicate with its USB Devices using HID Protocol

, 18 May 2018
Rate this:
Please Sign up or sign in to vote.
This article will help you to understand how to communicate with the USB devices using WinAPI in C#

(You need Visual Studio 2017 to compile it, the executable is in USB_Communication\bin\Release.)

Example of use:

DEVICE_VID  = 1A2C;
DEVICE_PID  = 0001;
USAGE_PAGE  = FF00;
USAGE       = 0000;
REPORT_ID   = FF;

Do not add 0x, else the application will crash (I haven't added 0x prefix support)

Unfortunately, the write function can only send 1 byte (+ Report ID byte), it's because I was too lazy to add a fully functional text parse function ^^

But it's working, the most important thing, you can verify it via any USB sniffer (use your own values for that).

Introduction

This article shows you how to using the USB/HID protocol under Windows to be able to send/receive USB packets from any USB devices connected to the PC.

And without using DLL, just an application is needed.

Background

This article was possible with this WDK sample:

Basically, it's just a rewrite of this sample but in a simple form.

Using the Code

Declaration of variables, WinAPI functions and using: https://pastebin.com/MSRAYJ5b

All what you need is those functions:

HidD_GetHidGuid
SetupDiGetClassDevs
SetupDiEnumDeviceInterfaces
SetupDiGetDeviceInterfaceDetail
SetupDiGetDeviceInterfaceDetail
CreateFile
HidD_GetPreparsedData
HidD_GetAttributes
HidP_GetCaps
WriteFile

Assembled, it will look like this, the core of your program:

This function also detects if the device you target is disconnected to avoid unnecessary call of WinAPI functions.

void HID()
{
    HID_DEVICE[] pDevice = new HID_DEVICE[1];

    while (true)
    {
        if (nbrDevices != FindNumberDevices())
        {
            nbrDevices = FindNumberDevices();
            pDevice    = new HID_DEVICE[nbrDevices];
            FindKnownHidDevices(ref pDevice);

            var i = 0;
            while (i < nbrDevices)
            {
                var count = 0;

                if (pDevice[i].Attributes.VendorID  == DEVICE_VID)
                    count++;
                if (pDevice[i].Attributes.ProductID == DEVICE_PID)
                    count++;
                if (pDevice[i].Caps.UsagePage       == USAGE_PAGE)
                    count++;
                if (pDevice[i].Caps.Usage           == USAGE)
                    count++;

                if (count == 4)
                {
                    iHIDD = i;
                    isConnected = true;

                    break;
                }
                else
                    isConnected = false;

                i++;
            }
        }

        if (isConnected)
        {
            //Read(pDevice[iHIDD]);
            //Write(pDevice[iHIDD]);
        }
    }
}

As the function says, it returns the number of devices, not really their number, but the number of functions devices that are defined by the VID/PID, Usage Page, Usage and Report ID.

int FindNumberDevices()
{
    Guid                     hidGuid        = new Guid();
    SP_DEVICE_INTERFACE_DATA deviceInfoData = new SP_DEVICE_INTERFACE_DATA();
    int index = 0;

    HidD_GetHidGuid(ref hidGuid);

    //
    // Open a handle to the plug and play dev node.
    //
    SetupDiDestroyDeviceInfoList(hardwareDeviceInfo);
    hardwareDeviceInfo    = SetupDiGetClassDevs
    (ref hidGuid, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
    deviceInfoData.cbSize = Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DATA));

    index = 0;
    while (SetupDiEnumDeviceInterfaces
          (hardwareDeviceInfo, IntPtr.Zero, ref hidGuid, index, ref deviceInfoData))
    {
        index++;
    }

    return (index);
}

This function returns all the data structures for each USB device needed for ReadFile() and WriteFile().

int FindKnownHidDevices(ref HID_DEVICE[] HidDevices)
{
    int                             iHIDD;
    int                             RequiredLength;

    Guid                            hidGuid                 = new Guid();
    SP_DEVICE_INTERFACE_DATA        deviceInfoData          = new SP_DEVICE_INTERFACE_DATA();
    SP_DEVICE_INTERFACE_DETAIL_DATA functionClassDeviceData =
                                             new SP_DEVICE_INTERFACE_DETAIL_DATA();

    HidD_GetHidGuid(ref hidGuid);

    //
    // Open a handle to the plug and play dev node.
    //
    SetupDiDestroyDeviceInfoList(hardwareDeviceInfo);
    hardwareDeviceInfo    = SetupDiGetClassDevs
    (ref hidGuid, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
    deviceInfoData.cbSize = Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DATA));

    iHIDD = 0;
    while (SetupDiEnumDeviceInterfaces
    (hardwareDeviceInfo, IntPtr.Zero, ref hidGuid, iHIDD, ref deviceInfoData))
    {
        RequiredLength = 0;

        //
        // allocate a function class device data structure to receive the
        // goods about this particular device.
        //
        SetupDiGetDeviceInterfaceDetail(hardwareDeviceInfo,
        ref deviceInfoData, IntPtr.Zero, 0, ref RequiredLength, IntPtr.Zero);

        if (IntPtr.Size == 8)
            functionClassDeviceData.cbSize = 8;
        else if (IntPtr.Size == 4)
            functionClassDeviceData.cbSize = 5;

        //
        // Retrieve the information from Plug and Play.
        //
        SetupDiGetDeviceInterfaceDetail(hardwareDeviceInfo, ref deviceInfoData,
        ref functionClassDeviceData, RequiredLength, ref RequiredLength, IntPtr.Zero);

        //
        // Open device with just generic query abilities to begin with
        //
        OpenHidDevice(functionClassDeviceData.DevicePath, ref HidDevices, iHIDD);

        iHIDD++;
    }

    return iHIDD;
}

This function depends on FindKnownHidDevices().

void OpenHidDevice(string DevicePath, ref HID_DEVICE[] HidDevice, int iHIDD)
{
    /*++
    RoutineDescription:
    Given the HardwareDeviceInfo, representing a handle to the plug and
    play information, and deviceInfoData, representing a specific hid device,
    open that device and fill in all the relevant information in the given
    HID_DEVICE structure.
    --*/

    HidDevice[iHIDD].DevicePath = DevicePath;

    //
    //  The hid.dll api's do not pass the overlapped structure into deviceiocontrol
    //  so to use them we must have a non overlapped device.  If the request is for
    //  an overlapped device we will close the device below and get a handle to an
    //  overlapped device
    //
    CloseHandle(HidDevice[iHIDD].HidDevice);
    HidDevice[iHIDD].HidDevice  = CreateFile(HidDevice[iHIDD].DevicePath,
    GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ |
    FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, IntPtr.Zero);
    HidDevice[iHIDD].Caps       = new HIDP_CAPS();
    HidDevice[iHIDD].Attributes = new HIDD_ATTRIBUTES();

    //
    // If the device was not opened as overlapped, then fill in the rest of the
    //  HidDevice structure.  However, if opened as overlapped, this handle cannot
    //  be used in the calls to the HidD_ exported functions since each of these
    //  functions does synchronous I/O.
    //
    HidD_FreePreparsedData(ref HidDevice[iHIDD].Ppd);
    HidDevice[iHIDD].Ppd = IntPtr.Zero;
    HidD_GetPreparsedData(HidDevice[iHIDD].HidDevice, ref HidDevice[iHIDD].Ppd);
    HidD_GetAttributes(HidDevice[iHIDD].HidDevice, ref HidDevice[iHIDD].Attributes);
    HidP_GetCaps(HidDevice[iHIDD].Ppd, ref HidDevice[iHIDD].Caps);

    //MessageBox.Show(GetLastError().ToString());

    //
    // At this point the client has a choice.  It may chose to look at the
    // Usage and Page of the top level collection found in the HIDP_CAPS
    // structure.  In this way --------*it could just use the usages it knows about.
    // If either HidP_GetUsages or HidP_GetUsageValue return an error then
    // that particular usage does not exist in the report.
    // This is most likely the preferred method as the application can only
    // use usages of which it already knows.
    // In this case the app need not even call GetButtonCaps or GetValueCaps.
    //
    // In this example, however, we will call FillDeviceInfo to look for all
    //    of the usages in the device.
    //
    //FillDeviceInfo(ref HidDevice);
}

Then come the two important functions that will make you able to read or write USB packets between an USB device and the PC.

void Write(HID_DEVICE HidDevice) // Read the Report[] and
                                 // send it to the USB device targeted by HidDevice.HidDevice
{
    byte[] Report = new byte[HidDevice.Caps.OutputReportByteLength];
    uint   tmp = 0;

    Report[0] = REPORT_ID;

    WriteFile(HidDevice.HidDevice, Report,
              HidDevice.Caps.OutputReportByteLength, ref tmp, IntPtr.Zero);
}

void Read(HID_DEVICE HidDevice) // Read what the USB device has sent
                                // to the PC and store the result into Report[]
{
    byte[] Report = new byte[HidDevice.Caps.InputReportByteLength];
    uint   tmp = 0;

    Report[0] = REPORT_ID;

    ReadFile(HidDevice.HidDevice, Report,
             HidDevice.Caps.InputReportByteLength, ref tmp, IntPtr.Zero);
}

To be able to do that, you need to set before calling Write() or Read():

  • DEVICE_VID
  • DEVICE_PID
  • USAGE_PAGE
  • USAGE
  • REPORT_ID

But be careful, you need to set the correct values for all those parameters, if one is false, you will not be able to read or write USB packets.

Also, you cannot read if the device cannot send data and you cannot write if the device cannot read data.

A device is defined by its DEVICE_VID/DEVICE_PID but shrunk into several functions defined by its USAGE_PAGE, USAGE and REPORT_ID.

As an example, the first function of a mouse is to send data, so you can read data from PC and the second function is to receive data, so you can send data from PC.

But also a function can read and send data, but it depends on the HID descriptor.

And to set those variables, you need to read the HID Descriptor of the USB devices that you target, it can be retrieved with a USB sniffer as https://github.com/djpnewton/busdog.

The HID Descriptor usually begins with 0x05, 0x01.

And to learn to read HID Descriptor, use this tool: http://www.usb.org/developers/hidpage#HID Descriptor Tool

Because this code is just a rewrite of an old C code from the 90s, it works on all Windows Versions.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

irakyu
France France
No Biography provided

You may also be interested in...

Pro
Pro

Comments and Discussions

 
QuestionLooks very good... Pin
0x01AA19-May-18 4:47
professional0x01AA19-May-18 4:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web01-2016 | 2.8.180621.3 | Last Updated 18 May 2018
Article Copyright 2018 by irakyu
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid