Sending joystick button presses to FS2020 with Arduino Leonardo

Working on a instrument project or just finished a project? Show it to others!

Moderators: russ, Ralph

Post Reply
Message
Author
Detlef
Posts: 304
Joined: Mon Nov 16, 2020 9:43 am
Location: Bavaria

Sending joystick button presses to FS2020 with Arduino Leonardo

#1 Post by Detlef »

Hi all,

I am still experimenting with FS2020. Fox X-Plane I can easily find datarefs needed to build Airmanager instruments. For FS2020 it is a lot more difficult.
I learned that some of the FS2020 events can be assigned to keys or joystick buttons when doing it directly in FS2020, but those events cannot be sent from outside the sim, per Airmanager for example. This is for controlling camera views for example.

To solve this, I have now added an Arduino Leonardo to my hardware. The Leonardo works as a USB joystick for Windows. I did not want to change my cockpit hardware, built for X-Plane. So I just connected four output pins from an existing Arduino Mega to four input pins of the Leonardo. That way I can use any switch and knob I already have for sending joystick buttons to Windows. And the user interface of FS2020 assigns it to any function, for example Show custom camera #1.

This is the CPP Arduino Leonardo code:

Code: Select all

//  Detlef von Reusner
//  May 7 2022
//  
//  Flash into an Arduino Leonardo.
//  The Leonardo then appears like a joystick with 64 buttons to Windows (when pin PIN_JOYSTICK_SELECT
//  is low and when #define JOYSTICK_FUNCTION preprocessor flag).
//  
//  When connected to another Arduino running with Airmanager software, and when connected via the pins
//  listed below, this program reads in joystick data.
//  
//  The program serially reads 9 bits, that is the button number (0..63), LSB first, and the button status,
//  1 for pressed, 0 for released.
//  
//  For example: receiving 0100 1000 1 will send 'button 18 is pressed'.


// set as required while debugging
#define JOYSTICK_FUNCTION

#include <Joystick.h>
#include <Keyboard.h>


// Pins used in my other Arduino (a Mega). These must be connected to the Leonardo
//#define PIN_JOYSTICK_SELECT A0 // low if joystick functionality is active
//#define PIN_JOYSTICK_DATA A1
//#define PIN_JOYSTICK_CLK A2
//#define PIN_JOYSTICK_LOAD A3

// Pins used in the Leonardo
#define PIN_JOYSTICK_SELECT 6 // low if joystick functionality is active
#define PIN_JOYSTICK_DATA 4
#define PIN_JOYSTICK_CLK 2
#define PIN_JOYSTICK_LOAD 3 // low to start reading data with rising clock edge

//--------------------------------------------------------------

#define PAUSE 1000
volatile int gBits = 0;
volatile unsigned int gData = 0;
unsigned long gTimeNext = millis()+PAUSE;

volatile int gCount1 = 0;
volatile int gCount2 = 0;
bool gJoystickRequest = false;

//Joystick_ joystick;
Joystick_ joystick(JOYSTICK_DEFAULT_REPORT_ID,JOYSTICK_TYPE_GAMEPAD,
  127, 2,                  // Button Count, Hat Switch Count
  false, false, false,     // no x y and Z Axis
  false, false, false,   // No Rx, Ry, or Rz
  false, false,          // No rudder or throttle
  false, false, false);  // No accelerator, brake, or steering

//----------------------------------------------
void Print(int data){
  char line[80];
  snprintf(line, 79, "Number: %03d  State: %d   loads:%d   clocks:%d", data&255, (data&256)/256, gCount1, gCount2);
  Serial.println(line);
}

void NewData(){
  // ISR Interrupt service routine
  gData = 0;
  gBits = 0;
  gCount1++;
}

void ReceiveBit(){
  // ISR Interrupt service routine

  gCount2++;
  gBits++;
  gData >>= 1;
  if (digitalRead(PIN_JOYSTICK_DATA) == HIGH)
    gData += 256;
}

bool gInitDone = false;
void setup() {
  // ----------- init all pins to avoid glitches from open pins --------------
  pinMode(13, OUTPUT); // built-in LED for Leonardo
  for (int pin=2; pin<=12; pin++) {
    pinMode(pin, INPUT_PULLUP);
  }
  pinMode(A0, INPUT_PULLUP);
  pinMode(A1, INPUT_PULLUP);
  pinMode(A2, INPUT_PULLUP);
  pinMode(A3, INPUT_PULLUP);
  pinMode(A4, INPUT_PULLUP);
  pinMode(A5, INPUT_PULLUP);

  // ------------ init functional pins ---------------

  pinMode(PIN_JOYSTICK_SELECT, INPUT_PULLUP);
  pinMode(PIN_JOYSTICK_DATA, INPUT_PULLUP);
  pinMode(PIN_JOYSTICK_CLK, INPUT_PULLUP);
  pinMode(PIN_JOYSTICK_LOAD, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(PIN_JOYSTICK_LOAD), NewData, FALLING);
  attachInterrupt(digitalPinToInterrupt(PIN_JOYSTICK_CLK), ReceiveBit, RISING);
  gTimeNext = millis()+PAUSE;
}


void loop() {
  unsigned int number;
  int setting;
  // -------------- switch joystick functionality on or off
  if (digitalRead(PIN_JOYSTICK_SELECT) == LOW) {
    if (!gJoystickRequest) {
      gJoystickRequest = true;
#ifdef JOYSTICK_FUNCTION
      joystick.begin(); // Initialize Joystick Library
#endif
    }
  } else if (gJoystickRequest) {
    gJoystickRequest = false;
#ifdef JOYSTICK_FUNCTION
    joystick.end();
#endif
  }
  // ------------------------------------------------

  if (gJoystickRequest) {
    if (gInitDone) {
      Serial.end();
      gInitDone = false;
    }
    if (gBits == 9){
      noInterrupts();
#ifdef JOYSTICK_FUNCTION
    number = gData & 0xff;
    if (number>=128 && number<=131){
      switch (number) {
      case 128: // up
        setting = 360;
        break;
      case 129: // right
        setting = 90;
        break;
      case 130: // down
        setting = 180;
        break;
      case 131: // left
        setting = 270;
        break;
      }
      if ((gData>>8)==0){
        setting = JOYSTICK_HATSWITCH_RELEASE;
      }
      joystick.setHatSwitch(0, setting);
    } else
        joystick.setButton(number, gData>>8);
#endif    
      gBits = 0;
      interrupts();
    }
  } else {
    if (!gInitDone) {
      Serial.begin(38400); //initialize serial communication
      gInitDone = true;
    }
    if (gBits == 9){
      noInterrupts();
      Print(gData);
      gBits = 0;
      interrupts();
    }
  }
}
And this is the Lua code in an Airmanager Instrument:

Code: Select all

function cbTimerSendBit(count, limit)
  if count < 3 then
    hw_output_set(gPinLoad, false)
  elseif count == 22 then
    timer_stop(gTimerClock)
    gTimerClock = nil
  elseif count > 20 then
    hw_output_set(gPinLoad, true)
  elseif (count & 1) == 0 then
    hw_output_set(gPinClock, true)
  else
    hw_output_set(gPinClock, false)
    hw_output_set(gPinData, (gData & 1)==1)
    gData = math.floor(gData/2)
  end
end

function internal_SendJoystickButton(number, pressed)
  -- the last bit sent is 1 for button pressed, 0 if button released
  if gTimerClock ~= nil then return end -- pressed too fast
  gData = number
  if pressed then gData = gData+256 end
  gTimerClock = timer_start(4, 4, 22, cbTimerSendBit) -- timer function transmits data LSB first
end

function cbTimerDelay(count, limit)
  if gTimerClock ~= nil and count < limit then
    return
  else
    if gTimerClock ~= nil then
      timer_stop(gTimerClock)
      gTimerClock = nil
    end
    timer_stop(gTimerDelay)
    gTimerDelay = nil
    internal_SendJoystickButton(gLastButton, gLastButtonState)
  end
end

function JoystickButton(number, pressed, delay)
  -- sends button number (0..255) and its state to the Arduino Leonardo
  -- if delay(optional) == true, sending is delayed
  if delay == nil then delay = false end
  gLastButton = number
  gLastButtonState = pressed
  if gTimerClock ~= nil then
    if delay == false then
      return
    elseif gTimerDelay == nil then
      gTimerDelay = timer_start(100, 5, 5, cbTimerDelay)
      return
    end
  end
  internal_SendJoystickButton(number, pressed, false)
end
So for example, calling:
SendJoystickButton(10, true)
SendJoystickButton(10, false, true)

in an Airmanager instrument for Windows looks like the joystick button 10+1 is being pressed. Then after some delay the button is released.

In the Arduino code you must use the library "ArduinoJoystickLibrary-master". It is under GNU public licence. (From the readme: This library can be used with Arduino IDE 1.6.6 (or above) to add one or more joysticks (or gamepads) to the list of HID devices an [Arduino Leonardo])

With the code above the Airmanager sends the joystick button number and state bit by bit to the Leonardo, who reads it bit by bit and then passes it on as a joystick button press. It is somewhat slow but fast enough.

This did help me so I just wanted to contribute it here.

Thank you
Detlef

User avatar
jph
Posts: 2846
Joined: Fri Apr 10, 2020 12:50 pm
Location: Somewhere over the rainbow..

Re: Sending joystick button presses to FS2020 with Arduino Leonardo

#2 Post by jph »

Cool ! :mrgreen:
Nice job Detlef.
I noticed a few weeks back that he updated the V2 to V2.1 - https://github.com/MHeironimus/ArduinoJoystickLibrary
Excellent use of AM and the standard HID game controller. Really neat.
Joe
Joe. CISSP, MSc.

Detlef
Posts: 304
Joined: Mon Nov 16, 2020 9:43 am
Location: Bavaria

Re: Sending joystick button presses to FS2020 with Arduino Leonardo

#3 Post by Detlef »

Thank you Joe!

Post Reply