You need to edit the HID report descriptor, but also the main code. Change this portion of the HID descriptor:
0x05, 0x09, /* Usage Page (Button) */
0x09, 0x02, /* Usage (Button 2) */
0x09, 0x01, /* Usage (Button 1) */
0x15, 0x00, /* Logical Minimum (0) */
0x25, 0x01, /* Logical Maximum (1) */
0x75, 0x01, /* Report Size (1) */
0x95, 0x02, /* Report Count (2) */
0x81, 0x02, /* Input (Data, Variable, Absolute) */
0x75, 0x06, /* Report Size (6) */
0x95, 0x01, /* Report Count (1) */
0x81, 0x01, /* Input (Constant) */
0xc0 /* End Collection */
To this:
0x05, 0x09, /* Usage Page (Button) */
0x09, 0x03, /* Usage (Button 3) */
0x09, 0x02, /* Usage (Button 2) */
0x09, 0x01, /* Usage (Button 1) */
0x15, 0x00, /* Logical Minimum (0) */
0x25, 0x01, /* Logical Maximum (1) */
0x75, 0x01, /* Report Size (1) */
0x95, 0x03, /* Report Count (3) */
0x81, 0x02, /* Input (Data, Variable, Absolute) */
0x75, 0x06, /* Report Size (5) */
0x95, 0x01, /* Report Count (1) */
0x81, 0x01, /* Input (Constant) */
0xc0 /* End Collection */
And set the third bit of the Buttons element in the element in the CALLBACK_HID_Device_CreateHIDReport()
function of the main source file, i.e. to "push" the new third button, use:
if (ButtonStatus_LCL & BUTTONS_BUTTON1)
JoystickReport->Button |= (1 << 2);
The USB documentation is pretty terrible. It suffers from excessive genericization, where they're trying to make everything so generic and general that it's hard to get from the docs to any specific application.
The descriptor format is located in the document called "Device Class Definition for HID" at the link to usb.org you provide.
The critical thing you're missing is that the various segments (such as 0x05) is not documented with the prefix 0x. In fact, they generally describe them in raw binary.
For example, with regard to the 0xA1:

You can see that the binary prefix 1010_00nn
indicates that is is a collection, and the postfix nnnn_nn01
indicates it is 1 byte long. Then subsequent byte is then interpreted as the collection type, in this case of type Application. This then sets the context in which the further bytes are interpreted, untill the HID descriptor parser either sees another collection tag, or an end collection marker.
You can see that END COLLECTION
is specified as 0b1100_00nn
, with nn
being ignored. This is where the 0xC0
is coming from.
You can also start to see how the other arguments are constructed. For example, LOGICAL MINIMUM
is 0x25
, or 0b0010_0101
. From that, we can see that we have a data-length of 0bnnnn_nn01
, or one byte, and the specifier for LOGICAL MINIMUM
is 0b0010_01nn

The structure for the USAGE PAGE
descriptor is the same. The command to select the usage page is 0000_01nn
, and nnnn_nn01
indicates it is 1 byte long. I would guess that, since the documentation states that usage-pages are 32 bits, the upper bits are assumed to be zero, or inferred from another part of the documentation. I actually don't know how they're specified.
There is a good page of the various HID constants here.
And a more recent version from the BSD sources here (Thanks, @crazysim!) (latest HEAD, may not last).
Best Answer
A wizard-like tool may be cute the first time you use it, but then it gets in the way. A better method is a decent structure of the descriptors in memory, and a few helpful macros and other preprocessing logic so that you can supply the information at a higher level and the right bytes and bits automatically get filled in.
My USB stack for the PIC 18 works like that. You can install that and a bunch of other stuff from my software downloads page. The static USB setup is defined in two files. The templates for these are named QQQ_USB.INS.ASPIC and QQQ_USB0.INS.ASPIC in the SOURCE > PIC directory. First you define the VID/PID, which endpoints you use, and which transfer types per endpoint in the usb.ins.aspic file. Then you modify the descriptors in usb0.ins.aspic with the little bit you need to customize. Since it already knows things like the transfer types and buffer sizes, you leave those symbols alone and just change what's unique to your implementation. In most cases, that's only adjusting the manufacturer name and the product name. These you write as ordinary strings and a macro takes care of the proper formatting.