Instructables

USB Wii Classic Controller

Step 5: USB HID Reports and Report Descriptors

Picture of USB HID Reports and Report Descriptors
When you make a HID device, the device sends short reports at regular intervals to the computer. These reports contain information about which buttons are pressed and how much the joysticks have moved. However, in order for the host to understand the data format, the device must "describe" the reports to the host. This is why we write a USB HID report descriptor.

Take a look at your Wii Classic Controller. It has 15 buttons (round up to 16 for simplicity), and two joysticks. This means we need to send 15 bits of data for the buttons, and 4 numbers, one for each axis of joystick data. We define our data format to look like this:

  Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
Byte 0 Button Button Button Button Button Button Button Button
Byte 1 Button Button Button Button Button Button Button Button
Byte 2 Left Stick X Axis as Signed Char Integer
Byte 3 Left Stick Y Axis as Signed Char Integer
Byte 4 Right Stick X Axis as Signed Char Integer
Byte 5 Right Stick Y Axis as Signed Char Integer

And then we can define a data structure in C/C++
struct gamepad_report_t
{
	uint16_t buttons;
	int8_t left_x;
	int8_t left_y;
	int8_t right_x;
	int8_t right_y;
}
Writing a report descriptor involves describing a usage context first, and then describing the meaning of the data relative to the usage context, and then describing the data in terms of its range and size.

First, make the computer understand that the device is a gamepad

USAGE_PAGE (Generic Desktop)
USAGE (Game Pad)
COLLECTION (Application)
    COLLECTION (Physical)
 
    ...
 
    END COLLECTION
END COLLECTION
Then describe the button data (16 bits)
USAGE_PAGE (Button)
USAGE_MINIMUM (Button 1)
USAGE_MAXIMUM (Button 16)
LOGICAL_MINIMUM (0)
LOGICAL_MAXIMUM (1)
REPORT_COUNT (16)
REPORT_SIZE (1)
INPUT (Data,Var,Abs)
Then describe the 4 axis data as signed 8-bit integers
USAGE_PAGE (Generic Desktop)
USAGE (X)
USAGE (Y)
USAGE (Z)
USAGE (Rx)
LOGICAL_MINIMUM (-127)
LOGICAL_MAXIMUM (127)
REPORT_SIZE (8)
REPORT_COUNT (4)
INPUT (Data,Var,Abs)
  • NOTE: Z is used to represent the right stick's X axis, Rx is used to represent the right stick's Y axis. This doesn't make sense but this is how most existing USB game pads work. I have tested this using Battlefield Bad Company 2, it works.
  • NOTE: Use "absolute" for something like joysticks, but "relative" for things like mouse.

Finally, the report descriptor looks like:
USAGE_PAGE (Generic Desktop)
USAGE (Game Pad)
COLLECTION (Application)
    COLLECTION (Physical)
        USAGE_PAGE (Button)
        USAGE_MINIMUM (Button 1)
        USAGE_MAXIMUM (Button 16)
        LOGICAL_MINIMUM (0)
        LOGICAL_MAXIMUM (1)
        REPORT_COUNT (16)
        REPORT_SIZE (1)
        INPUT (Data,Var,Abs)
        USAGE_PAGE (Generic Desktop)
        USAGE (X)
        USAGE (Y)
        USAGE (Z)
        USAGE (Rx)
        LOGICAL_MINIMUM (-127)
        LOGICAL_MAXIMUM (127)
        REPORT_SIZE (8)
        REPORT_COUNT (4)
        INPUT (Data,Var,Abs)
    END COLLECTION
END COLLECTION

Now that we have a report descriptor, how do we make our AVR tell this stuff to the computer? Go download the official "HID Descriptor Tool" from http://www.usb.org/developers/hidpage/ . Use it to put this stuff in, and the tool will generate the correct binary array for you.




When you save your result, go to "File" -> "Save As", and then make sure you choose "Header File (*.h)" in the save dialog. Then open the file, it should look like

char ReportDescriptor[ some number here (size of array)] {
  a lot of bytes here
};

Great, the size of array needs to be copied into V-USB's "usbconfig.h" where it says "USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH", and "char ReportDescriptor" needs to be renamed to "PROGMEM char usbHidReportDescriptor" so that it is stored into the AVR's flash memory. You end up with something like:
PROGMEM char usbHidReportDescriptor[46] = {
    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x05,                    // USAGE (Game Pad)
    0xa1, 0x01,                    // COLLECTION (Application)
    0xa1, 0x00,                    //   COLLECTION (Physical)
    0x05, 0x09,                    //     USAGE_PAGE (Button)
    0x19, 0x01,                    //     USAGE_MINIMUM (Button 1)
    0x29, 0x10,                    //     USAGE_MAXIMUM (Button 16)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
    0x95, 0x10,                    //     REPORT_COUNT (16)
    0x75, 0x01,                    //     REPORT_SIZE (1)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0x05, 0x01,                    //     USAGE_PAGE (Generic Desktop)
    0x09, 0x30,                    //     USAGE (X)
    0x09, 0x31,                    //     USAGE (Y)
    0x09, 0x32,                    //     USAGE (Z)
    0x09, 0x33,                    //     USAGE (Rx)
    0x15, 0x81,                    //     LOGICAL_MINIMUM (-127)
    0x25, 0x7f,                    //     LOGICAL_MAXIMUM (127)
    0x75, 0x08,                    //     REPORT_SIZE (8)
    0x95, 0x04,                    //     REPORT_COUNT (4)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0xc0,                          //   END_COLLECTION
    0xc0                           // END_COLLECTION
};
That piece of code, plus the struct typedef we did earlier, is included in our project source code (see whole source code) later.

More Reading:
 
Remove these adsRemove these ads by Signing Up
epsilonjon1 year ago
Why are the gamepad sticks absolute while a mouse is relative? If I press up on my gamepad, surely I am moving up relative to my current position? Pressing up does not (alone) define my position.

Thanks for the tutorial :-)