Many axis joystick

I tried with Windows 7 and fiddled with the report descriptor until Windows recognized it as a joystick.

This one has 128 buttons, 6 axes, 17 sliders, and 4 hat switches.

Nice work Paul, I wondered last night when I look at the descriptor if it was the usage min and max windows didn't like.

It's working here now but..

Only showing 1 hat under DirectX, but interestingly it is showing 128 buttons and 23 axis!

I just need to figure out how to access the extra sliders.

Regards,

Les
 
After another quick play it seems I am not getting any data for hat or sliders through DirectX.

Regards,

Les
 
Are any gamers or flight sim people following this thread? Can anyone test and see if those programs can access the many extra sliders?
 
Fixed Hats by tweaking descriptor...
Code:
static uint8_t joystick_report_desc[] = {
        0x05, 0x01,                     // Usage Page (Generic Desktop)
        0x09, 0x04,                     // Usage (Joystick)
        0xA1, 0x01,                     // Collection (Application)
        0x15, 0x00,                     // Logical Minimum (0)
        0x25, 0x01,                     // Logical 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 (variable,absolute)
        0x05, 0x01,                     // Usage Page (Generic Desktop)
        0x09, 0x01,                     // Usage (Pointer)
        0xA1, 0x00,                     // Collection ()
	0x15, 0x00,                     // Logical Minimum (0)
	0x27, 0xFF, 0xFF, 0, 0,         // Logical Maximum (65535)
        0x75, 0x10,                     // Report Size (16)
        0x95, 23,                       // Report Count (23)
        0x09, 0x30,                     // Usage (X)
        0x09, 0x31,                     // Usage (Y)
        0x09, 0x32,                     // Usage (Z)
        0x09, 0x33,                     // Usage (Rx)
        0x09, 0x34,                     // Usage (Ry)
        0x09, 0x35,                     // Usage (Rz)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x81, 0x02,                     // Input (variable,absolute)
        0xC0,                           // End Collection
        0x05, 0x01,                     // Usage Page (Generic Desktop)
        0x09, 0x39,                     // Usage (Hat switch)
        0x15, 0x00,                     // Logical Minimum (0)
        0x25, 0x07,                     // Logical Maximum (7)
        0x35, 0x00,                     // Physical Minimum (0)
        0x46, 0x3B, 0x01,               // Physical Maximum (315)
        0x65, 0x14,                     // Unit (20)
        0x75, 0x04,                     // Report Size (4)
        0x95, 0x01,                     // Report Count (1)
        0x81, 0x42,                     // Input (variable,absolute,null_state)
        0x05, 0x01,                     // Usage Page (Generic Desktop)
        0x09, 0x39,                     // Usage (Hat switch)
        0x15, 0x00,                     // Logical Minimum (0)
        0x25, 0x07,                     // Logical Maximum (7)
        0x35, 0x00,                     // Physical Minimum (0)
        0x46, 0x3B, 0x01,               // Physical Maximum (315)
        0x65, 0x14,                     // Unit (20)
        0x75, 0x04,                     // Report Size (4)
        0x95, 0x01,                     // Report Count (1)
        0x81, 0x42,                     // Input (variable,absolute,null_state)
        0x05, 0x01,                     // Usage Page (Generic Desktop)
        0x09, 0x39,                     // Usage (Hat switch)
        0x15, 0x00,                     // Logical Minimum (0)
        0x25, 0x07,                     // Logical Maximum (7)
        0x35, 0x00,                     // Physical Minimum (0)
        0x46, 0x3B, 0x01,               // Physical Maximum (315)
        0x65, 0x14,                     // Unit (20)
        0x75, 0x04,                     // Report Size (4)
        0x95, 0x01,                     // Report Count (1)
        0x81, 0x42,                     // Input (variable,absolute,null_state)
        0x05, 0x01,                     // Usage Page (Generic Desktop)
        0x09, 0x39,                     // Usage (Hat switch)
        0x15, 0x00,                     // Logical Minimum (0)
        0x25, 0x07,                     // Logical Maximum (7)
        0x35, 0x00,                     // Physical Minimum (0)
        0x46, 0x3B, 0x01,               // Physical Maximum (315)
        0x65, 0x14,                     // Unit (20)
        0x75, 0x04,                     // Report Size (4)
        0x95, 0x01,                     // Report Count (1)
        0x81, 0x42,                     // Input (variable,absolute,null_state)
        0xC0                            // End Collection
};

Regards,

Les
 
Oh, opps, I only put the usage number in 1 time. It should not be necessary to duplicate that whole section 4 times.

Does this work?

Code:
static uint8_t joystick_report_desc[] = {
        0x05, 0x01,                     // Usage Page (Generic Desktop)
        0x09, 0x04,                     // Usage (Joystick)
        0xA1, 0x01,                     // Collection (Application)
        0x15, 0x00,                     // Logical Minimum (0)
        0x25, 0x01,                     // Logical 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 (variable,absolute)
        0x05, 0x01,                     // Usage Page (Generic Desktop)
        0x09, 0x01,                     // Usage (Pointer)
        0xA1, 0x00,                     // Collection ()
        0x15, 0x00,                     // Logical Minimum (0)
        0x27, 0xFF, 0xFF, 0, 0,         // Logical Maximum (65535)
        0x75, 0x10,                     // Report Size (16)
        0x95, 23,                       // Report Count (23)
        0x09, 0x30,                     // Usage (X)
        0x09, 0x31,                     // Usage (Y)
        0x09, 0x32,                     // Usage (Z)
        0x09, 0x33,                     // Usage (Rx)
        0x09, 0x34,                     // Usage (Ry)
        0x09, 0x35,                     // Usage (Rz)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x81, 0x02,                     // Input (variable,absolute)
        0xC0,                           // End Collection
        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, 0x04,                     // Report Count (4)
        0x65, 0x14,                     // Unit (20)
        0x05, 0x01,                     // Usage Page (Generic Desktop)
        0x09, 0x39,                     // Usage (Hat switch)
        0x09, 0x39,                     // Usage (Hat switch)
        0x09, 0x39,                     // Usage (Hat switch)
        0x09, 0x39,                     // Usage (Hat switch)
        0x81, 0x42,                     // Input (variable,absolute,null_state)
        0xC0                            // End Collection
};
 
Oh, opps, I only put the usage number in 1 time. It should not be necessary to duplicate that whole section 4 times.

Does this work?

Code:
static uint8_t joystick_report_desc[] = {
        0x05, 0x01,                     // Usage Page (Generic Desktop)
        0x09, 0x04,                     // Usage (Joystick)
        0xA1, 0x01,                     // Collection (Application)
        0x15, 0x00,                     // Logical Minimum (0)
        0x25, 0x01,                     // Logical 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 (variable,absolute)
        0x05, 0x01,                     // Usage Page (Generic Desktop)
        0x09, 0x01,                     // Usage (Pointer)
        0xA1, 0x00,                     // Collection ()
        0x15, 0x00,                     // Logical Minimum (0)
        0x27, 0xFF, 0xFF, 0, 0,         // Logical Maximum (65535)
        0x75, 0x10,                     // Report Size (16)
        0x95, 23,                       // Report Count (23)
        0x09, 0x30,                     // Usage (X)
        0x09, 0x31,                     // Usage (Y)
        0x09, 0x32,                     // Usage (Z)
        0x09, 0x33,                     // Usage (Rx)
        0x09, 0x34,                     // Usage (Ry)
        0x09, 0x35,                     // Usage (Rz)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x09, 0x36,                     // Usage (Slider)
        0x81, 0x02,                     // Input (variable,absolute)
        0xC0,                           // End Collection
        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, 0x04,                     // Report Count (4)
        0x65, 0x14,                     // Unit (20)
        0x05, 0x01,                     // Usage Page (Generic Desktop)
        0x09, 0x39,                     // Usage (Hat switch)
        0x09, 0x39,                     // Usage (Hat switch)
        0x09, 0x39,                     // Usage (Hat switch)
        0x09, 0x39,                     // Usage (Hat switch)
        0x81, 0x42,                     // Input (variable,absolute,null_state)
        0xC0                            // End Collection
};

That fixes it more elegantly than my solution, but at least it showed you the way ;)

So now I can see all buttons, axis, sliders and hats, I am just having trouble reading them all.

In DirectX I can read the 6 normal axes, all 128 buttons and the 4 Hats, I can't seem to read any sliders though.

My rawInput code is still bugged so not sure yet.

Regards,

Les
 
Are you able to see 2 of the sliders in directx? It might be giving you the last 2, rather than the first 2.

Man it must be tough being right all the time ;)

Good call.

There was also a bug in my DirectX code for sliders anyway, which didn't help.

So now in DirectX I can read the 6 normal axes, all 128 buttons, 4 Hats, and 2 sliders (It does know there are 17 sliders present though, it just doesn't want to give me any data for them)

Regards,

Les
 
In case anyone else is following this Joystick thread, here's the latest code for Teensy 3.1. Just replace these 4 files in hardware/teensy/cores/teensy3 and your joystick will become 128 buttons, 6 axes, 17 sliders and 4 hats.
 

Attachments

  • extreme_joystick_test3.zip
    13 KB · Views: 2,721
  • ExtremeJoystickTest.ino
    3.2 KB · Views: 2,301
Tried the test program just now, version 091. It crashes on startup on my Win 7 test machine. :(

Edit: I tried this joystick test and also this one. Neither is able to recognize this huge joystick.

Edit again: I retested on Linux. "jstest-gtk /dev/input/js1" is able to recognize it, but only 64 buttons, and of course the 4 hats show up as the last 8 axes.

jstest.jpg
(click for full size)
 
Last edited:
Are any gamers or flight sim people following this thread? Can anyone test and see if those programs can access the many extra sliders?
I don't know about these sorts, but an avid robot builder is now following this. :) I want to create a remote (wireless, of course) long range robot controller. There are many things I am going to need to have easy control of. I need to get some components (sliders, pots, joysticks, etc.), and a couple more Teensy3.1, so I can play along with you guys. :)

8-Dale
 
So now in DirectX I can read the 6 normal axes, all 128 buttons, 4 Hats, and 2 sliders (It does know there are 17 sliders present though, it just doesn't want to give me any data for them)

Any chance you could post the latest version? I'd love to give it a try.

I'm considering trying to bring in a wider audience of people who might be interested and who might be able to help with testing what software can and can't work with this extreme joystick. Maybe Hack-a-Day would find this interesting?

I had hoped to publish this huge joystick in Teensyduino 1.18, but obviously more work is needed to figure out what software is compatible. It can't replace the normal joystick that works with pretty much all PC and Mac software, but if we can at least get a reliable test program on Windows, I'd love to add this as a new Tools > USB Type option in version 1.19.

Oh, also, here's a photo of the breakout board I'm using.

breakout.jpg
(click for full size)

It has "only" 14 pots and 34 sets of buttons... not nearly enough for all this joystick's functionality. Maybe I ought to make one with 23 pots and 128 buttons, using some sort of mux scheme to read them all?
 
23 pots and 128 buttons would be great Paul, I would like to make a HID Dj controller (as far as my music program is concerned any HID is ok) and this would be a great starting point, (my minimum want is 24 pots, my maximum would be 48 pots)

This may become the standard for DIY HID DJ controllers.

This will be open sauce won't it? *crosses fingers*
 
This will be open sauce won't it? *crosses fingers*

The source code is attached to reply #36. Just scroll up and grab it. ;)

To use it, first get Arduino 1.0.5. Then install Teensyduino 1.18-rc4. The replace 4 files in hardware/teensy/cores/teensy3 with the copies from reply #36. Open Arduino, and select Teensy 3.1 from Tools > Boards, and then Keyboard+Mouse+Joystick from Tools > USB Type.

Then open the example (also on reply #36) and click Upload to program it to your Teensy 3.1. You'll need to connect pots and buttons to the pins. Edit the example to your liking.
 
More sliders could be possible if the resolution were decreased. One of the common requests, after simply having more buttons and sliders, has been higher resolution. The existing joystick uses 10 bits. This extreme joystick uses 16 bit on all axes and sliders, to try to satisfy those requests.

Reducing to only 12 bits would allow 22 sliders. But are 5 more sliders worth the loss of resolution on all of them?

If your software could use 2 of these HID devices, that might be the best way to get to your goal.

Either way, can you give this a try with your DJ software and let me know if it works? I'd also like to know more about this software, if you can explain it. At this point, the extreme joystick appears to be working, but the main thing holding it back is testing what software can actually use it. So far, it's looking like "jstest" on Linux is the only program, and even that sees only 64 of the 128 buttons.
 
Thanks for the reply Paul

I can't help just yet as I don't have the components, I bought a Ardiuno at xmas and have been tinkering with other open source projects ( like Unojoy), the best I have managed is the standard limit for the microcontroller (6 pots @8bit, and a few buttons I have hardly any components)
So now I'm looking round at different projects before I buy big, (there is one guy using PICs for a HID project [impressive but no code yet], and there are several ardiuno MIDI projects) More than anything I'm looking for simplicity, Electronics is pretty easy for me but I am a coding noob. If teensy offers simplicity I'm sold.

As for resolution (a general DJ's perspective), 10 bit is absolutely fine for sliders/pots, (0.1% of any given slider) 8 bit is serviceable too (this matches the MIDI protocol) as for disc control (for scratching) 16 bit would be perfect but I can't see 12 bit being that bad (although if you want perfect scratching buy a turntable [personal perspective])


Regarding the DJ program (Virtual DJ), it is quite open ended, you use this
HID trace program (it is free to download and requires no install, this might be of some use for you)
to find which bit/bits/nibble/byte/short each button or axis controls in the HID report, then in a separate xml file you define a name for any given bit-short, further to that in another xml file you map a command to a defined name.

Anyway I'm waffling, I'll be in contact once I've been shopping.

*Edit*

Instead of using hardware for testing input why not write a sketch to simulate input (forgive me if this is patronising, I'm just sharing a thought)
 
Last edited:
The example sketch does rotate the hat switches automatically. More of that could be done on the other controls. I just haven't put the time into that, especially since I already had that test board sitting here (and especially since my main focus is wrapping up the 1.18 release and getting back to working on the audio library). The test board was built long before this effort on the joystick.

For DJ controllers, which have been very popular Teensy-based projects, most people have used the USB MIDI option. It's in the Tools > USB Type menu.
 
Last edited:
Any chance you could post the latest version? I'd love to give it a try.

I am still working on it, and I have been really struggling with the rawInput side. In actual fact rawInput is essentially rawHid as you have use the HidClass After several re-writes, I have the bulk of the code working, I just need to parse the actual report into the joystick values.

I don't really understand what Bitfield means here. For the axis and sliders it is always 0x02 but for the hats it's 0x42. This last part should be simple but my brain is frazzled, and math is not my strong point. I am sure I will get there in the end though.

Anyway I can pretty much confirm that rawInput would access all of the features of the Extreme Joystick. The device will work in DirectX, but you will just be limited to 2 sliders. Unfortunately MS hard coded the number of sliders you can access, and even though it knows there are 17 of them, it will only allow you to retrieve the data for the last 2. Another slight drawback is that DirectX doesn't seem to like lots of things changing at once. As part of my testing I was alternating between switching the 1st 64 buttons and the 2nd 64 on and off. DirectX only showed the last 15 or so changing, where as rawInput showed them all.

Regards,

Les
 
Thanks for that link, it has shone some light on multiplexing inputs, There are many MIDI projects but I would prefer HID as it is becoming the standard for controllers (faster transmission, higher resolution, easier to configure on the dj software) I'll keep an eye on this thread.
 
I don't really understand what Bitfield means here. For the axis and sliders it is always 0x02 but for the hats it's 0x42.

I believe bit field is documented on page 30-31 (40th and 41st pages of the PDF) of the HID 1.11 spec. I'll attach a copy to this message.

Bit 6 (0x40) is set to indicate a variable has a null state. Here's the text from the HID spec:

Indicates whether the control has a state in which it
is not sending meaningful data. One possible use of
the null state is for controls that require the user to
physically interact with the control in order for it to
report useful data. For example, some joysticks
have a multidirectional switch (a hat switch).
When a hat switch is not being pressed it is in a
null state. When in a null state, the control will
report a value outside of the specified Logical
Minimum and Logical Maximum (the most
negative value, such as -128 for an 8-bit value).
 

Attachments

  • HID1_11.pdf
    659.9 KB · Views: 15,228
I believe bit field is documented on page 30-31 (40th and 41st pages of the PDF) of the HID 1.11 spec. I'll attach a copy to this message.

Bit 6 (0x40) is set to indicate a variable has a null state. Here's the text from the HID spec:

Thanks Paul, I didn't have that pdf.

After sleeping on it, I have fixed another annoying problem this morning, so I am hopeful of a release over the next couple of days.

Regards,

Les
 
Will any of this code work on the teensy2+?

Not directly, no. But it should be fairly straightforward to port it back to Teensy2.

I'm still debating whether this belongs in a future version of Teensyduino's Tools > USB Type menu. Maybe when there's at least a Windows test program? It'd be nice to know at least a few programs that can really use so many sliders and buttons...
 
Back
Top