Converting Thrustmaster F16 gameport joystick into USB with Leonardo

I bought an old Thrustmater F16 FLCS gameport joystick and want to convert it to a USB stick, using a Leonardo board. The buttons/hats use SPI and the two pots for Pitch and Bank will obviously connect to two analog inputs.

I found this code for Teensy http://forum.il2sturmovik.com/topic/1790-there-any-better-joystick-ms-sidewinder-force-feedback-2/#entry46472 which obviously relies on the “Joystick” code embedded in that board, so can’t be used with the Leonardo but might give some ideas:

/* USB FLCS Grip
   You must select Joystick from the "Tools > USB Type" menu
*/

// Buttons are muxed into shift registers, use the SPI protocol to read them
#include <SPI.h>

const int slaveSelectPin = 0;

unsigned int buttonInputs1;   // data read from SPI
unsigned int buttonInputs2;
unsigned int buttonInputs3;

// Use some macros to clean things up
#define S3   !(buttonInputs1 & 0x80)    /* Pinky Switch */
#define TG1  !(buttonInputs1 & 0x40)    /* Trigger 1 */
#define TG2  !(buttonInputs1 & 0x20)    /* Trigger 2 */
#define S1   !(buttonInputs1 & 0x10)    /* Nose Wheel Steering */
#define S4   !(buttonInputs1 & 0x08)    /* Paddle Switch */
#define S2   !(buttonInputs1 & 0x04)    /* Pickle */

#define H1D  !(buttonInputs2 & 0x80)    /* Trim */
#define H1R  !(buttonInputs2 & 0x40)
#define H1U  !(buttonInputs2 & 0x20)
#define H1L  !(buttonInputs2 & 0x10)
#define H4U  !(buttonInputs2 & 0x08)    /* CMS */
#define H4L  !(buttonInputs2 & 0x04)
#define H4D  !(buttonInputs2 & 0x02)
#define H4R  !(buttonInputs2 & 0x01)

#define H3D  !(buttonInputs3 & 0x80)    /* DMS */
#define H3R  !(buttonInputs3 & 0x40)
#define H3U  !(buttonInputs3 & 0x20)
#define H3L  !(buttonInputs3 & 0x10)
#define H2D  !(buttonInputs3 & 0x08)    /* TMS */
#define H2R  !(buttonInputs3 & 0x04)
#define H2U  !(buttonInputs3 & 0x02)
#define H2L  !(buttonInputs3 & 0x01)

// setup() runs once on boot
void setup() {
  // set the slaveSelectPin as an output:
  pinMode (slaveSelectPin, OUTPUT);
  // start the SPI library:
  SPI.begin();
  // configure the joystick to manual send mode.  This gives precise
  // control over when the computer receives updates, but it does
  // require you to manually call Joystick.send_now().
  Joystick.useManualSend(true);
}


// loop() runs for as long as power is applied
void loop() {
  // take the SS pin low to select the chip
  digitalWrite(slaveSelectPin,LOW);
  // send a value of 0 to read the SPI bytes
  buttonInputs1 = SPI.transfer(0x00);
  buttonInputs2 = SPI.transfer(0x00);
  buttonInputs3 = SPI.transfer(0x00);
  // take the SS pin high to de-select the chip:
  digitalWrite(slaveSelectPin,HIGH); 

  // Write to joystick buttons
  Joystick.button(1,  TG1);
  Joystick.button(2,  S2);
  Joystick.button(3,  S3);
  Joystick.button(4,  S4);
  Joystick.button(5,  S1);
  Joystick.button(6,  TG2);
  Joystick.button(7,  H2U);
  Joystick.button(8,  H2R);
  Joystick.button(9,  H2D);
  Joystick.button(10, H2L);
  Joystick.button(11, H3U);
  Joystick.button(12, H3R);
  Joystick.button(13, H3D);
  Joystick.button(14, H3L);
  Joystick.button(15, H4U);
  Joystick.button(16, H4R);
  Joystick.button(17, H4D);
  Joystick.button(18, H4L);
  //Joystick.button(19, H1U);
  //Joystick.button(20, H1R);
  //Joystick.button(21, H1D);
  //Joystick.button(22, H1L);
  
  // Determine Joystick Hat Position
  int angle = -1;

  if (H1U) {
    if (H1R) {
      angle = 45;
    } else if (H1L) {
      angle = 315;
    } else {
      angle = 0;
    }
  } else if (H1D) {
    if (H1R) {
      angle = 135;
    } else if (H1L) {
      angle = 225;
    } else {
      angle = 180;
    }
  } else if (H1R) {
    angle = 90;
  } else if (H1L) {
    angle = 270;
  }
  Joystick.hat(angle);
  
  // Because setup configured the Joystick manual send,
  // the computer does not see any of the changes yet.
  // This send_now() transmits everything all at once.
  Joystick.send_now();
}

I also found this code for using a Leonardo as a USB Joystick http://www.imaginaryindustries.com/blog/?p=80 and that works as expected and sends random output with nothing connected. I have no idea how to translate the Teensy code and merge it with the Leonardo code to achieve what I want though, so was wondering if there’s anyone here with some understanding of Arduino code who could help me?

I don’t really know where to start but in USBAPI.h, the main joystick-related code seems to be:

//	Joystick
//  Implemented in HID.cpp
//  The list of parameters here needs to match the implementation in HID.cpp


typedef struct JoyState 		// Pretty self explanitory. Simple state to store all the joystick parameters
{
	uint8_t		xAxis;
	uint8_t		yAxis;
	uint8_t		zAxis;

	uint8_t		xRotAxis;
	uint8_t		yRotAxis;
	uint8_t		zRotAxis;

	uint8_t		throttle;
	uint8_t		rudder;

	uint8_t		hatSw1;
	uint8_t		hatSw2;

	uint32_t	buttons;		// 32 general buttons

} JoyState_t;

class Joystick_
{
public:
	Joystick_();

	void setState(JoyState_t *joySt);

};
extern Joystick_ Joystick;

and in HID.cpp:

//	Joystick
//  Usage: Joystick.move(inputs go here)
//
//  The report data format must match the one defined in the descriptor exactly
//  or it either won't work, or the pc will make a mess of unpacking the data
//

Joystick_::Joystick_()
{
}


#define joyBytes 13 		// should be equivalent to sizeof(JoyState_t)

void Joystick_::setState(JoyState_t *joySt)
{
	uint8_t data[joyBytes];
	uint32_t buttonTmp;
	buttonTmp = joySt->buttons;

	data[0] = buttonTmp & 0xFF;		// Break 32 bit button-state out into 4 bytes, to send over USB
	buttonTmp >>= 8;
	data[1] = buttonTmp & 0xFF;
	buttonTmp >>= 8;
	data[2] = buttonTmp & 0xFF;
	buttonTmp >>= 8;
	data[3] = buttonTmp & 0xFF;

	data[4] = joySt->throttle;		// Throttle
	data[5] = joySt->rudder;		// Steering

	data[6] = (joySt->hatSw2 << 4) | joySt->hatSw1;		// Pack hat-switch states into a single byte

	data[7] = joySt->xAxis;		// X axis
	data[8] = joySt->yAxis;		// Y axis
	data[9] = joySt->zAxis;		// Z axis
	data[10] = joySt->xRotAxis;		// rX axis
	data[11] = joySt->yRotAxis;		// rY axis
	data[12] = joySt->zRotAxis;		// rZ axis

	//HID_SendReport(Report number, array of values in same order as HID descriptor, length)
	HID_SendReport(3, data, joyBytes);
	// The joystick is specified as using report 3 in the descriptor. That's where the "3" comes from
}

bump. Anyone able to help?

I bought an old Thrustmater F16 FLCS gameport joystick and want to convert it to a USB stick, using a Leonardo board.

Probably cheaper and a lot more practical to look for a game port to USB adapter on ebay.

zoomkat:

I bought an old Thrustmater F16 FLCS gameport joystick and want to convert it to a USB stick, using a Leonardo board.

Probably cheaper and a lot more practical to look for a game port to USB adapter on ebay.

http://www.ebay.com/sch/i.html?_from=R40&_trksid=p2050601.m570.l1311.R4.TR4.TRC2.A0.H1.Xusb+gameport&_nkw=gameport+to+usb+adapter&_sacat=0

Well it's not going to be cheaper as I already have a spare Pro Micro. There's a number of other reasons why that's not a solution for me as well:

  1. I don't know if the existing circuitry between the stick and the gameport even works
  2. At some point, I might want to put the stick on a MS FFB2 base, when it wouldn't be practical to retain the existing circuitry
  3. Where's the fun it that?

You might also ask what you have learned... Too.
The Teensy is cheap enough @ 19.80 (cheaper on OSH Park) for the features and speed as it can overclock to 96 Mhz while supporting USB serial...
The 1.05 IDE supports it well.... I installed two instances of the 1.05 IDE one for my Arduino stuff and one for my Teensy code.
This was a personal preference Primarily because I have a lot of unused storage area... and I wanted to keep stuff for the Teensy unique...
And there is an Arduino compatible breakout board sold on Tindie for about $6.50 bare. There are some DIL headers required so I bought the header kit for $1.20 (as I remember) and it's a nice board Too.

Doc

Docedison:
You might also ask what you have learned... Too.

Indeed.

The Teensy is cheap enough @ 19.80 (cheaper on OSH Park) for the features and speed as it can overclock to 96 Mhz while supporting USB serial...
The 1.05 IDE supports it well.... I installed two instances of the 1.05 IDE one for my Arduino stuff and one for my Teensy code.
This was a personal preference Primarily because I have a lot of unused storage area... and I wanted to keep stuff for the Teensy unique...
And there is an Arduino compatible breakout board sold on Tindie for about $6.50 bare. There are some DIL headers required so I bought the header kit for $1.20 (as I remember) and it's a nice board Too.

If no-one can help me adapt the code for the Pro Micro, I'll have to just buy a Teensy. As you say, they're not exactly expensive but it seems a shame when I already have a spare Pro Micro that would do the job and even if I didn't, they're about a third of the cost of the Teensy.

That teensy code doesn't contain any code for the joystick X, Y pots but maybe that's handled by the in-built code that's activated by selecting Joystick from the Tools > USB menu. If not, I don't imagine it will be very difficult to add the necessary code, certainly nothing like the SPI code needed for the buttons and hats.

I Think you are making a wise decision. Be sure to look at the OSH Park price.. Paul Stoffregen seems to endorse them too.
@ $`17.00 They're a steal.. I bought two.
One down and second from the right..Search For DIY Hardware Products on Tindie.
The construction does require fair to good soldering skills but the assy diagrams are more than adequate.
Buy the header kit Too. a full set of parts is only $4.95 more including the headers. Or $11.40 + $4.00 shipping from AU.
It looks nice and goes together well, Or at least it did for me.

Doc

That takes me to Teensy 3/LC Proto board, Arduino Shield, nRF24L01+ from pico on Tindie

I don't see anything on that page for $17.

Found them on OSH Park though: http://store.oshpark.com/products/teensy-3-1

First class (no tracking) to the UK is only $2, making the total $19 or £11.81, which is good as if the total is over £15, I'll get hit with 20% VAT + an £8 handling charge by Royal Mail! With no tracking however, it doesn't say what will happen if the order doesn't arrive and I don't really like risking even £11.81 without knowing there's insurance in case it doesn't get here.

I was thinking of getting it from here: http://www.adafruit.com/product/1625

They have the cheapest shipping I've found, at $12.50 for Airmail with insurance. I'm in the UK, so that comes to about £20.50 but that's over the £15 import limit.

So it'll just be easier to buy it from a UK seller for £19.80 http://www.ebay.co.uk/itm/Adafruit-Teensy-3-1-header-/161393801841?pt=UK_Computing_Other_Computing_Networking&hash=item2593d1fa71#shpCntId

One reason I wanted to adapt the code for my Pro Micro is because then I could use it with a Mega and just use one board for the joystick and a lot of other switches for my cockpit but it's probably easier to give the stick it's own board.

One down and second from the right..Search For DIY Hardware Products on Tindie.
The construction does require fair to good soldering skills but the assy diagrams are more than adequate.
Buy the header kit Too. a full set of parts is only $4.95 more including the headers. Or $11.40 + $4.00 shipping from AU.
It looks nice and goes together well, Or at least it did for me.

Doc

I 'thought I was quite clear about either OSH Park or PJRC for the Teensy.
I wasn't aware of the Adafruit offering or I would also have mentioned it too.
I never indicated that Tindie sold the Teensy at all although you can buy a wired and tested Teensy BOB from another of the Teensy offerings Here:Teensy 3.1 Breakout from Tall Dog on Tindie
Fully assembled with Teensy @ ~ $40.00...
I do see my mistake.. this is where the others are Buy and Sell DIY Hardware Products - Tindie and I humbly apologize.

Doc

So I've got my Teensy 3.1 and wiring up the SPI interface to the stick is easy enough SPI Arduino Library, connecting SPI devices to Teensy There's no code in the original sketch for the X and Y axis though, so do I just need to add a couple of lines, so the end looks like this?

// read analog inputs and set X-Y position
  Joystick.X(analogRead(0));
  Joystick.Y(analogRead(1));
  
  // Because setup configured the Joystick manual send,
  // the computer does not see any of the changes yet.
  // This send_now() transmits everything all at once.
  Joystick.send_now();
}

The guides all seem to say to select USB - Joystick in Arduino but I don't have that, only "Keyboard + Mouse + Joystick" or "Serial + Keyboard + Mouse + Joystick", so will either of those work just as well?

Then how do I wire up the pots? The original thread http://forum.il2sturmovik.com/topic/1790-there-any-better-joystick-ms-sidewinder-force-feedback-2/#entry46472 doesn't discuss this and refers to the stick's SPI interface needing +5v, which I can take from the Teensy's USB +ve but the analog inputs only tolerate +3.3v Teensy 3.2 & 3.1: New Features so I don't think I can feed +5v into the pots, so should I use the "3.3v (100 ma max)" or the 3.3v pin at the opposite end of the board to the USB connector? I presume I'd connect +ve to one side of the pot, GND to the other and the middle pin to A0 or A1?

Well I thought I’d test the buttons before I tackle the pots. Using this code:

/* USB FLCS Grip
   You must select Joystick from the "Tools > USB Type" menu
*/

// Buttons are muxed into shift registers, use the SPI protocol to read them
#include <SPI.h>

const int slaveSelectPin = 10;

unsigned int buttonInputs1;   // data read from SPI
unsigned int buttonInputs2;
unsigned int buttonInputs3;

// Use some macros to clean things up
#define S3   !(buttonInputs1 & 0x80)    /* Pinky Switch */
#define TG1  !(buttonInputs1 & 0x40)    /* Trigger 1 */
#define TG2  !(buttonInputs1 & 0x20)    /* Trigger 2 */
#define S1   !(buttonInputs1 & 0x10)    /* Nose Wheel Steering */
#define S4   !(buttonInputs1 & 0x08)    /* Paddle Switch */
#define S2   !(buttonInputs1 & 0x04)    /* Pickle */

#define H1D  !(buttonInputs2 & 0x80)    /* Trim */
#define H1R  !(buttonInputs2 & 0x40)
#define H1U  !(buttonInputs2 & 0x20)
#define H1L  !(buttonInputs2 & 0x10)
#define H4U  !(buttonInputs2 & 0x08)    /* CMS */
#define H4L  !(buttonInputs2 & 0x04)
#define H4D  !(buttonInputs2 & 0x02)
#define H4R  !(buttonInputs2 & 0x01)

#define H3D  !(buttonInputs3 & 0x80)    /* DMS */
#define H3R  !(buttonInputs3 & 0x40)
#define H3U  !(buttonInputs3 & 0x20)
#define H3L  !(buttonInputs3 & 0x10)
#define H2D  !(buttonInputs3 & 0x08)    /* TMS */
#define H2R  !(buttonInputs3 & 0x04)
#define H2U  !(buttonInputs3 & 0x02)
#define H2L  !(buttonInputs3 & 0x01)

// setup() runs once on boot
void setup() {
  // set the slaveSelectPin as an output:
  pinMode (slaveSelectPin, OUTPUT);
  // start the SPI library:
  SPI.begin();
  // configure the joystick to manual send mode.  This gives precise
  // control over when the computer receives updates, but it does
  // require you to manually call Joystick.send_now().
  Joystick.useManualSend(true);
}


// loop() runs for as long as power is applied
void loop() {
  // take the SS pin low to select the chip
  digitalWrite(slaveSelectPin,LOW);
  // send a value of 0 to read the SPI bytes
  buttonInputs1 = SPI.transfer(0x00);
  buttonInputs2 = SPI.transfer(0x00);
  buttonInputs3 = SPI.transfer(0x00);
  // take the SS pin high to de-select the chip:
  digitalWrite(slaveSelectPin,HIGH); 

  // Write to joystick buttons
  Joystick.button(1,  TG1);
  Joystick.button(2,  S2);
  Joystick.button(3,  S3);
  Joystick.button(4,  S4);
  Joystick.button(5,  S1);
  Joystick.button(6,  TG2);
  Joystick.button(7,  H2U);
  Joystick.button(8,  H2R);
  Joystick.button(9,  H2D);
  Joystick.button(10, H2L);
  Joystick.button(11, H3U);
  Joystick.button(12, H3R);
  Joystick.button(13, H3D);
  Joystick.button(14, H3L);
  Joystick.button(15, H4U);
  Joystick.button(16, H4R);
  Joystick.button(17, H4D);
  Joystick.button(18, H4L);
  //Joystick.button(19, H1U);
  //Joystick.button(20, H1R);
  //Joystick.button(21, H1D);
  //Joystick.button(22, H1L);
  
  // Determine Joystick Hat Position
  int angle = -1;

  if (H1U) {
    if (H1R) {
      angle = 45;
    } else if (H1L) {
      angle = 315;
    } else {
      angle = 0;
    }
  } else if (H1D) {
    if (H1R) {
      angle = 135;
    } else if (H1L) {
      angle = 225;
    } else {
      angle = 180;
    }
  } else if (H1R) {
    angle = 90;
  } else if (H1L) {
    angle = 270;
  }
  Joystick.hat(angle);
  
  // Because setup configured the Joystick manual send,
  // the computer does not see any of the changes yet.
  // This send_now() transmits everything all at once.
  Joystick.send_now();
}

and with Brown connected to Vin, Green to GND, Orange to SCK (Pin 13), Red to SS (Pin 10) and Yellow to DIN/MISO (12).

Unfortunately, that doesn’t work and the only button that does anything is the red pinky button at the base of the stick, which triggers on all buttons 1-18 and upper-right diagonal on the hat as well!

Anyone got any ideas as to what might be wrong?

Look at this project Arduino-PC-Gameport-HID. I test it with six button gamepad and joystick with hat.
Project contaion complete schematic and code part.

Thanks. I managed to get it working by editing the code as described here: