GithubHelp home page GithubHelp logo

falconpanel's Introduction

Falconpanel

An Arduino program for surfacing switches and knobs as a USB game controller.

Show Me

Here’s a video demonstrating the features of Falconpanel:

https://www.youtube.com/watch?v=VwVLXjgCeJg

Status

Alpha - all code subject to change. May cause tingling in the extremities.

What’s with the name?

I developed this in the process of building a control panel for my favorite flight simulator Falcon BMS. However, since this surfaces the Arduino as a regular USB game controller, there’s really no reason that it has to be used just with Falcon; it should work with any program that’s expecting DirectX buttons or axes.

My Hardware

panel-800.jpg

The buttons and switches are at the left edge. The thing in the middle is a Logitech G13 that I use for the ICP/DED, and has nothing to do with Falconpanel, other than sitting on the same stand. The point of the picture is to show how I was able to make a pretty simple panel by drilling some holes in some 1/4” plywood and mounting various things in it. Here’s a shot of the back:

panel-back-800.jpg

The hardware I used:

But you can use any Arduino, you don’t need the protoshield nor the breadboard, and you can use whatever switches, knobs, and buttons you like. I used these.

The software

Prerequisities

If you want to compile and build exactly what I did (unlikely), you’ll need to install the Arduino tools and then - as the key part of all this - The HID Project libraries. NicoHood has done all the hard work here, and I will refer you to his documentation for installation.

Falconpanel features

The key idea in Falconpanel is that of Components. These map physical controls and other electronics to USB buttons and axes. You will need to map these to your particular setup by modifying the code in Falconpanel that looks like this:

// I've got a 74LS151 3-to-8 mux with its address pins connected to
// Arduino pins 2-4, and with its output pin connected to Arduino pin
// 5. We have to declare this outside the components array below
// because we reference it in there.
IC74LS151* mux1 = new IC74LS151(new DigitalOutputPin(2),
                                new DigitalOutputPin(3),
                                new DigitalOutputPin(4),
                                new DigitalInputPullupPin(5));

// Keep track of the button number so I don't have to keep looking at
// what I used.
int dxButton = 1;

Component* components[] =   {
  // List the mux here so its setup gets called
  mux1,
  // FACK
  new PushButton(mux1->input(0), new DxButton(dxButton++)),
  // Laser Arm
  new OnOffSwitch(mux1->input(2),
                  new MomentaryButton(new DxButton(dxButton++)),
                  new MomentaryButton(new DxButton(dxButton++))),
  // Master Arm
  new OnOffOnSwitch(mux1->input(3),
                    mux1->input(4),
                    new MomentaryButton(new DxButton(dxButton++)),
                    new MomentaryButton(new DxButton(dxButton++)),
                    new MomentaryButton(new DxButton(dxButton++))),
  // HMCS
  new SwitchingRotary(new AnalogInputPin(0),
                      DxAxis::XRotation(),
                      new MomentaryButton(new DxButton(dxButton++)),
                      new MomentaryButton(new DxButton(dxButton++)),
                      0.05)
};

Falconpanel currently supports the following types of component

PushButton

The simplest of the controls, this maps a Arduino input pin directly to a DirectX button. Intended to be connect to a momentary, pushbutton switch. Constructor:

PushButton(DigitalInput* in, DxButton* dxButton)

Watches the digital input in and maps it to DirectX button dxButton (DirectX buttons are numbered from 1, with a max of 32). The DirectX button stays pressed for as long as the physical button does.

OnOffSwitch

Maps a two-position switch to DirectX buttons for its up and down states. The DirectX button presses are momentary, even though the switch is not.

Constructor:

OnOffSwitch(DigitalInput* in, DxButton* dxButtonUp, DxButton* dxButtonDown, int duration)

Watches the digital input in, and when it changes state, presses DirectX button dxButtonUp or dxButtonDown (DirectX buttons are numbered from 1, with a max of 32) depending on whether the switch has been flipped up or down. The button stays pressed for duration “ticks”, or until the switch state is changed. A tick is currently about 150ms.

Note that one switch therefore generates two different DirectX button presses.

OnOffOnSwitch

Maps a three-position switch to DirectX buttons for its up, middle, and down states. The DirectX button presses are momentary, even though the switch is not.

Constructor:

OnOffOnSwitch(DigitalInput* inUp, DigitalInput* inDown,
              DxButton* dxButtonUp, DxButton* dxButtonMiddle, DxButton* dxButtonDown,
              int duration)

Watches the digital inputs inUp and inDown, which should be connected to the up and down leads of the physical switch, and when the switch changes state, presses DirectX button dxButtonUp, dxButtonMiddle, or dxButtonDown (DirectX buttons are numbered from 1, with a max of 32) depending on which position the switch has been flipped to. The button stays pressed for duration “ticks”, or until the switch state is changed. A tick is currently about 150ms.

Note that one switch therefore generates three different DirectX button presses.

SwitchingRotary

Maps a potentiometer to a DirectX axis and two buttons - one for “switching on” and one for “switching off”. Note that there is no need to use a potentiometer with an actual switch - on/off state is tracked by watching whether the pot is below a configurable threshold.

Constructor:

SwitchingRotary(AnalogInput* in,
                DxAxis* dxAxis, DxButton* dxButtonOn, DxButton* dxButtonOff,
                int duration, float threshold)

Watches the analog input in, which should be connected to the middle lead of a potentiometer, ideally in the 10K Ohm range. When the pot is below threshold, reports the specified DirectX axis as being at its minimum value. When above threshold, reports values scaled between the minimum and maximum DirectX axis values.

When the pot passes through the threshold value in the increasing direction, sends a momentary press on DirectX button dxButtonOn. When the pot passes through the threshold value in the decreasing direction, sends a momentary press on DirectX button dxButtonOff. Momentary presses are of duration duration ticks, where a tick is currently about 150ms.

Note that one pot therefore generates two different DirectX button presses and one DirectX axis.

PulseRotary

DEPRECATED If you’re looking at this, it’s much more likely you want to use RotaryEncoder.

Maps a potentiometer to two buttons - one for motion in the direction of increasing input values (the “up” direction), and one for motion in the opposite direction (the “down” direction).

Constructor:

PulseRotary(AnalogInput* in, DxButton* dxButtonUp, DxButton* dxButtonDown, int divisions);

Watches the analog input in, which should be connected to the middle lead of a potentiometer, ideally in the 10K Ohm range. When the pot moves more than 1/divisions of its range, the up or down button is triggered, depending on the direction of motion. The position the pot is in when this threshold is crossed is the new “home” position for determining the next transition, so divisions does not result in a strict division of the pot range.

This control does take into account the possibility of the pot “wrapping around”, as can happen with a pot that was made or modified to have full 360 degree rotation, and will correctly calculate the direction.

The up or down button is pressed each time the threshold is crossed in that direction, at which time the opposite button is released. It maintains its own internal buffer of contiguous presses in one direction, so it probably does not make sense to use this with buttons wrapped in a MomentaryButton.

RotaryEncoder

Maps a rotary encoder onto two buttons: one for one direction, one for the other.

Constructor:

RotaryEncoder(DigitalInput* in1, DigitalInput in2,
              Button* buttonForward, Button* buttonBackward,
              int queueLimit);

Watches digital inputs in1 and in2, which should be connected to the non-ground leads of a rotary encoder. When the encoder is rotated in each direction, a DirectX button press/release for “forward” or “backward” is sent for each “click” of the encoder. Presses are enqueued (up to queueLimit), so if rotation of the physical control can get ahead of the DirectX presses. queueLimit should never be set lower than one.

If your notion of forward and backward is the opposite of the DirectX events you’re seeing, just reverse the order of the digital inputs.

It probably doesn’t make any sense to use this with a MomentaryButton, as RotaryEncoder is already inherently momentary.

IC74LS151

Represents a 74LS151 3-to-8 mulitplexer (mux). These can be used to multiplex three input pins and one output pin on the Arduino to 8 input pins on the mux, effectively doubling the number of input wires you can have connected to a single Arduino.

IC74LS151 is a component mainly so it can be listed in the components array and have its setup function called; the primary use of it is via its input method, which is an adapter that bridges from an IC54LS151 mux instance to anything that’s expecting a digital input, like the PushButton class.

Constructor:

IC74LS151(DigitalOutput* dout0, DigitalOutput* dout1, DigitalOutput* dout2, DigitalInput* din)

Sets up a 74LS151 multiplexer with its address lines driven by dout0 (LSB), dout1, and dout2 (MSB). Input will arrive on din. Use the input method to connect the input pins of the mux to other controls, as in this example:

// I've got a 74LS151 3-to-8 mux with its address pins connected to
// Arduino pins 2-4, and with its output pin connected to Arduino pin
// 5. We have to declare this outside the components array below
// because we reference it in there.
IC74LS151* mux1 = new IC74LS151(new DigitalOutputPin(2),
                                new DigitalOutputPin(3),
                                new DigitalOutputPin(4),
                                new DigitalInputPullupPin(5));

// A simple example with only one control connect to the mux.
// Ordinarily you would connect several, since saving Arduino pins is
// the point of the mux.
Component* components[] =  {
  // List the mux here so its setup gets called
  mux1,
  // Connect an On/Off switch to the mux input 3 (D3 on the data sheet)
  new OnOffSwitch(mux1->input(3),
                  new DxButton(1),
                  new DxButton(2),
                  3)
};

To learn more about the mux, read the datasheet.

Each of these components can be hooked up to either Buttons, Axes or both, depending on the component. Currently, an axis is always a direct connect to a DirectX axis. Axis values are represented as floating point numbers in the range 0.0 to 1.0, inclusive.

Buttons outputs of components, however, can either be a direct connect to a DirectX button on the virtual gamepad, or can go through a MomentaryButton adapter. MomentaryButton turns a button press into a press-and-release, where the release happens automatically a configurable number of ticks later. A tick is currently about 150ms, and the default delay is three ticks. This approach is useful in having the button presses coming out of a component like an OnOffSwitch indicate changes in state rather than switch position. This can help with mapping in a game, where holding buttons down may cause problems.

Feedback

Feel free to drop an issue here on the project or contact me at [email protected] if you have questions or feature requests. Hope you find it useful!

falconpanel's People

Contributors

candera avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

Forkers

bacon8tor hmx7

falconpanel's Issues

RotaryAxis suggestion

After much searching for a method to do a similar project as your falcon panel, I finally came across your project! I do have one suggestion, though, that you add the following to the library in order to have a fully functioning axis without taking up any DirectX button assignments

`/* Adapts a simple potentiometer into a DX axis. /
class RotaryAxis : public Component {
private:
AnalogInput
_in;
DxAxis* _dxAxis;

public:
RotaryAxis(AnalogInput* in,
DxAxis* dxAxis) {
_in = in;
_dxAxis = dxAxis;
}

virtual void setup() {
_in->setup();

}
virtual void update() {
float val = _in->read();
_dxAxis->report(val);
}
};`

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.