Howto:Use Arduino with FlightGear
Thanks to FlightGear's generic protocol, hardware can easily interface with FlightGear. This hardware can be used to improve the immersion and/or realism of the simulation. Arduino is no exception.
About Arduino
Arduino is an open-source electronics prototyping platform based on flexible, easy-to-use hardware and software. The hardware is a microcontroller designed around an 8-bit or 32-bit microcontroller, with several digital and analog I/O ports. The software is the Arduino IDE.
Example 1: 2-axis joystick
By ScottBouch
This example demonstrates use of two potentiometers (2-axis joystic) with a simple calibration in arduino code. Example is done with Linux Mint. To see more detailed version of this quide go to 2-Axis Potentiometer Joystick:Integration With Flightgear Flight Sim.
Wiring
Connect 5V to other terminal of potentiometers and 0V to other terminal. Connect potentiometers wiper terminals to Arduino boards A0 and A1.
Arduino code
/*
Flightgear hardware integration 01: Stick X and Y only so far.
Scott Bouchard UK www.scottbouch.com 14-06-2017
*/
const int stickxio = A0; //Define stick aileron (x) input
const int stickyio = A1; //Define stick elevator (y) input
float stickx = 0; //Start aileron (x) central
float sticky = 0; //Start elevator (y) central
void setup() {
Serial.begin(9600); //Open up serial communication to PC
}
void loop() {
stickx = (analogRead(stickxio)/512.0)-0.99; //Calibration span and offset
sticky = (analogRead(stickyio)/512.0)-0.99; //Calibration span and offset
Serial.print(stickx); //Send aileron position
Serial.print(","); //Variable (var) separator
Serial.print(sticky); //Send elevator position
Serial.print("\n"); //Line separator
}
Slightly smoother control is possible if a third digit is added to the output by changing the print statements to Serial.print(stickx, 3);
and Serial.print(sticky, 3);
.
Calibration
Use Arduino serial monitor to see that serial data acquired from Arduino board is between -1.00...1.00 when potentiometers are rotated. Potentiometers middle position should send 0.00. If potentiometers are not giving good readings, modify Arduino code "Calibration span and offset" row to fix it.
Flightgear protocol code
Create a file called hardware.xml to /usr/share/games/flightgear/Protocol directory and paste following lines to it:
<?xml version="1.0"?>
<PropertyList>
<generic>
<input>
<line_separator>\n</line_separator>
<var_separator>,</var_separator>
<chunk>
<name>aileron</name>
<type>float</type>
<node>/controls/flight/aileron</node>
</chunk>
<chunk>
<name>elevator</name>
<type>float</type>
<node>/controls/flight/elevator</node>
</chunk>
</input>
</generic>
</PropertyList>
Make Flightgear read serial data
Find the port where the Arduino is connected. Look for Arduino IDE Tools... Serial Port... Should be something like ttyACM0. (Note: Scott Bouch tutorial uses FGRUN which is not used anymore) Start Flightgear and paste following code to Settings... Additional settings... when starting Flightgear. Change serial port to correct port name. Note that the ".xml" extension for the protocol file should be omitted.
--generic=serial,in,30,/dev/ttyACM0,9600,hardware
Example 2: Controlling internal properties
By Vaipe
This example demonstrates the use of a switch and a potentiometer to control the Property Tree.
Equipment and software
The following equipment was used for this example:
- FlightGear 3.2
- FGRun
- Cessna 172P Skyhawk (default aircraft)
- Arduino Uno
- Linux (Ubuntu 14.04)
- Simple on/off switch
- Potentiometer
Input protocol file
Input protocol file is used to specify how serial information is read by Flightgear. In Ubuntu protocol files are found in:
/usr/share/games/flightgear/protocol
directory.
Protocol file structure
Create controltest.xml
file in your protocol folder and paste code from below to it.
<?xml version="1.0"?>
<PropertyList>
<generic>
<input>
<line_separator>\n</line_separator>
<var_separator>,</var_separator>
<chunk>
<name>Strobe</name>
<node>/controls/lighting/strobe</node>
<type>bool</type>
</chunk>
<chunk>
<name>Throttle</name>
<node>/controls/engines/engine/throttle</node>
<type>float</type>
</chunk>
</input>
</generic>
</PropertyList>
See Generic protocol for a description of the various XML tags.
Wiring and coding
Wiring
A potentiometer is connected to Arduinos ground and +5 volts. The potentiometer's middle connector is connected to A0 analog input. Switch is connected to ground with 10 kOhms pull-down resistor and +5 and digital pin 7. The diagram below illustrates the setup.
Code
Copy this C code to Arduino IDE and send it to the Arduino Uno:
/*
FGFS Input Test
Reads a digital input on pin 7, prints the result to the serial port.
Reads a potentiometer input on A0 and print result to serial port.
This example code is in the public domain.
*/
int potPin = 0; // potentiometer on A0
int switchPin = 7; // switch on pin 7
float potValue = 0; // float variable to store potentiometer value
void setup() {
Serial.begin(9600); // open serial connection
pinMode(switchPin, INPUT); // pin 7 declared as input
}
void loop() {
Serial.print(digitalRead(switchPin)); // read and print switch state
Serial.print(","); // print ,
potValue = analogRead(potPin); // read potentiometer and store it to potValue
potValue = potValue / 1024; // divide potValue with 1024 to make it between 0 and 1
PrintDouble(potValue, 2); // pass potValue to PrintDouble-function, read from below what magic happens
Serial.print("\n"); // print new line
delay(500); // delay only for making this guide easier to follow on serial monitor
}
void PrintDouble(double val, byte precision){
// prints val with number of decimal places determine by precision
// precision is a number from 0 to 6 indicating the desired decimial places
// example: lcdPrintDouble( 3.1415, 2); // prints 3.14 (two decimal places)
// From http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1207226548
if(val < 0.0){
Serial.print('-');
val = -val;
}
Serial.print (int(val)); //prints the int part
if( precision > 0) {
Serial.print("."); // print the decimal point
unsigned long frac;
unsigned long mult = 1;
byte padding = precision -1;
while(precision--)
mult *=10;
if(val >= 0)
frac = (val - int(val)) * mult;
else
frac = (int(val)- val ) * mult;
unsigned long frac1 = frac;
while( frac1 /= 10 )
padding--;
while( padding--)
Serial.print("0");
Serial.print(frac,DEC) ;
}
}
Testing serial output
Use Arduino IDE's serial monitor and you should see something like this:
The first number is switch data, so it's either 0 (switch off) or 1 (switch on). After the "," mark is our throttle data. First it's 0.00, which meaning idle throttle and then potentiometer is gradually turned until it reaches 0.99.
Note Remember to unplug Arduino's USB cable and plug it back.
FlightGear will not be able to read serial without doing this! You have to do this every time after you use the Arduino IDE. |
Note The above note may not be relevant to newer versions of the Arduino IDE software. |
Starting FlightGear
Method 1: Command line
FlightGear needs to be started with a correct command line option for it to be able to read serial connection. This example uses following option:
--generic=serial,in,30,/dev/ttyACM0,9600,controltest
Method 2: FGRun
Alternatively, you can use FlightGear's graphical user interface (FGRun) to launch FlightGear. See the image below for the correct settings.
If you don't know your correct port is , you can check it with a following command in terminal:
dmesg | tail
It should give you a message something like ttyACM0: USB ACM device
or ttyACM1: USB ACM device
.
Note This command gives you the last event in the stack,
so you need to make sure you plug in or unplug your Arduino to the serial port immediately prior to running the command. |
That is your port. Finally, save setting by clicking "OK" and click "Run" to start FlightGear. For a more detailed guide, see Flightgear, Arduino and Linux
Note In some installations you need set permission for $user
to the groups tty and dialout or the Arduino will fail to establish a connection to FlightGear. |
Example 3: Outputting properties
By Rubdos
This example uses the example using the Generic protocol and an Arduino Mega 2560. Below is the protocol XML file used to control the Arduino.
<?xml version="1.0"?>
<PropertyList>
<generic>
<output>
<binary_mode>false</binary_mode>
<line_separator>newline</line_separator>
<var_separator>newline</var_separator>
<preamble></preamble>
<postamble></postamble>
<chunk>
<name>Altitude</name>
<node>/position/altitude-ft</node>
<type>integer</type>
<format>altitude=%i</format>
</chunk>
<chunk>
<name>RPM</name>
<node>/engines/engine/rpm</node>
<type>integer</type>
<format>rpm=%i</format>
</chunk>
</output>
<!-- <input>
<line_separator>newline</line_separator>
<var_separator>tab</var_separator>
<chunk>
</chunk>
</input> -->
</generic>
</PropertyList>
Below is the C code used for the example, taken from https://gist.github.com/rubdos/5422870.
//PIN 0 -> 7 has positive segment part
// the setup routine runs once when you press reset:
void setup() {
// initialize the digital pin as an output.
pinMode(2, OUTPUT);
pinMode(3, OUTPUT);
pinMode(4, OUTPUT);
pinMode(5, OUTPUT);
pinMode(6, OUTPUT);
pinMode(7, OUTPUT);
pinMode(8, OUTPUT);
pinMode(9, OUTPUT);
pinMode(49, OUTPUT);
pinMode(50, OUTPUT);
pinMode(51, OUTPUT);
pinMode(52, OUTPUT);
pinMode(53, OUTPUT);
Serial.begin(9600);
}
void writeNumber(int nr)
{
if(nr == 0)
{
digitalWrite(2, LOW); // midden
digitalWrite(3, HIGH); // lt
digitalWrite(4, HIGH); // t
digitalWrite(5, HIGH); // rt
digitalWrite(6, HIGH); // lb
digitalWrite(7, HIGH); // b
digitalWrite(8, HIGH); // rb
digitalWrite(9, LOW); // dot
}
else if(nr == 1)
{
digitalWrite(2, LOW); // midden
digitalWrite(3, LOW); // lt
digitalWrite(4, LOW); // t
digitalWrite(5, HIGH); // rt
digitalWrite(6, LOW); // lb
digitalWrite(7, LOW); // b
digitalWrite(8, HIGH); // rb
digitalWrite(9, LOW); // dot
}
else if(nr == 2)
{
digitalWrite(2, HIGH); // midden
digitalWrite(3, LOW); // lt
digitalWrite(4, HIGH); // t
digitalWrite(5, HIGH); // rt
digitalWrite(6, HIGH); // lb
digitalWrite(7, HIGH); // b
digitalWrite(8, LOW); // rb
digitalWrite(9, LOW); // dot
}
else if(nr == 3)
{
digitalWrite(2, HIGH); // midden
digitalWrite(3, LOW); // lt
digitalWrite(4, HIGH); // t
digitalWrite(5, HIGH); // rt
digitalWrite(6, LOW); // lb
digitalWrite(7, HIGH); // b
digitalWrite(8, HIGH); // rb
digitalWrite(9, LOW); // dot
}
else if(nr == 4)
{
digitalWrite(2, HIGH); // midden
digitalWrite(3, HIGH); // lt
digitalWrite(4, LOW); // t
digitalWrite(5, HIGH); // rt
digitalWrite(6, LOW); // lb
digitalWrite(7, LOW); // b
digitalWrite(8, HIGH); // rb
digitalWrite(9, LOW); // dot
}
else if(nr == 5)
{
digitalWrite(2, HIGH); // midden
digitalWrite(3, HIGH); // lt
digitalWrite(4, HIGH); // t
digitalWrite(5, LOW); // rt
digitalWrite(6, LOW); // lb
digitalWrite(7, HIGH); // b
digitalWrite(8, HIGH); // rb
digitalWrite(9, LOW); // dot
}
else if(nr == 6)
{
digitalWrite(2, HIGH); // midden
digitalWrite(3, HIGH); // lt
digitalWrite(4, HIGH); // t
digitalWrite(5, LOW); // rt
digitalWrite(6, HIGH); // lb
digitalWrite(7, HIGH); // b
digitalWrite(8, HIGH); // rb
digitalWrite(9, LOW); // dot
}
else if(nr == 7)
{
digitalWrite(2, LOW); // midden
digitalWrite(3, LOW); // lt
digitalWrite(4, HIGH); // t
digitalWrite(5, HIGH); // rt
digitalWrite(6, LOW); // lb
digitalWrite(7, LOW); // b
digitalWrite(8, HIGH); // rb
digitalWrite(9, LOW); // dot
}
else if(nr == 8)
{
digitalWrite(2, HIGH); // midden
digitalWrite(3, HIGH); // lt
digitalWrite(4, HIGH); // t
digitalWrite(5, HIGH); // rt
digitalWrite(6, HIGH); // lb
digitalWrite(7, HIGH); // b
digitalWrite(8, HIGH); // rb
digitalWrite(9, LOW); // dot
}
else if(nr == 9)
{
digitalWrite(2, HIGH); // midden
digitalWrite(3, HIGH); // lt
digitalWrite(4, HIGH); // t
digitalWrite(5, HIGH); // rt
digitalWrite(6, LOW); // lb
digitalWrite(7, HIGH); // b
digitalWrite(8, HIGH); // rb
digitalWrite(9, LOW); // dot
}
else
{
digitalWrite(2, LOW); // midden
digitalWrite(3, LOW); // lt
digitalWrite(4, LOW); // t
digitalWrite(5, LOW); // rt
digitalWrite(6, LOW); // lb
digitalWrite(7, LOW); // b
digitalWrite(8, LOW); // rb
digitalWrite(9, LOW); // dot
}
}
// the loop routine runs over and over again forever
long number = 0;
int decimals[5] = {0, 0, 0, 0, 0};
void loop() {
for(int i = 49; i < 54; i++)
{
// Disable the incorrect segment displays
if(i == 49)
{
digitalWrite(53, HIGH);
}
else
{
digitalWrite(i - 1, HIGH);
}
digitalWrite(i, LOW);
// Enable the segments
writeNumber(decimals[4 - (i - 49)]);
delay(1);
}
if(Serial.available() > 14) // Wait until there are two bytes available. Then read them out.
{
String command;
String var;
char lastchar;
while(lastchar != '=')
{
lastchar = Serial.read();
if(lastchar != '=')
{
command += lastchar;
}
}
while(lastchar != '\n')
{
lastchar = Serial.read();
if(lastchar != '\n')
{
var += lastchar;
}
}
if(command == "altitude" )
{
char buf[50];
var.toCharArray(buf, 50);
number = atol(buf);
}
/*if(number == 10000)
{
number = 0;
}*/
long currentnumber = number;
int remainder = currentnumber % 10;
currentnumber = (currentnumber - remainder) / 10;
decimals[4] = remainder;
remainder = currentnumber % 10;
currentnumber = (currentnumber - remainder) / 10;
decimals[3] = remainder;
remainder = currentnumber % 10;
currentnumber = (currentnumber - remainder) / 10;
decimals[2] = remainder;
remainder = currentnumber % 10;
currentnumber = (currentnumber - remainder) / 10;
decimals[1] = remainder;
remainder = currentnumber % 10;
currentnumber = (currentnumber - remainder) / 10;
decimals[0] = remainder;
}
}
The hardware used was five seven-segment displays, multiplexed straight on the Arduino device. Ideally, you'd rather use some 74HC595 or other shift register chips to drive them, to unload the Arduino and have more current.
Below is a demo uploaded to YouTube, with voiceover in which the display shows the RPM of Robin DR400's single engine.
Related content
External links
- Official Arduino website
- FlightGear Serial Communications with Arduino (tutorial)
- Arduino LCD and FlightGear (FlightGear forum)
- Flightgear, Arduino and Linux (potentiometer and switch interfacing tutorial)