The toggle switch. They occupy a prominent place in the heart of most flight simmers. From controlling the lights with a few rocker switches in a light aircraft to controlling complex systems of airliners. Do you want more advantages? They can be bought cheaply, are easy to install, and are pieces easily fitted onto any material. Just drill a hole, add two wires, and we are ready.
In a previous post, I explained how to use a momentary pushbutton with Microsoft Flight Simulator 2020 in full detail. If we strip it all down, the toggle/rocker switch functions the same as a momentary pushbutton. The only difference is that we don't have to physically hold on for dear life when we want to keep the lights on. The code, however, requires some tweaks to work properly in MFS2020.
I will cover two methods:
Most toggle switches require soldering or connectors to connect to your projects. They aren't breadboard-friendly out of the box.
Various switches available can differ slightly on how to wire them properly. Some have 2 pins, some have 3 pins, and some even have 6 pins (more options are available). Depending on your needs, you might look for some keywords. Usually, the switches are accompanied by a description like on-off, on-off-on, on-off-momentary, or on-on. On-off and on-on switches have 2 states and are usually easily recognizable by the 2 physical pins on the switch. The on-off-on and on-off-momentary have 3 states, and you guessed it, they are often found holding 3 pins. To make your life easier, you can often find the markings on the switches themselves (see the cute group photo below).
3 different switch types
In this article, we will wire these puppies up like normal pushbuttons utilizing the internal pull-down resistors of our boards (if you want to learn more about this, head over to our back-to-basics: momentary pushbuttons article). Whether we have 2 or 3 pins, we always need a pin running to our ground line. To keep things concise, I like to use the pin in the middle of the switch (with 2 pins, there is no middle, but one of the 2 will be closer to the absolute middle of the component). The more concise we work, the easier it becomes to check our circuit for faults or breakages. The remaining pins will each run toward a free IO slot on your board.
An example with (left) an on-off switch and (right) an on-off-on switch
The ground line can be daisy-chained through multiple switches (convenient when soldering). This will result in a cleaner, clutter-free, and, most importantly, headache-free experience. There are ways to minimize the number of pins used, but we will stick to the basics for now.
Let's start by simplifying the logic. We begin with the on-off toggle. Even though it says on-off, that doesn't mean we have to treat it like an off-state. To simplify it even further, we have state A and state B. If the toggle is in state A, we want to trigger an event, and if it is in state B, we want to trigger another. Now, the events could be anything. Strobes are on, strobes are off, and the fuel pump is on and off, etc.
The switches with 3 pins allow us to use an extra state: State A, state B, and State C (on-off-on). Like the previous switch, we can freely bind any event to either state.
Now, we can't completely ignore the on-off logic. It becomes important to determine whether we are in state A or B. If the signal is LOW, we are in state A; if the signal is HIGH, the switch is in state B. The Same goes for the switch with 3 pins. The difference is that state A is wired to a different pin on the board than state C. This logic is visualized in the table below.
Pin 1 Ground pin Pin 2
State ALOWHIGHLOW
State BHIGHLOWLOW
State CLOWLOWHIGHstate overview
Let us start with a universal approach to coding our switches. This approach can be used in any game to bind controls through the menu. For this, we specifically need an Arduino Pro-micro or Leonardo.
The first thing we need to do is install the HID-Project library. This can be done through the built-in library manager in your Arduino IDE or by downloading the ZIP file directly from their GitHub page. Other options are available, but this one always struck home because of its Github page ease of use.
To start using our gamepad, we must include the library at the top of our sketch and start a Gamepad object in the setup block.
cpp#include <HID-Project.h> void setup() { Gamepad.begin(); }
If we run this sketch, nothing will happen. This makes sense since the board doesn't know where we connected the switches. We do this by defining the pins underneath the library initialization for each switch. Because we use our board's internal pullup resistors, we also need to initialize these in the setup block.
cpp#include <HID-Project.h> //Define the pin belonging to our on-off switch const byte switchAPin1 = 3; //Define the location of 2 pins belonging to our on-off-on switch const byte switchBPin1 = 4; const byte switchBPin2 = 5; void setup() { Gamepad.begin(); //Initialize the internal pullup resistors off all our switches pinMode(switchAPin1, INPUT_PULLUP); pinMode(switchBPin1, INPUT_PULLUP); pinMode(switchBPin2, INPUT_PULLUP); }
The last piece of the puzzle we miss is logic. It knows where the pins are but still has no clue what to do with them. We want to check the current state of each pin to determine the button we press. Our gamepad can support up to 31 buttons in Windows, numbered 1 - 31. If we combine the press of a Gamepad button with one of our switch states, we can create all kinds of cool stuff. To spice things up, there are some options we can still choose from. If we flip the switch, we can hold the Gamepad button, short press it, or anything between. Depending on the game you use it for, this could be important to keep in mind. It's essential to include the line Gamepad.write() whenever you want to update the Gamepad. Without this command, you will never register a keypress on the Gamepad.
cpp#include <HID-Project.h> //Define the pin belonging to our on-off switch const byte switchAPin1 = 3; //Define the location of 2 pins belonging to our on-off-on switch const byte switchBPin1 = 4; const byte switchBPin2 = 5; void setup() { Gamepad.begin(); //Initialize the internal pullup resistors off all our switches pinMode(switchAPin1, INPUT_PULLUP); pinMode(switchBPin1, INPUT_PULLUP); pinMode(switchBPin2, INPUT_PULLUP); } void loop(){ //----!!SWITCH A LOGIC!!---- //read the pin state bool switchAPin1State = digitalRead(switchAPin1); //if the switchAPin1State == true //this is an alternative approach because HIGH = 1 and true = 1 as well //the next example will contain the traditional approach if(switchAPin1State){ Gamepad.press(1); Gamepad.release(11); } //if this statement == false == LOW == 0 else if(!switchAPin1State){ Gamepad.release(1); Gamepad.press(11); } //----!!SWITCH B LOGIC!!---- //read the pin state byte switchBPin1State = digitalRead(switchBPin1); byte switchBPin2State = digitalRead(switchBPin2); if(switchBPin1State == LOW){ Gamepad.press(2); Gamepad.release(4); } if(switchBPin1State == HIGH){ Gamepad.release(2); } if(switchBPin1State == LOW){ Gamepad.press(3); Gamepad.release(4); } if(switchBPin1State == HIGH){ Gamepad.release(3); } //Both are off if(switchBPin2State == LOW && switchBPin1State == LOW){ Gamepad.press(4); } Gamepad.write(); }
This is a fundamental approach and demonstrates that many options are available. By just focussing on 1 switch, I'll demonstrate some alternative approaches below. There are games out there that have widely different keybinds. One game might have you toggle to start an engine, another might have you hold a button to keep the engine running, and perhaps another game has a sequence you need to toggle through. These are all valid options that require custom solutions.
cpp#include <HID-Project.h> byte oldBtnState; ... void loop(){ //----!!SWITCH A LOGIC!!---- //read the pin state byte switchAPin1State = digitalRead(switchAPin1); //----- 2 Button presses depending if the state = HIGH or LOW if(switchAPin1State == LOW){ Gamepad.press(1); Gamepad.release(11); } //if this statement == false == LOW == 0 if(switchAPin1State == HIGH){ Gamepad.release(1); Gamepad.press(11); } //----- 1 Button press if the state = LOW if(switchAPin1State == LOW){ Gamepad.press(1); } //if this statement == false == LOW == 0 if(switchAPin1State == HIGH){ Gamepad.release(1); } //----- 2 Button presses momentary if(switchAPin1State == LOW && oldBtnState == HIGH){ Gamepad.press(1); delay(200); Gamepad.write(); delay(20); Gamepad.release(1); delay(20); Gamepad.write(); oldBtnState = LOW; } //if this statement == false == LOW == 0 if(switchAPin1State == HIGH){ Gamepad.press(2); delay(200); Gamepad.write(); delay(20); Gamepad.release(2); delay(20); Gamepad.write(); oldBtnState = HIGH; } }
Once again, there are cleaner ways to code this, but I want you to understand what happens under the hood without adding too many sparkles. We can always add sprinkles to our cake at a later point.
Using my connector, you can directly send commands to the connector without binding any keys ingame. The most basic approach would look like this. Another plus is using any Arduino (or microcontroller that can communicate over serial) with this approach.
cpp#include <BitsAndDroidsFlightConnector.h> //Create a connector object BitsAndDroidsFlightConnector connector(false); //Define the pin const byte switchAPin1 = 3; //To check against the current state byte oldState; void setup(){ //With the connector it's important to initialize the Serial line Serial.begin(115200); pinMode(switchAPin1, INPUT_PULLUP); } void loop(){ byte currentState = digitalRead(switchAPin1); if(currentState != oldState){ if(currentState == HIGH){ //Send a command to send Strobes on Serial.println(connector.sendStrobesOn()); } else{ //Send a command to send Strobes off Serial.println(connector.sendStrobesOff()); } oldState = currentState; } }
Now, you might wonder why someone would prefer this over a Gamepad. One of the reasons might be the flexibility to switch between outputs on the fly. Let's take a look at the code below.
cpp#include <BitsAndDroidsFlightConnector.h> //We add a variable to track the current mode byte mode = 0; ... void loop(){ byte currentState = digitalRead(switchAPin1); if(currentState != oldState){ if(currentState == HIGH){ //send output based on switch(mode){ case 0: Serial.println(connector.sendStrobesOn()); break; case 1: Serial.println(connector.sendApuGeneratorSwitchToggle()); break; } } ... oldState = currentState; } }
In this example, the output differs depending on a certain mode variable. We could easily add a pushbutton or rotary encoder to switch between modes. This way, we can have 1 controller that performs differently with each mode (without going into the key-binds menu and rebinding everything or switching pre-sets).
Now, with this, the possibilities are endless. Want to make a combination lock to start your plane? Go ahead! Just want a plain old toggle box? Go for it! As with all of these articles, you can use the acquired knowledge to mix and match the components to your needs. Combine some encoders and LED panels with toggle switches to create something truly yours, or keep it simple with just some toggles.
When you make it this far, I want to compliment you on your attention span, which is bigger than mine. If you have any questions or suggestions or spot a mistake, let me know in the comments below. Thanks for reading, and I hope to see you at the next one.