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();
}
}
}
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
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