Sample Image - maximum width is 600 pixels

Introduction

This work describes a HID C++ class, planned to be used in electronics / robotics.
HID (Human Interface Devices) is very complex and even the simplest thing will be difficult to implement.

This is the reason I have developed the CUsbHidIO C++ class. This work makes it much more simple for a developer in electronics or robotics, without much knowledge about software development, the using of any HID gamepad or joystick available in market (almost 100% USB gamepads) like a computer interface for either analogical or digital input signals. It is also possible, thanks to HID Led UsagePage, to use them as digital output control signals.

This work is an extract of the project I named: AINECC ROBOTDOG, and you can get further information on my personal web site, including how to built the electronic board that emulates a gamepad device with use of 10 analog input signals, 1 digital input signal and 16 output signals to robot control, but you don’t need to use this board, just get a gamepad, open it, connect its potentiometers to your robot sensors and its buttons to your robot detectors and get to work.

To Take into Account

There is something that you have to take into account:

  • Some Joysticks or gamepads only send its analogical values or button states, just after something has changed. If nothing changes (no button pressed or axis moved), then no report (no values) is send. This restriction must be considered especially if you have planned to work in robotics and you need readings just after switching on the robot.
  • The CUsbHidIO class, makes heavy internal use of “handles” and “memory allocation”, so try do not run pointers. It is better using mypointer[i] than mypointer++.
  • Be careful when using this code because a lot of parameters were discarded in order to get the functionality as much simple as possible.

Using the Code

The Files

As I referred before, the CUsbHidIO class was planned to be used in robotics. The way to use it is so simple. There are only 2 or 3 steps that you will need to obtain readings in either button state or analogical values. It is also possible to switch on any HID device LED, so that you can control robot drivers.

The main code has 3(4) C++ files (xx.cpp):

  • RobotDogD1.cpp: This is the main application source file that contains the application. There is nothing inside but the command to launch the main dialog, but if your project is getting more complex (more dialog box) you will need it.
  • UsbHidIO.cpp. This is the source file that contains the CUsbHidIO class. This code includes every function that is going to be needed to make HID interface so simple. You should not need to modify anything of it, just keep it like that, but if you see something wrong let me know.
  • RobotDogD1DLG.cpp. This file contains the sample code about how to use the CUsbHidIO class.

The Class CUsbHidIO

CUsbHidIO class has the following methods:

  • GetHIDCollectionDevices()
  • GetHIDValueCaps()
  • GetHIDButtonCaps()
  • GetHIDUsagesValues()
  • GetHIDButtonState()
  • SetHIDLEDState()
  • SendHIDReport()

The List of Devices: GetHIDCollectionDevices()

CUsbHidIO::GetHIDCollectionDevices()

This is the most important function. All the parameters are output ones. This is the first function that you need to run. When running, it scans your computer searching for all the HID devices plugged into it. Every time that a new HID device is found, all their important information is stored in 3 different arrays.

DWORD GetHIDCollectionDevices(
	USHORT &NumHIDDevices, 
	PSP_DEVICE_INTERFACE_DETAIL_DATA aDetailData[RD_MAXHIDDEVICES], 
	PHIDD_ATTRIBUTES aAttributes,
	HIDP_CAPS aNumValueCaps[RD_MAXHIDDEVICES]); 
aDetailData[index]

This array contains every internal path to every device data available and plugged into the computer. This “path” identifies every one of the HID device (keyboard, mouse, joysticks, gamepad,…). The path will be the same even if the device is unplugged and plug it back later on. The path obtained will be used by the rest of the functions.

aAttributes[index]

This array contains every VendorID and ProductID of every HID device. Since the position in the array is the same, these parameters can be used to obtain from aDetailData the “path” of the VendorID-ProductID device that we are looking for.

aNumValueCaps

Thanks to this array, we can get a lot of information from each one of the devices such as the number of analog values, buttons or LED that it has implemented.

Getting the Capabilities of the Devices: GetHIDValueCaps()

CUsbHidIO:: GetHIDValueCaps()

This function is used to get all the capabilities and data about all the analog values that are implemented in one of the devices identified before with the function
CUsbHidIO::GetHIDCollectionDevices and that were stored in the array aDetailData[index]. Actually, this function is calling the routine HidP_GetValueCaps().

DWORD GetHIDValueCaps (
	PSP_DEVICE_INTERFACE_DETAIL_DATA aDetailData,
	HIDP_REPORT_TYPE ReportType,
	PHIDP_VALUE_CAPS ValueCaps, USHORT &ValueCapsLength,
	HANDLE DeviceHandle);

DWORD GetHIDButtonCaps (
	PSP_DEVICE_INTERFACE_DETAIL_DATA aDetailData,
	HIDP_REPORT_TYPE ReportType,
	PHIDP_BUTTON_CAPS ButtonCaps, USHORT &ButtonCapsLength,
	HANDLE DeviceHandle); 
aDetailData

This parameter must be set to point to an only one of the devices detected before.

ReportType

Specifies a HIDP_REPORT_TYPE enumerator value that identifies the report type. It is usually set to HidP_Input.

ValueCaps

Pointer to a caller-allocated buffer (an array) in which the function returns the list of HIDP_VALUE_CAPS structures implemented and readable in the device. UsagePage, Usage, LogicalMin, LogicalMax, etc. are data included in this structure that will provide good information about each one of the analog values implemented into the device.

ValueCapsLength

The number of elements (structures) added into the array/buffer of ValueCaps.

DeviceHandle

This parameter could be NULL or could be the handle provided for CreateFile() function when opening the Device(aDetailData.path). If you set the value NULL, the handle is created inside, used, and destroyed before leaving the function. Creating the Handle outside the function will be better for system.

CUsbHidIO::GetHIDButtonCaps()

Quite similar to CUsbHidIO:: GetHIDValueCaps but for Buttons (digital inputs).

Reading Inputs: Axis, Values, Buttons: GetHIDUsagesValues(), GetHIDButtonState()

CUsbHidIO:: GetHIDUsagesValues()

This function is used to capture, at the same time, all the analog values (Axis) that are implemented in one of the devices identified before and that were stored in the array aDetailData[index]. Actually this function is calling the routine HidP_GetUsageValue() with ValueCapsLength analog values.

aDetailData, ReportType, ValueCaps, and ValueCapsLength are parameters captured by GetHIDCollectionDevices() and GetHIDValueCaps().

DWORD GetHIDUsagesValues (
	PSP_DEVICE_INTERFACE_DETAIL_DATA aDetailData,
	HIDP_REPORT_TYPE ReportType,
	PHIDP_VALUE_CAPS ValueCaps, USHORT ValueCapsLength,
	PULONG UsageValue,DWORD WaitForMsc, __timeb64* pAdquiredAt,
	HANDLE ReadHandle);
DWORD GetHIDButtonState (
	PSP_DEVICE_INTERFACE_DETAIL_DATA aDetailData,
	HIDP_REPORT_TYPE ReportType,
	USAGE UsagePage, PUSAGE UsageList, PULONG UsageLength,
	DWORD WaitForMsc, __timeb64* AdquiredAt,
	HANDLE ReadHandle);
UsageValue

Pointer to a caller-allocated buffer (an array of ValueCapsLength elements) in which the function returns a list of analog values captured from the device analog inputs.

WaitForMsc

Every analog value is read from reports that usually are sent for device continuously, but some devices only send reports if at least one value changes. If nothing changes, no report will be sent. This could be a problem so we have two different possibilities to minimize it. The first one is waiting for the report. This value for waiting is set in milliseconds by WaitForMsc. The second possibility is to create a different thread for the reading.

pAdquiredAt

If the function returns a good reading, then they will came with the time when they will get them. If the function does not return HIDP_STATUS_SUCCESS the parameter pAdquiredAt must not be read.

CUsbHidIO:: GetHIDButtonState()

This function is quite similar to GetHIDUsagesValues(). This is a call to the routine HidP_GetUsages().

UsageList

Pointer to a caller-allocated buffer that the function uses to return the usages of all buttons that are set to ON and belong to the usage page specified by UsagePage parameter.

Setting Outputs to ON/OFF: SetHIDLEDState(), SendHIDReport()

CUsbHidIO:: SetHIDLEDState()

This function is used to set the LEDs state to ON or OFF. Although DirectInput control (Force Feedback, Rumble) is typically the way used in Joysticks for Motor Control, there is another way for motor control, using HID routines about LEDs control. It doesn't matter if you are going to switch ON a LED or a Motor, it is just a matter of power. Actually, this function calls the routine HidP_SetUsages().

DWORD SetHIDLEDState (
	PSP_DEVICE_INTERFACE_DETAIL_DATA aDetailData,
	HIDP_REPORT_TYPE ReportType,
	USAGE UsagePage, PUSAGE UsageList, PULONG UsageLength,
	HANDLE WriteHandle);

DWORD SendHIDReport (
	PSP_DEVICE_INTERFACE_DETAIL_DATA aDetailData,
	PCHAR pReport,
	HANDLE tmpWriteHandle); 
UsageList

Pointer to a caller-allocated buffer that the function uses to set the usages that must be set to ON and belongs to the usage page specified by UsagePage parameter. If UsagePage is set to 0x08, then Usages makes reference to LEDs.

ReportType

In this function, it is usually set to HidP_Output.

CUsbHidIO:: SendHIDReport ()

This function is used to set a raw “report” to the device. Actually, this function calls the routine WriteFile(). The report will be truncated inside the routine to the length of the parameter “OutputReportByteLength”.

The Application Sample: RobotDogD1DLG.cpp

This section of the code was developed under MFC (Microsoft Foundation Class), this allows more simplicity of code.

The picture below shows the main dialog.

Reading Axis (Analog values)

Steps

1st - Getting the list of devices

Click on button [Load HID List].

To get the list of the devices plugged into the system, we need to call GetHIDCollectionDevices(). This can be done only once, just on loading the application, but I add Main_OnDeviceChange() to get the possibility of updating the list just after plugging or unplugging any HID device into the system.

2nd - Getting the device capabilities

Click on button [Read Value] and it will be called the method .GetHIDValueCaps().

This can be done just one time, just after selecting your device.

This sample uses manual device selection by clicking the mouse over one of the devices listed in the "HID Devices" ListBox, but usually you should select it automatically by comparing VendorID/ProductID.

3rd - Reading the values

Click on button [Read Value].

In this example, we made a call to the method .GetHIDUsagesValues(), but it could be better if you implement this method inside a different thread and even more accurate if you create your own handle to the device selected.

Sample Image - maximum width is 600 pixels

// allocate memory for the list of capabilities
mValueCaps = (PHIDP_VALUE_CAPS)calloc (MyDevCaps.NumberInputValueCaps, 
		sizeof (HIDP_VALUE_CAPS));

// calling the class to get all the capabilities of the device selected
Result = mUsbHidIO.GetHIDValueCaps (MyDevPath ,HidP_Input,mValueCaps,numValues,NULL);

// allocate memory for the list of values
	aUsageValue =(PULONG)calloc (numValues, sizeof (ULONG));
	
// calling the class to get all the values of the device selected
	Result = mUsbHidIO.GetHIDUsagesValues (MyDevPath, HidP_Input,
		 mValueCaps, numValues, aUsageValue, WaitForMsc, &AdquiredAt,NULL);

	if (Result == HIDP_STATUS_SUCCESS)
	{
		ctime_s( timeline, 26, & (AdquiredAt.time ) );
		csAdquiredAt = _T(timeline);
		csMilliseconds.Format("%u", AdquiredAt.millitm );
	}
	else
	{
		csAdquiredAt = _T("-:-");
		csMilliseconds = _T("-");
	}

The rest of the code is quite similar, contact me if you have any doubts.

References

Jan Axelson is a very good reference and much better and "solid" code. There is also a good example in the Windows DDK code source sample: "HCLIENT".

History

  • First revision
推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"