Browse over 10,000 Electronics Projects

USB HID device development on the STM32 F042

USB HID device development on the STM32 F042

Custom HID interaction with the PC

A custom HID is, well, custom. The host has no idea how to process the data that’s being exchanged so you need to write application code on the PC to communicate with your device. Thankfully you don’t need to write a device driver so there are no installation steps that require admin rights on the user’s PC. If you’re producing a device for the corporate environment then this is one major headache avoided.

Interacting with a USB device on the PC is a little bit tricky if you’ve never had cause to work directly with hardware devices before. The good news is that once you’ve located your device you can interact with it through the usual CreateFile, ReadFile and WriteFile Win32 APIs and by extension that means you can also get at the device using C#. In these examples I’ll be using C++ so that we can see all the detail.

Find your device name

USB devices on the PC have special filenames that encode, amongst other things, the VID and PID codes. In my example code I am using VID 0xF055 and PID 0x7201. My device name will look like this:

\?hid#vid_f055&pid_7201#7&23588de&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}

Ugly huh? These special filenames are not meant to be seen by humans although you can piece together the filename from the strings visible in the device manager entry for your USB device. The correct way to get this name is to enumerate all the attached devices and query for the filename when you find your device.

Here’s a copy-and-pastable C++ class to do just that. It has dependencies on the venerable MFC CString class as well as the <stdint> header. Both of these dependencies are easily replaceable if you so wish.



Advertisement1


class UsbEnumerate {

public:
  CString _path;

public:
  UsbEnumerate(uint16_t vid,uint16_t pid);

  const CString& getPath() const;
};


/*
 * Get the path to the device or empty string if not found
 */

inline const CString& UsbEnumerate::getPath() const {
  return _path;
}


/*
 * Search for the device and set the path
 */

inline UsbEnumerate::UsbEnumerate(uint16_t vid,uint16_t pid) {

  HDEVINFO                         hDevInfo;
  SP_DEVICE_INTERFACE_DATA         DevIntfData;
  PSP_DEVICE_INTERFACE_DETAIL_DATA DevIntfDetailData;
  SP_DEVINFO_DATA                  DevData;
  DWORD dwSize,dwMemberIdx;
  TCHAR devid[100];
  GUID InterfaceClassGuid = GUID_DEVINTERFACE_HID;

  wsprintf(devid,_T("vid_%04hx&pid_%04hx"),vid,pid);

  // We will try to get device information set for all USB devices that have a
  // device interface and are currently present on the system (plugged in).

  hDevInfo=SetupDiGetClassDevs(&InterfaceClassGuid,NULL,0,DIGCF_DEVICEINTERFACE|DIGCF_PRESENT);

  if(hDevInfo!=INVALID_HANDLE_VALUE) {

    // Prepare to enumerate all device interfaces for the device information
    // set that we retrieved with SetupDiGetClassDevs(..)
    DevIntfData.cbSize=sizeof(SP_DEVICE_INTERFACE_DATA);
    dwMemberIdx=0;

    // Next, we will keep calling this SetupDiEnumDeviceInterfaces(..) until this
    // function causes GetLastError() to return  ERROR_NO_MORE_ITEMS. With each
    // call the dwMemberIdx value needs to be incremented to retrieve the next
    // device interface information.

    SetupDiEnumDeviceInterfaces(hDevInfo,NULL,&InterfaceClassGuid,dwMemberIdx,&DevIntfData);

    while(GetLastError()!=ERROR_NO_MORE_ITEMS) {

      // As a last step we will need to get some more details for each
      // of device interface information we are able to retrieve. This
      // device interface detail gives us the information we need to identify
      // the device (VID/PID), and decide if it's useful to us. It will also
      // provide a DEVINFO_DATA structure which we can use to know the serial
      // port name for a virtual com port.

      DevData.cbSize=sizeof(DevData);

      // Get the required buffer size. Call SetupDiGetDeviceInterfaceDetail with
      // a NULL DevIntfDetailData pointer, a DevIntfDetailDataSize
      // of zero, and a valid RequiredSize variable. In response to such a call,
      // this function returns the required buffer size at dwSize.

      SetupDiGetDeviceInterfaceDetail(hDevInfo,&DevIntfData,NULL,0,&dwSize,NULL);

      // Allocate memory for the DeviceInterfaceDetail struct

      DevIntfDetailData=(PSP_DEVICE_INTERFACE_DETAIL_DATA)HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,dwSize);
      DevIntfDetailData->cbSize=sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);

      if(SetupDiGetDeviceInterfaceDetail(hDevInfo,&DevIntfData,DevIntfDetailData,dwSize,&dwSize,&DevData)) {
        // Finally we can start checking if we've found a useable device,
        // by inspecting the DevIntfDetailData->DevicePath variable.
        // The DevicePath looks something like this:
        //
        // \?usb#vid_04d8&pid_0033#5&19f2438f&0&2#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
        //
        // As you can see it contains the VID/PID for the device, so we can check
        // for the right VID/PID with string handling routines.

        if(_tcsstr((TCHAR*)DevIntfDetailData->DevicePath,devid)!=NULL) {

          _path=DevIntfDetailData->DevicePath;
          HeapFree(GetProcessHeap(),0,DevIntfDetailData);
          break;
        }
      }

      HeapFree(GetProcessHeap(),0,DevIntfDetailData);

      // Continue looping
      SetupDiEnumDeviceInterfaces(hDevInfo,NULL,&InterfaceClassGuid,++dwMemberIdx,&DevIntfData);
    }

    SetupDiDestroyDeviceInfoList(hDevInfo);
  }
}

Make sure you add the following lines to your stdafx.h file so that you get the HID GUID declaration and the linker pulls in the .lib file for the setup API functions.

#include <initguid.h>
#include <hidclass.h>

#pragma comment(lib,"setupapi")

Here’s an example of how to use the UsbEnumerate class.

UsbEnumerate usb(0xF055,0x7201);
HANDLE deviceHandle;

if(usb.getPath().IsEmpty())
  MessageBox(_T("Cannot find USB device. Please ensure that it's switched on and connected to the PC"));
else {

  // open the device

  if((deviceHandle=CreateFile(
                      usb.getPath(),
                      GENERIC_READ | GENERIC_WRITE,
                      FILE_SHARE_READ | FILE_SHARE_WRITE,
                      NULL,
                      OPEN_EXISTING,
                      FILE_FLAG_OVERLAPPED,
                      NULL))==INVALID_HANDLE_VALUE) {
    MessageBox(_T("The USB device has been located but we failed to open it for reading"));
  }
}

Now that you’ve got a handle to the device you can start reading and writing data. I use overlapped asynchronous IO to talk to the device so that the application can do other things while waiting for IO to complete.

Pages: 1 2 3 4 5 6 7 8 9

 


Top