Bits and Droids logo
Mail icon

Back to basics: program a button for the flight sim connector

3/30/2024 | by Bits and Droids

In this new series, I want to go back to basics. I will illuminate various components, how they work, how to code them, and, most importantly, how to use them with the Bits and Droids connector to interface with Microsoft Flight Simulator 2020.

This particular one will be quite a long read since I try to cover some of the basics you need to get started with the connector and Arduinos. Upcoming entries will, therefore, reference these basics and will be shorter.

This series wouldn't be called back to basics if we didn't start with momentary push buttons. Momentary pushbuttons are buttons (.... well, ehm... obviously) that allow current to flow through them as long as you press the button.

In the off-state, the current flow would look like this:

button power not pressed

The power (yellow line) could run up until the button, which theoretically could go up if another connection were present. The button isn't pressed, so no current can run from left to right.

When we press the button, power will run through it towards the ground pin on our board.

button pressed power flow

When you see the red 5V line, you might instantly think of your Arduino's 5V power output. But we won't use the circuit below for our projects.

arduino button 5v powered

If the power ran from the 5V pin to ground through our button, our code could not read the button's current state.

input pullup button

This is a circuit we'll encounter many times when constructing our projects. The pins marked 0-13 and A0 - A5 can all be used for inputs or outputs. A button is a digital signal (so either a 1(HIGH) or 0 (LOW)); therefore, we're able to use 0-13 and A0-A5.

A potentiometer, for instance, needs to be read as an analog signal (a signal that returns an actual value). For a potentiometer, we could only use pins A0 - A5 (the prefix A refers to Analog).

Remember how I said that power wants to flow from source to ground? In the schematic above, the power would run from the source to the button, but the current has no place to go if no button is pressed. The readings from this setup would result in noise and unreliable results. Luckily, the Arduino has an internal resistor that we can activate, giving the current a place to flow to the ground if the button isn't pressed. Because power likes to follow the path with the least resistance, the power will flow through the button to the ground wire when pressed. This resistor will also ensure that the signal state is HIGH when no button is pressed and LOW when we press the button. We will see later in this post how we enable this resistor with code.

Setting our coding environment up

When working with microcontrollers, getting a button to work is a rite of passage.

Luckily, coding them is quite simple once you get the hang of it. Even if it feels like you've entered the magic realm of witchcraft, I promise you that in a few days, you will look back and hear that famous sound in your mind: "Ah, I get it."

The Arduino programs we write are sketches and are usually created in the Arduino IDE. The Arduino IDE can be downloaded for free from

https://www.arduino.cc/en/software.

Select the version that matches your operating system, and you're ready.

Once You've installed and opened the IDE, you should be greeted by a screen that looks like this:

We'll code our sketches in C++. The Arduino environment adds some extra functionalities to the C++ language to make it more beginner-friendly.

There is already some basic code present, "void setup(){}" and "void loop(){}." These are functions (which are blocks of code) that get executed. The setup block gets executed (you guessed it) during the setup of the device. So, every time the device gets powered on, it will execute once. The loop block will be executed as often as the Arduino can. This block could be executed hundreds of times per second if we don't add any delays (or the code gets too complex).

Coding the button

Now that everything is set up, it's time to program our button. I always find it helps to break down what I want to achieve. So, in this case, let's simplify it to every step required.

  • There is a physical button.
  • That is connected to pin 2 on our Arduino.
  • Activate the internal pull-up resistor.
  • We want to know what the button state is (pressed or not pressed)
  • If the button is pressed, do something
  • We want a longer click time to release the button before retriggering the command.

Let's begin in the beginning. In our Arduino code, we're able to define where our inputs are connected by adding the following line at the top:

cpp
//#define lets us define the pin location and associate a name to the pin #define buttonA 2

It is common practice to place the #defines at the top of the file. If we include a library, we place the # defined underneath the libraries. We'll go over this later in this post.

Now that we've defined the button pin, we can proceed to the next part of the list: "enabling the internal pull-up resistors." We add a single line of code to the setup block to enable the internal pull-up resistors.

cpp
void setup(){ pinMode(buttonA, INPUT_PULLUP); }

The pinMode function lets you define how the pin will be used. This could be an INPUT, INPUT_PULLUP, or OUTPUT. By selecting INPUT_PULLUP, we enable the internal pullup resistor while we can read the state of the button.

If statement

The only thing left is handling the reading of the button states. Because we continuously want to check if the button is checked, we put our logic in the loop block. I already gave a hint by typing the word IF in capital letters. In coding, we use the if statement on many occasions. An "if statement" is a statement that checks if a certain condition is true or false and executes the code contained in the statement if the condition is true. We want to check IF the button is pressed -> Do X. In code, this translates to this:

cpp
void loop(){ if(digitalRead(buttonA) == LOW){ //Do something } }

If we break the if statement down, we can spot certain elements. An empty statement looks like this: "if(){}." The word if tells our Arduino an if statement is present. Between the (), we find the condition. In our case, digitalRead(buttonA) will return the current state of our button. digitalRead(buttonA) == LOW declares that if digitalRead(buttonA) IS EQUAL TO LOW. So only if our state is LOW will we execute the code between the {}.

Between the {}, we place the code that needs to be executed when the condition is true. And it will only run if the condition is true. If we want to make it a bit more complicated, we could also write this line of code instead:

cpp
void loop(){ if(!digitalRead(buttonA) == LOW){ //Do something } }

At first glance, it might look very similar, but pay close attention to the exclamation mark in front of the digitalRead. The exclamation mark usually refers to NOT in code. So, in this example, IF NOT digitalRead(buttonA) IS EQUAL TO LOW, execute the code. This inverts our logic, where we execute the code if the button is not pressed (if the state is HIGH).

Now, we already have the foundation needed to use our button. The next step would be to implement the Bits and Droids library and bind our first action to our button. So far, the complete code should look like this:

cpp
//#define let's us define the pin location and associate a name to the pin #define buttonA 2 void setup() { pinMode(buttonA, INPUT_PULLUP); } void loop() { // put your main code here, to run repeatedly: if(digitalRead(buttonA) == LOW){ //Do something } }

The Library

The Arduino library's beauty is that we can use libraries for most heavy lifting. Libraries are files of code that contain functions and variables like we just encountered in our code. To receive Microsoft flight simulator 2020 data from our PC over our USB connection, we'd usually need a code block that looks like this:

cpp
void BitsAndDroidsFlightConnector::dataHandling() { if (Serial.available() > 0) { receivedValue = Serial.readStringUntil('\n'); switchHandling(); } } void BitsAndDroidsFlightConnector::switchHandling(){ prefix = receivedValue.substring(0, 3); cutValue = receivedValue.substring(3); int prefixVal = prefix.toInt(); lastPrefix = prefixVal; switch (prefixVal) { // Ap // lights case 33: { lightTaxiOn = convBool(cutValue); break; }

I've removed this block after a few rules since the full function spans over 400 lines. Theoretically, you could write this code whenever you want to work with the connector. Still, your code would become unreadable rather quickly (not to mention the cumbersome and error-prone task it provides). The library contains the full block of code you can reference without reinventing the wheel.

If you haven't installed the library already, you can open the Arduino IDE. In the top bar, navigate to sketch -> manage libraries -> search for the bits and droids flight sim library and hit install.

As mentioned earlier, we must include the library at the top of our sketch. We only need 1 line of code for this:

cpp
#include<BitsAndDroidsFlightConnector.h>

This will ensure that when we compile the sketch (upload the sketch to our Arduino), the code contained in the library is also uploaded to your Arduino.

To use the library, we need to create an object. An object is a digital container with functions, properties, and variables. We can give the object any name, but I would like to stick with the name connector. In code, it would look like this.

cpp
#include<BitsAndDroidsFlightConnector.h> #define buttonA 2 BitsAndDroidsFlightConnector connector = BitsAndDroidsFlightConnector();

This line gets placed underneath the #define section. Since the connector transmits and receives data over a Serial connection, we need to initiate this connection. In the setup block, we place:

cpp
void setup(){ pinMode(buttonA, INPUT_PULLUP); Serial.begin(115200); Serial.setTimeout(15); }

Serial.begin() starts a Serial connection at a baud rate of 115200. The baud rate is the rate at which the Serial connection sends and transmits data. The higher the value, the faster the connection, but the faster the connection, the more data corruption/loss risk. The most important part is that it matches the same rate as the connector. If no settings are changed, it runs at 115200.

Sending the action

When we send commands over serial with our Arduino, we use the following function:

cpp
Serial.println("hello");

This line would send the text hello over our serial line that we initiated in the setup block. I've already told you that the connector uses the serial line as the chosen form of communication. A command to the connector is just a message sent via a connector.send(). In the documentation, you'll find all supported commands that are currently available. We need to look for functions that are prefixed with the word send.

Some examples are:

ActionLibrary command com 1 Mhz +sendCom1WholeIncCom 1 Mhz –sendCom1WholeDecCom 1 Khz +sendKhzComStandby1PlusCom 1 Khz –sendKhzComStandby1MinCom 1 SwapsendSwapCom1Com 1 Fract increasesendCom1FractIncCom 1 Fract increase carrysendCom1FractIncCarryCom 1 Fract decreasesendCom1FractDecrCom 1 Fract decrease carrysendCom1FractDecrCarry

We could pick any of these functions and send our code the corresponding command over Serial when the button is pushed. For example, I'll show you how to fire sendSwapCom1 (), which fills and swaps our com 1 frequency in the airplane. We will fit this into the code we have made so far.

cpp
#include <BitsAndDroidsFlightConnector.h> //#define let's us define the pin location and associate a name to the pin #define buttonA 2 BitsAndDroidsFlightConnector connector = BitsAndDroidsFlightConnector(); void setup() { Serial.begin(115200); Serial.setTimeout(15); pinMode(buttonA, INPUT_PULLUP); } void loop() { // put your main code here, to run repeatedly: if(digitalRead(buttonA) == LOW){ connector.send(sendSwapCom1); //This delay value can be altered to your liking delay(200); } }

In summary, we've created the logic that checks if the button is pressed. We then said if it is pressed, send connector.sendSwapCom1() over our Serial line. This is followed by a delay of 200 milliseconds to ensure we don't send the command multiple times per click. The delay could be altered to your liking. If you have heavy fingers, I'd suggest increasing this value, or if you want it to fire rapidly, you could lower the value.

When learning to code, it's important to train in spotting patterns. Let's say we don't want to swap the frequency but increase the Mhz part by 1. We first look in the documentation to see which function is associated with the desired action.

You will probably find sendCom1WholeInc(), which sounds like it will do exactly what we need. We can take our previous code and interchange the function in question.

cpp
#include <BitsAndDroidsFlightConnector.h> //#define let's us define the pin location and associate a name to the pin #define buttonA 2 BitsAndDroidsFlightConnector connector = BitsAndDroidsFlightConnector(); void setup() { Serial.begin(115200); Serial.setTimeout(15); pinMode(buttonA, INPUT_PULLUP); } void loop() { // put your main code here, to run repeatedly: if(digitalRead(buttonA) == LOW){ connector.send(sendCom1WholeInc); //This delay value can be altered to your liking delay(200); } }

Another example is if we wanted to add a second button that looks like this. If you're completely new to programming, it's important to consider which code multiplies when adding buttons and which doesn't.

cpp
#include <BitsAndDroidsFlightConnector.h> //#define let's us define the pin location and associate a name to the pin #define buttonA 2 #define buttonB 3 BitsAndDroidsFlightConnector connector = BitsAndDroidsFlightConnector(); void setup() { Serial.begin(115200); Serial.setTimeout(15); pinMode(buttonA, INPUT_PULLUP); pinMode(buttonB, INPUT_PULLUP); } void loop() { // put your main code here, to run repeatedly: if(digitalRead(buttonA) == LOW){ connector.send(sendCom1WholeInc); //This delay value can be altered to your liking delay(200); } if(digitalRead(buttonB) == LOW){ connector.send(sendCom1Swap); //This delay value can be altered to your liking delay(200); } }

And that's it. Hopefully, you know all the ins and outs you need to add as many buttons as you wish to your projects. If you still don't fully grasp the subject, I'd suggest going for a walk, having a nice cup of coffee on your couch, and coming back later. Doing something else can sometimes be the best remedy for a clearer mind. Even after that, you will know where to find me if you still have questions.

If you are still reading this part, I would like to thank you for hanging out, and I hope to see you in the next one!