Many axis joystick

Can anyone see any problems with trying to use a rotary encoder to act as a Mouse.scroll() wheel in a Teensy build of a hybrid mouse-keyboard-gamepad-mega-joystick with more than 8 axes?

This should be possible. Certainly Teensy gives you the way to implement these HID interfaces and precisely control what actually gets sent to your PC.

To specifically answer your question, here are some challenges you might face.

If using Microsoft Windows, the default joystick driver might not recognize more than 8 axes, even though it's properly implemented on the Teensy side (assuming you've edited the code to enable the bigger HID joystick report - or otherwise edited the USB code).

In designing your code on the Teensy side, you'll want to avoid delays, so you're always being responsive to all the input signals. Simple as this sounds, real-world signals often aren't as nice as you might like and require "debouncing" or other techniques. Many people have gone down a path of adding delays in their code, which tends to work for only 1 signal, but then becomes really difficult when the delays to clean up 1 signal cause everything to be unresponsive to all the others. Teensyduino has features like elapsedMillis to make this easier, so if you need to do this sort of work I recommend using those and avoid delay().

HID provides fixed bandwidth. At best you can send 1000 events per second. That's usually plenty, but it can quickly become a limitation if you do things inefficiently. The joystick functions have a way to freeze, so you can change multiple settings and have it all go out the USB cable to your PC as a single packet.

If you do approach the bandwidth limits, those functions to send will end up waiting. If using the Encoder library, you may see the result of several changes at once. Likewise for other inputs, if those functions have to wait, your code may be "blind" to changes. Teensy's USB stack does buffer several outgoing packets, which gives you some flexibility. But as a general rule, if you try to sustain close to or more than 1000 updates per second, you will run into HID bandwidth limits and ultimately your code will suffer waits in the transmit functions.
 
Fantastic, thanks for the detailed reply Paul.

Sounds like I could reasonably expect to build something that will work using this approach. I'll see how I go.
Thanks for your work on the Teensy boards Paul, they're my favourite "do anything" solution. I always keep a couple on hand.
 
I tried to code a 8 axis joystick. I took the many axis joystick disabling all sliders beyond 2. Only data from the 6 axis are transmitted, sliders remain at zero
Joystick.JPG
 
Hello Paul, i replaced the files and now i'm getting a serial error, like it was not recognized:
"C:\\Program Files (x86)\\Arduino\\hardware\\teensy/../tools/arm/bin/arm-none-eabi-g++" -c -O2 -g -Wall -ffunction-sections -fdata-sections -nostdlib -MMD -fno-exceptions -felide-constructors -std=gnu++14 -Wno-error=narrowing -fno-rtti -mthumb -mcpu=cortex-m4 -fsingle-precision-constant -D__MK20DX256__ -DTEENSYDUINO=144 -DARDUINO=10807 -DF_CPU=96000000 -DUSB_EVERYTHING -DLAYOUT_US_ENGLISH "-IC:\\Users\\NICOLA~1\\AppData\\Local\\Temp\\arduino_build_113933/pch" "-IC:\\Program Files (x86)\\Arduino\\hardware\\teensy\\avr\\cores\\teensy3" "C:\\Users\\NICOLA~1\\AppData\\Local\\Temp\\arduino_build_113933\\sketch\\sbus2joystick.ino.cpp" -o "C:\\Users\\NICOLA~1\\AppData\\Local\\Temp\\arduino_build_113933\\sketch\\sbus2joystick.ino.cpp.o"
In file included from C:\Program Files (x86)\Arduino\hardware\teensy\avr\cores\teensy3/usb_serial.h:34:0,

from C:\Program Files (x86)\Arduino\hardware\teensy\avr\cores\teensy3/WProgram.h:54,

from C:\Users\NICOLA~1\AppData\Local\Temp\arduino_build_113933/pch/Arduino.h:6,

from C:\Users\NICOLA~1\AppData\Local\Temp\arduino_build_113933\sketch\sbus2joystick.ino.cpp:1:

C:\Program Files (x86)\Arduino\hardware\teensy\avr\cores\teensy3/usb_desc.h:297:48: error: 'NUM_ENDPOINTS' was not declared in this scope

extern const uint8_t usb_endpoint_config_table[NUM_ENDPOINTS];

Any idea?
thanks
 
I have a question.. I sort of asked this in another thread where I was pointed to here, so this will probably be a better place to ask this..

I have read through the thread and think I understand a bit about what is going on here but then again maybe not because I have never messed with this stuff..

Anyway, what I would like to do is Expand the USB HID Joystick interface to access at least 64 buttons, Maybe 128. Unlike most of the examples on here I am not really in need of extending the Axis Count, However I also need to retain serial functions for the device for other things I plan on doing.

I would LIKE TO do this to the Teensy LC as that is the primary device I am using, though I have noticed that most of this work has been done on the 3's so I am not sure of the possibility. I have located the USB headers information based on an earlier post but don't see anything specifically for the LC in there so any info on where to do adjustments would be helpful if it is even possible to do..

Because I am not familiar with HID headers pointers in where to research this or guidance on the changes would be greatly appreciated.

Thank you
 
I am no expert on this end of it. I have done more on the side of usbhost_t36 to try to handle different USB devices... I don't think there is much difference here between T3.x and TLC for this, they both use the teensy3 directory under cores.

But if I were doing it, I think some of the areas I would look to change include:

usb_desc.c - need to change the number of buttons... For example if you look for the JOYSTICK_SIZE==12 portion, you will see:
Code:
        0x15, 0x00,                     //   Logical Minimum (0)
        0x25, 0x01,                     //   Logical Maximum (1)
        0x75, 0x01,                     //   Report Size (1)
        0x95, 0x20,                     //   Report Count (32)
        0x05, 0x09,                     //   Usage Page (Button)
        0x19, 0x01,                     //   Usage Minimum (Button #1)
        0x29, 0x20,                     //   Usage Maximum (Button #32)
        0x81, 0x02,                     //   Input (variable,absolute)
So would try changing those 0x20 (32) to the size you need. Note: That will change the JOYSTICK_SIZE. Each 8 bits will add 1 byte to the size...
So you may want to either modify these tables and/or make another copy #elif JOYSTICK_SIZE == <YOUR SIZE>

You will need to update usb_desc.h to have the new size for your joyststick data...

You will probably need to update usb_joystick.h to handle the additional buttons, that is the code again has specific code for size ==12 and == 64 again not knowing enough of which way you wish to start from (like how many axis) not sure which one is easiest for you to start from.

My guess is probably the 64 one as the ==12 assumes all of the buttons fit into one uint32_t value and all of the axis are aligned to the 32 bit starting memory locations... Actually both assume a certain alignment in memory for where the axis values are stored...

Again there are probably other things I am missing, but that is where I would start
 
Yea that is my concern as in some ways it seems so simple , but is it really just a matter of changing the size of those parameters.. Thank you though on the information about the LC using the T3 core code as that one makes it seem feasible and two points me in the direction of the files to mess with..

I think I may be able to figure it out from there but if anyone else or you have any further information it is always appreciated.
 
Oh Hmm.. It looks like Paul has included the code for the extreme Joystick IN the core data as I was just looking at the usb_desc.c in the teensy3/Core/avr and noted this:

"
#elif JOYSTICK_SIZE == 64
// extreme joystick (to use this, edit JOYSTICK_SIZE to 64 in usb_desc.h)
// 128 buttons 16
// 6 axes 12
// 17 sliders 34
// 4 pov 2
"

This will be beyond of course what I generally would need but hey seems much easier now
 
More than 6 axis working?

Oh Hmm.. It looks like Paul has included the code for the extreme Joystick IN the core data as I was just looking at the usb_desc.c in the teensy3/Core/avr and noted this:

"
#elif JOYSTICK_SIZE == 64
// extreme joystick (to use this, edit JOYSTICK_SIZE to 64 in usb_desc.h)
// 128 buttons 16
// 6 axes 12
// 17 sliders 34
// 4 pov 2
"

This will be beyond of course what I generally would need but hey seems much easier now

Hi, your thread has helped me greatly in creating my own custom flight sim controls. I am having the issue of not being able to get more than 6 axis working at one time however and have tried tweaking the usb_joystick.h file with no success. I've tried the vanilla extreme joystick test and still cant get more than 6 axis working at once. Am I missing something here? Any help would be greatly appreciated!

- Silco
 
Hi, is it maybe possible to emulate an USB Hub and group the control axis like 4 throttle, pitch, mixture, oil & water cooler so that windows has no problem to find all axis, it would also make the settings in the actual sim easier? Thx
 
Somewhat of a newbie here. Planning on making a controller that is entirely standard aside from the addition of 6 more sliders.

I'm thinking of ordering a Teensy 4.0 just because. I haven't delved too deep into this thread yet, so I still barely know where to start, but I did see that everything here isn't compatible with Teensy 2.x boards, since it was done on 3.x. Will there be any compatibility problems if I were to do this on the 4.0?

Thanks
 
4.0 is very new, unsure how complete USB joystick support is. It also has ADC hardware that seems to take more effort to get good results out of. You certainly do not need the performance for this, so would suggest the LC or 3.2 would be a better fit. What may get complicated is your axis, and the number of them, both Teensy LC and 3.2 have only 10 analog inputs that are easy to access so 4 axis but two sliders will only just fit. You will also need to make decisions about how much hacking around you want to do making those 10 axis work. The less pretty solution may involve having two teensies sharing the axis in a more default configuration.

Certainly before buying anything would download and install https://www.pjrc.com/teensy/td_download.html teensyduino and put together some test code and make sure it compiles.
 
I am trying to make a 64-Button Joystick since days, but I am stuck to compilation errors.
I used the Arduino IDE 1.8.9 with Teensyduino 1.47 and 1.8.10 with 1.48 on Windows 10 with a teensy 3.2.
In Tools set the USB Type to "Keyboard + Mouse + Joystick" and tried different values in "Optimize:".

I started with a simple Joystick .ino demo and the default headers and .c files in ...\teensy\avr\cores\teensy3 and I got a 32-Button Joystick.ok.
Then I replaced the 4 files of the .zip of post 36 of this thread into the teensy3 dir and got compilation errors which were similar to zeflor's of post #108.
I fiddled around (don't remember) until (after a change to a version which I already tried before) until it compiled without an error for the 1st time and I uploaded it to the teensy.
I got a 32-button joystick with the hat moving (from the .ino file of post #36).
The I tried to customize the header and .c files for my needs but got compilation errors again.
I always copied back the "original" files of post 36 and sometimes it compiled without an error.

The strange thing: After a successful compile I just added a whitespace in usb_desc.c behind a line-comment(!) and it gave me a compilation error. Restored the "original" file and got no error.

I also installed v1.8.10 and v1.48 on a Mac, used the 4 files of post #36 and go a compilation error (only one try).
Are the files no longer valid for the newer Arduino IDE/teensduino versions , since this thread is pretty old...?

All in all I never had a 128-button joystick (which the unchanged files of post #36 should give). It has always 32 buttons. (also by setting the value 64 (for extreme) instead of 12 (don't remember, where)

Question 1:
I believe there is a pre-compiler cache that causes this problem. I do not know, how to tell the IDE to do a complete new compile.
Deleting any files in my Windows profile?
Starting the IDE new before compiling does not help.

Question 2:
I fiddled around in lots of tries and managed it sometimes to upload it to the teensy. But in joy.cpl I saw no changes.
Except plugging it into a "fresh" PC and there I saw my custom name for the joystick (no longer "Keyboard/Mouse/Joystick").
Then I saw the notes in this thread (and in usb_dec.h), that Windows caches the joystick informations and won't show new capabilities or name until the version or PRODUCT_ID has changed.

But where to change the version in usb_desc.c?
Trying different things ended often with a compilation-error (see question 1) or did not change anything windows recognized as a new joystick...

Is it the line 86 which is untouched: 0x16, 0x01, // bcdDevice?
Code:
#endif
        EP0_SIZE,                               // bMaxPacketSize0
        LSB(VENDOR_ID), MSB(VENDOR_ID),         // idVendor
        LSB(PRODUCT_ID), MSB(PRODUCT_ID),       // idProduct
        0x16, 0x01,                             // bcdDevice
        1,                                      // iManufacturer
        2,                                      // iProduct
        3,                                      // iSerialNumber
        1                                       // bNumConfigurations
};

changing to 0x16, 0x02 ? Or a different line?
How to count up a version-number?

Thanks in advance for Your help!
 
Last edited:
For question 2 the easiest way I find to do this with my midi devices is by changing the serial number, this is really simple for midi, but I don’t remember if it works for all devices types or not. For midi you can create a file called name.c in your project folder and this lets you change the manufacturer name, the product name, and the serial number.

Here’s an example of what that looks like:
Code:
 // To give your project a unique name, this code must be
// placed into a .c file (its own tab).  It can not be in
// a .cpp file or your main sketch (the .ino file).

#include "usb_names.h"

// Edit these lines to create your own name.  The length must
// match the number of characters in your custom name.

#define MANUFACTURER_NAME   {'O','p','e','n',' ','L','a','b','s'}
#define MANUFACTURER_NAME_LEN  9
#define MIDI_NAME   {'N','E','K','O'}
#define MIDI_NAME_LEN  4
#define SERIAL_NUM {'F','a','d','e','r',' ','P','a','n','e','l'}
#define SERIAL_NUM_LEN 11

// Do not change this part.  This exact format is required by USB.

struct usb_string_descriptor_struct usb_string_manufacturer_name = {
        2 + MANUFACTURER_NAME_LEN * 2,
        3,
        MANUFACTURER_NAME
};

struct usb_string_descriptor_struct usb_string_product_name = {
        2 + MIDI_NAME_LEN * 2,
        3,
        MIDI_NAME
};


struct usb_string_descriptor_struct usb_string_serial_number = {
        2 + SERIAL_NUM_LEN * 2,
        3,
        SERIAL_NUM
};

Like I say, this may or may not work for anything other than midi but if it does it the easiest way to change things per project instead of messing with the core files.
 
Well, I do not use the name.c file since it can be done in desc.h. But changing the MANUFACTURER_NAME or PRODUCT_NAME alone does not lead to a new devie in my case.
Anyway: Thanks for Your note, but I want to create a joystick, not a midi-device.c

But You statet: "...name.c in your project folder".
I try to put the customized core files into my project folder; but have no hope...
 
Indeed changing the name is not enough, it’s when the serial number gets changed that the device is registered as new so the existing cache from the old number isn’t used. Changing the serial number is also what you have to do to connect multiple of the same device types, devices are assigned by their serial numbers so everything else can be the same but the serial number has to be different between each device. That applies to all devices and not just midi, although this tends to come up more for midi devices in this forum since it’s more common to need to connect multiple to one system. So just changing the serial number is enough to get the device registered as “new” without having to delete any kind of cache files from the system and is the simplest way to do so.
 
Ok, I get a new configuration by editing the VENTOR_ID in usb_dec.h now (counting up in hex 0x16yy, yy= is now D3)
But I never get a Joystick that has more buttons than 32.

I had to make 2 changes to let it compile without errors:
in usb_desc.h I had to make the changes of zeflor's of post #108, to get no error
"...usb_desc.h:304:3: error: conflicting declaration 'typedef struct usb_descriptor_list_t usb_descriptor_list_t'"

Code:
#ifndef _usb_dec_t
#define _usb_dec_t
extern const uint8_t usb_endpoint_config_table[NUM_ENDPOINTS];

typedef struct {
	uint16_t	wValue;
	uint16_t	wIndex;
	const uint8_t	*addr;
	uint16_t	length;
} usb_descriptor_list_t;

extern const usb_descriptor_list_t usb_descriptor_list[];
#endif

But I am not sure, if that is a proper solution.

To get no error "TX_TIMEOUT is undefined", I had to bring the hole block of if-statements of the newer original file usb_joystick.c
Code:
#define TX_TIMEOUT_MSEC 30
#if F_CPU == 256000000
  #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 1706)
#elif F_CPU == 240000000
  #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 1600)
#elif F_CPU == 216000000
  #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 1440)
#elif F_CPU == 192000000
  #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 1280)
#elif F_CPU == 180000000
  #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 1200)
#elif F_CPU == 168000000
  #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 1100)
#elif F_CPU == 144000000
  #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 932)
#elif F_CPU == 120000000
  #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 764)
#elif F_CPU == 96000000
  #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 596)
#elif F_CPU == 72000000
  #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 512)
#elif F_CPU == 48000000
  #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 428)
#elif F_CPU == 24000000
  #define TX_TIMEOUT (TX_TIMEOUT_MSEC * 262)
#endif

So it compiles now.
But I do not get more than 32 buttons. Due to the typedef struct which only will defined when #ifndef _usb_dec_t?

And the hat does not move in a circle in joy.cpl, in the .ino the code is unchanged:
Code:
  // make the hat switch automatically move in a circle
  angle = angle + 1;
  if (angle >= 360) angle = 0;
  
  Joystick.hat(1, angle);
  Joystick.hat(2, angle);
  Joystick.hat(3, angle);
  Joystick.hat(4, angle);
  
  Joystick.send_now();

(yesterday it was working...)
 
Oh man,
it was working all the times (when having a successfull compile!
Some pages older I saw a screenshot from Pointy of a diagnostic tool showing 128 buttons. I just looked at the thumnail in the post and thought, this would be the UI of joy.cpl of Win7.
I just testet with joy.cpl and only saw 32 buttons...
No! It may look like joy.cpl but it is Pointy's Joystick Test!
Loading this one and testing my teensy is showing my 64 I defined. Yeah!
2 days of senseless debugging...

Now I can work in my sketch for 8 encoders and 48 buttons.
I will post it, when it's done to share my way to deal with the encoders
 
Hi everybody! Sorry for the marketing of another thread, but I thought it'd be better than a proper hijack :). Initially I was hoping to make a "translation" of a radio controller to USB gamepad/joystick, by utilizing 12 of the many axis provided here. But then I found out from the thread that this code won't work on a teensy 2.0. Google brought my to Les "Pointy"'s blog post on the subject of a custom Teensy++ 2.0 joystick, and based on that I started this thread to ask for your guidance on my project, and hopefully it will be of use to other as well!
 
Hello! I've been trying to use this forum to make a joystick, and have run into the issue that while using the modified descriptors I can't use the slider!
The code inside the loop is
Code:
Joystick.slider(0,analogRead(0));
delay(50);
I set the read resolution to 16 bit, and the pinmode to input_pullup
What is wrong here?

EDIT: note that the exact same circuit works when I change from using a slider to using the X, Y, or Z axes
 
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.

Have you updated these files to suit the Teensy 4.0?
If so, will they allow the correct use of analogReadResolution to set the 16bit setting?
 
Hi Paul,

first of all thank you for this excellent extreme joystick support.

I am using 13bits for my axes and its working quite nicely.

I was wondering if there was any progress on a tester for Windows 10. The default one in control panel doesn't show all sliders, buttons etc.

Cheers,
E.
 
Hi Guys,

Because of compatibility issues with my target software, I had to get rid of 64 buttons and the top hat switches.

After pulling my hair our for 3 days not knowing much about how usb descriptors worked, I finally got it sorted. I could not have figured it out without all your posts so thank you very much.

Here is my code so anyone who needs it can use it.

In usb_joystick.h add
Code:
#elif JOYSTICK_SIZE == 54
	void button(unsigned int num, bool val) {
		if (--num >= 64) return;
		uint32_t *p = usb_joystick_data + (num >> 5);
		num &= 0x1F;
		if (val) *p |= (1 << num);
		else *p &= ~(1 << num);
		if (!manual_mode) usb_joystick_send();
	}
	void X(unsigned int position) { analog16(0, position); }
	void Y(unsigned int position) { analog16(1, position); }
	void Z(unsigned int position) { analog16(2, position); }
	void Xrotate(unsigned int position) { analog16(3, position); }
	void Yrotate(unsigned int position) { analog16(4, position); }
	void Zrotate(unsigned int position) { analog16(5, position); }
	void slider(unsigned int num, unsigned int position) {
		if (--num >= 17) return;
		analog16(num + 6, position);
	}

and also at the end.
Code:
#if JOYSTICK_SIZE == 54
	void analog16(unsigned int num, unsigned int value) {
		if (value > 0xFFFF) value = 0xFFFF;
		uint16_t *p = (uint16_t *)(&usb_joystick_data[2]);
		p[num] = value;
                if (!manual_mode) usb_joystick_send();
	}
#endif

Note the position of the data has shifted because I reduced the buttons from 128 to 64

in usb_desc.h, change the joystick size
Code:
#define JOYSTICK_SIZE         54

In usb_desc.c, add the following
Code:
#elif JOYSTICK_SIZE == 54
// delta joystick  (to use this, edit JOYSTICK_SIZE to 54 in usb_desc.h)
//  64 buttons
//    6 axes
//   17 sliders
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, 0x40,                     // Report Count (128)
        0x05, 0x09,                     // Usage Page (Button)
        0x19, 0x01,                     // Usage Minimum (Button #1)
        0x29, 0x40,                     // 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, 0x17,                       // 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
        0xC0                            // End Collection
};

Cheers,
E.
 
Back
Top