USB Descriptor packet fun!

Status
Not open for further replies.

timonemycat

New member
Hey PRJC,


I'm using teensy 3.2 to create a joystick, and have a need to remove all unused axes and buttons/switches from what appears when connected through USB. I've been following along with what Kenton was doing on his blog (https://hamaluik.com/posts/making-a-custom-teensy3-hid-joystick/), but I loose him somewhere in that process, and would like to try just modifying the bare minimums.

I started off doing exactly the same things he did, and couldn't get my code to compile. I started backtracking to just things that I'd need, and am at the stage where I'd like to be able to figure out where the structure of the usb descriptor file packet is made. I'm just trying to change the descriptor file for the joystick so that there are 3 axes, and 4 buttons total. The files I'm modifying are usb_desc.h and usb_desc.c.

static uint8_t joystick_report_desc[] = {
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x04, // USAGE (Joystick)
0xa1, 0x01, // COLLECTION (Application)
0x09, 0x04, // USAGE (Joystick)
0xa1, 0x00, // COLLECTION (Physical)
0x09, 0x30, // USAGE (X)
0x09, 0x31, // USAGE (Y)
0x09, 0x32, // USAGE (Z)
0x75, 0x06, // REPORT_SIZE (6)
0x95, 0x03, // REPORT_COUNT (3)
0x45, 0x7f, // PHYSICAL_MAXIMUM (127)
0x35, 0x81, // PHYSICAL_MINIMUM (-127)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x05, 0x09, // USAGE_PAGE (Button)
0x19, 0x01, // USAGE_MINIMUM (Button 1)
0x29, 0x04, // USAGE_MAXIMUM (Button 4)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x75, 0x01, // REPORT_SIZE (1)
0x95, 0x04, // REPORT_COUNT (4)
0x81, 0x02, // INPUT (Data,Var,Abs)
0xc0, // END_COLLECTION
0xc0 // END_COLLECTION
};

I know that this isn't the only area that needs to change, and I was wondering if anybody has any further resources on this?
Thanks a bunch!
 
Others can probably answer this better than I can, I have played more at the other end of trying to get these to work with the USB Host code.

But some of things that I would guess include;
Code:
static uint8_t joystick_report_desc[] = {
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x04, // USAGE (Joystick)
0xa1, 0x01, // COLLECTION (Application)
0x09, 0x04, // USAGE (Joystick)
0xa1, 0x00, // COLLECTION (Physical)
0x09, 0x30, // USAGE (X)
0x09, 0x31, // USAGE (Y)
0x09, 0x32, // USAGE (Z)
[COLOR="#FF0000"]0x75, 0x06, // REPORT_SIZE (6)[/COLOR]
0x95, 0x03, // REPORT_COUNT (3)
0x45, 0x7f, // PHYSICAL_MAXIMUM (127)
0x35, 0x81, // PHYSICAL_MINIMUM (-127)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x05, 0x09, // USAGE_PAGE (Button)
0x19, 0x01, // USAGE_MINIMUM (Button 1)
0x29, 0x04, // USAGE_MAXIMUM (Button 4)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x75, 0x01, // REPORT_SIZE (1)
0x95, 0x04, // REPORT_COUNT (4)
0x81, 0x02, // INPUT (Data,Var,Abs)
0xc0, // END_COLLECTION
0xc0 // END_COLLECTION
};
Report size of 6 is wrong (6 bits) as you have min/max of 127/-127 so should be 8 (or min/max need to change...

But then you may have to muck with usb_desc.h in the cores\teensy3 to change things like
Code:
  #define JOYSTICK_SIZE         12	//  12 = normal, 64 = extreme joystick

Then I believe you also need to then update the usb_joystick.h and usb_joystick.c in the cores\teensy3 directory.
To handle the packing of the data into the outgoing USB packet. Currently it looks like this code is setup only to handle
JOYSTICK_SIZE==12 or ==64, so you would need to add a new section.

Also the code assumes that the buttons are the first thing in the packet and you have the X, Y, Z as the first ones so again you need to adjust that code to pack the data into the format that you need.

Plus make sure that it is not packing data for several of the Axis that you don't have, like Xrotate, Yrotate, ...
 
HID gives you a lot of flexibility to define your data, but you must transmit packets that match the data format you define.

On top of that, there are 2 issues with Windows.

1: Windows loves a cache USB info in the Windows Registry. When you change the descriptor, often you need to increment the BCD version field or change the PID or VID numbers to get Windows to use the descriptors rather than the old info from its registry.

2: Windows imposes an extra requirement that the entire HID report must add up to a multiple of 8 bits. The one you posted has three 6 bit axes and four 1 bit buttons, which adds up to 22 bits. To make this work, you'd need to add a constant input with 2 more bits. You'd add something like this:

Code:
        0x95, 0x01,                     //   Report Count (1),
        0x75, 0x02,                     //   Report Size (2),
        0x81, 0x03,                     //   Input (Constant),

Of course, when you transmit the packet, the number of bytes must exactly match the data. If you send the wrong number, Windows will not work with your device at all.

The HID report descriptor Teensyduino uses for Joystick has 10 bit per axis. Maybe you have some good reason to reduce to only 6 bits? If you go this route, you'll need to edit usb_joystick.h and usb_joystick.c to pack the data into the correct 6 bits. But this is less critical. As long as you transmit the correct number of bytes, the PC will recognize your device and it will just do weird things because the data is wrong.
 
Thank you for your responses.

I'm seeing all kinds of different data as to how many bits are being output.

Right now, I can get the descriptor to work with one axis and 4 buttons. but if I try to add the 2nd axis, I cannot get it to work. See any problems with my new descriptor?
Code:
    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x04,                    // USAGE (Joystick)
    0xa1, 0x01,                    // COLLECTION (Application)
    0x09, 0x04,                    //   USAGE (Joystick)
    0xa1, 0x00,                    //   COLLECTION (Physical)
    0x05, 0x09,                    //     USAGE_PAGE (Button)
    0x19, 0x01,                    //     USAGE_MINIMUM (Button 1)
    0x29, 0x04,                    //     USAGE_MAXIMUM (Button 4)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
    0x95, 0x20,                    //     REPORT_COUNT (32)
    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)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,              //     LOGICAL_MAXIMUM (255)
    0x75, 0x0a,                    //     REPORT_SIZE (10)
    0x95, 0x02,                    //     REPORT_COUNT (2)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0x75, 0x0c,                    //     REPORT_SIZE (12)
    0x95, 0x01,                    //     REPORT_COUNT (1)
    0x81, 0x03,                    //     INPUT (Cnst,Var,Abs)
    0xc0,                          //   END_COLLECTION
    0xc0                           // END_COLLECTION



Here is the crux of my joystick.h file.

Code:
int usb_joystick_send(void);
extern uint32_t usb_joystick_data[(JOYSTICK_SIZE+3)/4];

#if JOYSTICK_SIZE == 5
	void button(uint8_t button, bool val) {
		if (--button >= 4) return;
		if (val) usb_joystick_data[0] |= (1 << button);
		else usb_joystick_data[0] &= ~(1 << button);
		if (!manual_mode) usb_joystick_send();
	}
	void X(unsigned int val) {
		if (val > 1023) val = 1023;
		usb_joystick_data[1] = (usb_joystick_data[1] & 0xFFFFFC00) | (val);
		if (!manual_mode) usb_joystick_send();
	}
	void Y(unsigned int val) {
		if (val > 1023) val = 1023;
		Serial.println(val);
		usb_joystick_data[1] = (usb_joystick_data[1] & 0xFFF003FF) | (val << 10);
		if (!manual_mode) usb_joystick_send();

I guess my question is how to combine the dummy values with the data in the descriptor file. I'm doing something wrong there, and I've tried a bunch of configurations, and can't figure out how to combine them correctly.
 
Last edited:
Was there any solution to this? how do I reduce the number of buttons to 10?

Some practical and working example how to change something simple like this would be very useful.

what I edit in usb_desc.c does not seem to make any difference; where is the actual usb_desc.c used??

I have been editing this (and by now it should be broken)

Code:
#ifdef JOYSTICK_INTERFACE
#if JOYSTICK_SIZE == 12
static uint8_t joystick_report_desc[] = {

        0x05, 0x01,                     // Usage Page (Generic Desktop)
        0x09, 0x04,                     // Usage (Joystick)
        0xA1, 0x01,                     // Collection (Application)

        0x05, 0x09,                     //   Usage Page (Button)
        0x19, 0x01,                     //   Usage Minimum (Button #1)
        0x29, 0x0A,                     //   Usage Maximum (Button #32) was 0x20, reduced to 10 buttons 0x0A
        0x15, 0x00,                     //   Logical Minimum (0)
        0x25, 0x01,                     //   Logical Maximum (1)
        0x75, 0x01,                     //   Report Size (1)
        0x95, 0x0A,                     //   Report Count (32) was 0x20, reduced to 10 buttons 0x0A



        0x81, 0x02,                     //   Input (variable,absolute)

        0x75, 0x01,                     //   Report Size (1)
        0x95, 0x16,                     //   Report Count (22) to fill the original 32 buttons
        0x81, 0x01,                     //   Constant


        0x15, 0x00,                     //   Logical Minimum (0)
        0x25, 0x07,                     //   Logical Maximum (7)
        0x35, 0x00,                     //   Physical Minimum (0)
        0x46, 0x3B, 0x01,               //   Physical Maximum (315)
        0x75, 0x04,                     //   Report Size (4)
        0x95, 0x01,                     //   Report Count (1)
        0x65, 0x14,                     //   Unit (20)

        0x05, 0x01,                     //   Usage Page (Generic Desktop)
        0x09, 0x39,                     //   Usage (Hat switch)
        0x81, 0x42,                     //   Input (variable,absolute,null_state)

        0x05, 0x01,                     //   Usage Page (Generic Desktop)
        0x09, 0x01,                     //   Usage (Pointer)
        0xA1, 0x00,                     //   Collection ()
        0x15, 0x00,                     //     Logical Minimum (0)
        0x26, 0xFF, 0x03,               //     Logical Maximum (1023)
        0x75, 0x0A,                     //     Report Size (10)
        0x95, 0x04,                     //     Report Count (4)
        0x09, 0x30,                     //     Usage (X)
        0x09, 0x31,                     //     Usage (Y)
        0x09, 0x32,                     //     Usage (Z)
        0x09, 0x35,                     //     Usage (Rz)
        0x81, 0x02,                     //     Input (variable,absolute)
        0xC0,                           //   End Collection
        0x15, 0x00,                     //   Logical Minimum (0)
        0x26, 0xFF, 0x03,               //   Logical Maximum (1023)
        0x75, 0x0A,                     //   Report Size (10)
        0x95, 0x02,                     //   Report Count (2)
        0x09, 0x36,                     //   Usage (Slider)
        0x09, 0x36,                     //   Usage (Slider)
        0x81, 0x02,                     //   Input (variable,absolute)
        0xC0                            // End Collection

It looks very weird how it is originally organised, and would be so much easier to read if instead comments the commands would be named like this

Code:
               USAGE_PAGE(1), 0x09,            // Buttons
               USAGE_MINIMUM(1), 0x01,         // 1
               USAGE_MAXIMUM(1), 0x04,         // 4
               LOGICAL_MINIMUM(1), 0x00,       // 0
               LOGICAL_MAXIMUM(1), 0x01,       // 1
               REPORT_SIZE(1), 0x01,
               REPORT_COUNT(1), 0x04,
               UNIT_EXPONENT(1), 0x00,         // Unit_Exponent (0)
               UNIT(1), 0x00,                  // Unit (None)                                           
               INPUT(1), 0x02,                 // Data, Variable, Absolute

This is with Teensy 4.0 and Teensyuino 1.53 . It seems that usb_desc.h and usb_joystick.h are on the teensy4 folder as expected, usb_joystick.c and usb_desc.c are also there, but these files are not used, where are these files used from, where should I edit them?
 
Last edited:
I do not get it, I have changed all usb_desc.c to Uusb_desc.c and it just compiles and works as before, where are the USB Joystick definitions??
 
Sorry it is sort of hard to know what is going on without all of the information.
Simple things like what is: JOYSTICK_SIZE defined as ?
What do you expect to be different? that is if you only changed it such that 10 buttons were defined and hole filler of 22 bits, not much different.

If you actually do change the format of the data, there are rules that I believe HID imposes, like you need to define the data such that it all adds up such that the number of bits must be a multiple of 8...

This data is again only used if your USB type that you build for includes JOYSTICK.

In which case the files usb_joystick.h and .cpp will also be built into your sketch.

And if you change the data format these files must change to generate the data in the format you are now defining the data to be sent.

Example: for JOYSTICK_SIZE == 12
Code:
	void button(uint8_t button, bool val) {
		if (--button >= 32) return;
		if (val) usb_joystick_data[0] |= (1 << button);
		else usb_joystick_data[0] &= ~(1 << button);
		if (!manual_mode) usb_joystick_send();
	}
The usb_joystick_data is defined as an array of uint32_t values.
So you can see if you are trying to set button 0 does it still go into the same bit in the data? Or should it now be shifted up 22 bits?

Obviously if you make any more significant changes to the format you will need to update all of that code as it is pretty hard coded for that specific alignment of bits.
 
My main problem is that edits or deletion of usb_desc.c are not effective. I spend couple of hours reading forums and trying things before realising I can delete the file and it compiles just the same. I tried restarting the Arduino environment and even computer. Edits on usb_desc.h are effective, edits on usb_desc.c are not.

The problem was that I made a copy of the usb_desc.c and and left it on the teensy4 directory and for some reason it used the usb_desc_copy.c instead the usb_desc.c that I edited. Now it is working fine and got it showing only the 10 buttons as I wanted.
 
Last edited:
Maybe this?: Temp, spare or backup files left in source directories must not have usable 'code' extensions - put .txt on them or something that compiler won't consider.
 
Yes, the usb_desc_copy.c was a bad idea, I really could not imagine compiler would use it just because it is on that directory and prefer it over usb_desc.c that I was editing. Anyway, now the backups are on completely different directory.
 
It is in the nature of the build process - without an explicit list of files it gathers source files where it is pointed.

Makes usage easy without make files - saw this here before doing simple rename doesn't work.
 
Status
Not open for further replies.
Back
Top