Professional Documents
Culture Documents
Every now and then I get the urge to take to the sky (in a virtual sense) in a Cessna using Microsoft’s Flight
Simulator X (FSX). A few years ago, having faced some difficulty trying to tune the COM radios using a
mouse during a bout of turbulence, I decided to build a unit to control setting the radio frequencies,
transponder code and auto pilot functions. The project used a universal joystick controller (BU0836X)
obtained from Leo Bodnar (www.leobodnar.com). Here is a photograph of the unit attached to the top of
my Saitek switch panel which in turn is fixed to the Saitek Yoke.
The switches on the upper left replicate the functions of the top row of switches on the FSX radio stack. The
individual COM/NAV radios are selected using the upper rotary switch, which also has a position to select
the transponder. A dual rotary encoder (centre – also obtained from Leo Bodnar) is used to change
frequencies of the COM/NAV radios and also changes the individual auto-pilot parameters (selected by the
lower rotary switch). The upper row of push buttons control auto-pilot operation, whilst the bottom row of
buttons set the transponder code.
The unit is built into a 6” x 4” plastic box and interfaces with FSX using FSUIPC over USB. It works quite well,
but as the BU0836X controller only has inputs, it still relies on the on-screen radio stack display to provide
visual feedback. It occurred to me then that one day I would revisit the task and update the unit to not only
control the inputs but also to include the displays, thereby negating the need to bring up the radio stack on
the screen. That day has now arrived.
Introduction
This latest project began when I decided to buy an Arduino Mega 2560 off a well-known online shopping
site. I used to do a lot of programming in C, pascal, et-al., many years ago (more years than I care to
remember!) and this micro-processor board gave me the opportunity to re-visit the radio stack project (as
well as putting some of my old grey-cells back through their paces now that I am retired). It is ongoing and
as such this blog will be updated accordingly.
Having never used an Arduino before, I was on a learning curve. I started to breadboard my ideas and
determine the options available for the revised radio stack design. I had a basic idea of what I wanted to
include and initially invested in some 0.36 inch 7-segment displays (from www.squirrel-labs.net). This I felt
was the largest useable display size to keep the overall footprint down. I soon discovered the MAX7219/21
LED driver chips and this appears to be the best way to not only interface the 7-segment displays but they
can also drive the other LEDs in the project. The original incarnation used momentary buttons to program
the “squawk” code into the transponder. I wanted to replace the buttons with a 3x4 numeric keypad (which
I already had) in this version. The keypad will be connected via a single analogue input pin on the Arduino
using a resistor matrix. This technique is well documented on numerous websites.
For the switches, although most are latching, I decided to go with momentary push buttons and perform the
latching operation in software. Each switch will have a LED to indicate its current latch state. The main
reason for this is that some of the buttons are dependent on the state of others (e.g. if COM2 is pressed
when COM1 is operational, then COM2 becomes operational and COM1 needs to automatically un-latch.
Also, most of the auto-pilot buttons have similar dependencies.). Talking of the auto-pilot, I am going to use
a 2x16 LCD display for this as, a) it keeps the size down and b) the auto pilot display uses characters (e.g.
NAV, HDG, APR, etc.) that could not be displayed using standard 7-segment displays. The transponder will
use a 4-digit 7-sement display.
Finally there are a number of other controls that can be difficult to adjust with a mouse on the PC screen,
such as, heading bug, barometric pressure adjustment, etc. I want to incorporate these into the project
with rotary encoders. Frequency adjustment of the COM/NAV radios will use the same dual rotary encoder
used in the original design. With that in mind, I bought another one with the idea that each radio pair (i.e.
COM and NAV) would be controlled by its own encoder. However, this increases the overall size
significantly so I have opted to use a single dual rotary encoder and a button to cycle through the four radios
(with a LED showing which radio is currently under control of the encoder). The built-in switch on the dual
rotary encoder is used to swap the active/standby frequencies.
Whilst the original project used FSUIPC as the interface between the BU0836X and FSX, it is intended that
this version will use a program written by Jim NZ called Link2fs Multi for FSX. Information and download
links for this program are available here: http://www.jimspage.co.nz/intro.htm
FSX users will almost certainly recognise the various elements incorporated in this design.
I intend to use a 10” x 8” x 3mm sheet of Perspex with a paper artwork sheet behind it. The displays will
mount behind the Perspex but remain visible through cutouts in the artwork sheet. Although the buttons
have a square front bezel, they mount through circular holes which can be easily drilled. The only tricky part
will be accurately cutting out the area for the keypad. Each LED will be inserted into some small black tubing
(probably heat-shrink, as I already have that) with a small hole in the artwork to provide a small dot of light
when the associated button is latched, i.e. operated. The artwork, which provides the black background and
all the text labels, will be printed onto premium quality photographic paper, similar to how I did for the
original version.
I know, it looks like a real rat’s nest but it has evolved overtime. I have added some labels to the image to
help identify the various elements. As I added a new functionality to the breadboard, I wrote and tested the
necessary code on the Arduino to process it. It initially began with just the 7-segment displays driven by two
MAX7221 LED drivers (ultimately, six MAX7221 chips will be required in the final unit). This was based on an
article on the Arduino site at http://playground.arduino.cc/Main/MAX72XXHardware. Then came the dual
rotary encoder used to set the frequency of the standby (rightmost 6-digit display). The rotary encoders are
interrupt-driven using a finite state machine to determine direction of rotation. The idea for this method
was prompted by an article by Oleg Mazurov at http://www.circuitsathome.com/mcu/reading-rotary-
encoder-on-arduino. However, my implementation differs considerably from his. The displays are swapped
when the rotary encoder button is pressed.
The keypad and its attendant potential-divider resistors came next. The keypad is simply a 3x4 matrix, with
a button at each inter-section, which when pressed presents a unique voltage to the analogue input pin to
which the resistor network is connected. This voltage on the analogue pin is polled by the software to
determine if a key has been pressed and if so, which one. The keypad wiring was based on an article at
http://www.instructables.com/id/Arduino-3-wire-Matrix-Keypad/step2/Wiring-up-the-resistors.
It is assumed only one button will be pressed at a time and I initially waited (in the code) for the button to be
released before continuing. This was modified when I added the UP/DOWN buttons (the two round red
ones on the lower right of the breadboard) which change the Altitude and Vertical Speed of the Auto-Pilot.
When these are held pressed, the values start to change more rapidly. So a time interval was introduced in
the button process routine. You can just make out the LEDs above each button on the breadboard image
which indicates the latch state of the associated button. The LEDs are currently controlled via two 8-bit shift
registers (SN74HC959N), but I am planning to control these with the MAX7221 devices in the final unit.
I am currently using a 2 (rows) by 16 (columns) LCD shield for the auto-pilot display, which can be seen
connected to the Arduino on the left hand side of the breadboard image. The final design will contain a
separate 2x16 LCD. Although I tend to limit the use of 3rd-party libraries in my code, I made an exception
here for expediency. See http://arduino.cc/en/Reference/LiquidCrystal?from=Tutorial.LCDLibrary for
details of the LCD library.
Finally I fitted another rotary encoder to the breadboard (just left of the 7-segment displays) which I
programmed to control the heading bug on the heading indicator (one of the six primary flight indicators).
The heading bug value is also output to the auto-pilot display as it is used by the auto-pilot to maintain
direction of flight. I also wanted to make sure the interrupt driven code worked just as well with a cheap
rotary encoder. It did, so I now have ten more of these on their way from China!
I created a header file (RadioStack.h) to define structures for the 7-segment displays and rotary encoders.
normal valid states: 0011 (3) and 1100 (12) indicating CCW movement
and 0110 (6) and 1001 (9) indicating CW movement. However...
after initialising (i.e. states == 0000), transition to 0010 (2) or 0001 (1) indicates CW movement
and 0000 (0) or 0011 (3) indicates CCW movement
when direction changes these states occur: 0111 (7) or 1000 (8) when direction changes from CW to CCW
0010 (2) or 1101 (13)when direction changes from CCW to CW
Remaining states are invalid: 0100 (4), 0101 (5), 1010 (10), 1011 (11), 1110 (14) and 1111 (15)
Hence... 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
===================================================================================================*/
const int offsets[] = { -1, 1, 1, -1, 0, 0, 1, -1, -1, 1, 0, 0, -1, 1, 0, 0 };
The Arduino mandatory loop() routine detects when an interrupt has been fired and calls the relevant
interrupt handling routine…
loop() { // example
…
if (eCOM1a.flags & ENC_FIRED) serviceCOMInt(&eCOM1a, &dCOM1s); // service any encoder interrupts
…
}
/*==================================================================================================
gets current keypad key if pressed and return it, otherwise return NULL.
==================================================================================================*/
char getKey() {
char key = NULL; // NULL indicates no key pressed
unsigned int adcValue = readAnalogPin(KEYPAD_PIN, KEY_NONE, KEY_MARGIN);
if (! btnPressed(adcValue, KEY_NONE, KEY_MARGIN))
key = mapKeypad(adcValue);
return key; // returns the key pressed or NULL if none
} // end of getKey()
/*==================================================================================================
returns true if <adcValue> is within range defined by <btnValue> + or - <margin>, otherwise, false
==================================================================================================*/
boolean btnPressed(unsigned int adcValue, unsigned int btnValue, unsigned int margin) {
boolean retval = false;
if ( (adcValue >= (btnValue-margin)) && (adcValue <= (btnValue+margin)) )
retval = true;
return retval;
} // end of btnPressed()
The code delimited by /* DEBUG ADC Values … */ is used to ascertain the actual values produced by each
button/key. The ADC value (0-1023) is displayed on the LCD screen for 1 second. Each key/button should
be pressed a few times and the values averaged. This value is then defined in the global section (see
previous). These represent the nominal values to which a margin (plus and minus) is applied to define a
range of values for each key/button.
/*==================================================================================================
read the ADC value on the associated analogue pin (<Pin>) and return it to the calling program.
<noBtn> holds the "no key press" nominal value. <margin> holds the +/- range used to define the
acceptable values representing key presses. The function applies debouncing and waits for
key/button to be released (or a time limit to be exceeded) before returning.
The time limit is initially set to 1 second to allow sufficient time to press and release a button
however, if this time limit is exceeded (i.e. by holding down a button), the time limit is reduced
to permit multiple reads to occur more quickly. This is useful, for example, to increment
a value rapidly by holding down the UP button. Once released, the original time limit is restored.
==================================================================================================*/
unsigned int readAnalogPin(byte Pin, unsigned int noBtn, unsigned int margin) {
static boolean oldState = false; // holds previous state
unsigned int adcValue, value;
adcValue = analogRead(Pin); // read the analog input value (0-1023)
/* DEBUG ADC Values
debugInt("ADC:", adcValue, 1000);
*/
boolean newState = ! btnPressed(adcValue, noBtn, margin);// see if a button has been pressed
if (newState) { // if a key has been pressed...
long timelimit;
if (newState == oldState) // if key state has not changed
timelimit = 100; // accelerate time limit
else
timelimit = 1000;
delay(5); // wait a short time to debounce
adcValue = analogRead(Pin); // having settled, read the value again
long timethen = millis(); // get current time
do { // wait for button to be released
value = analogRead(Pin); // get current analog pin value
oldState = ! btnPressed(value, noBtn, margin); // true if button is still pressed
if (! oldState) break; // exit if button has been released
} while (millis() < timethen + timelimit ); // or wait for time limit to be exceeded
} // end of if (newState)
return adcValue; // returns the ADC value
} // end of readAnalogPin()
#define BTN_MARGIN 8
#define BTN_NONE 1023 - BTN_MARGIN // ADC value when no buttons are pressed
// use to determine which button was pressed
#define BTN_01 72 // COM1
#define BTN_02 122 // COM2
#define BTN_03 168 // BOTH
#define BTN_04 209 // NAV1
#define BTN_05 248 // NAV2
#define BTN_06 281 // MKR
#define BTN_07 312 // DME
#define BTN_08 BTN_MARGIN // reserved for ADF
#define BTN_09 340 // AP
#define BTN_10 367 // HDG
#define BTN_11 389 // NAV
#define BTN_12 410 // APR
#define BTN_13 429 // REV
#define BTN_14 446 // ALT
#define BTN_15 465 // UP
#define BTN_16 484 // DN
// this is where the current latch state of each button (currently max 16) is stored
unsigned int buttons = 0x0000; // initialise switch states to all off
#define BTN_COM1 0x8000 // defines bit masks to access relevant bit in buttons variable
#define BTN_COM2 0x4000
#define BTN_BOTH 0x2000
#define BTN_NAV1 0x1000
#define BTN_NAV2 0x0800
#define BTN_MKR 0x0400
#define BTN_DME 0x0200
#define BTN_ADF 0x0100
#define BTN_AP 0x0080
#define BTN_HDG 0x0040
#define BTN_NAV 0x0020
#define BTN_APR 0x0010
#define BTN_REV 0x0008
#define BTN_ALT 0x0004
#define BTN_UP 0x0002
#define BTN_DN 0x0001
/*==================================================================================================
reads the ADC value (0-1023) of the button resistive ladder
and return a mask to determine which button is pressed.
if no button is pressed, it returns 0x0000.
==================================================================================================*/
unsigned int checkButtons() {
unsigned int adcValue = readAnalogPin(BUTTON_PIN, BTN_NONE, BTN_MARGIN); // get ADC value (0-1023)
unsigned int btn = 0x0000; // return 0x0000 if no button has been pressed
if ( ! btnPressed(adcValue, BTN_NONE, BTN_MARGIN) // however, if a button has been pressed...
btn = mapButtons(adcValue); // determine which button caused the event
return btn; // and return bit mask for the button pressed
} // end of checkButtons()
The project is now ready to move into the build stage. This will include making PCBs, installing the
components, building the unit into a suitable housing and finalising the Arduino code. This will be subject of
an update.
TO BE CONTINUED…
After taking a brief hiatus, work on the project has re-commenced, so here is an update on progress.
Some years ago, I built a UV LED light-box in which I expose photo-sensitive copper clad boards using (inkjet)
printed designs to make single sided PCBs. Although it is possible to use this set-up to create double sided
PCBs, obtaining exact alignment for each side does present a challenge. Thus I decided to stick to single-
sided PCBS in this project. However, mapping the pins between the 7-segment displays and LED driver chips
required too many jumper wires on a single-sided PCB so I eventually adopted a piggy-back method whereby
the display units are mounted on one PCB and the drivers on another. The boards are then ‘plugged’
together via male/female connectors. This method keeps the overall size down as well as supporting design
modularity. To reduce the number of screws required to fit them to the front panel, I included two radio
units (each consisting of an active and a standby display) on each board, but as seen from the two
schematics a single radio board is easily be produced if required.
The cathodes of each LED on each strip are connected together in similar fashion to a single 7-segment
common cathode display unit. The LED anodes are then connected as though there were individual
segments to the driver module. The MAX7219/21 chip can operate in both decode mode (whereby the
required numeric value is sent to the chip where it is decoded to light up the correct segments) or non-
decode mode whereby each individual segment can be controlled separately. Fortunately both modes can
co-exist on the same chip, thereby allowing the transponder display and LEDs to share the same chip. The
LED strips have been glued to the back of the front panel art-work and small 1.5mm holes allow their light to
be visible from the front.
The two sets of eight 680Ω resistors on the bottom left are used to limit the current between the
driver chips and the LEDs. The buttons which control the auto pilot mount around the LCD and
there is also a switch which selects whether ALT (altitude) or VS (vertical speed) is controlled by the
UP/DN buttons. The UP/DN buttons initially increment/decrement the ALT/VS value by 100 feet
with each button press. However, if the button is held pressed for more than a second the ALT/VS
values change at a rate of 1000 feet for each 150 milliseconds (this period may change!) the button
remains pressed.
Although pretty much all the functionality of the complete project has been coded and tested, I have yet to
add the communication functionality between the unit and FSX. This is the next stage. As previously stated,
this will use an excellent piece of software called link2fs available at http://www.jimspage.co.nz/intro.htm.
Here is the working front panel of the radio stack. Note the blue item labelled DREMEL on the lower left of
the image is not part of the unit. Rather it is a jaw of the vice used to hold the bare-bones unit during
construction and testing. As can be seen in the final image overleaf, the breadboard is no more and all the
connections exist entirely between the Arduino Mega256, front panel modules and the small shift register
board. The Arduino and shift register boards will ultimately be fixed to the inside of the enclosure.
The wiring will also be tidied up when the panel is mounted in its enclosure. This has yet to be built, but it is
anticipated it will be fabricated from MDF and painted black to match the panel.
A final update will be posted once the project is completed with hopefully a video showing it in action.