If you've tried to build your own PIC USB devices (perhaps following my earlier tutorial Building a PIC18F USB device) you will have noticed that the Windows host code (based on the Microchip USB stack examples) is far from clear on how to communicate with the device. To make matters easier for people who are not familiar with the intricacy of Windows programming and USB I've created a generic Visual C++ Class which handles all of the device communication issues including the plug and play aspects of device attachment and detachment detection.
The generic class is suitable for all types of Generic HID USB devices (including devices using the generic HID protocol based on common microcontrollers supporting USB communication other than the PIC18F).
In this article I will cover how to use this class, how the class functions and also give some practical examples of how it can be used to make your own Windows software.
The public methods of the class are those which are exposed to the programmer using the class. You can utilise the class using these methods without needing to dig into the code and understand how it works. The following sections detail the public methods, what they do and how to invoke them
usbHidCommunication(System::Void) - Constructor
The constructor method should be called by adding a definition of the class into a 'private:' section of the Form's .h file, it should look something like this:
// Initialise the USB HID Communication objectprivate: usbHidCommunicationNameSpace::usbHidCommunication a_usbHidCommunication;
This action makes the class ready to use (it instantiates an object from the class).
This method requests that device notifications are sent to the main form which allows the class to receive device attachment and detachment messages from the Windows operating system. You have to pass the 'handle' of the main form to the method so it knows where to direct the notifications. To do this you simply place the following code in to your form's constructor:
////TODO: Add the constructor code here//// Request that this form receives device notifications
System::Void handleDeviceChangeMessages(Message% m, System::Int16 vid, System::Int16 pid)
This method is responsible for handling device change notification messages coming from windows (which tell the class when USB devices attach and detach from the host). To use this you have to include the following code in the main form's class:
// This call back function receives the Windows messages coming into the form.protected:virtualvoid WndProc(Message% m) override
a_usbHidCommunication.handleDeviceChangeMessages(m, VID, PID);
Form::WndProc( m );}// END WndProc method
You have to pass this method the VID and PID of your device which can be done either directly (by simply specifying the numbers such as 0x1234, 0x4321) or (as in the example above) from two #define commands at the top of your form's .h file.
This method returns 'true' if the device is currently attached and 'false' if it isn't and can be used to see if the USB device is available for communication.
This method returns 'true' if a device is attached but, for some reason, is considered 'broken' (i.e. communication problems, etc.). Note that if this flag is 'true' the isDeviceAttached method will return 'false'. This allows you to add in some extra warning information if anything unexpected happens to the device.
This method attempts to find the USB device with the VID and PID supplied. If successful isDeviceAttached will return 'true' when queried. You should perform an initial search for the device when your main form is created, if the device is attached later the device notification will cause it to call this method automatically. You should include a call to this method in the main form's constructor similar to the following code:
InitializeComponent();////TODO: Add the constructor code here//// Request that this form receives device notifications
a_usbHidCommunication.requestDeviceNotificationsToForm(this->Handle);// Perform an initial search for the desired USB device
a_usbHidCommunication.findDevice(VID, PID);// VID, PID}
This method sends a command (and possibly some data) to the USB device, but does not expect the USB device to send anything back in response. To use this you simply set the usbCommandId to the value you wish to send and call the method. The commandId is in fact the 1st byte of the 64-byte packet that generic HID uses to communicate. The other 63 bytes are available for data and can be set by using the writeToTheOutputBuffer() method detailed below.
Here is an example of this method in action:
// Send the toggle LED commandif(a_usbHidCommunication.sendUsbCommandWriteOnly(0x80)!=true)
this->lastErrorMessageLabel->Text = L"Read/Write command failed";else this->lastErrorMessageLabel->Text = L"Read/Write command succeeded";
This method returns 'true' if the write was successful and 'false' if it fails.
This method is similar to the sendUsbCommandWriteOnly() method, however it expects the USB device to reply to the command. The response from the USB device is accessed using the readFromTheInputBuffer() command detailed below.
Here is the method in action:
// Check the status of the switch
this->switchStatusLabel->Text = L"Switch ON";else this->switchStatusLabel->Text = L"Switch OFF";
This method allows you to write bytes to the output buffer to be sent when the next send command method is called. You simply supply the byte number (2-64) of the byte you want to set (bytes 0 and 1 are reserved for communication) and the value of the byte. The method returns 'true' if successful and 'false' if unsuccessful.
This method allows you to read bytes from the input buffer (data which is returned after a sendUsbCommandWriteRead() method is called). Simply specify the byte number you are interested in (2-64) and the method returns the value.
This method removes the USB device from your code and cleans up any file handles, threads, etc. leaving the class in the state when it was constructed. This is useful if you wish to detach the device for any reason. Note: Since this class is in 'user-space' this doesn't detach the device from the host, only from your application.
Required include files
In order to use this class you have to include the four required libraries in your stdafx.h file. It should look similar to the following:
// stdafx.h : include file for standard system include files,// or project specific include files that are used frequently, but// are changed infrequently#pragma once// TODO: reference additional headers your program requires here#include <Windows.h>#include <setupapi.h>#include <Dbt.h>
Status and Limitations
This class is relatively new and untested. If you use it and find anything that is incorrect or could be done better please let me know, I'd love to hear from you. The main limitation of this class is that it can only handle one device at a time, i.e. if you have 2 or more devices with the same VID and PID it will simply pick the first one it finds. I hope to remove this limitation in the near future.
Further information about the class
If you would like to know more about how this class works please go ahead and look directly in the source code. I spent a lot of time making the code as clear as possible as well as including lots of commentary about how it works and what's going on.
Below you will find an example windows application with the class code included. This code is designed to work with my simple PIC18F generic HID device which you can find on this page.
It is designed to use the same PIC firmware as the original project. Obviously this is a very simple device, however the class should allow you to build far more complex devices without having to worry about the low-level details of USB HID programming on Windows.
This code will compile in Visual C++ 2008 for both (the free) Visual Studio 2008 express and the professional version. To compile load the project file and select compile from within Visual Studio.
To use the class in your own code simply make your own project and copy over the usbHidCommunication.h file into your project directory.
Files for download
This software is distributed under the GPL Licence: