Microchip’s PIC18 USB HID example Win32 Host Software (native c++)

In a previous post I covered the coding of the host side for Microchip’s PIC18 USB HID demo and the implementation of the demo itself on custom-desgined hardware (you can read it here). Even though Microchip provides the host source code for windows, such example is outdated and available only in managed .net code  (c++, c# and VB), which took me several hours to port to .net 4.0. Furthermore, despite the fact that the managed source actually uses pinvoke calls to Win32 functions, it is somewhat difficult to get it working on a native c++ implementation.

Based on the official Microchip example and the Microsoft documentation for the HID API of the WDK, I came up with a working  host application for the USB HID firmware for PIC18 devices, hoping it will be useful for other people.

The hardware

Some things have changed since I wrote the last tutorial on PIC HID devices. First of all, Microchip’s  USB Framework does not exist anymore and has been replaced by the Microchip Librares for Application (MLA, http://www.microchip.com/pagehandler/en-us/devtools/mla/home.html), which now uses MPLAB X (an IDE based on the NetBeans project) and the XC compilers.

You have to download and install the MLA and look for the HID USB device example (<mla root>/apps/usb/device/hid_custom). Build it for the PICDEM_FSUSB configuration.

ATTENTION: As for the date, there is a bug with the 1.34 version of the XC8 compiler that causes the build for the PICDEM_FSUSB configuration to fail. You can use version 1.33.

If you do not own a PICDEM™ FSUSB demo board, you can, UNDER YOUR OWN RISK build a compatible one following these schematics:

Schematics for the USB HID example
Schematics for the USB HID example

You’ll notice that the push button has a pull-up resistor and the demo board actually has a pull-down. You can easily change this if you like (or consider it on the software).

The software

 Device Initialization

The first thing to do is to get a list of all the HID devices, that is, with the GUID 4D1E55B2-F16F-11CF-88CB-001111000030.


GUID HIDClassGuid = { 0x4d1e55b2, 0xf16f, 0x11cf, 0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 };

//Enumerate devices from HID class

unsigned int index = 0;
unsigned int lastError;
DWORD registryType = 0;
DWORD registrySize = 0;
DWORD registrySize2 = 0;
DWORD interfaceInfoSize;
BYTE buffer[1024];
wchar_t tempString[4096];

//DIGCF_PRESENT Return only devices that are currently present in a system.
//DIGCF_DEVICEINTERFACEReturn devices that support device interfaces for the specified device interface classes
deviceTable= SetupDiGetClassDevs(&HIDClassGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);

Then we will search amongst these devices for the one we are interested in, which, for the Microchip Demo is the one with VID 0x04D and PID 0x003. To do so we extract the information of each device on the list and then compare its ID string with the one we are looking for. You’ll notice that the device will be listed as HID\\VID_04D8&PID_003F&REV_XXXX where XXXX is the number of revision which will depend on the firmware version you are using (for instance it is 0002 for the USB framework and 0100 for the MLA). To avoid incompatibilities with previous/future versions we’ll ignore the revision part using the substring function and look only for  HID\\VID_04D8&PID_003F. Finally we associate the device with both a Read and a Write file handle to perform stantard ReadFile() WriteFile() operations on them.


while (index < MAX_USB_DEVICES)
{
if (SetupDiEnumDeviceInterfaces(deviceTable, NULL, &HIDClassGuid, index, &deviceData))
{
lastError = GetLastError();
if (lastError == ERROR_NO_MORE_ITEMS)
{
SetupDiDestroyDeviceInfoList(&deviceTable);
PrintError("The device was not on the list", true);
break;
}

}
else //unknown error
{
SetupDiDestroyDeviceInfoList(&deviceTable);
PrintError("something went wrong when looking for the device", true);
}

//get device info ID string
deviceInfo.cbSize = sizeof(SP_DEVINFO_DATA);
SetupDiEnumDeviceInfo(deviceTable, index, &deviceInfo);
//we call this function twice, one to get the necessary buffer size and the second to get the actual ID info
SetupDiGetDeviceRegistryProperty(deviceTable, &deviceInfo, SPDRP_HARDWAREID, &registryType, NULL, 0, &registrySize);
SetupDiGetDeviceRegistryProperty(deviceTable, &deviceInfo, SPDRP_HARDWAREID, &registryType, buffer, registrySize, &registrySize2);
lstrcpy(tempString, (LPCWSTR)buffer);
if (lstrcmp(wstring(tempString).substr(0,21).c_str(), VID_PID) == 0)
{
//We also call this function twice, to get the size and then the actual info
SetupDiGetDeviceInterfaceDetail(deviceTable, &deviceData, NULL, 0, &interfaceInfoSize, NULL);

deviceInterfaceDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(interfaceInfoSize);
if (deviceInterfaceDetail)
{
deviceInterfaceDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
ZeroMemory(deviceInterfaceDetail->DevicePath, sizeof(deviceInterfaceDetail->DevicePath));
}
if (SetupDiGetDeviceInterfaceDetail(deviceTable, &deviceData, deviceInterfaceDetail, interfaceInfoSize, NULL, NULL))
{
SetupDiDestroyDeviceInfoList(deviceTable);
USBReadHandle = CreateFile(deviceInterfaceDetail->DevicePath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (USBReadHandle == INVALID_HANDLE_VALUE) PrintError("Error opening usb reading handle. Closing...", true);
USBWriteHandle = CreateFile(deviceInterfaceDetail->DevicePath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (!USBWriteHandle || USBWriteHandle == INVALID_HANDLE_VALUE) PrintError("Error opening usb write handle. Closing...", true);

break;

}

}

index++;
}

Read/Write opeartions Read and Write operations must use byte arrays of size 65. As stated in the firmware, byte 0 is always set to 0.


//This function writes a byte buffer with the specified command ID to the HID device
void WriteToUSB(byte commandID)
{
 //verify that the handle has already been created
 if (USBWriteHandle)
 {
 byte buffer[65];
 DWORD bytesWritten;
 buffer[0] = 0; //Report ID set to 0 always
 buffer[1] = commandID;
 WriteFile(USBWriteHandle, buffer, 65, &bytesWritten, NULL);
 //if no bytes were written then something went wrong
 if (bytesWritten <= 0)
 PrintError("Error writing to USB", false);
 }
}
//Reads a byte buffer from the HID device and stores it on the provided buffer
void ReadFromUSB(byte buffer[65])
{
 //verify that the handle has already been created
 if (USBReadHandle)
 {
 DWORD readBytes;
 ReadFile(USBReadHandle, buffer, 65, &readBytes, NULL);
 //if no bytes were read then something went wrong
 if (readBytes <= 0)
 PrintError("Error reading from USB", false);
 }
}

Usage

Your main function should read and write the appropriate values to communicate with the HID device.

Get POT :  write 0x37 and read response
Get Button state :  write 0x81 and read response (NOTE: if you are using a demo board you should interpret a 1 as a button not pressed).
Toggle LED: write 0x80


//Read POT. Response is little endian
WriteToUSB(0x37);
ReadFromUSB(response);

//Get button state
WriteToUSB(0x81);
ReadFromUSB(response);
if (response[2])
printf("Button is pressed \n");
else
printf("Button is NOT pressed \n ");

//toggle LED
WriteToUSB(0x80);

You can download the full code for this example as a Visual Studio 2013 solution from: here

Watch it working here: http://youtu.be/bHM4PhVOmJI

Advertisements

One thought on “Microchip’s PIC18 USB HID example Win32 Host Software (native c++)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s