Note: staff-provided content does not represent an official statement from FARGOS Development, LLC. The policy on staff-authored content can be found here.
Go back to Geoff's home page.
Note: this is one of a set of three related notes on converting Thrustmaster F-22 Pro joysticks, Thrustmaster F-16 TQS and CH Pro Rudder Pedals from obsolete game port interfaces to modern USB-based connectivity. They use an Adafruit ItsyBitsy 32u4 5V board to interface to the existing game controller circuitry and publish the respective state as a USB Human Interface Device.
This set of conversions makes provision for a momentary push button used to trigger a self-calibration cycle. The accompanying software will react to such a button press by flashing the indicator LED rapidly and monitoring the perceived bounds of the various analog inputs. The user should exercise the limits of all of the respective axis associated with the device within the calibration period (typically 15 seconds). The obtained values are saved persistently to the device's EEPROM, thus enabling calibration to survive power cycle events.
The Thrustmaster F-22 Pro joystick was a high-quality, programmable joystick available in the 1990's that used the game port interface as well plugging into the keyboard port (either the old 5-pin DIN connector or the PS/2 port). It could be used with a Thrustmaster F-16 Throttle Quadrant System as well as rudder pedals, which made for some complicated daisy-chained wiring harnesses. Unfortunately, Microsoft stopped supported the game port interface with the release of Microsoft Vista in 2006.
The programming capabilities of the Thrustmaster F-22 Pro joystick and F-16 Throttle Quadrant System were quite feature rich. Most of the definition language was focused on emitting characters in response to button presses or movement of the various potentiometers. None of those capabilities are reproduced by this USB conversion. Instead, all of the buttons and the axes are exposed and either a modern game allows the user to customize their control setup or one can make use of third-party software such as AntiMicroX as an intermediate device layer.
Rather than make use of black-box software such as MMJoy2, the decoding of electrical signals is handled via an Arduino sketch and exposed using the Arduino Joystick library. The Arduino sketch implementing the control logic is found at the bottom of this document.
The F-22 Pro Joystick contains two potentiometers that are used to to measure the amount of deflection from center on the X and Y axes. There are three wires to each potentiometer that carry the voltage in, ground and relative voltage out, which varies depending on the position of the joystick handle. Because these are soldered to original circuit board, the wires must be either cut or unsoldered from the circuit board or potentiometers. The approach documented here was to cut them off cleanly place them side-by-side into an IDC plug.
| Color | Function | Board Pin |
|---|---|---|
| White | +5 VDC | 5V |
| Gray | X-Axis | A2 |
| Purple | Ground | GND |
| Yellow | +5 VDC | 5V |
| Green | Y-Axis | A3 |
| Blue | Ground | GND |
The F-22 Pro joystick handle has 4 buttons and a two-position trigger along with 4 hat switches. These are represented as 22 bits of state, with a 0 indicating that the corresponding button is being depressed. The button state is transmitted using 5 wires that are connected to a chain of three 4021BE shift registers embedded in the handle. Two of the wires carry the voltage in and ground, while a third is used to latch the state when performing a measurement. The board's native Serial Peripheral Interface (SPI) hardware is used to read out 24 bits of state, which means one is forced to use the SCK and MISO pins for their intended function.
Like the joystick potentiometers, the 5 handle wires are soldered to the circuit board. The approach documented here was to cut cleanly across the 5-wire cable and position them into an IDC plug.
| Color | Function | Board Pin |
|---|---|---|
| Brown | +5 VDC | 5V |
| Red | Latch | 7 |
| Orange | Clock | SCK |
| Yellow | Data | MISO |
| Green | Ground | GND |
Optional self-calibration is supported. A calibration cycle is triggered by depressing a normally open momentary push button switch. Feedback is provided via an indicator LED.
| Function | Board Pin | Notes |
|---|---|---|
| Calibrate | 10 | Other side of switch is connected to ground |
| LED | 11 | Other side of LED is connected to ground |
The means to connect the 11 wires is personal preference. The images shown below illustrate the use of 10-pin IDC connectors for the joystick axes and button cables and 2-pin JST connectors for the indicator LED and calibration request push button. The 5 wires from the button shift registers were cut to disconnect them from the original control board and then spliced into half of a 10-wire ribbon cable. This makes it easy to ensure proper positioning in the 10-pin IDC plug. The 6 wires comprising the joysticks were carefully positioned into another 10-pin IDC plug.
The source code for the F-22 Pro joystick interface is illustrated below. The most current release can be retrieved from this F22joystick.ino download link. The .ino files are really C++ source with an alternate file suffix to permit association with the Arduino IDE application.
The comments within the source code provide more detail that will not be repeated here.
For the purposes of programming, the Adafruit ItsyBitsy 32u4 5V is nearly identical to an Arduino Leonardo. Because we want the resulting device to appear correctly named under the Game Controllers control panel, a custom entry is added to the local boards.txt file. It is a duplicate of the leonardo board entry with changes made to the build, build.vid, build.pid and build.usb_product lines. The changes are highlighted below.
itsybitsy_f22.name=F22 Pro Thrustmaster atmega32u4
itsybitsy_f22.build.vid=0x2341
itsybitsy_f22.build.pid=0x1022
itsybitsy_f22.build.usb_product="Thrustmaster F-22 Pro"
If the behavior of any of the axes is the reverse of your intent, just alter the corresponding INVERT_X_AXIS or INVERT_Y_AXIS #define.
/*! \brief Thrustmaster F-22 joystick USB conversion using * Adafruit ItsyBitsy 32u4 5V. https://www.adafruit.com/product/3677? * * \author Geoff Carpenter gcc@fargos.net http://www.fargos.net/gcc.html * * Requires additions to boards.txt found in: * "C:\Users\${USER}\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.8\boards.txt" * Entry is a clone of leonardo board, with modified name, build.vid, * build.pid and build.usb_product. itsybitsy_f22.name=F22 Pro Thrustmaster atmega32u4 itsybitsy_f22.vid.0=0x2341 itsybitsy_f22.pid.0=0x0036 itsybitsy_f22.vid.1=0x2341 itsybitsy_f22.pid.1=0x8036 itsybitsy_f22.vid.2=0x2A03 itsybitsy_f22.pid.2=0x0036 itsybitsy_f22.vid.3=0x2A03 itsybitsy_f22.pid.3=0x8036 itsybitsy_f22.upload_port.0.vid=0x2341 itsybitsy_f22.upload_port.0.pid=0x0036 itsybitsy_f22.upload_port.1.vid=0x2341 itsybitsy_f22.upload_port.1.pid=0x8036 itsybitsy_f22.upload_port.2.vid=0x2A03 itsybitsy_f22.upload_port.2.pid=0x0036 itsybitsy_f22.upload_port.3.vid=0x2A03 itsybitsy_f22.upload_port.3.pid=0x8036 itsybitsy_f22.upload_port.4.board=leonardo itsybitsy_f22.xupload.tool=avrdude itsybitsy_f2x2.upload.tool.default=avrdude itsybitsy_xf22.upload.tool.network=arduino_ota itsybitsxy_f22.upload.protocol=avr109 itsybixtsy_f22.upload.maximum_size=28672 itsyxbitsy_f22.upload.maximum_data_size=2560 itxsybitsy_f22.upload.speed=57600 xitsybitsy_f22.upload.disable_flushing=true itsybitsy_f22.upload.use_1200bps_touch=true itsybitsy_f22.upload.wait_for_upload_port=true itsybitsy_f22.bootloader.tool=avrdude itsybitsy_f22.bootloader.tool.default=avrdude itsybitsy_f22.bootloader.low_fuses=0xff itsybitsy_f22.bootloader.high_fuses=0xd8 itsybitsy_f22.bootloader.extended_fuses=0xcb itsybitsy_f22.bootloader.file=caterina/Caterina-Leonardo.hex itsybitsy_f22.bootloader.unlock_bits=0x3F itsybitsy_f22.bootloader.lock_bits=0x2F itsybitsy_f22.build.mcu=atmega32u4 itsybitsy_f22.build.f_cpu=16000000L itsybitsy_f22.build.vid=0x2341 itsybitsy_f22.build.pid=0x1022 itsybitsy_f22.build.usb_product="Thrustmaster F-22 Pro" itsybitsy_f22.build.board=AVR_LEONARDO itsybitsy_f22.build.core=arduino itsybitsy_f22.build.variant=leonardo itsybitsy_f22.build.extra_flags={build.usb_flags} */ /* NOTE: button state is obtained from taking a parallel snapshot of the * on/off states and reading them serially from a set of 4021BE shift registers. * The hardware SPI interface is exploited to extract the bitstream, which means * one is forced to use the SCLK and MISO pins, but one does have the freedom * to choose the pin used to latch the state. * * The shift registers in the handle are connected via a set of five (5) * distinctly colored wires. * * + The brown wire is fed +5 VDC. * + The green wire is connected to ground. * + The latch/chip select signal is connected via the red wire. As noted above, * there is some freedom selecting the GPIO pin to be used for this LATCH_PIN. * + The orange wire carries the CLOCK_PIN signal and should be connected * to SCLK when using the native SPI interface. * + The yellow wire carries the bitstream representing the button states * to the DATA_PIN and should be connected to MISO when using the * native SPI hardware. */ /*! \brief GPIO Pin used for shift register clock */ #define CLOCK_PIN SCK // orange wire /*! \brief GPIO pin used to read switch state bits */ #define DATA_PIN MISO // yellow wire /*! \brief GPIO pin used to latch button state when held low. */ #define LATCH_PIN 7 // red /* There are two potentiometers in the handle that are used to encode the * left/right and up/down axis values. Each potentiometer is connected to * ground at one end and the highest input signal at the other. * The middle tap is connected to one of the analog GPIO pins to read the * relative position of the joystick. The voltage fed to the potentiometers * depends upon the MCU in use; 3.3 volts is common, but 5 volts would be used * with an Adafruit ItsyBitsy 32u4 5V. */ /*! \brief GPIO pin to read X-axis analog signal. */ #define X_AXIS_PIN A2 /*! \brief GPIO pin to read Y-axis analog signal. */ #define Y_AXIS_PIN A3 /*! \brief Enable manual calibration */ #define SUPPORT_CALIBRATION 1 /*! \brief GPIO pin used with momentary push button to trigger manual calibration. The button is connected to ground and this GPIO pin. */ #define CALIBRATE_BUTTON_PIN 10 /*!\brief GPIO pin used to drive an indicator LED. */ #define INDICATOR_LED 11 /*! \brief Amount of time manual calibration cycle lasts in milliseconds */ #define CALIBRATION_DURATION_MS (15 * 1000) /*! \brief Sample rate per second. Not guaranteed to be achieved, but rate will be no faster. Used to derive POLL_DELAY_MS. */ #define SAMPLE_RATE_PER_SECOND 100 /*! brief Delay between polling cycles in milliseconds. Normally derived from SAMPLE_RATE_PER_SECOND. */ #define POLL_DELAY_MS (1000 / (SAMPLE_RATE_PER_SECOND)) /*! \brief Set to 0 if only the primary hat switch should be exposed. The remaining are exposed as distinct buttons.*/ #define EXPOSE_ALL_HAT_SWITCHES 0 #if EXPOSE_ALL_HAT_SWITCHES == 1 #define F22_BUTTONS (6) #define F22_HAT_SWITCHES 4 /* hat switches are reported as 0, 45, 90, 135, 180, 225. 270, 315 degrees*/ #else #define F22_BUTTONS (6 + (3 * 4)) #define F22_HAT_SWITCHES 1 /* hat switches are reported as 0, 45, 90, 135, 180, 225. 270, 315 degrees*/ #endif /* Unique id for device to be handed to Joystick constructor. */ #define MY_F22_JOYSTICK_ID 4 #define LOG_ENABLED 1 /* These log interfaces are compatible with the advanced thread-safe * logging API made available by FARGOS Development, LLC. * See http://www.fargos.net/documents/FARGOSutilsLibrary.html */ #if LOG_ENABLED > 0 #include <Streaming.h> #if LOG_ENABLED > 2 #define LOG_COUT(level) Serial << F(__FILE__) << F(":") << __LINE__ << F("\t") << F(#level) << F("\t") #else #define LOG_COUT(level) Serial << F(":") << __LINE__ << F("\t") << F(#level) << F("\t") #endif #define LOG_ENDLINE endl #endif /*! \brief Console baud rate * * This value should match the baud rate selected in the Arduino IDE's * serial monitor window or a dedicated terminal program like Putty. */ #define CONSOLE_BAUD_RATE 115200 #include <Joystick.h> #include <SPI.h> #include <EEPROM.h> #define MAX_ANALOG_VALUE 1023 #define ANALOG_MIDPOINT (((MAX_ANALOG_VALUE + 1) / 2) - 1) #define MAX_AXIS_VALUE (((MAX_ANALOG_VALUE + 1) / 2) - 1) #define MIN_AXIS_VALUE (-MAX_AXIS_VALUE) #define INVERT_X_AXIS false #define INVERT_Y_AXIS true // Initialize the Joystick (with 8 buttons enabled) static Joystick_ Joystick(MY_F22_JOYSTICK_ID, JOYSTICK_TYPE_JOYSTICK, F22_BUTTONS, F22_HAT_SWITCHES, true, true, false, false, false, false, false, false, false, false, false); static unsigned long startCalibrateMode_ms; static uint8_t indicatorState; static uint32_t indicatorBlinkCount; static uint32_t indicatorBlinkRate; static unsigned long indicatorBlinkUntilMillis; static const char compiledOnDate[6 + 1] = { // YY year __DATE__[9], __DATE__[10], // First month letter, Oct Nov Dec = '1' otherwise '0' (__DATE__[0] == 'O' || __DATE__[0] == 'N' || __DATE__[0] == 'D') ? '1' : '0', // Second month letter (__DATE__[0] == 'J') ? ((__DATE__[1] == 'a') ? '1' : // Jan, Jun or Jul ((__DATE__[2] == 'n') ? '6' : '7')) : (__DATE__[0] == 'F') ? '2' : // Feb (__DATE__[0] == 'M') ? (__DATE__[2] == 'r') ? '3' : '5' : // Mar or May (__DATE__[0] == 'A') ? (__DATE__[1] == 'p') ? '4' : '8' : // Apr or Aug (__DATE__[0] == 'S') ? '9' : // Sep (__DATE__[0] == 'O') ? '0' : // Oct (__DATE__[0] == 'N') ? '1' : // Nov (__DATE__[0] == 'D') ? '2' : // Dec 0, // First day letter, replace space with digit __DATE__[4] == ' ' ? '0' : __DATE__[4], // Second day letter __DATE__[5], '\0' }; #define TOTAL_AXES 2 static const uint8_t axisPin[TOTAL_AXES] = { X_AXIS_PIN, Y_AXIS_PIN }; static const bool invertAxis[TOTAL_AXES] = { INVERT_X_AXIS, INVERT_Y_AXIS }; struct AxisCalibrationData { uint16_t minCalibration; uint16_t maxCalibration; uint16_t centerCalibration; }; static AxisCalibrationData calibrationData[TOTAL_AXES] = { { 0, MAX_ANALOG_VALUE, (MAX_ANALOG_VALUE + 1) / 2 }, { 0, MAX_ANALOG_VALUE, (MAX_ANALOG_VALUE + 1) / 2 } }; static void load_calibration_data(uint8_t axisId) { uint8_t *record = reinterpret_cast<uint8_t *>(calibrationData + axisId); uint8_t *base = reinterpret_cast<uint8_t *>(calibrationData); int offset = record - base; EEPROM.get(offset, calibrationData[axisId]); if (calibrationData[axisId].minCalibration == ~0) { // not set in EEPROM, assign default #if LOG_ENABLED LOG_COUT(info) << F("set minCalibration[") << axisId << F("] to 0") << LOG_ENDLINE; #endif calibrationData[axisId].minCalibration = 0; } if (calibrationData[axisId].maxCalibration == ~0) { // not set in EEPROM, assign default #if LOG_ENABLED LOG_COUT(info) << F("set maxCalibration[") << axisId << F("] to MAX_ANALOG") << LOG_ENDLINE; #endif calibrationData[axisId].maxCalibration = MAX_ANALOG_VALUE; } if (calibrationData[axisId].centerCalibration == ~0) { // not set #if LOG_ENABLED LOG_COUT(info) << F("set centerCalibration[") << axisId << F("] to midpoint") << LOG_ENDLINE; #endif calibrationData[axisId].centerCalibration = (calibrationData[axisId].minCalibration + calibrationData[axisId].maxCalibration) / 2; } #if LOG_ENABLED LOG_COUT(info) << F("loaded calibration[") << axisId << F("] min=") << calibrationData[axisId].minCalibration << F(" max=") << calibrationData[axisId].maxCalibration << F(" center=") << calibrationData[axisId].centerCalibration << LOG_ENDLINE; #endif } static void save_calibration_data(uint8_t axisId) { uint8_t *record = reinterpret_cast<uint8_t *>(calibrationData + axisId); uint8_t *base = reinterpret_cast<uint8_t *>(calibrationData); int offset = record - base; EEPROM.put(offset, calibrationData[axisId]); } uint32_t totalReadCount; static int32_t read_joystick_pin(uint8_t pin, uint16_t minValue, uint16_t maxValue, uint16_t midpoint, bool invert = false) { totalReadCount += 1; int16_t value = analogRead(pin); if (invert) { value = maxValue - value; if (value < 0) value = 0; } if (value < minValue) value = minValue; if (value > maxValue) value = maxValue; int32_t scaled_result; if (value < midpoint) { int32_t range = (midpoint - minValue) + 1; int32_t offset = midpoint - value; if (offset >= range) offset = range - 1; scaled_result = (offset * ANALOG_MIDPOINT) / range; scaled_result = ANALOG_MIDPOINT - scaled_result; if (scaled_result < 0) scaled_result = 0; } else { int32_t range = (maxValue - midpoint) + 1; int32_t offset = value - midpoint; if (offset >= range) offset = range - 1; scaled_result = (offset * ANALOG_MIDPOINT) / range; scaled_result += ANALOG_MIDPOINT; if (scaled_result > MAX_ANALOG_VALUE) scaled_result = MAX_ANALOG_VALUE; } int32_t result = (scaled_result * (MAX_ANALOG_VALUE + 1)) / ((MAX_AXIS_VALUE - MIN_AXIS_VALUE) + 1); #if LOG_ENABLED > 3 LOG_COUT(info) << F("pin=") << pin << F(" val=") << value << F(" minVal=") << minValue << F(" maxVal=") << maxValue << F(" midPoint=") << midpoint << F(" scaled_result=") << scaled_result << LOG_ENDLINE; #endif return (result); } static void read_joystick_state() { for (uint8_t i = 0; i < TOTAL_AXES; i += 1) { int32_t discardValue = read_joystick_pin(axisPin[i], calibrationData[i].minCalibration, calibrationData[i].maxCalibration, calibrationData[i].centerCalibration, invertAxis[i]); int32_t reading = read_joystick_pin(axisPin[i], calibrationData[i].minCalibration, calibrationData[i].maxCalibration, calibrationData[i].centerCalibration, invertAxis[i]); int32_t scaled_value = reading + MIN_AXIS_VALUE; #if 1 if (scaled_value > (MAX_AXIS_VALUE - 1)) { #if LOG_ENABLED LOG_COUT(info) << F("scaled result=") << scaled_value << LOG_ENDLINE; #endif scaled_value = MAX_AXIS_VALUE - 1; } #endif #if LOG_ENABLED > 2 LOG_COUT(info) << F("axis=") << i << F(" pin=") << axisPin[i] << F(" reading=") << reading << F(" scaled=") << scaled_value << LOG_ENDLINE; #endif switch (i) { case 0: Joystick.setXAxis(scaled_value); break; case 1: Joystick.setYAxis(scaled_value); break; default: #if LOG_ENABLED LOG_COUT(info) << F("no support") << LOG_ENDLINE; #endif break; } // end switch } // end for } #if SUPPORT_CALIBRATION static void monitorCalibration() { for (uint8_t i = 0; i < TOTAL_AXES; i += 1) { uint8_t pin = axisPin[i]; uint16_t value = analogRead(pin); if (value > calibrationData[i].maxCalibration) calibrationData[i].maxCalibration = value; if (value < calibrationData[i].minCalibration) calibrationData[i].minCalibration = value; } } static bool checkForCalibration() { if (startCalibrateMode_ms == 0) { uint8_t val = digitalRead(CALIBRATE_BUTTON_PIN); if (val == HIGH) { return (false); } #if LOG_ENABLED > 1 LOG_COUT(info) << F("start calibration") << LOG_ENDLINE; #endif startCalibrateMode_ms = millis(); indicatorState = HIGH; digitalWrite(INDICATOR_LED, HIGH); indicatorBlinkRate = 10; // we assume joystick is centered at time of initial button press, // take several samples and average for calculation of center for (uint8_t i = 0; i < TOTAL_AXES; i += 1) { uint8_t pin = axisPin[i]; int32_t aveValue = 0; enum { SAMPLE_COUNT = 5 }; for (uint8_t count = 0; count < SAMPLE_COUNT; count += 1) { int32_t center_value = read_joystick_pin(axisPin[i], calibrationData[i].minCalibration, calibrationData[i].maxCalibration, calibrationData[i].centerCalibration, invertAxis[i]); aveValue += center_value; } calibrationData[i].centerCalibration = aveValue / SAMPLE_COUNT; #if LOG_ENABLED LOG_COUT(info) << F("set center ") << i << F(" to ") << calibrationData[i].centerCalibration << LOG_ENDLINE; #endif calibrationData[i].minCalibration = calibrationData[i].centerCalibration; calibrationData[i].maxCalibration = calibrationData[i].centerCalibration; } } // in calibration mode monitorCalibration(); unsigned long now = millis(); if (now > (startCalibrateMode_ms + CALIBRATION_DURATION_MS)) { #if LOG_ENABLED > 1 LOG_COUT(info) << F("calibration ends") << LOG_ENDLINE; #endif for (uint8_t i = 0; i < TOTAL_AXES; i += 1) { save_calibration_data(i); } startCalibrateMode_ms = 0; // turn off indicatorState = LOW; indicatorBlinkRate = 0; digitalWrite(INDICATOR_LED, LOW); } } #endif /* SUPPORT_CALIBRATION */ #define PINKY !(buttonInputs1 & 0x80) /* Pinky Switch */ #define TG1 !(buttonInputs1 & 0x40) /* Trigger 1 */ #define TG2 !(buttonInputs1 & 0x20) /* Trigger 2 */ #define S1 !(buttonInputs1 & 0x10) /* Nose Wheel Steering */ #define PADDLE !(buttonInputs1 & 0x08) /* Paddle Switch */ #define THUMB !(buttonInputs1 & 0x04) /* Pickle */ #define H1D !(buttonInputs2 & 0x80) /* HAT */ #define H1R !(buttonInputs2 & 0x40) #define H1U !(buttonInputs2 & 0x20) #define H1L !(buttonInputs2 & 0x10) #define H4U !(buttonInputs2 & 0x08) /* Castle */ #define H4L !(buttonInputs2 & 0x04) #define H4D !(buttonInputs2 & 0x02) #define H4R !(buttonInputs2 & 0x01) #define H3D !(buttonInputs3 & 0x80) /* Weap */ #define H3R !(buttonInputs3 & 0x40) #define H3U !(buttonInputs3 & 0x20) #define H3L !(buttonInputs3 & 0x10) #define H2D !(buttonInputs3 & 0x08) /* Target */ #define H2R !(buttonInputs3 & 0x04) #define H2U !(buttonInputs3 & 0x02) #define H2L !(buttonInputs3 & 0x01) /* returns 0, 45, 90, 135, 180, 225, 270, 315 based on hat bits */ static int16_t decodeHatAngle(uint8_t up, uint8_t down, uint8_t right, uint8_t left) { int16_t angle = -1; if (up) { if (right) { angle = 45; } else if (left) { angle = 315; } else { angle = 0; } } else if (down) { if (right) { angle = 135; } else if (left) { angle = 225; } else { angle = 180; } } else if (right) { angle = 90; } else if (left) { angle = 270; } return (angle); } static void read_button_state() { digitalWrite(LATCH_PIN, LOW); uint8_t buttonInputs1 = SPI.transfer(0x00); uint8_t buttonInputs2 = SPI.transfer(0x00); uint8_t buttonInputs3 = SPI.transfer(0x00); digitalWrite(LATCH_PIN, HIGH); #if LOG_ENABLED > 1 if ((buttonInputs1 != 0xff) || (buttonInputs2 != 0xff) || (buttonInputs3 != 0xff)) { LOG_COUT(info) << F("in1=") << buttonInputs1 << F(" in2=") << buttonInputs2 << F(" in3=") << buttonInputs3 << LOG_ENDLINE; } #endif Joystick.setButton(0, TG1); Joystick.setButton(1, THUMB); Joystick.setButton(2, PINKY); Joystick.setButton(3, PADDLE); Joystick.setButton(4, S1); Joystick.setButton(5, TG2); int16_t angle = decodeHatAngle(H1U, H1D, H1R, H1L); Joystick.setHatSwitch(0, angle); #if EXPOSE_ALL_HAT_SWITCHES == 0 // expose remaining hat switches as distinct buttons Joystick.setButton(6, H2U); Joystick.setButton(7, H2R); Joystick.setButton(8, H2D); Joystick.setButton(9, H2L); Joystick.setButton(10, H3U); Joystick.setButton(11, H3R); Joystick.setButton(12, H3D); Joystick.setButton(13, H3L); Joystick.setButton(14, H4U); Joystick.setButton(15, H4R); Joystick.setButton(16, H4D); Joystick.setButton(17, H4L); #else // expose remaining 3 hat switches angle = decodeHatAngle(H2U, H2D, H2R, H2L); Joystick.setHatSwitch(1, angle); angle = decodeHatAngle(H3U, H3D, H3R, H3L); Joystick.setHatSwitch(2, angle); angle = decodeHatAngle(H4U, H4D, H4R, H4L); Joystick.setHatSwitch(3, angle); #endif } void setup() { #if LOG_ENABLED // Setup hardware serial port Serial.begin(CONSOLE_BAUD_RATE); delay(500); // stabilize after power-on unsigned long start = millis(); while (!Serial) { unsigned long delayed = millis() - start; if (delayed > 5000) break; } LOG_COUT(info) << F("Thrustmaster F-22 Pro Joystick firmware compiled on date ") << compiledOnDate << LOG_ENDLINE; LOG_COUT(info) << F("Original from Geoff Carpenter gcc@fargos.net http://www.fargos.net/gcc.html") << LOG_ENDLINE; #endif pinMode(LATCH_PIN, OUTPUT); digitalWrite(LATCH_PIN, HIGH); // default state is high pinMode(CLOCK_PIN, OUTPUT); pinMode(DATA_PIN, INPUT); pinMode(X_AXIS_PIN, INPUT); pinMode(Y_AXIS_PIN, INPUT); pinMode(LED_BUILTIN, OUTPUT); #if SUPPORT_CALIBRATION pinMode(CALIBRATE_BUTTON_PIN, INPUT_PULLUP); pinMode(INDICATOR_LED, OUTPUT); digitalWrite(INDICATOR_LED, LOW); indicatorBlinkRate = 100; indicatorBlinkUntilMillis = millis() + 10000; for (uint8_t i = 0; i < TOTAL_AXES; i += 1) { load_calibration_data(i); } #endif /* SUPPORT CALIBRATION */ Joystick.begin(false); // Set auto-send to false for better performance Joystick.setXAxisRange(MIN_AXIS_VALUE, MAX_AXIS_VALUE); Joystick.setYAxisRange(MIN_AXIS_VALUE, MAX_AXIS_VALUE); #if LOG_ENABLED LOG_COUT(info) << F("clock=") << CLOCK_PIN << F(" ss=") << LATCH_PIN << F(" miso=") << DATA_PIN << LOG_ENDLINE; #endif SPI.begin(); } static bool blinkLED(unsigned long currentTime) { static uint32_t count; static uint8_t ledState; count += 1; if (count < 100) return (false); // toggle LED state count = 0; ledState = 1 - ledState; digitalWrite(LED_BUILTIN, ledState); // digitalWrite(LATCH_PIN, ledState); return (true); } #if SUPPORT_CALIBRATION static bool blinkIndicatorLED(unsigned long currentTime) { bool changed = false; if (indicatorBlinkUntilMillis != 0) { if (indicatorBlinkUntilMillis <= currentTime) { // reached end of cycle changed = indicatorState; indicatorBlinkUntilMillis = 0; // turn off indicatorBlinkRate = 0; indicatorState = LOW; digitalWrite(INDICATOR_LED, LOW); } } if (indicatorBlinkRate != 0) { indicatorBlinkCount += 1; if (indicatorBlinkCount >= indicatorBlinkRate) { indicatorBlinkCount = 0; indicatorState = 1 - indicatorState; digitalWrite(INDICATOR_LED, indicatorState); changed = true; } } return (changed); } #endif /* SUPPORT_CALIBRATION */ static unsigned long delayIfNeeded(unsigned long now) { static unsigned long lastTime; unsigned long nextTime = lastTime + POLL_DELAY_MS; if (now < nextTime) { delay(nextTime - now); now = millis(); } lastTime = now; return (now); } void loop() { unsigned long now = delayIfNeeded(millis()); blinkLED(now); #if SUPPORT_CALIBRATION blinkIndicatorLED(now); checkForCalibration(); #endif read_button_state(); read_joystick_state(); // Send the updated states to the PC all at once Joystick.sendState(); } /* vim: set expandtab shiftwidth=4 tabstop=4: */