Now that the main components of the USB device has been set up, it’s time to worry about the application specific implementation. There are three files that we need to modify before we can compile and load the device.
main.c
The first of these is “main.c”. In the Microchip version of the file, there are a ton of “#pragma” directives used to declare data and program space on the PIC18. On the C30 compiler for the PIC24, “#pragma” directives are not supported. You can safely delete these lines. You can also delete all lines with the directive pertaining to “#if defined(__18CXX)” since we are exclusively covering PIC24 usage. We are also defining all board specific variables and types in our “bsp.c” and “bsp.h” files, so we can go ahead and delete the joystick report type definitions and variable declaration in the “main.c” file as well. We must keep the global USB function that are located towards the end of the file. In the end your “main.c” file should look something like this:
/******************************************************************************
Engscope.com
Authro: JL
Created May 27, 2010
Modified Mar 27, 2010
USB Framework
******************************************************************************/
//headers for QP
#include "dev.h"
//configuration bits
_CONFIG1(JTAGEN_OFF & GCP_OFF & GWRP_OFF & FWDTEN_OFF)
//jtag off
//write protect off
//emulation off
//watchdog off
_CONFIG2(IESO_OFF & PLLDIV_DIV2 & FNOSC_PRIPLL & FCKSM_CSECMD \
& IOL1WAY_OFF & OSCIOFNC_OFF & POSCMOD_EC)
//secondary oscillator off
//set pll to 24Mhz
//use fast RC
//disable clock switching
//use osco
//unlimited writes to RP
//disable primary oscillator
_CONFIG3(WPDIS_WPDIS & SOSCSEL_IO)
//disable write protect
/** PRIVATE PROTOTYPES *********************************************/
static void InitializeSystem(void);
//main entry
int main(void)
{
//initialize the Board Support Package
BSP_init();
//initialize USB
InitializeSystem();
//attach device
#if defined(USB_INTERRUPT)
USBDeviceAttach();
#endif
//loop forever
while(1){
}
}
/********************************************************************
* Function: static void InitializeSystem(void)
*
//... rest of global USB functions
Device Descriptor
Now we need to describe our USB device to the USB host. This is done through the file “usb_descriptors.c”. When you open the file, you will notice that it is structured exactly like the USB hierarchy.
/* Device Descriptor */
ROM USB_DEVICE_DESCRIPTOR device_dsc=
{
0x12, // Size of this descriptor in bytes
USB_DESCRIPTOR_DEVICE, // DEVICE descriptor type
0x0200, // USB Spec Release Number in BCD format
0x00, // Class Code
0x00, // Subclass code
0x00, // Protocol code
USB_EP0_BUFF_SIZE, // Max packet size for EP0, see usb_config.h
MY_VID, // Vendor ID, see usb_config.h
MY_PID, // Product ID, see usb_config.h
0x0001, // Device release number in BCD format
0x01, // Manufacturer string index
0x02, // Product string index
0x00, // Device serial number string index
0x01 // Number of possible configurations
};
/* Configuration 1 Descriptor */
ROM BYTE configDescriptor1[]={
/* Configuration Descriptor */
0x09,//sizeof(USB_CFG_DSC), // Size of this descriptor in bytes
USB_DESCRIPTOR_CONFIGURATION, // CONFIGURATION descriptor type
DESC_CONFIG_WORD(0x0029), // Total length of data for this cfg
1, // Number of interfaces in this cfg
1, // Index value of this configuration
0, // Configuration string index
_DEFAULT | _SELF, // Attributes, see usb_device.h
50, // Max power consumption (2X mA)
/************************ JOYSTICK INTERFACE START************************/
/* Interface Descriptor */
0x09,//sizeof(USB_INTF_DSC), // Size of this descriptor in bytes
USB_DESCRIPTOR_INTERFACE, // INTERFACE descriptor type
0, // Interface Number
0, // Alternate Setting Number
2, // Number of endpoints in this intf
HID_INTF, // Class code
0, // Subclass code
0, // Protocol code
0, // Interface string index
/* HID Class-Specific Descriptor */
0x09,//sizeof(USB_HID_DSC)+3, // Size of this descriptor in bytes RRoj hack
DSC_HID, // HID descriptor type
DESC_CONFIG_WORD(0x0111), // HID Spec Release Number in BCD format (1.11)
0x00, // Country Code (0x00 for Not supported)
HID_NUM_OF_DSC, // Number of class descriptors, see usbcfg.h
DSC_RPT, // Report descriptor type
DESC_CONFIG_WORD(HID_RPT01_SIZE), //sizeof(hid_rpt01), // Size of the report descriptor
/* Endpoint Descriptor */
0x07,/*sizeof(USB_EP_DSC)*/
USB_DESCRIPTOR_ENDPOINT, //Endpoint Descriptor
HID_EP | _EP_IN, //EndpointAddress
_INTERRUPT, //Attributes
DESC_CONFIG_WORD(64), //size
0xFF, //Interval
/* Endpoint Descriptor */
0x07,/*sizeof(USB_EP_DSC)*/
USB_DESCRIPTOR_ENDPOINT, //Endpoint Descriptor
HID_EP | _EP_OUT, //EndpointAddress
_INTERRUPT, //Attributes
DESC_CONFIG_WORD(64), //size
0xFF //Interval
};
The file first describes the device, then the configuration, the interface, and lastly the endpoint. In addition, there is a special description for the HID class that tells the USB host which report to use when sending data back and forth. Since the Microchip example is already describing an HID device that only sends data to the USB host (recall that the joystick only sends data one way, it does not receive any data from the host), there is not much that we need to modify in the device descriptor.
Report Descriptor
In addition to describing the device, we also need to describe the report. This report descriptor tells the USB host how to use the data that is being received and how to transmit data in a way that the host can understand. Since the Microchip joystick is not the same as the joystick we are building, we need to build the report descriptor from scratch. We will use the HID descriptor tool, provided by the USB-IF to do this step. After starting a new descriptor, I have loaded the following settings and clicked on “Parse Descriptor” to properly format the indentations.

At this point, it really pays to have a brief read into USB HID Standard. The document describes the requirements for defining the abilities of an HID device. Here I define the device and states that it is used as a general desktop device and a joystick. Next I define 6 buttons which can have a value of 0 or 1, defined by the variable input descriptor (INPUT (Data,Var,Abs). However, I need to use a single byte to represent 6 buttons. I need 2 filler bits in order to fill up the byte, which is defined by the constant input descriptor (INPUT (Cnst,Var,Abs)).
You can then use the “save as header” feature in the HID descriptor tool to get the C version of the descriptor directly. Then copy and paste the code into the report descriptor section of the “usb_descriptor.c” file. In the end your report descriptor should look like this:
ROM struct{BYTE report[HID_RPT01_SIZE];}hid_rpt01={{
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x05, // USAGE (Joystick)
0xa1, 0x01, // COLLECTION (Application)
0x05, 0x09, // USAGE_PAGE (Button)
0x19, 0x01, // USAGE_MINIMUM (Button 1)
0x29, 0x06, // USAGE_MAXIMUM (Button 6)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x95, 0x06, // REPORT_COUNT (6)
0x75, 0x01, // REPORT_SIZE (1)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x02, // REPORT_SIZE (2)
0x81, 0x03, // INPUT (Cnst,Var,Abs)
0xc0
}
};
String Descriptors
Finally, USB supports custom strings embedded into the device. These are character strings that are used when the operating system wants to refer to the device. In our example, I have named the device “Simple HID Joystick”. This will cause the device to show up in the “Devices and Printers” folder of Windows with the proper name.

String 0 must always indicate the language code. String 1 and greater can be any custom string that you might want to use for the device. Under the Microchip USB framework, the strings are defined as a ROM, and collectively pointed with a pointer:
//Language code string descriptor
ROM struct{BYTE bLength;BYTE bDscType;WORD string[1];}sd000={
sizeof(sd000),USB_DESCRIPTOR_STRING,{0x0409
}};
//Manufacturer string descriptor
ROM struct{BYTE bLength;BYTE bDscType;WORD string[8];}sd001={
sizeof(sd001),USB_DESCRIPTOR_STRING,
{'E','N','G','S','C','O','P','E'
}};
//Product string descriptor
ROM struct{BYTE bLength;BYTE bDscType;WORD string[19];}sd002={
sizeof(sd002),USB_DESCRIPTOR_STRING,
{'S','i','m','p','l','e',' ','H','I','D',' ','J','o','y','s','t','i','c','k'
}};
//Array of string descriptors
ROM BYTE *ROM USB_SD_Ptr[]=
{
(ROM BYTE *ROM)&sd000,
(ROM BYTE *ROM)&sd001,
(ROM BYTE *ROM)&sd002
};
You can then specify which string to use for the manufacturer and product name in the device descriptor.
/* Device Descriptor */
ROM USB_DEVICE_DESCRIPTOR device_dsc=
{
0x12, // Size of this descriptor in bytes
USB_DESCRIPTOR_DEVICE, // DEVICE descriptor type
0x0200, // USB Spec Release Number in BCD format
0x00, // Class Code
0x00, // Subclass code
0x00, // Protocol code
USB_EP0_BUFF_SIZE, // Max packet size for EP0, see usb_config.h
MY_VID, // Vendor ID, see usb_config.h
MY_PID, // Product ID, see usb_config.h
0x0001, // Device release number in BCD format
0x01, // Manufacturer string index
0x02, // Product string index
0x00, // Device serial number string index
0x01 // Number of possible configurations
};
usb_config.c
The last file that needs to be modified is the “usb_config.c” file. This file contains your VID and PID information, as well as ROM memory reservations.
If you need a refresh of the function of the VID and PID values, please read section 2 of the USB tutorial. We will be borrowing Mircrochip’s VID, and randomly giving ourselves a PID of 0×0100.
#define MY_VID 0x04D8
#define MY_PID 0x0100
Since we have changed the length of our report descriptor, we will need to change the size of the ROM memory reservations. Here I have modified the size of my report descriptor by changing the symbol “HID_RPT01_SIZE”.
/** ENDPOINTS ALLOCATION *******************************************/
/* HID */
#define HID_INTF_ID 0x00
#define HID_EP 1
#define HID_INT_OUT_EP_SIZE 64
#define HID_INT_IN_EP_SIZE 64
#define HID_NUM_OF_DSC 1
#define HID_RPT01_SIZE 29
Finishing Touches
We also need to create a file that sends out data in the struct through the API defined by Microchip. These files are called “nlib_hid.c” and “nlib_hid.h”. There is really nothing very interesting to see in these files, but basically they take care of transmitting the tokens and keeping track of whether a transfer is completed or not. The “nlib_hid.h” contains the function prototypes, while “nlib_hid.c” is where the action takes place, shown below:
#include
#include "nlib_hid.h"
static USB_HANDLE devLastTransmission; //transmission status
static HID_CONTROLS hid_input; //stores current control values (button on/off, axis etc)
static int endPoint; //this stores the EP number on initiation
//this is the function called during the process, updates Input Controls
static void (*processInput)(HID_CONTROLS *);
//this is the Initiation routine for custom user code
void hid_Init(int ep, void (*proc)(HID_CONTROLS *))
{
//get the end point number
endPoint = ep;
//initialize the variable holding the handle for the last transmission
devLastTransmission = 0;
//link the function pointer
processInput = proc;
//hid_input should be zero by default (declaration zeros everything)
}//end UserInit
//function takes IO and pocoesses
void hid_Proc(void)
{
// User Application USB tasks
if((USBDeviceState < CONFIGURED_STATE)||(USBSuspendControl==1)) return;
//for interface 0
//If the last transmision is complete
if(!HIDTxHandleBusy(devLastTransmission))
{
//processInput(&hid_input); //process the input before transmitting it
processInput(&hid_input);
//Send the packet over USB to the host.
devLastTransmission = HIDTxPacket(endPoint, (BYTE*)&hid_input, sizeof(hid_input));
}
}//end ProcessIO
The main function called hid_Proc() takes the data structure that we defined earlier for our buttons and transfers it through the USB bus. It keeps track of whether the USB connection is ready for the next transfer. The “hid_input” variable, which was defined in our “bsp.h” is declared in here as a static. We update the values of variable hid_input through the function BSP_ProcHid() and transfers the variable with hid_Proc().
First Connection and Enumeration
I have taken the liberty of including the modified code below. The code should compile and ready to be loaded into a PIC24 device.
Engscope Simple HID Joystick Project
Please read the disclaimer before downloading.
After compiling and loading the hex file, Windows will automatically load the drivers. If you cannot get past this step, then either your firmware was not loaded properly or something in the hardware is not working. Keep checking your circuit to find the culprit if you are having problems with enumeration (look for bad voltages, bad oscillators, shorts, opens etc.). The compiled code is verified and working on my systems.

You can then open the “Set up USB game controllers” utility to test the device.


Lastly, to see the actually data being transferred from the device to the USB host, we can pop up the Simple HID Write tool and select the Simple HID Joystick.

The first byte indicates the report number, which is used if you have multiple reports on a simple device (e.g. a mouse/keyboard combo can use two reports to separate the data and functionality). Since we only have one report, the first byte is always 0×00. The second byte is defined by our report descriptor. You will see that the first 6 bits will change when the buttons are pressed, and the top 2 bits are always zero.
There you have it. We have just created a custom 6 button joystick.
Hi, could you explain the structure of the “Report Descriptor” in a bit more detail, or is studying the USB HID standard mandatory for being able to understand this?
I have two C64 retro Joysticks. Would it be possible to connect both of them to a single microcontroller and have them then appear as two USB HID devices in the Windows device manager? If it is, what changes have to be made to your example project?
@Markus Gritsch. I would recommend a brief read in the USB HID as it is hard to explain in a short article.
-J
@Markus Gritsh. This is possible and I have done something similar. It is not as straight forward as just modifying the report. Microchip’s USB stack does NOT support (as of right now) two devices of the same class. However you can get around this modifying the USB stack. I will show the procedure on the composite USB section of the tutorial.
-J
The examples and explanations on this page http://frank.circleofcurrent.com/cache/hid_tutorial_1.htm helped me to understand report descriptors a bit better. The last example on that page also indicates, that one can have multiple Joysticks by using “report IDs”. It seems that a composite USB device is not necessary to achieve this. What do you think about this?
@Markus Gritsche. I have done both.
The first received byte is usually the report number. If there is only a single report, the report number will always be 0×00. If there are more than one report, I believe the report numbering systems begins at 0×01 (not completely sure, it’s been a while since I wrote it). However, we must consider the bigger picture. Sure you can get your device to work, and in most cases this is just fine. However, in certain cases, there is a large advantage in using multiple interfaces rather than multiple reports. The most obvious advantage has to do with the limitation of the interrupt transfer. On full speed, a single transfer allows up to 64 bytes per transfer per endpoint. On multiple reports, a single endpoint must handle all reports, so if you have more than 64 bytes for all your reports per single transfer, you are out of luck. However, if you are using multiple interfaces, you are essentially using two different USB devices, with endpoint for each sub-device. This allows you to use all 64 bytes per sub-device.
On a more subtle level, there’s programming advantages on the host side. Many programs are written without the composite aspect in mind, and will usually only receive the 0×00 report. If you are writing a custom program to interact with your embedded device, you will need retrieve and sort out the report number before you can process information. If multiple interfaces are used, depending on the API, the sub-device will look like a full USB device. It also allows more compatibility across platforms, as report 0×00 MUST be support, while multiple reports may or may not be supported. Personally I like to keep my programming model constant across devices.
I will use the example of a USB debugging interface. On such an interface for USB devices, it would be nice to have access to the device regardless of whether the device is composite or single interface. This guarantees that I will be able to access my device using report 0×00. Certain devices might ONLY have the USB debugging interface, and other will have a regular USB function interface in addition to the USB debugging interface. When I’m writing code on the host side to retrieve data, I don’t want to have two sets of code for when the device is composite and when it is a single device. By using multiple interfaces, I can access either one of the USB interfaces as if they were complete and separate USB devices.
Hope that helps,
-J
Thanks for the detailed explanation.
May I ask one more question: After I changed the “Product string descriptor” from ‘J’,'o’,'y’,'s’,'t’,'i’,'c’,'k’,’ ‘,’D',’e',’m',’o’ to ‘C’,'o’,'m’,'p’,'e’,'t’,'i’,'t’,'i’,'o’,'n’,’ ‘,’P',’r',’o', the device shows up with the new name in the “Devices and Printers” folder, which is good. However, when selecting “Game controller settings” from the context menu, it still shows the controller as “Joystick Demo”, and in the C64 emulator VICE the device also shows up as “Joystick Demo”. Windows seems to cache the name with which the device was connected the first time somewhere, because when I connect the device for the first time at another computer having already the new name, everything is well. Do you know how this can be fixed?
@ Markus Gritsch. This has to do with the Windows registry. Upon first enumeration, it will store a copy of the strings and other information about the device. There are two way to get windows to re-cache the device info.
The first is to change the PID and VID of the device. On the next enumeration, windows will think that it is a new device and will enumerate accordingly.
The second way is to change the registry. You can access enumeration data on XP machines only. Go to: loca_machine/system/controlset001/enum/. Once there, look under the USB folder, and find your device, delete the entry. Next go to the HID folder, find your device and delete the entry as well. Do this at your own risk. On Windows 7, the registry is protected so hacked can’t do what I just mentioned. To remove a device, plug it in, right click on it and remove drivers. This will remove all stored information. You will need to re-plug your device for Windows to re-cache the new data.
-J
Hmm, I use Windows 7 and the context menu in the device manager has no “remove driver”, but only “uninstall”. When I do this and re-plug the device, i comes up again as “Joystick Demo” in the “Game controller settings” dialog. Did you refer to some other context menu?
BTW, here is the project I made: http://dangerousprototypes.com/forum/viewtopic.php?f=56&t=2971
@Markus Gritsch. Under the device manager, your joystick will show up as a HID device but also as a USB device. You need to “uninstall” as you say, both. So, again, remove it from “Universal Serial Bus controllers” (you will need to look for your VID/PID), but also remove it from “Human Interface Devices”. Now if your device is not connected, you will not see the device, so follow the steps here to show installed but unconnected devices. Make sure the VID/PID matches!
-J
It didn’t help. Even after uninstalling both devices, the “Game controller settings dialog still showed “Joystick Demo” as the controller name. So I searched the registry for the VID and PID key I use, and it turned out that the following key was the culprit:
System\CurrentControlSet\Control\MediaProperties\Joystick\OEM\VID_04D8&PID_005E
After deleting this key, the device is now displayed as “Competition Pro”
Thanks for all the help!
@Markus Gritsch. Ah yes, the good old Windows registry. I have lost a good portion of my youth plowing through that monster of a mess. Glad to know it worked and nice project. If you have time, a link to Engscope for others on your forum/site would be greatly appreciated.
Thanks,
-J
Sure, I just updated the forum post with a link to this site.