[Beginner] Universal Flight Sim Panel

Status
Not open for further replies.

Daniel_Malloy

New member
Beginner here when it comes to programming... I can get by editing existing code and am slowly understanding how it works so please bear with me.

I am attempting to make a panel I can use for flight sims and other games as a joystick/gamepad. As pictured, I am wanting to have 10 mini toggles, 5 momentary push buttons, 5 rocker switches, 2 large toggles, and 4 rotary encoders.

FlightPanel_v1_Small.png FlightPanel_v1_02_Small.png

I have it programmed so that when a switch is flipped, it momentarily activates a button. I am using a delay to accomplish this, but am unsure whether there is a better way using elapsedMillis. I tried to understand how to use elapsedMillis, but everything is over my head for the time being.

The last thing holding me back is I can't figure out how to program the encoders... All I'm wanting to do is have them press a joystick button when rotated one direction, and press another button when rotated in the other direction.

Any feedback/guidance would be greatly appreciated.

Code:
/*
   Flight Sim Panel V1
   Make sure to select Joystick from the "Tools > USB Type" menu
   
10 pins for mini toggle switches,	Pins 0-9... Program buttons 1-10
5 pins for momentary push buttons,	Pins 10-12 & 24-25... Program buttons 11-15
2 pins for large toggle switches,	Pins 26-27... program buttons 16-17
5 pins for rocker switches,			Pins 28-32... Program buttons 18-22
4 pins for rotary push buttons,		Pins 20-23... Program buttons 23-26
8 pins for Rotaries,				Pins 14-20 & 41.... Program bottom to 27&28, 29&30 and top to to hat Left&Right, Up&Down
*/

#include <Bounce.h>
// 10 = 10 ms debounce time
// Mini Toggles
Bounce button0 = Bounce(0, 10);
Bounce button1 = Bounce(1, 10);  
Bounce button2 = Bounce(2, 10);
Bounce button3 = Bounce(3, 10);
Bounce button4 = Bounce(4, 10);
Bounce button5 = Bounce(5, 10);
Bounce button6 = Bounce(6, 10);
Bounce button7 = Bounce(7, 10);
Bounce button8 = Bounce(8, 10);
Bounce button9 = Bounce(9, 10);
//Momentary Push Buttons
Bounce button10 = Bounce(10, 10);
Bounce button11 = Bounce(11, 10);
Bounce button12 = Bounce(12, 10);
Bounce button24 = Bounce(24, 10);
Bounce button25 = Bounce(25, 10);
//Large Toggles
Bounce button26 = Bounce(26, 10);
Bounce button27 = Bounce(27, 10);
//Rocker Switches
Bounce button28 = Bounce(28, 10);
Bounce button29 = Bounce(29, 10);
Bounce button30 = Bounce(30, 10);
Bounce button31 = Bounce(31, 10);
Bounce button32 = Bounce(32, 10);
//Rotary Push buttons
Bounce button20 = Bounce(20, 10);
Bounce button21 = Bounce(21, 10);
Bounce button22 = Bounce(22, 10);
Bounce button23 = Bounce(23, 10);

void setup() {
  // same as Windows default
  Joystick.Z(512);
  Joystick.X(512);
  Joystick.Y(512);
  Joystick.Zrotate(512);
  Joystick.sliderLeft(512);
  Joystick.sliderRight(512);
  Joystick.hat(-1);
  //Mini Toggles Pullup
  pinMode(0, INPUT_PULLUP);
  pinMode(1, INPUT_PULLUP);
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  pinMode(4, INPUT_PULLUP);
  pinMode(5, INPUT_PULLUP);
  pinMode(6, INPUT_PULLUP);
  pinMode(7, INPUT_PULLUP);
  pinMode(8, INPUT_PULLUP);
  pinMode(9, INPUT_PULLUP);
  //Momentary Push Buttons Pullup
  pinMode(10, INPUT_PULLUP);
  pinMode(11, INPUT_PULLUP);
  pinMode(12, INPUT_PULLUP);
  pinMode(24, INPUT_PULLUP);
  pinMode(25, INPUT_PULLUP);
  //Large Toggles Pullup
  pinMode(26, INPUT_PULLUP);
  pinMode(27, INPUT_PULLUP);
  //Rocker Switches Pullup
  pinMode(28, INPUT_PULLUP);
  pinMode(29, INPUT_PULLUP);
  pinMode(30, INPUT_PULLUP);
  pinMode(31, INPUT_PULLUP);
  pinMode(32, INPUT_PULLUP);
  //Rotary Push Buttons Pullup
  pinMode(20, INPUT_PULLUP);
  pinMode(21, INPUT_PULLUP);
  pinMode(22, INPUT_PULLUP);
  pinMode(23, INPUT_PULLUP);
}

void loop() {
  // Update all the buttons.  There should not be any long
  // delays in loop(), so this runs repetitively at a rate
  // faster than the buttons could be pressed and released.
  //Update Mini Toggles
  button0.update();
  button1.update();
  button2.update();
  button3.update();
  button4.update();
  button5.update();
  button6.update();
  button7.update();
  button8.update();
  button9.update();
  //Update Momentary Push Buttons
  button10.update();
  button11.update();
  button12.update();
  button24.update();
  button25.update();
  //Update Large Toggles
  button26.update();
  button27.update();
  //Update Rocker Switches
  button28.update();
  button29.update();
  button30.update();
  button31.update();
  button32.update();
  //Update Rotary Push Buttons
  button20.update();
  button21.update();
  button22.update();
  button23.update();
  
  //Mini Toggle Update if falling
  if (button0.fallingEdge()) {
	Joystick.button(1, 1);
	delay(50);
	Joystick.button(1, 0);
  }
  if (button1.fallingEdge()) {
	Joystick.button(2, 1);
	delay(50);
	Joystick.button(2, 0);
  }
  if (button2.fallingEdge()) {
	Joystick.button(3, 1);
	delay(50);
	Joystick.button(3, 0);
  }
  if (button3.fallingEdge()) {
	Joystick.button(4, 1);
	delay(50);
	Joystick.button(4, 0);
  }
  if (button4.fallingEdge()) {
	Joystick.button(5, 1);
	delay(50);
	Joystick.button(5, 0);
  }
  if (button5.fallingEdge()) {
	Joystick.button(6, 1);
	delay(50);
	Joystick.button(6, 0);
  }
  if (button6.fallingEdge()) {
	Joystick.button(7, 1);
	delay(50);
	Joystick.button(7, 0);
  }
  if (button7.fallingEdge()) {
	Joystick.button(8, 1);
	delay(50);
	Joystick.button(8, 0);
  }
  if (button8.fallingEdge()) {
	Joystick.button(9, 1);
	delay(50);
	Joystick.button(9, 0);
  }
  if (button9.fallingEdge()) {
	Joystick.button(10, 1);
	delay(50);
	Joystick.button(10, 0);
  }
  //Momentary Push Buttons Update if falling
  if (button10.fallingEdge()) {
	Joystick.button(11, 1);
  }
  if (button11.fallingEdge()) {
	Joystick.button(12, 1);
  }
  if (button12.fallingEdge()) {
	Joystick.button(13, 1);
  }
  if (button24.fallingEdge()) {
	Joystick.button(14, 1);
  }
  if (button25.fallingEdge()) {
	Joystick.button(15, 1);
  }
  //Large Toggles Update if falling
  if (button26.fallingEdge()) {
	Joystick.button(16, 1);
	delay(50);
	Joystick.button(16, 0);
  }
  if (button27.fallingEdge()) {
	Joystick.button(17, 1);
	delay(50);
	Joystick.button(17, 0);
  }
  //Rocker Switches Update if falling
  if (button28.fallingEdge()) {
	Joystick.button(18, 1);
	delay(50);
	Joystick.button(18, 0);
  }
  if (button29.fallingEdge()) {
	Joystick.button(19, 1);
	delay(50);
	Joystick.button(19, 0);
  }
  if (button30.fallingEdge()) {
	Joystick.button(20, 1);
	delay(50);
	Joystick.button(20, 0);
  }
  if (button31.fallingEdge()) {
	Joystick.button(21, 1);
	delay(50);
	Joystick.button(21, 0);
  }
  if (button32.fallingEdge()) {
	Joystick.button(22, 1);
	delay(50);
	Joystick.button(22, 0);
  }
  //Rotary Push Encoders Update if falling
  if (button20.fallingEdge()) {
	Joystick.button(23, 1);
  }
  if (button21.fallingEdge()) {
	Joystick.button(24, 1);
  }
  if (button22.fallingEdge()) {
	Joystick.button(25, 1);
  }
  if (button23.fallingEdge()) {
	Joystick.button(26, 1);
  }
  
  
  //Mini Toggle Update if rising
  if (button0.risingEdge()) {
	Joystick.button(1, 1);
	delay(50);
	Joystick.button(1, 0);
  }
  if (button1.risingEdge()) {
	Joystick.button(2, 1);
	delay(50);
	Joystick.button(2, 0);
  }
  if (button2.risingEdge()) {
	Joystick.button(3, 1);
	delay(50);
	Joystick.button(3, 0);
  }
  if (button3.risingEdge()) {
	Joystick.button(4, 1);
	delay(50);
	Joystick.button(4, 0);
  }
  if (button4.risingEdge()) {
	Joystick.button(5, 1);
	delay(50);
	Joystick.button(5, 0);
  }
  if (button5.risingEdge()) {
	Joystick.button(6, 1);
	delay(50);
	Joystick.button(6, 0);
  }
  if (button6.risingEdge()) {
	Joystick.button(7, 1);
	delay(50);
	Joystick.button(7, 0);
  }
  if (button7.risingEdge()) {
	Joystick.button(8, 1);
	delay(50);
	Joystick.button(8, 0);
  }
  if (button8.risingEdge()) {
	Joystick.button(9, 1);
	delay(50);
	Joystick.button(9, 0);
  }
  if (button9.risingEdge()) {
	Joystick.button(10, 1);
	delay(50);
	Joystick.button(10, 0);
  }
  //Momentary Push Buttons Update if rising
  if (button10.risingEdge()) {
	Joystick.button(11, 0);
  }
  if (button11.risingEdge()) {
	Joystick.button(12, 0);
  }
  if (button12.risingEdge()) {
	Joystick.button(13, 0);
  }
  if (button24.risingEdge()) {
	Joystick.button(14, 0);
  }
  if (button25.risingEdge()) {
	Joystick.button(15, 0);
  }
  //Large Toggles Update if rising
  if (button26.risingEdge()) {
	Joystick.button(16, 1);
	delay(50);
	Joystick.button(16, 0);
  }
  if (button27.risingEdge()) {
	Joystick.button(17, 1);
	delay(50);
	Joystick.button(17, 0);
  }
  //Rocker Switches Update if rising
  if (button28.risingEdge()) {
	Joystick.button(18, 1);
	delay(50);
	Joystick.button(18, 0);
  }
  if (button29.risingEdge()) {
	Joystick.button(19, 1);
	delay(50);
	Joystick.button(19, 0);
  }
  if (button30.risingEdge()) {
	Joystick.button(20, 1);
	delay(50);
	Joystick.button(20, 0);
  }
  if (button31.risingEdge()) {
	Joystick.button(21, 1);
	delay(50);
	Joystick.button(21, 0);
  }
  if (button32.risingEdge()) {
	Joystick.button(22, 1);
	delay(50);
	Joystick.button(22, 0);
  }
  //Rotary Push Encoders Update if rising
  if (button20.risingEdge()) {
	Joystick.button(23, 0);
  }
  if (button21.risingEdge()) {
	Joystick.button(24, 0);
  }
  if (button22.risingEdge()) {
	Joystick.button(25, 0);
  }
  if (button23.risingEdge()) {
	Joystick.button(26, 0);
  }
  }
 
Hi Daniel,

Those renders look really cool, the panel looks quite neat.

There is a encoder library included with Teensy. You can find it here.

I hope this helps you.
 
There are several ways to use ellapsedmillis to handle the buttons. One is to have an ellapsedmillis for every button, if button triggered, set the output and zero the timer and later cycle through checking if they are currently pressed and timer >50us. This will get very very messy in code but is conceptually easy. Need to make sure logic does not send that the button is release every cycle after the timer is >50ms

Another way is to have a timer called 'buttontimeout'. Whenever ANY button is pressed it gets set to zero and the button number stored, and when timer has run out you release that button. This is simpler but means pressing multiple buttons will not work so still rather blocking.

A more complete method is to use arrays
https://www.arduino.cc/reference/en/language/variables/data-types/array/
Specifically an array that stores buttons that were pressed in the past and are in the process of timing out in

byte ButtonTimers[33];

And an array that maps physical buttons to joystick buttons to avoid a messy IF tree and make it easier to shuffle things later
// physical buttons 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
byte buttonMap[] ={1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 15, 16, 17, 18, 19, 20,21, 22};

Then in a sample button press handler
if (button22.fallingEdge()) {
Joystick.button(buttonMap[24] , 1); //looks up vale at 24 in the button map array, 14 and passes that to joystick.button
ButtonTimers[24]=5;
}

Then we have an elapsedMillis buttonhandlertimer that is checked every 10ms and does a for loop through all the buttons checking if they are active, and clears them if they have been for a while

if (buttonhandlertimer>10){
for (byte buttonArrayIndex = 0; buttonArrayIndex <= 22; buttonArrayIndex++) { //arrays start from zero so we go up to or equal to 22
if (ButtonTimers[buttonArrayIndex]>0) ButtonTimers[buttonArrayIndex]=ButtonTimers[buttonArrayIndex]-1; //if we are in count down decrement by one
if (ButtonTimers[buttonArrayIndex]==1) Joystick.button(buttonMap[buttonArrayIndex] , 0); //if we have decremented from five down to 1 clear the relevant joystick button
}
buttonhandlertimer=0;//always remember to clear timers after using them!
}

This will have some timing jitter, depending on when in a given 10ms window the button was pressed and when the timeout happens but will have a none zero delay period. It handles double presses by extending the timeout, which may or maynot be what you want. Having logic to queue up presses is possible but gets complicated quickly.
 
With the encoders, as per TGlev's post, the encoder library does most of the work and in your case it becomes really really simple since you are just firing once for any amount of rotation*
Read the encoder and store the new value
newEncoder1Value = encoder1.read();
//If value is higher do one thing
if (newEncoder1Value>oldEncoder1Value) {Joystick.button(buttonMap[1] , 1); ButtonTimers[1]=5;}
if (newEncoder1Value<oldEncoder1Value) {Joystick.button(buttonMap[2] , 1); ButtonTimers[2]=5;}
//then store the updated value
oldEncoder1Value=newEncoder1Value;

*many encoders move for than one step in code per click, and often you need to handle things like hitting the end of a a range, determining speed and other such things.
 
Thanks guys for the posts... Will be going through them now and seeing what I can improve and how my attempt at adding the rotaries goes.
 
I would make a class that handles queued events. Otherwise, you are going to get into iteration spaghetti hell. You will have enormous trees of if ... else if ... else if ... else if ..., or a giant stack of code where you are hard-coding individual device IDs, and so on. That is a clue that you are making your life harder, and you will pay for it later.

Code to see how much time has passed is here: https://www.tutorialspoint.com/how-to-measure-time-taken-by-a-function-in-c

If you build a Queue object that can be told "do operation X at or after time Y, and once that's done de-queue the operation", you will gain "fire-and-forget" capability (you don't use delays), and you will be able to do these timed trigger-and-release operations on multiple buttons, or with one input device that is being rapidly operated.

For example, if someone is turning the radio knob aggressively (they're at 117.something and they want 123.something), and you are doing a 50ms delay each time, you are eventually going to miss a pulse because it happened while you were stuck in usleep(). With this suggested method, you can scan thousands of times a second, and never miss a pulse.

Code:
// Assumptions:
// input[] is an array of pointers to Input objects, which can poll the various things on the panel
// output[] is an array of pointers to Output objects, which can set the state of a virtual device that gets composited into the "joystick" output
// JoystickOutput is an object that composites all the output[] stuff into a unified virtual device
// queue has an object that can be told to call setHigh() or setLow() on an Output object after some time has elapsed

// Don't use magic numbers. Define values as obviously as possible in one place.
#DEFINE NUM_DEVICES 8
#DEFINE JOYSTICK_ACTIVATION_TIME_MILLISECONDS 50


// This function takes values for # of devices and activation time, rather than using the DEFINEs directly, because
// each function should have only a single responsibility - and loading configuration is a responsibility! If you
// were to change the code to load this config off an SD card (which ain't a bad idea!) you would not have to touch
// this function. It is therefore easier to test, and less averse to change.
void pollDevices(int numDevices, Input input[], Output output[], Queue &queue, int activationTimeMilliseconds) {
    for(int x=0; x < numDevices; x++) {
        if(input[x].risingEdge()) {
            // Call output[x].setHigh(), wait some milliseconds, then call output[x].setLow
            queue.setHighThenLow(output[x], activationTimeMilliseconds);
        }
    }
}


// You can swap this for some logic that gets it off the SD card (or however) without breaking anything else.
int numDevices() {
    return NUM_DEVICES;
}


// Ditto.
int activationTimeMilliseconds() {
    return JOYSTICK_ACTIVATION_TIME_MILLISECONDS;
}


void mainLoop() {

    // (Instantiate objects here...)

    int nDevices = numDevices();
    int activationTime = activationTimeMilliseconds();
    int run = 1;

    while(1) {
        pollDevices(nDevices, input, output, queue, activationTime);
        queue.tick();
        JoystickOutput.composite(output); // Build state of virtual joystick from all the output objects
        JoystickOutput.send(); // Make the virtual joystick's new state visible to the USB host
        // Can call usleep() here if you want to run more efficiently
    }
}
 
Status
Not open for further replies.
Back
Top