Skip to content

Gear panel tutorial, part 3 – toggle switch input

3 November, 2012

Let’s add a landing gear switch to our panel.

To simplify things, let’s start a new Arduino sketch, work on the switch without being distracted by the LED code, then merge the two together again afterwards.

I got a basic toggle switch from my local electronics shop. It’s a latching (i.e., not momentary) ON-ON switch, but ON-OFF would work just as well here. For a simple application we can assume that ‘not down’ is the same thing as ‘up’. I can’t just plug it into the breadboard because it has solder-tag connections instead of PCB pins, so I had to solder leads onto it to connect it to the Teensy.

(It is a toggle switch, not a slide switch, but Fritzing doesn’t have any images for those.)

Connecting switches and buttons to a Teensy is very simple. The switch/button just connects one of the Teensy’s pins to the ground. As with the LEDs, it doesn’t matter which pin we use, as long as it’s not:
– one of the power supply pins (marked +5V and GND)
– the analog reference pin (marked R)
– the pin with the built-in LED wired into it. For Teensy++ 2.0 this is pin 6; for the smaller Teensy 2.0 it is pin 11.

A toggle switch generally has three pins. A common pin (in the middle), and two output pins. The position of the switch determines which output pin is connected to the common pin. We should connect the common pin to ground, and one of the output pins to an ordinary Teensy pin. I’m using pin 25.

While I’m talking about wires in tedious detail, it’s a really good idea to get a lot of different coloured wire. It’s not necessary to perfectly adhere to a comprehensive colour-coding system, but if you always use black wire to for the ground connections, red wire for the +5v connections, and other colours for other connections, it will be much harder to become confused while wiring things up. And a stack of different-coloured spools of wire looks pretty.

Let’s test the switch by making the onboard LED light up. It’s always worth testing each new thing and only adding to something you know is working. Whenever I jump ahead I end up with code which doesn’t work for many possible reasons and it takes much longer to find out which part exactly contains the fault. I end up rewriting code which contains no faults to see if that fixes the problem, which then introduces new faults…

This code will read the switch/button connected to pin 25, and light up the on-board LED with the result.

#include <Bounce.h>

const int mySwitchPin = 25;
Bounce mySwitch = Bounce (mySwitchPin, 5);

void setup() {
  pinMode (mySwitchPin, INPUT_PULLUP);
  pinMode (LED_BUILTIN, OUTPUT);
}

void loop() {
  mySwitch.update();

  if(mySwitch.read() == LOW) {
    // it's LOW if we've closed the switch
    // and connected the pin to the ground
    digitalWrite(LED_BUILTIN, HIGH);
  } else {
    digitalWrite(LED_BUILTIN, LOW);
  }
}

A ‘Bounce’ object smooths out the data coming from the input pin, to hide any ‘chatter’ or flickering in input in the milliseconds where the switch is half-closed (a phenomena known as ‘bouncing’). To use a Bounce object, we need to include the Bounce header file in our project. #include <Bounce.h> essentially is an automatic copy-paste from the Bounce.h file where Bounces are defined.

In this line:

Bounce mySwitch = Bounce (mySwitchPin, 5);

we create a Bounce object named mySwitch which listens to pin 25 and waits for 5 milliseconds before deciding the input has definitely changed. Instead of investigating the pin directly with the digitalRead function, we look at mySwitch to find out if the switch is open or closed.

We do this with this line.

if (mySwitch.read() == LOW) {

mySwitch.read() will be low if the switch is closed, connecting it to ground. If the switch is open, the pullup resistor we activated when we said pinMode (mySwitchPin, INPUT_PULLUP); will make the pin voltage high, which makes mySwitch.read() also be high.

Let’s rename mySwitch to gearSwitch and link it to a dataref. We need to find a dataref which describes the gear handle position and is writeable. We could dig around with DataRefEditor again, but as it happens we found one in part 2: sim/cockpit2/controls/gear_handle_down. We can write to the dataref in the same way as we just updated the LED:

#include <Bounce.h>

const int gearSwitchPin = 25;
Bounce gearSwitch = Bounce (gearSwitchPin, 5);

// Landing gear control dataref
FlightSimInteger gearHandleDown;

void setup() {
  pinMode (gearSwitchPin, INPUT_PULLUP);
  pinMode (LED_BUILTIN, OUTPUT);

  gearHandleDown = XPlaneRef("sim/cockpit2/controls/gear_handle_down");
}

void loop() {
  FlightSim.update();
  gearSwitch.update();

  if(gearSwitch.read() == LOW) { // if switch is closed
    digitalWrite(LED_BUILTIN, HIGH); // turn on LED
    // move gear handle to 'down' position
    gearHandleDown = 1;
  } else { // if switch is up
    digitalWrite(LED_BUILTIN, LOW);  // turn off LED
    // move gear handle to 'up' position
    gearHandleDown = 0;
  }
}

You should find your switch now controls the gear handle as well as the on-board LED!

But this is not a very good way of controlling a dataref. Open up the TeensyControls monitor screen (Plugins menu, TeensyControls, and ‘Show Communication’). Move the switch a few times and you’ll see how frantically often we’re updating the gear handle. Carelessly re-sending the same information over and over again is wasteful. But we can avoid this by only writing to gearHandleDown when it isn’t already the value we want it to be.

if(gearSwitch.read() == LOW) { // if hardware switch is closed
    digitalWrite(LED_BUILTIN, HIGH);
    if (gearHandleDown == 0) { // and X-Plane's gear handle is up
      gearHandleDown = 1; // move it down
    }
  } else { // if the switch is open
    digitalWrite(LED_BUILTIN, LOW);
    if (gearHandleDown == 1) { // and gear handle is down
      gearHandleDown = 0; // move it up
    }
  }

Look again at the TeensyControls communication screen. No more frantic repetition!

You should also find that nothing else can move the gear handle. Press G, click on the handle – the Teensy moves it straight back again so it agrees with our toggle switch. We’ve just implemented what I call a blocking input. The Teensy is blocking out all the other inputs.

The alternative is to set up as a sharing input. Here, we only update the dataref when we move the switch, but we ignore the dataref otherwise. This means other inputs (like the G key) can act on the dataref and not immediately get overridden by the Teensy.

  if (gearSwitch.fallingEdge() ) {
    digitalWrite(LED_BUILTIN, HIGH);
    gearHandleDown = 1;
  }
  if (gearSwitch.risingEdge() ) { 
    digitalWrite(LED_BUILTIN, LOW);
    gearHandleDown = 0;
  }

The fallingEdge and risingEdge functions are pretty useful. When you call them, they return true, one time only, if the input has just changed. fallingEdge is true if the input’s just gone low, and risingEdge means it’s just gone high. In plain English, they mean ‘Has this input gone low (or high) since the last time I asked?’.

We need one extra refinement though. Move the hardware switch down (so the orange light goes on), then unplug the Teensy. Move the gear lever up in the simulator, and plug the Teensy back in again. The gear lever is still up, but our hardware says it should be down. It will be tiresome to need to move every single switch up and down to check X-Plane matches its position!

It turns out that Bounce has a function which solves this perfectly, called rebounce(int milliseconds). It triggers a false ‘I’ve just moved’ signal on the Bounce object at a number of milliseconds in the future. We can just slap it into the end of the setup function:

void setup() {
  pinMode (gearSwitchPin, INPUT_PULLUP);
  pinMode (LED_BUILTIN, OUTPUT);

  gearHandleDown = XPlaneRef("sim/cockpit2/controls/gear_handle_down");
  gearSwitch.rebounce(2000);
}

and after 2 seconds (after everything is set up and running) either risingEdge or fallingEdge will be true. And the ordinary this-switch-just-moved code will send the correct number to the dataref. This is great news, we don’t need to repeat ourselves or do anything elaborate to make this special one-off update happen. If we change what happens when we move the switch normally, we’re also changing what happens when the Teensy board first connects!

Let’s do exactly that. We’re writing to a dataref to move the gear. But it is, generally, better to use commands for input, if a suitable command is available. Teensyduino FlightSimCommand objects are very similar to FlightSimInteger and FlightSimFloat, except we can’t read them and we can only set them to 1 or 0, which represent Command Start and Command End. There’s also a .once() function, which combines Start and End so the command is only active momentarily. We can easily swap one FlightSimInteger for two FlightSimCommands, gearUp and gearDown, and call them .once() when the switch is moved. This is a non-blocking input, we’re only calling the commands when the switch moves.

#include <Bounce.h>

const int gearSwitchPin = 25;
Bounce gearSwitch = Bounce (gearSwitchPin, 5);

// Landing gear control commands
FlightSimCommand gearUp;
FlightSimCommand gearDown;

void setup() {
  pinMode (gearSwitchPin, INPUT_PULLUP);
  pinMode (LED_BUILTIN, OUTPUT);

  gearUp = XPlaneRef("sim/flight_controls/landing_gear_up");
  gearDown = XPlaneRef("sim/flight_controls/landing_gear_down");

  gearSwitch.rebounce(2000);
}

void loop() {
  FlightSim.update();
  gearSwitch.update();

  if (gearSwitch.fallingEdge() ) {
    digitalWrite(LED_BUILTIN, HIGH);
    gearDown.once();
  }
  if (gearSwitch.risingEdge() ) { 
    digitalWrite(LED_BUILTIN, LOW);
    gearUp.once();
  }
}

The choice of blocking or sharing input is up to you. The advantage of a blocking input is that the position of your hardware switches is definitely the position of the simulated switches. The downside is you can’t use other inputs, and two blocking inputs on the same dataref will fight each other. I prefer blocking inputs, because I can reach a switch with my fingers and tell by touch what state the input is in. Your system may vary!

One more thing to do today. Let’s combine the new input code with the LED-output code to have a complete gear panel, with a blocking-input gear position switch:

#include <Bounce.h>

////////////////////////////////////////
// Hardware input
//
const int gearSwitchPin = 25;
Bounce gearSwitch = Bounce (gearSwitchPin, 5);

////////////////////////////////////////
// Hardware output
//
const int greenLeftPin  = 10;
const int greenNosePin  = 11;
const int greenRightPin = 12;

const int redLeftPin    = 20;
const int redNosePin    = 19;
const int redRightPin   = 18;

////////////////////////////////////////
// X-Plane input and output
//
// Landing gear handle commands
FlightSimCommand gearUp;
FlightSimCommand gearDown;

// Landing gear actual position 
FlightSimFloat gearDeployLeft;
FlightSimFloat gearDeployNose;
FlightSimFloat gearDeployRight;

// Landing gear handle position 
FlightSimInteger gearHandleDown;

// Aircraft essential bus voltage
FlightSimFloat supplyVolts;

void setup() {

  //////////////////
  // Input
  //
  pinMode (gearSwitchPin, INPUT_PULLUP);

  //////////////////
  // Output
  //
  pinMode(greenLeftPin,  OUTPUT);
  pinMode(greenNosePin,  OUTPUT);
  pinMode(greenRightPin, OUTPUT);

  pinMode(redLeftPin,    OUTPUT);
  pinMode(redNosePin,    OUTPUT);
  pinMode(redRightPin,   OUTPUT);

  //////////////////
  // X-Plane
  //
  gearUp          = XPlaneRef("sim/flight_controls/landing_gear_up");
  gearDown        = XPlaneRef("sim/flight_controls/landing_gear_down");

  gearDeployLeft  = XPlaneRef("sim/flightmodel2/gear/deploy_ratio[1]");
  gearDeployNose  = XPlaneRef("sim/flightmodel2/gear/deploy_ratio[0]");
  gearDeployRight = XPlaneRef("sim/flightmodel2/gear/deploy_ratio[2]");

  gearHandleDown  = XPlaneRef("sim/cockpit2/controls/gear_handle_down");  

  supplyVolts     = XPlaneRef("sim/cockpit2/electrical/bus_volts[0]");
}

void loop() {

  FlightSim.update();
  gearSwitch.update();

  //////////////////
  // Process input
  //

  // blocking input on gear handle position
  if(gearSwitch.read() == LOW) { // if the switch is closed
    if (gearHandleDown == 0) {   // if gear handle is up
      gearDown.once();           // move it down
    }
  } else {
    if (gearHandleDown == 1) {   // if gear handle is down
      gearUp.once();             // move it up
    }
  } // gearSwitch

  //////////////////
  // Process output
  //

  // we need 10V and the sim to be running to light the LEDs
  bool canLight = (supplyVolts > 10.0) && FlightSim.isEnabled();

  // light red LEDs if gear handle and position disagree and we have power
  digitalWrite(redLeftPin,  (gearHandleDown != gearDeployLeft)  && canLight);
  digitalWrite(redNosePin,  (gearHandleDown != gearDeployNose)  && canLight);
  digitalWrite(redRightPin, (gearHandleDown != gearDeployRight) && canLight);

  // light green LEDs if gear down and we have power
  digitalWrite(greenLeftPin,  (gearDeployLeft  == 1.0) && canLight);
  digitalWrite(greenNosePin,  (gearDeployNose  == 1.0) && canLight);
  digitalWrite(greenRightPin, (gearDeployRight == 1.0) && canLight);

}

I’ve changed the formatting a bit, and we don’t need the rebounce with this blocking implementation. Notice we’re checking the handle position using the dataref, then using a command to change the position if the it is wrong.

That’s all for today! I’ll continue this series when the post arrives from ebay, and we’ll add a flaps gauge to the panel. Any suggestions or feedback, you can get in touch in the comments or through simulationelectronics@gmail.com.

– Jack

Advertisements
One Comment
  1. Peter Dobson. permalink

    Excellent project, I’ve already added some further functionality. Thought I have not got my head around PWM drive. Would be good to drive analogue instruments with this. Keenly awaiting further tutorials.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s