loading

Step 5: 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:
<p>Hi, thanks for the tutorial.</p><p>I'm having trouble in here: </p><p>twi_writeTo(WIIEXT_TWI_ADDR,twiBuffer,7,1);</p><p>It seems program cannot get past this line due to waiting for writing to finish (setting last parameter to 0 lets program continue, however, no communication with classic controller seems to be happening).</p><p>WIIEXT_TWI_ADDR is defined as 0x52</p><p>I'm using Atmel Studio 6.2 to compile and a clone Classic Controller, any idea on what i can try?</p>
<p>You've encoded the analog positions as signed ints from -127 to 127... this is against spec and very unconventional. I'd be interested in hearing if people have had difficulties with this report descriptor configuration on some platforms, as this may be the cause. The spec states that analogs should range from 0 to 255, with 127 as a neutral position. I've checked the report descriptors of several commercial gamepads (made by Microsoft, Logitech and Hori, and with vintages ranging from 2005 to 2012) using USBLyzer, and confirmed that this standard is respected in all cases (though the wired xbox360 actually uses 256, instead of 255 like all the others).<br><br>See:<br><a href="http://www.usb.org/developers/hidpage/Hut1_12v2.pdf" rel="nofollow">http://www.usb.org/developers/hidpage/Hut1_12v2.pd...</a></p>
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. <br> <br>Thanks for the tutorial :-)
Hello sir ,I am using avrstudio4 for compiling your HID gamepad code and after compiling i am getting error message : <br>dep/usbdrv.c:8: *** target pattern contains no `%'. Stop. <br>sir please help me ,this is my final year project. <br>THANKING YOU IN ANTICIPATION.
Is it possible to remove the middle man and have a controller port coming out of the PC also will it work on a hackintoshi i am a noob when it comes to programing also a noob when it comes to computer hardware?
You must have the &quot;middle man&quot; if you want to use the Wii Classic Controller
im wondering if you know if a usb controll pad can be converted to plug into a wii remote? i have a usb arcade stick and would like to make it wirless by plugging it into a wiimote. is there a way of cutting off the nunchuk plug and wire it up to my arcade stick? <br> <br>thanks
kind of hard, I know how to do it, but is it worth the effort? how skilled are you? what kind of equipment do you have?<br><br>basically you need a microcontroller that can perform USB host duties, and also I2C slave duties. I have already created the code for the I2C stuff. The USB host stuff can either be LUFA if you are using a AT90USB1287 or you can use a &quot;USB host shield&quot; or similar. Or you can dive into ARM but then I don't have the code for it.
Hey! i love your project! but one question... do you think you can compile this on attyin85? similar to the http://hobbyelektronik.org/w/index.php?title=SNES-Joypad
It'll need some major changes. I don't think the ATtiny has dedicated I2C so that has to be changed to bit-banging. It is certainly possible.
wow thats great! i recently finished a nes to usb controller and i loved it. but i would of loved to be able to use a classic controller. But thus im not a avr programer so i dont know much. just enough to mess around a bit with the code.<br><br>but great tutorial! loved it! hopefully one day i can implement this on a attiny85.
Thank you for this project! <br> <br>Not because I've actually used it&hellip; But I copy-pasted this HID Descriptor into my project (that has a quite similar HID with keyboard+mouse), and found that your HID did work, while mine didn't. <br> <br>4 hours later, I finally found the bug in my project. I had written 0x0f where should have been 0x10, somewhere in the middle of my HID descriptor. <br> <br>So, thank you to help me find the typo in my code! :-) <br>I guess you never thought this intructable would be useful for that! :-P
You really put a lot of work into this instructable. Great work! <br><br>I have a question in regards to the format of your instructable. How did you get those dotted borders around your code? It makes the instructable look so neat and orderly.
In the editor, press &quot;view source&quot; to edit your Instructable in HTML (I think this is a &quot;Pro&quot; feature)<br><br><br>The bordered code is done within tags, but with a style attribute that specifies the border style<br><br><br>http://www.w3schools.com/TAGS/tag_pre.asp<br><br>http://www.w3schools.com/css/css_border.asp
Now do you think it's possible to do this the other way around? Like spoof the classic controller?<br>
Yes, perfectly possible, I have done this already but I re-created a Rock Band drum set<br><br>If you want to see it, go here http://frank.circleofcurrent.com/index.php?page=wii_drum
Oh by the way, the Wii version of Guitar Hero World Tour comes with a drum set that you plug into the Wiimote, which operates very similarly to a Classic Controller (the encryption is the same, the difference is one byte in the ID field and a slightly different data packet format). The library I created allows for any Wiimote extensions to be created. I use a drum as my best example of its usage.

About This Instructable

62,834views

52favorites

License:

Bio: I am an electrical engineer. I graduated from U of Waterloo. I used to work for Adafruit Industries as an EE. Now I work for ... More »
More by frank26080115:LED Pocket WatchEasy Cord Wrapping Around Power BricksUsing SMD Components on Breadboards
Add instructable to: