Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 6 of 6

Thread: [Beginner] Universal Flight Sim Panel

  1. #1

    [Beginner] Universal Flight Sim Panel

    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.

    Click image for larger version. 

Name:	FlightPanel_v1_Small.png 
Views:	17 
Size:	232.4 KB 
ID:	21045 Click image for larger version. 

Name:	FlightPanel_v1_02_Small.png 
Views:	19 
Size:	319.1 KB 
ID:	21044

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

  2. #2
    Junior Member
    Join Date
    Apr 2020
    Posts
    3
    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.

  3. #3
    Senior Member
    Join Date
    Apr 2013
    Posts
    1,935
    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/...a-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.

  4. #4
    Senior Member
    Join Date
    Apr 2013
    Posts
    1,935
    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.

  5. #5
    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.

  6. #6
    Senior Member
    Join Date
    Jul 2020
    Posts
    127
    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-t...-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
        }
    }

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •