Tutorial about USB HID Report Descriptors

This page is from my old website, and it is sort of popular, so I’ve moved it here.

A USB HID report descriptor is one of the descriptors that a USB host can request from a USB device. HID devices send data to the host using reports, and the descriptor tells the host how to interpret the data. I will try to show you how to write one of these descriptors.

First, go to this page http://www.usb.org/developers/hidpage/ and find the document titled “Device Class Definition for HID”. What I will be talking about is essentially paraphrasing the important sections of that document.

Second, go get the HID descriptor tool from the same page. You’ll want to play with it as you go through this tutorial. It is an absolute headache to write the HID report descriptors manually (converting between binary and hex and looking up the meanings of the numbers) so this tool is essential.


What is a USB HID report descriptor?

The HID protocol makes implementation of devices very simple. Devices define their data packets and then present a “HID descriptor” to the host. The HID descriptor is a hard coded array of bytes that describe the device’s data packets. This includes: how many packets the device supports, how large are the packets, and the purpose of each byte and bit in the packet. For example, a keyboard with a calculator program button can tell the host that the button’s pressed/released state is stored as the 2nd bit in the 6th byte in data packet number 4 (note: these locations are only illustrative and are device specific). The device typically stores the HID descriptor in ROM and does not need to intrinsically understand or parse the HID descriptor. Some mouse and keyboard hardware in the market today are implemented using only an 8-bit CPU.

– Wikipedia on Human Interface Device

I’m going to try teaching you about USB HID report descriptors by walking you through writing a few.


For a simple starting point, let us make a standard mouse. Just three buttons, and movement on the X and Y axis. So we want to send data regarding the buttons and movement. It takes one bit to represent each button, and one byte to represent the movement on one axis as a signed integer. So we can say that we want the data structure to look something like this

  Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
Byte 0 Useless Useless Useless Useless Useless Left Button Middle Button Right Button
Byte 1 X Axis Relative Movement as Signed Integer
Byte 2 Y Axis Relative Movement as Signed Integer

And then we can say our data structure in C looks like

struct mouse_report_t
{
	uint8_t buttons;
	int8_t x;
	int8_t y;
}

So now in our descriptor, our first item must describe buttons, three of them

USAGE_PAGE (Button)
USAGE_MINIMUM (Button 1)
USAGE_MAXIMUM (Button 3)

each button status is represented by a bit, 0 or 1

LOGICAL_MINIMUM (0)
LOGICAL_MAXIMUM (1)

there are three of these bits

REPORT_COUNT (3)
REPORT_SIZE (1)

send this variable data to the computer

INPUT (Data,Var,Abs)

and the final result looks like

USAGE_PAGE (Button)
USAGE_MINIMUM (Button 1)
USAGE_MAXIMUM (Button 3)
LOGICAL_MINIMUM (0)
LOGICAL_MAXIMUM (1)
REPORT_COUNT (3)
REPORT_SIZE (1)
INPUT (Data,Var,Abs)

that will represent the buttons

but what about the five useless padding bits?

REPORT_COUNT (1)
REPORT_SIZE (5)
INPUT (Cnst,Var,Abs)

now we make the X axis movement

USAGE_PAGE (Generic Desktop)
USAGE (X)

we want it to be a signed integer that takes one byte, so it has a value between -127 and +127 (actually -128 and +127, but I want to keep things even)

LOGICAL_MINIMUM (-127)
LOGICAL_MAXIMUM (127)

we want it to take an entire byte which is 8 bits

REPORT_SIZE (8)
REPORT_COUNT (1)

and send it to the computer as a variable relative coordinate

INPUT (Data,Var,Rel)

you end up with something like this to represent the X axis movement

USAGE_PAGE (Generic Desktop)
USAGE (X)
LOGICAL_MINIMUM (-127)
LOGICAL_MAXIMUM (127)
REPORT_SIZE (8)
REPORT_COUNT (1)
INPUT (Data,Var,Rel)

How about Y axis? You can try

USAGE_PAGE (Generic Desktop)
USAGE (X)
LOGICAL_MINIMUM (-127)
LOGICAL_MAXIMUM (127)
REPORT_SIZE (8)
REPORT_COUNT (1)
INPUT (Data,Var,Rel)
USAGE_PAGE (Generic Desktop)
USAGE (Y)
LOGICAL_MINIMUM (-127)
LOGICAL_MAXIMUM (127)
REPORT_SIZE (8)
REPORT_COUNT (1)
INPUT (Data,Var,Rel)

Which will work, but to save memory, we can do this instead

USAGE_PAGE (Generic Desktop)
USAGE (X)
USAGE (Y)
LOGICAL_MINIMUM (-127)
LOGICAL_MAXIMUM (127)
REPORT_SIZE (8)
REPORT_COUNT (2)
INPUT (Data,Var,Rel)

So all your data will end up looking like

USAGE_PAGE (Button)
USAGE_MINIMUM (Button 1)
USAGE_MAXIMUM (Button 3)
LOGICAL_MINIMUM (0)
LOGICAL_MAXIMUM (1)
REPORT_COUNT (3)
REPORT_SIZE (1)
INPUT (Data,Var,Abs)
REPORT_COUNT (1)
REPORT_SIZE (5)
INPUT (Cnst,Var,Abs)
USAGE_PAGE (Generic Desktop)
USAGE (X)
USAGE (Y)
LOGICAL_MINIMUM (-127)
LOGICAL_MAXIMUM (127)
REPORT_SIZE (8)
REPORT_COUNT (2)
INPUT (Data,Var,Rel)

Ah but we are not done, in order to make the computer know that this is a mouse, we do

USAGE_PAGE (Generic Desktop)
USAGE (Mouse)
COLLECTION (Application)
	USAGE (Pointer)
	COLLECTION (Physical)
	
	... What we wrote already goes here
	
	END COLLECTION
END COLLECTION

So in the end, this is the USB HID report descriptor for a standard mouse

0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
0x09, 0x02,                    // USAGE (Mouse)
0xa1, 0x01,                    // COLLECTION (Application)
0x09, 0x01,                    //   USAGE (Pointer)
0xa1, 0x00,                    //   COLLECTION (Physical)
0x05, 0x09,                    //     USAGE_PAGE (Button)
0x19, 0x01,                    //     USAGE_MINIMUM (Button 1)
0x29, 0x03,                    //     USAGE_MAXIMUM (Button 3)
0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
0x95, 0x03,                    //     REPORT_COUNT (3)
0x75, 0x01,                    //     REPORT_SIZE (1)
0x81, 0x02,                    //     INPUT (Data,Var,Abs)
0x95, 0x01,                    //     REPORT_COUNT (1)
0x75, 0x05,                    //     REPORT_SIZE (5)
0x81, 0x03,                    //     INPUT (Cnst,Var,Abs)
0x05, 0x01,                    //     USAGE_PAGE (Generic Desktop)
0x09, 0x30,                    //     USAGE (X)
0x09, 0x31,                    //     USAGE (Y)
0x15, 0x81,                    //     LOGICAL_MINIMUM (-127)
0x25, 0x7f,                    //     LOGICAL_MAXIMUM (127)
0x75, 0x08,                    //     REPORT_SIZE (8)
0x95, 0x02,                    //     REPORT_COUNT (2)
0x81, 0x06,                    //     INPUT (Data,Var,Rel)
0xc0,                          //   END_COLLECTION
0xc0                           // END_COLLECTION

This is actually the example descriptor provided with the USB HID documentation, and you can also find this as an example provided with the HID tool.


Cool, at this point, you will have encountered some concepts that you may have questions about, you should research the following:

Usage Pages

There’s one thing that I think isn’t explained well in the documentation, USAGE, USAGE_PAGE, USAGE_MINIMUM and USAGE_MAXIMUM. In a descriptor, you first set a USAGE_PAGE, and certain USAGEs are available. In the mouse example, USAGE_PAGE (Generic Desktop) allowed you to use USAGE (Mouse), and when the usage page was changed to USAGE_PAGE (Button), then the USAGE_MINIMUM and USAGE_MAXIMUM allowed you to specify the buttons, and before you can use USAGE (X) and USAGE (Y), the usage page was changed back to USAGE_PAGE (Generic Desktop). The usage page is like a namespace, changing the usage page affects what “usages” are available. Read the documentation called ” HID Usage Tables” for more info.

Collections

Read the documentation about the official proper use of collections. In my own words, collections can be used to organize your data, for example, a keyboard may have a built-in touchpad, then the data for the keyboard should be kept in one application collection while the touchpad data is kept in another. We can assign an “Report ID” to each collection, which I will show you later.


Hey here’s something you can do, by turning “USAGE (Mouse)” into “USAGE (Gamepad)”, you make the computer think that it’s a game pad with one joystick and 3 buttons. How about converting a Playstation 2 controller into a USB gamepad? The controller has 16 buttons and two thumb sticks, so we want the data to look like

  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 X Axis as Signed Integer
Byte 3 Left Y Axis as Signed Integer
Byte 4 Right X Axis as Signed Integer
Byte 5 Right Y Axis as Signed Integer

So our data structure looks like

struct gamepad_report_t
{
	uint16_t buttons;
	int8_t left_x;
	int8_t left_y;
	int8_t right_x;
	int8_t right_y;
}

We make the computer understand that it’s a game pad

USAGE_PAGE (Generic Desktop)
USAGE (Game Pad)
COLLECTION (Application)
	COLLECTION (Physical)

	...

	END COLLECTION
END COLLECTION

for the buttons

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)

for the four thumb stick axis

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.

So now you end up with

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

Hey how about two players? Here’s where collections get handy

USAGE_PAGE (Generic Desktop)
USAGE (Game Pad)
COLLECTION (Application)
	COLLECTION (Physical)
		REPORT_ID (1)
		...
	END COLLECTION
END COLLECTION
USAGE_PAGE (Generic Desktop)
USAGE (Game Pad)
COLLECTION (Application)
	COLLECTION (Physical)
		REPORT_ID (2)
		...
	END COLLECTION
END COLLECTION

fill in the data areas and you end up with

USAGE_PAGE (Generic Desktop)
USAGE (Game Pad)
COLLECTION (Application)
	COLLECTION (Physical)
		REPORT_ID (1)
		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
USAGE_PAGE (Generic Desktop)
USAGE (Game Pad)
COLLECTION (Application)
	COLLECTION (Physical)
		REPORT_ID (2)
		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

This is really important: You must change your data structure to include the report ID

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

struct multiplayer_gamepad_report_t
{
	uint8_t report_id;
	uint16_t buttons;
	int8_t left_x;
	int8_t left_y;
	int8_t right_x;
	int8_t right_y;
}

You must manually set the report ID before you send the data to the computer in order for the computer to understand which player the data belongs to.

multiplayer_gamepad_report_t player1_report;
multiplayer_gamepad_report_t player2_report;
player1_report.report_id = 1;
player2_report.report_id = 2;


You can also use collections and report IDs to make composite devices. So far I’ve shown you the keyboard, mouse, and gamepad. Here’s something that describes a composite device that is a keyboard, mouse, and two player game pad.

USAGE_PAGE (Generic Desktop)
USAGE (Keyboard)
COLLECTION (Application)
	REPORT_ID (1)
	...
END COLLECTION
USAGE_PAGE (Generic Desktop)
USAGE (Mouse)
COLLECTION (Application)
	USAGE (Pointer)
	COLLECTION (Physical)
		REPORT_ID (2)
		...
	END COLLECTION
END COLLECTION
USAGE_PAGE (Generic Desktop)
USAGE (Game Pad)
COLLECTION (Application)
	COLLECTION (Physical)
		REPORT_ID (3)
		...
	END COLLECTION
END COLLECTION
USAGE_PAGE (Generic Desktop)
USAGE (Game Pad)
COLLECTION (Application)
	COLLECTION (Physical)
		REPORT_ID (4)
		...
	END COLLECTION
END COLLECTION

and of course, your data structures with the added report ID.

struct keyboard_report_t
{
	uint8_t report_id;
	uint8_t modifier;
	uint8_t reserved;
	uint8_t keycode[6];
}

struct mouse_report_t
{
	uint8_t report_id;
	uint8_t buttons;
	int8_t x;
	int8_t y;
}

struct gamepad_report_t
{
	uint8_t report_id;
	uint16_t buttons;
	int8_t left_x;
	int8_t left_y;
	int8_t right_x;
	int8_t right_y;
}

But since we changed the data structure, your device no-longer supports boot protocol, and hence you will not need to define a protocol. So make sure you change usbconfig.h accordingly.

Want to see this in action? Load up this example project into USnooBie and let Windows’s “Devices and Printers” show you!

Example Project Files

hid_tutorial_1_ss[1]

57 thoughts on “Tutorial about USB HID Report Descriptors

  1. SD Meijer

    Can you help me with a combo device (keyboard & mouse)?
    I have two usbHidReportDescriptors.
    The first one works under Windows, but under Linux everything works except the left mouse button.
    The second one works under Linux, but under Windows the mouse doesn’t work at all.
    I posted a message on the V-Usb forum, maybe you can help?
    http://forums.obdev.at/viewtopic.php?f=8&t=8785

    Reply
  2. SD Meijer

    Yes, I did. I used your example. But that one doesn’t work under Windows (at least not in combination with a keyboard). See the forum post at obdev.at.

    Reply
    1. SD Meijer

      I tested a lot. It looks like that whatever I set as buttonmask, it always is a right mouse click (under Linux). Under Windows it works without problems.
      P.S. This is not your descriptor, but the other one. Because your descriptor doesn’t work under Windows (7), although my Device Manager recognizes a mouse and says it’s working correct.

      Reply
  3. SD Meijer

    Tried your descriptor again (without keyboard) and that worked.
    Then I’d added the keyboard part and now both works!
    I don’t know what happened, but I’m happy with the outcome.

    Reply
  4. G Snyder

    Thanks so much for this outline. I’m sure I would have spent days pawing uncomprehendingly through hundreds of pages of specification without it. I just wanted to set up a simple Play/Pause control for an iPhone via BTLE, and this was exactly what I needed.

    Reply
  5. locodog

    Hello and thanks for the great outline, I’m trying to make a custom generic HID device with 128 single bit buttons and 48 10bit ADC analogue inputs, I’m a bit stumped as to what usage type to use?

    All the source I have been using is for a gamepad, but I suspect I’ll have to change this.

    (I’m making a HID dj software controller NOT MIDI, as far as the software is concerned HID is HID)

    Reply
    1. Admin Post author

      If you don’t see an appropriate “usage” or “usage page” in the HID specifications, then just use “vendor” or “gamepad”. If you write your own DJ software, it doesn’t matter as long as the data is accessible.

      Picking the right “usage” is only important when you are dealing with other people’s software, if you have total control over the software, it doesn’t matter, as long as the data gets there. If you can get the data, you can use it.

      Reply
      1. locodog

        Many thanks for the reply, I am pleased to say I’ve got my project reporting 64 Bytes accurately, next thing is to work out 2 page reports (and I’m sure the answer is here, I just need to read and try the examples) after that I’ll look at return packets (from the pc to the usb device)

        Many thanks for the tutorial.

        Reply
  6. The Sparkbuzz

    Thanks for this article. It’s been a great help. Working through the USB documentation is a horrible nightmare…

    Reply
  7. Jumbo

    Hi, thanks for all the info..
    I also found it hard to find good info on HID. I have noticed it is possible to create a seemingly valid report description in the Report Descriptor Tool, but windows will not enumerate it.
    I need to do bi-directional data transfer between stm32-f105 and windows host.

    First I tried to customize the casual mouse report description, by setting the report size to 64 .. but my device would not enumerate under windows. ..Then I found this on internet:
    HID_UsagePageVendor(0x00),
    HID_Usage(0x01),
    HID_Collection(HID_Application),

    HID_LogicalMin(0),
    HID_LogicalMax(255),
    HID_ReportSize(8),

    HID_ReportCount(64),
    HID_Usage(0x01),
    HID_Input(HID_Data | HID_Variable | HID_Absolute),

    HID_ReportCount(64),
    HID_Usage(0x01),
    HID_Output(HID_Data | HID_Variable | HID_Absolute),
    HID_EndCollection

    It enumerates, but does it look correct to you? (I get weird behaviour on my device, so I would like to know if my report description is good before I start searching for bugs in the code – I already spend 3 days trying to get it to work :-/ )

    Reply
    1. Admin Post author

      I’ve seen the same thing before, maybe from LUFA or Teensy’s website or somewhere, I don’t remember exactly. It usually works but doesn’t do anything without a host application.

      Compare it against your own descriptor.

      Reply
  8. locodog

    If I wanted to send more than 64 bytes from one device, what would I do, just add a
    REPORT_ID (1) after the collection, then REPORT_ID (2) before the end collection and a then repeat what was in the collection when it was a 1 page, 64 byte report?

    static const uint8_t PROGMEM gamepad_hid_report_desc[] = {
    0x05, 0x01, // USAGE_PAGE (Generic Desktop)
    0x09, 0x05, // USAGE (Gamepad)
    0xa1, 0x01, // COLLECTION (Application)
    0x15, 0x00, // LOGICAL_MINIMUM (0)
    0x25, 0x01, // LOGICAL_MAXIMUM (1)
    0x35, 0x00, // PHYSICAL_MINIMUM (0)
    0x45, 0x01, // PHYSICAL_MAXIMUM (1)
    0x75, 0x01, // REPORT_SIZE (1)
    0x95, 0x80, // REPORT_COUNT (128)
    0x05, 0x09, // USAGE_PAGE (Button)
    0x19, 0x01, // USAGE_MINIMUM (Button 1)
    0x29, 0x80, // USAGE_MAXIMUM (Button 128)
    0x81, 0x02, // INPUT (Data,Var,Abs)
    0x05, 0x01, // USAGE_PAGE (Generic Desktop)
    0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
    0x46, 0xff, 0x00, // PHYSICAL_MAXIMUM (255)
    0x09, 0x30, // USAGE (X)
    0x09, 0x31, // USAGE (Y)
    0x09, 0x32, // USAGE (Z)
    0x09, 0x35, // USAGE (Rz)
    0x09, 0x36, // Usage (Slider)
    …….43 more sliders.
    0x09, 0x36, // Usage (Slider)
    0x75, 0x08, // REPORT_SIZE (8)
    0x95, 0x30, // REPORT_COUNT (48)
    0x81, 0x02, // INPUT (Data,Var,Abs)
    0xc0 // END_COLLECTION
    };

    Reply
    1. Admin Post author

      You should read this page http://www.beyondlogic.org/usbnutshell/usb4.shtml where it talks about the size limits of the different endpoint types.

      Basically, you are not supposed to design something that needs more than 64 bytes if you plan on using full-speed specs. There is no “work around” unless you use high-speed specs (meaning you need high-speed capable hardware, typically a ULPI PHY). You can try to split your data up in the application layer though, but that wouldn’t work well if you want generic HID to work. There is no easy answer, sorry.

      Reply
  9. locodog

    Hi again Frank, further to >64 bytes output, I believe I may have used the wrong language and caused confusion, My device sents 64 bytes of data to the pc, I expanded the HID report descriptor to include an extra 32 * 16 bit sliders, (also declared and defined these inputs in other parts of the code where needed)
    My device now sends a HID report of 128 bytes of data, but the problem is that the new 64 bytes is an exact copy of the first 64 bytes (byte 64 is a mirror of byte zero, etc)

    Does this sound like a Report descriptor error and can you recommend anything I should check?

    Reply
    1. Admin Post author

      If the second chunk is a copy of the first chunk, then it’s a programming error, not a descriptor error, descriptor is not code. Make sure the first chunk is sent successfully and then fill in the memory for the second chunk.

      I don’t think sending 128 bytes in two chunks is a good idea for HID if you want a generic driver to take care of it.

      Reply
  10. Gavin

    Hi this is helping a lot with understanding USB protocols but i am still struggling with the hid descriptor tool and 10 bit axis values for X,Y,Z and slider, could you help.
    Thanks, Gavin.

    Reply
      1. Gavin

        Sorry didnt even give a good reply, i have the demo code mentioned below and have modified by adding i2c communication to an mcp23017, that all works have button registers and an led flashing on a timer.
        I don’t know how to implement 10bit on the analog axis or really how to modify the HID and USBAPI files included. the idea is to bump it all up to 7 10bit axis and 32 buttons.

        Reply
        1. Admin Post author

          I don’t know what demo code you are talking about.

          Did you define your struct yet? Do that first. Then determine the “usage page” and “usage” for the axis in a similar manner as the tutorial. Obviously set the report size to the correct number.

          It’s easier to up convert your data to be 16 bits to avoid padding bits.

          It helps to write a portion of the descriptor, and then think “if I was a computer, how would I interpret this using the information available and the rules I need to follow”.

          Reply
          1. Gavin

            Right this is what I have so far, taken a break and broke my chain of thought.

            0x05, 0x01, // USAGE_PAGE (Generic Desktop)
            0x09, 0x04, // USAGE (Joystick)
            0xa1, 0x01, // COLLECTION (Application)
            0x85, 0x04, // REPORT_ID (4)
            0x05, 0x01, // USAGE_PAGE (Generic Desktop)
            0x09, 0x01, // USAGE (Pointer)
            0xa1, 0x00, // COLLECTION (Physical)
            0x09, 0x30, // USAGE (X)
            0x09, 0x31, // USAGE (Y)
            0x09, 0x32, // USAGE (Z)
            0x09, 0x36, // USAGE (Slider)
            0x15, 0x00, // LOGICAL_MINIMUM (0)
            0x26, 0x00, 0x04, // LOGICAL_MAXIMUM (1024)
            0x75, 0x0a, // REPORT_SIZE (10)
            0x95, 0x04, // REPORT_COUNT (4)
            0x81, 0x02, // INPUT (Data,Var,Abs)
            0x05, 0x09, // USAGE_PAGE (Button)
            0x19, 0x01, // USAGE_MINIMUM (Button 1)
            0x29, 0x20, // USAGE_MAXIMUM (Button 32)
            0x15, 0x00, // LOGICAL_MINIMUM (0)
            0x25, 0x01, // LOGICAL_MAXIMUM (1)
            0x75, 0x01, // REPORT_SIZE (1)
            0x95, 0x20, // REPORT_COUNT (32)
            0x55, 0x00, // UNIT_EXPONENT (0)
            0x65, 0x00, // UNIT (None)
            0x81, 0x02, // INPUT (Data,Var,Abs)
            0xc0 // END_COLLECTION

            does this part look right to you

          2. Admin Post author

            Nope, you did not pad your 10 bit values, which makes it difficult to create a struct that represents your report. Either add the padding to the descriptor, or change the descriptor to say 16 bits instead and pad the value in the struct.

  11. Gavin

    I’m using borrowed code from freetronics forum and running on a arduino leonardo.
    The part i’m using is the third post from the bottom, i’m literally day two into testing parts of the code.
    Might have to write all i have learnt up and make a nice joystick tutorial, no body seems to use higher the higher resolutions the ADC can provide.
    Thanks for the reply, Gavin.

    Reply
  12. Gavin

    I think I have this right now

    0x05, 0x01, // USAGE_PAGE (Generic Desktop)
    0x09, 0x04, // USAGE (Joystick)
    0xa1, 0x01, // COLLECTION (Application)
    0x85, 0x04, // REPORT_ID (4)
    0x05, 0x01, // USAGE_PAGE (Generic Desktop)
    0x09, 0x01, // USAGE (Pointer)
    0xa1, 0x00, // COLLECTION (Physical)
    0x09, 0x30, // USAGE (X)
    0x09, 0x31, // USAGE (Y)
    0x09, 0x32, // USAGE (Z)
    0x09, 0x36, // USAGE (Slider)
    0x15, 0x00, // LOGICAL_MINIMUM (0)
    0x26, 0x00, 0x04, // LOGICAL_MAXIMUM (1024)
    0x75, 0x0a, // REPORT_SIZE (10)
    0x95, 0x04, // REPORT_COUNT (4)
    0x81, 0x02, // INPUT (Data,Var,Abs)
    0x95, 0x01, // REPORT_COUNT (1)
    0x75, 0x06, // REPORT_SIZE (6)
    0x81, 0x03, // INPUT (Cnst,Var,Abs)
    0x05, 0x09, // USAGE_PAGE (Button)
    0x19, 0x01, // USAGE_MINIMUM (Button 1)
    0x29, 0x20, // USAGE_MAXIMUM (Button 32)
    0x15, 0x00, // LOGICAL_MINIMUM (0)
    0x25, 0x01, // LOGICAL_MAXIMUM (1)
    0x75, 0x01, // REPORT_SIZE (1)
    0x95, 0x20, // REPORT_COUNT (32)
    0x55, 0x00, // UNIT_EXPONENT (0)
    0x65, 0x00, // UNIT (None)
    0x81, 0x02, // INPUT (Data,Var,Abs)
    0xc0 // END_COLLECTION

    Reply
    1. Admin Post author

      No, you’ve defined 10 bits 4 times, and then another 6 bits of padding, giving you 46 in total, which is not divisible by 8. You want 10, then 6, then 10 again then 6 again, then 10 again then 6 again, then 10 again then 6 again.

      That’s why it’s easier to just define 16 bit 4 times instead.

      Reply
  13. gavin

    Ok I see, I have finally got my head around inserting all the pieces into the right places when adding an extra axis. The problem I have is no reference to what I need to add to make it a 16bit statement in hid descriptor and in the struct. Thanks for being patient, Gavin

    Reply
  14. Gavin

    Just stripping one axis out and modifying it should look like this then for a 16bit descriptor, would this also still be usable if i decided to use a 12bit or 16 bit adc?

    0x05, 0x02, // USAGE_PAGE (Simulation Controls)
    0x09, 0xbb, // USAGE (Throttle)
    0x15, 0x00, // LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x03, // LOGICAL_MAXIMUM (1023)
    0x75, 0x10, // REPORT_SIZE (16)
    0x95, 0x01, // REPORT_COUNT (1)
    0x81, 0x02 // INPUT (Data,Var,Abs)

    and the stuct would use instead of “uint8_t throttle,” “uint16_t throttle,” and that will work?

    Reply
      1. Gavin

        No joy with that, pardon the pun. It will upload with no error but will not provide input. think it may be the struct.
        This it just snippets of the code i have changed

        /////////////////////////////////////////////////////////////////////
        0x05, 0x02, // USAGE_PAGE (Simulation Controls)
        0x09, 0xbb, // USAGE (Throttle)
        0x15, 0x00, // LOGICAL_MINIMUM (0)
        0x26, 0xff, 0x03, // LOGICAL_MAXIMUM (1023)
        0x75, 0x10, // REPORT_SIZE (16) // putting this as 16 bit for
        0x95, 0x01, // REPORT_COUNT (1) // future ADC upgrade
        0x81, 0x02 // INPUT (Data,Var,Abs)
        /////////////////////////////////////////////////////////////////////
        Joystick_::Joystick_()
        {
        }

        void Joystick_::move(uint8_t x, uint8_t y, uint8_t Rudder, uint16_t throttle, uint8_t buttons)
        {
        u8 j[5];
        j[0] = x;
        j[1] = y;
        j[2] = Rudder;
        j[3] = throttle;
        j[4] = buttons;
        //HID_SendReport(Report number, array of values in same order as HID descriptor, length)
        HID_SendReport(4, j, 5);
        }
        /////////////////////////////////////////////////////////////////////
        class Joystick_
        {
        public:
        Joystick_();
        void move(uint8_t x,
        uint8_t y,
        uint8_t Rudder,
        uint16_t throttle,
        uint8_t buttons);
        };
        extern Joystick_ Joystick;
        /////////////////////////////////////////////////////////////////

        Reply
        1. Admin Post author

          You are not really using that struct, you are trying to pack 16 bit variables into a 8 bit array. You need to learn to use type casting and cast your struct into an array, not use a separate array.

          Reply
    1. Admin Post author

      This is true but it’s a preference of mine to keep it between -127 and +127 for the sake of keeping 0 right in the middle.

      Reply
  15. NicoHood

    Okay, thats a good point :D

    I played a bit with the descriptors, but i dont really understand the whole thing. Its so complicated :/
    I tried to add a System shutdown or sleep functionality, but it doesnt work. Any idea whats wrong here? i tried different version, nothing was working. or is just my win7 the problem?

    char ReportDescriptor[26] = {
    0x05, 0x01, // USAGE_PAGE (Generic Desktop)
    0x09, 0x80, // USAGE (System Control)
    0xa1, 0x01, // COLLECTION (Application)
    0x85, 0x05, // REPORT_ID (5)
    0x05, 0x01, // USAGE_PAGE (Generic Desktop)
    0x19, 0x00, // USAGE_MINIMUM (Undefined)
    0x29, 0x8d, // USAGE_MAXIMUM (System Menu Down)
    0x95, 0x01, // REPORT_COUNT (1)
    0x75, 0x10, // REPORT_SIZE (16)
    0x15, 0x00, // LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x03, // LOGICAL_MAXIMUM (1023)
    0x81, 0x00, // INPUT (Data,Ary,Abs)
    0xc0 // END_COLLECTION
    };

    And i found another working code for Media keys like play/pause. Its working, i understood it but the line with 0x00, 0x00 i dont understand. it is nesessary, but why?

    0x05,0x0C, // usage page (consumer device)
    0x09,0x01, // usage — consumer control
    0xA1,0x01, // collection (application)
    0x85,0x04, // report id (4)

    0x05,0x0C, // usage page (consumer)
    0x19,0x00, // usage minimum (0)
    0x2A,0xFF,0x03, //usage maximum (3ff)
    0x95,0x01, //report count (1)
    0x75,0x10, //report size (16)
    0x15,0x00, //logical minimum
    0x27,0xFF,0x03, //logical maximum (3ff)
    0x00,0x00,
    0x81,0x00, //input

    0xC0,//end collection

    thanks for you answer and thanks for the tutorial. its the only tutorial out there i could find. and these datasheets are so complicated for me, also with this technical english :S

    Reply
    1. Admin Post author

      Search for ST USB Library, which probably contains an example of an USB keyboard implementation.

      Reply
  16. Andrey

    Hi Frank,

    First of all, thank you very much for the tutorial, it helps a lot.

    I’m developing a kind of controller for home automation, with a bunch of relays and buttons, and I deciided to use HID to be able to go without all these drivers. So, I (ab)used the FEATUREs to send data back and forth, just like it is done in one of the examples at obdev. The state of my device consists of 4 structures, 64 bytes each, and there are ‘commands’ sent to the device, either ‘short’ (8 bytes) or ‘long’ (16 bytes), all including that one byte for the report id. So I’ve made my report descriptor to show six different features. Here is it:

    PROGMEM char usbHidReportDescriptor[78] = {
    0x06, 0x00, 0xff, // USAGE_PAGE (Generic Desktop)
    0x09, 0x01, // USAGE (Vendor Usage 1)
    0xa1, 0x01, // COLLECTION (Application)
    0x15, 0x00, // LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
    0x75, 0x08, // REPORT_SIZE (8)

    0x85, uncreq_query_eeprom, // REPORT_ID (uncreq_query_eeprom)
    0x95, 0x7f, // REPORT_COUNT (127)
    0x09, 0x00, // USAGE (Undefined)
    0xb2, 0x80, 0x01, // FEATURE (Data,Ary,Abs,Vol,Buf)

    0x85, uncreq_command, // REPORT_ID (uncreq_command)
    0x95, 0x07, // REPORT_COUNT (7)
    0x09, 0x00, // USAGE (Undefined)
    0xb2, 0x80, 0x01, // FEATURE (Data,Ary,Abs,Vol,Buf)

    0x85, uncreq_long_command, // REPORT_ID (uncreq_long_command)
    0x95, 0x0f, // REPORT_COUNT (15)
    0x09, 0x00, // USAGE (Undefined)
    0xb2, 0x80, 0x01, // FEATURE (Data,Ary,Abs,Vol,Buf)

    0x85, uncreq_zero_query_counters,// REPORT_ID (uncreq_zero_query_counters)
    0x95, 0x10, // REPORT_COUNT (16)
    0x09, 0x00, // USAGE (Undefined)
    0xb2, 0xa0, 0x01, // FEATURE (Data,Ary,Abs,NPrf,Vol,Buf)

    0x85, uncreq_query_fullstate, // REPORT_ID (uncreq_query_fullstate)
    0x95, 0x3f, // REPORT_COUNT (63)
    0x09, 0x00, // USAGE (Undefined)
    0xb2, 0x00, 0x01, // FEATURE (Data,Ary,Abs,Buf)

    0x85, uncreq_query_configuration,// REPORT_ID (uncreq_query_configuration)
    0x95, 0x3f, // REPORT_COUNT (63)
    0x09, 0x00, // USAGE (Undefined)
    0xb2, 0x00, 0x01, // FEATURE (Data,Ary,Abs,Buf)

    0x85, uncreq_query_extrastate, // REPORT_ID (uncreq_query_extrastate)
    0x95, 0x3f, // REPORT_COUNT (63)
    0x09, 0x00, // USAGE (Undefined)
    0xb2, 0x00, 0x01, // FEATURE (Data,Ary,Abs,Buf)

    0xc0 // END_COLLECTION
    };

    Everything works fine both under Windows and Linux (under Linux, I use libusb). However, my device also sends to the host a kind of ascii stream, and under Linux I can read it like this:

    return usb_interrupt_read(dev_hdl, 0x81, buf, 8, 0);

    Under windows, the same should (?) be achieved with

    return hid_read(dev_hdl, (unsigned char*)buf, 8);

    but it doesn’t work because (as far as I understand) my descriptor doesn’t describe any INPUTs. However, I several times tried to add something to my descriptor so that it tells the host there’s input as well, and every time Windows just refused to see my device at all, perhaps because of malformed report descriptor. Under Linux, libusb simply ignores all the descriptor-related stuff and works as it is told to, but under Windows, well, libusb-win32 is a monster I never actually convinced to work as I want.

    So, what is the right way, that is, what should I add to my descriptor so that the host understands there’s also an input stream?

    Thanks a lot!

    Reply
    1. Admin Post author

      It is very hard for me to help you like this, adding an input item should be simple, it could be a very minor mistake that’s causing the problem. Did you check the length of your descriptor and what actual length is transmitted?

      Reply
  17. Stephan

    Thanks for the wonderful tutorial, it really helped me get the ball rolling. Also thanks for linking to the source pages it really helps to understand WHY things work they way they do. I have how ever been banging my head against a wall here for the last week or two. I am using a teensy 2.0 board on windows8. after reading your tutorial here I decided to modify their descriptor to include a second joystick. this is what I ended up with :

    static const uint8_t PROGMEM joystick_hid_report_desc[] = {
    0x05, 0x01, // Usage Page (Generic Desktop)
    0x09, 0x04, // Usage (Joystick)
    0xA1, 0x01, // Collection (Application)
    0xA1, 0x00, // Collection (Phy.)
    0x85, 0x04,//ID
    /* button and axis definitions for 1stcontroller */
    0xC0, // End Collection
    0xC0, // End Collection

    0x05, 0x01, // Usage Page (Generic Desktop)
    0x09, 0x04, // Usage (Joystick)
    0xA1, 0x01, // Collection (Application)
    0xA1, 0x00, // Collection (phy)
    0x85, 0x05,//ID
    /* button and axis definitions for 2nd controller */
    0xC0, // End Collection
    0xC0 // End Collection

    when I plug the device in windows creates two controllers, and the 2nd one work perfectly, but the first one gives me a blank pages, as if no attributes have been defined for it.
    the descriptor is identical for both joysticks with the exception of the ID field.

    my data buffer contains the id field as the first byte.
    i’ve tried using different IDs i.e (1,2 2,1 3,4 4,3 4,5 5,4 1,5 5,1) the second controller always works perfectly and the first one is always just an empty controller. I’ve got no idea where I messed up.

    Does anyone have any suggestions for me?

    Thanks in advance

    Reply
    1. Admin Post author

      What hardware are you using?

      The reason why I wrote the tutorial with two report IDs is that V-USB does not support multiple interfaces, only a single interface. V-USB is only supported on AVRs, so if you are not using AVRs, you can try another method.

      Another way of achieving a two player controller is to create a device with two interfaces. Basically define more interface descriptors and use more endpoints. It’s a bit more tricky but it avoids having a confusing HID descriptors.

      Reply
      1. Stephan

        I am using the atmega32u4, I’ve been doing some deeper digging since I asked that question. I am currently implementing a solution from the ground-up (rather than hacking apart the example I have) I think I have a good idea where I messed up. I will post my solution when (if) I figure it out.

        Reply
  18. Wesko

    Thanks for being the only website that DOES actually expain HID. Still looking for a tool that converts a certain message I design as an interface blob to a descriptor. For instance, I send volume,channel as 2 bytes. What would be the descriptor?

    Reply
  19. Wesko

    Thanks, awesome. I am still looking into reliable tooling. The spec is not so clear, logical_minimum is signed for instance. And I cant find the report_count() max value. It is quite annoying all info is spread out over the internet :-)

    Reply
  20. Alex

    Hi, This is a really handy article. I recently used this to implement a mouse trackball using .net micro and expanded the XY axis to 32 bit as i have high resolution ADC’s on my board. I had some questions about button limits and what not for a mouse in windows. The device im building has support for 64 or more buttons. I was reading about the windows mouse driver and they say it supports 5 buttons. Is it possible to support more than 5 buttons? I was actually able to implement a hid descriptor that shows up as a mouse and has a report defined for the states of the 64 buttons im just unsure if those additional buttons beyond the first 5 will continue to raise pressed events in windows when they are pushed. Would it better to define the mouse and then define another interface for the keypad. Really interested in your thoughts on this.

    Reply
    1. Admin Post author

      Different interface is much better. The reason why I wrote about using only one interface is that V-USB doesn’t support multiple interfaces.

      I’d design your device as if it was a keyboard instead. I think you need N-key rollover which is hard to accomplish but I think there are some open source code on GitHub for that stuff already. (I forgot where, sorry, I found it before while researching MX Cherry keyboards)

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *



You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>