Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/candera/falconpanel
Arduino program for turning physical switches, knobs, and buttons into a DirectX game controller.
https://github.com/candera/falconpanel
Last synced: 20 days ago
JSON representation
Arduino program for turning physical switches, knobs, and buttons into a DirectX game controller.
- Host: GitHub
- URL: https://github.com/candera/falconpanel
- Owner: candera
- Created: 2015-02-07T03:30:24.000Z (almost 10 years ago)
- Default Branch: master
- Last Pushed: 2015-08-14T22:46:20.000Z (over 9 years ago)
- Last Synced: 2024-12-09T09:51:21.061Z (25 days ago)
- Language: C++
- Homepage:
- Size: 602 KB
- Stars: 6
- Watchers: 5
- Forks: 2
- Open Issues: 1
-
Metadata Files:
- Readme: README.org
Awesome Lists containing this project
README
* 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 [[http://www.bmsforum.org/forum/content.php][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 [[http://gaming.logitech.com/en-us/product/g13-advanced-gameboard][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:
- An [[http://www.adafruit.com/products/849][Arduino Leonardo]]
- An [[http://www.adafruit.com/products/192][Adafruit Protoshield]]
- A [[http://www.adafruit.com/products/64][tiny breadboard]]
- [[http://www.amazon.com/gp/product/B0094GRZPE/ref%3Doh_aui_detailpage_o01_s00?ie%3DUTF8&psc%3D1][Momentary switches]]
- [[http://www.amazon.com/gp/product/B008ICKO30/ref%3Doh_aui_detailpage_o05_s00?ie%3DUTF8&psc%3D1][Two-position switches]]
- [[http://www.amazon.com/gp/product/B008ICEJM2/ref%3Doh_aui_detailpage_o07_s01?ie%3DUTF8&psc%3D1][Three-position switches]]
- [[http://www.amazon.com/gp/product/B009QFU9H4/ref%3Doh_aui_detailpage_o06_s00?ie%3DUTF8&psc%3D1][10K potentiometer]]
- [[http://www.ebay.com/itm/251785644683][A 74LS151 3-to-8 multiplexer]]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
*** PrerequisitiesIf 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 - [[https://github.com/NicoHood/HID][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:#+begin_src cpp
// 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)
};
#+end_srcFalconpanel 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:#+begin_src cpp
PushButton(DigitalInput* in, DxButton* dxButton)
#+end_srcWatches 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:
#+begin_src cpp
OnOffSwitch(DigitalInput* in, DxButton* dxButtonUp, DxButton* dxButtonDown, int duration)
#+end_srcWatches 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:
#+begin_src cpp
OnOffOnSwitch(DigitalInput* inUp, DigitalInput* inDown,
DxButton* dxButtonUp, DxButton* dxButtonMiddle, DxButton* dxButtonDown,
int duration)
#+end_srcWatches 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:
#+begin_src cpp
SwitchingRotary(AnalogInput* in,
DxAxis* dxAxis, DxButton* dxButtonOn, DxButton* dxButtonOff,
int duration, float threshold)
#+end_srcWatches 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:
#+begin_src cpp
PulseRotary(AnalogInput* in, DxButton* dxButtonUp, DxButton* dxButtonDown, int divisions);
#+end_srcWatches 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:
#+begin_src cpp
RotaryEncoder(DigitalInput* in1, DigitalInput in2,
Button* buttonForward, Button* buttonBackward,
int queueLimit);
#+end_srcWatches digital inputs =in1= and =in2=, which should be connected to
the non-ground leads of a [[https://en.wikipedia.org/wiki/Rotary_encoder][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:
#+begin_src cpp
IC74LS151(DigitalOutput* dout0, DigitalOutput* dout1, DigitalOutput* dout2, DigitalInput* din)
#+end_srcSets 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:#+begin_src cpp
// 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)
};
#+end_srcTo learn more about the mux, read the [[http://pdf.datasheetcatalog.com/datasheet2/2/05zhla9si2dxjf61z5qx4spz7uyy.pdf][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!