Howto:Use Arduino with FlightGear: Difference between revisions

From FlightGear wiki
Jump to navigation Jump to search
mNo edit summary
No edit summary
 
(14 intermediate revisions by 7 users not shown)
Line 1: Line 1:
'''[http://www.arduino.cc/ Arduino]''' is an open-source electronics prototyping platform based on flexible, easy-to-use [[:Category:Hardware|hardware]] (consisting of a board designed around an 8-bit or a 32-bit microcontroller) and software [http://arduino.cc/en/main/software Arduino IDE]).
Thanks to FlightGear's [[generic protocol]], [[:Category:Hardware|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.
[[FlightGear]]'s IO interface allows easy development of hardware that can improve the immersion and realism of the simulation. The output [[Generic protocol|protocol]] allow hardware to respond to simulation data, while the input protocol allows FlightGear to reply to hardware events (e.g., on the press of a button).


== How to: Control Flightgear with Arduino ==
== About Arduino ==
'''[http://www.arduino.cc/ 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 {{Abbr|I/O|Input/Output}} ports.  The software is the [http://arduino.cc/en/Main/Software Arduino {{Abbr|IDE|Integrated Development Environment}}].


With this "how to", you are able to do a switch and a potentiometer interface to control Flightgear's properties such as aileron, elevator, gears, lights, fuel valve, etc..
== Example 1: 2-axis joystick ==
<big>By ScottBouch</big>
 
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 [http://www.scottbouch.com/flightgear-sim-arduino-serial-hardware-2-axis-potentiometer-joystick.html 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 ===
<syntaxhighlight lang="c">
/*
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
}
</syntaxhighlight>
Slightly smoother control is possible if a third digit is added to the output by changing the print statements to <code>Serial.print(stickx, 3);</code> and <code>Serial.print(sticky, 3);</code>.
 
=== 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:
 
<syntaxhighlight lang="xml">
<?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>
</syntaxhighlight>
 
=== 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.
 
<syntaxhighlight>
--generic=serial,in,30,/dev/ttyACM0,9600,hardware
</syntaxhighlight>
 
== Example 2: Controlling internal properties ==
<big>By {{usr|Vaipe}}</big>
 
This example demonstrates the use of a switch and a potentiometer to control the [[Property Tree]].


=== Equipment and software ===
=== Equipment and software ===
 
The following equipment was used for this example:
This example uses following components and software:
* [[Changelog_3.2|FlightGear 3.2]]
* [[Changelog_3.2|FlightGear 3.2]]
* [[Flightgear Launch Control]] (program to make FlightGear start without terminal command and options)
* [[FGRun]]
* Arduino UNO
* [[Cessna 172P|Cessna 172P Skyhawk]] (default aircraft)  
* Linux (Ubuntu 14.04)
* [http://arduino.cc/en/Main/ArduinoBoardUno Arduino Uno]
* On/off switch
* Linux ([http://en.wikipedia.org/wiki/List_of_Ubuntu_releases#Ubuntu_10.04_LTS_.28Lucid_Lynx.29 Ubuntu 14.04])
* Simple on/off switch
* Potentiometer
* Potentiometer
* Flightgear's Cessna 172P Skyhawk


===Input protocol file===
=== Input protocol file ===
Input protocol file is used to specify how serial information is read by Flightgear. In Ubuntu protocol files are found in:
<code>''/usr/share/games/flightgear/protocol''</code> directory.


Input protocol file is used to specify how serial information is read by Flightgear. In Ubuntu protocol files are found in:
==== Protocol file structure ====
"/usr/share/games/flightgear/protocol" directory.
Create <code>''controltest.xml''</code> file in your protocol folder and paste code from below to it.
<syntaxhighlight land="xml">
<?xml version="1.0"?>


====Protocol file structure====
<PropertyList>


Create controltest.xml file to your protocol folder and paste code from below to it.
<generic>
<syntaxhighlight land="xml">
    <input>  
<?xml version="1.0"?>
        <line_separator>\n</line_separator>
<PropertyList>
        <var_separator>,</var_separator>
<generic>
  <input>
    
    
  <line_separator>\n</line_separator>
        <chunk>
  <var_separator>,</var_separator>
            <name>Strobe</name>
            <node>/controls/lighting/strobe</node>
            <type>bool</type>
        </chunk>
    
    
  <chunk>
        <chunk>
    <name>Strobe</name>
            <name>Throttle</name>
    <node>/controls/lighting/strobe</node>
            <node>/controls/engines/engine/throttle</node>
    <type>bool</type>
            <type>float</type>
  </chunk>
        </chunk>
 
  <chunk>
    <name>Throttle</name>
    <node>/controls/engines/engine/throttle</node>
    <type>float</type>
  </chunk>
   
   
  </input>
    </input>
  </generic>
</generic>
</PropertyList>
 
</code>
</PropertyList>
</syntaxhighlight>
</syntaxhighlight>
Explanation of lines:
See [[Generic protocol]] for a description of the various XML tags.
 
*Line separator: "line_separator" is used to tell Flightgear that a line of commands is at end and new one is starting. In this example file its "/n" and it means new line.
*Variable separator: "variable_separator" is used to tell Flightgear that another variable is after this mark. In this protocol file it's ",".
*Chunk: "chunk" is to specify area for one variable and it's information.
*Name: "name" is used only for identifying variable so it can be anything.
*Node: "node" is an address to tell what property in Flightgear is changed. In this example it is "/controls/lighting/strobe" and "controls/engines/engine/throttle".
*Type: "type" line tells what kind of command is send. In this example it is a "bool" and that means a boolean type of property of a "float" for float.


For a better explanation of configuration file, read [[Generic_protocol|Generic Protocol]] wiki page. You can find a list of properties that can be controlled from a Flightgears docs-folder a file called "README.properties". Or you can use [[Property_browser|Flightgears Property Browser]] from main menu: Debug > Browse Internal Properties. Properties in protocol file needs to be in same order as they are send from Arduino.
=== Wiring and coding ===


===Wiring and coding Arduino===
==== 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.
[[File:Arduino switch and potentiometer wiring.png|frame|none|Wiring schematic for connecting the potentiometer and switch to Arduino]]


====Wiring====
==== Code ====
Potentiometer is connected to Arduinos ground and +5 volts. Potentiometers middle connector is connected to A0 analoq input. Switch is connected to ground with 10 kOhms pull-down resistor and +5 and digital pin 7.
Copy this C code to Arduino IDE and send it to the Arduino Uno:
[[File:Arduino switch and potentiometer wiring.png|frame|none|Wiring schematic for connecting potentiometer and switch to Arduino]]
<syntaxhighlight lang="c">
 
====Code====
Copy this code to Arduino IDE and send it to Arduino Uno:
<syntaxhighlight lang="">
     /*
     /*
       FGFS Input Test
       FGFS Input Test
Line 135: Line 208:
</syntaxhighlight>
</syntaxhighlight>


====Test serial output====
==== Testing serial output ====
Use Arduino IDE's serial monitor and you should see something like this:
[[File:Flightgear arduino serial monitor.png|frame|none|Arduino IDE's serial monitor output]]


Use Arduino IDEs 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.
[[File:Flightgear arduino serial monitor.png|frame|none|Arduino IDEs serial monitor output]]


First number is switch data, so it's either 0 (switch off) or 1 (switch on). After "," mark is our throttle data. First it's 0.00 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'''.


{{Warning|Remember to '''unplug Arduino's USB cable and plug it back''' in because Flightgear won't be able to read serial without doing this! You have to do this every time after you use Arduino IDE.}}
FlightGear will not be able to read serial without doing this!


====Start Flightgear====
You have to do this every time after you use the Arduino IDE.}}


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:
{{Note|The above note may not be relevant to newer versions of the Arduino IDE software.}}


--generic=serial,in,30,/dev/ttyACM0,controltest
==== Starting FlightGear ====


If you like, you can use graphical user interface, Flightgear Launch Control (aka FGRun), to launch Flightgear. Select correct settings from Advanced Option tab. [[File:Starting Flightgear with input options enabled.jpg|thumb|none|Starting Flightgear with FGLaunch Control, selecting input/output options]]
===== 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:
<syntaxhighlight>
--generic=serial,in,30,/dev/ttyACM0,9600,controltest
</syntaxhighlight>
 
===== Method 2: FGRun =====
Alternatively, you can use FlightGear's graphical user interface (FGRun) to launch FlightGear. See the image below for the correct settings.
[[File:Starting Flightgear with input options enabled.jpg|thumb|none|Starting Flightgear with FGRun, selecting input/output options]]
 
If you don't know your correct port is , you can check it with a following command in terminal:
<syntaxhighlight>
dmesg | tail
</syntaxhighlight>
It should give you a message something like <code>ttyACM0: USB ACM device</code> or <code>ttyACM1: USB ACM device</code>.
 
{{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 [https://sites.google.com/site/flightgeararduinoandlinux/home 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


If you don't now your correct port, 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". That's your port. Finally save setting by clicking 'OK' and click 'Run' to start flightgear. For more detailed guide, see [https://sites.google.com/site/flightgeararduinoandlinux/home Flightgear, Arduino and Linux]
establish a connection to FlightGear.}}


== Example 3: Outputting properties ==
<big>By {{usr|Rubdos}}</big>
[[File:Arduinofgfs.jpg|thumb|270px|Arduino LCD panel displaying speed, heading and altitude.]]
[[File:Arduinofgfs.jpg|thumb|270px|Arduino LCD panel displaying speed, heading and altitude.]]
== Display/Generic protocol Example by rubdos ==
This example uses the example using the [[Generic protocol]] and an [http://arduino.cc/en/Main/arduinoBoardMega2560 Arduino Mega 2560].
Below is the protocol XML file used to control the Arduino.
<syntaxhighlight lang="xml">
<?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>
</syntaxhighlight>
 
Below is the C code used for the example, taken from https://gist.github.com/rubdos/5422870.
<syntaxhighlight lang="c">
//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;


Rubdos (Ruben De Smet) has built an example using the [[Generic Protocol]] and an Arduino Mega 2560.
    while(lastchar != '=')
The code used to control the Arduino with generic protocol was:
    {
<syntaxhighlight lang="xml">
      lastchar = Serial.read();
<?xml version="1.0"?>
      if(lastchar != '=')
<PropertyList>
      {
     <generic>
        command += lastchar;
        <output>
      }
            <binary_mode>false</binary_mode>
    }
            <line_separator>newline</line_separator>
    while(lastchar != '\n')
            <var_separator>newline</var_separator>
    {
            <preamble></preamble>
      lastchar = Serial.read();
            <postamble></postamble>
      if(lastchar != '\n')
            <chunk>
      {
                <name>Altitude</name>
        var += lastchar;
                <node>/position/altitude-ft</node>
      }
                <type>integer</type>
    }
                <format>altitude=%i</format>
   
            </chunk>
    if(command == "altitude" )
            <chunk>
    {
                <name>RPM</name>
      char buf[50];
                <node>/engines/engine/rpm</node>
      var.toCharArray(buf, 50);
                <type>integer</type>
      number = atol(buf);
                <format>rpm=%i</format>
     }
             </chunk>
   
        </output>
    /*if(number == 10000)
  <!--        <input>
    {
            <line_separator>newline</line_separator>
      number = 0;
            <var_separator>tab</var_separator>
    }*/
             <chunk>
   
            </chunk>
    long currentnumber = number;
        </input>
   
        -->
    int remainder = currentnumber % 10;
    </generic>
    currentnumber =  (currentnumber - remainder) / 10;
</PropertyList>
    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;
  }
}
</syntaxhighlight>
</syntaxhighlight>
It is a simple plaintext protocol, which can easily be parsed by an Arduino. The code used on the Arduino is available on github as a gist: [https://gist.github.com/rubdos/5422870]


As hardware, five seven segment displays were used, multiplexed straight on the Arduino device. In production, you'd rather use some 74HC595 or other shift register chips to drive them, to unload the Arduino and have more current.
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.
A demo is uploaded to youtube, with voiceover in which the display shows the RPM of the first engine of (the single engine) [[DR400]]: [https://www.youtube.com/watch?v=lVtV9-CgqBo]
 
Below is a demo uploaded to YouTube, with voiceover in which the display shows the RPM of [[Robin DR400]]'s single engine.
{{#ev:youtube|lVtV9-CgqBo}}


== Related content ==
== Related content ==
Line 201: Line 541:


== External links ==
== External links ==
* [http://arduino.cc/ Official website]
* [http://arduino.cc/ Official Arduino website]
* [http://playground.arduino.cc/Main/FlightGear FlightGear Serial Communications with Arduino] (tutorial)
* [http://playground.arduino.cc/Main/FlightGear FlightGear Serial Communications with Arduino] (tutorial)
* [http://forum.flightgear.org/viewtopic.php?f=18&t=11126 Arduino LCD and FlightGear] (FlightGear forum)
* [http://forum.flightgear.org/viewtopic.php?f=18&t=11126 Arduino LCD and FlightGear] (FlightGear forum)

Latest revision as of 21:07, 5 November 2021

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:

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.

Wiring schematic for connecting the potentiometer and switch to Arduino

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:

Arduino IDE's serial monitor output

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.

Starting Flightgear with FGRun, selecting input/output options

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

Arduino LCD panel displaying speed, heading and altitude.

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