Results 1 to 10 of 10
  1. #1
    10+ Posting Member
    Join Date
    Jan 2013
    Contribute If you enjoy reading the
    content here, click the below
    image to support MyCockpit site.
    Click Here To Contribute To Our Site

    Radio Stack for FSX v2

    Radio Stack for Microsoft Flight Simulator X – version 1

    This is my first post on mycockpit...

    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 ( 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!

    Attached Images Attached Images

  2. #2
    10+ Posting Member
    Join Date
    Jan 2013
    Contribute If you enjoy reading the
    content here, click the below
    image to support MyCockpit site.
    Click Here To Contribute To Our Site

    Re: Radio Stack & Auto Pilot for FSX

    Radio Stack for Microsoft Flight Simulator X – version 2, part 1


    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 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:

    Panel Layout

    Here is a concept drawing of the panel layout of the proposed radio stack.

    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.

    Breadboard prototype

    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 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 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

    Next on the list were the momentary push buttons (currently 15 in all) which are wired to a series resistor network. The far end of the resistor network is connected to +5v, with 2.2Kohm resistors between that and each button. This eventually connects to another analogue pin. The buttons connect to ground as shown here...

    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 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!

    In the following post I will provide a number of Arduino code snippets...
    Attached Images Attached Images

  3. #3
    10+ Posting Member
    Join Date
    Jan 2013
    Contribute If you enjoy reading the
    content here, click the below
    image to support MyCockpit site.
    Click Here To Contribute To Our Site

    Re: Radio Stack & Auto Pilot for FSX

    This post needs deleting!!!
    Last edited by DLPublic; 10-10-2015 at 04:11 AM. Reason: Duplicate post

  4. Likes Fess_ter liked this post
  5. #4
    10+ Posting Member
    Join Date
    Jan 2013
    Contribute If you enjoy reading the
    content here, click the below
    image to support MyCockpit site.
    Click Here To Contribute To Our Site

    Re: Radio Stack & Auto Pilot for FSX

    This post needs to be deleted!!!
    Last edited by DLPublic; 10-10-2015 at 04:10 AM. Reason: Duplicate post

  6. #5
    10+ Posting Member
    Join Date
    Jan 2013
    Contribute If you enjoy reading the
    content here, click the below
    image to support MyCockpit site.
    Click Here To Contribute To Our Site

    Re: Radio Stack & Auto Pilot for FSX


    Something seems to have got screwed up with these posts (I did say it was my first one!) and I cannot delete the duplicated posts! If someone can advise how to delete posts I will be able to tidy this thread up. I assume the delete facility has been disabled by the moderator.
    Last edited by DLPublic; 10-09-2015 at 04:07 AM.

  7. #6
    10+ Posting Member
    Join Date
    Jan 2013
    Contribute If you enjoy reading the
    content here, click the below
    image to support MyCockpit site.
    Click Here To Contribute To Our Site

    Re: Radio Stack & Auto Pilot for FSX

    This post needs to be deleted!!!
    Last edited by DLPublic; 10-10-2015 at 04:13 AM. Reason: Duplicated post

  8. #7
    10+ Posting Member
    Join Date
    Jan 2013
    Contribute If you enjoy reading the
    content here, click the below
    image to support MyCockpit site.
    Click Here To Contribute To Our Site

    Re: Radio Stack & Auto Pilot for FSX

    Arduino Code: Rotary Encoders and 7-Segment Displays

    The code is likely to change as the project progresses, but what is shown here are code snippets used for the breadboard version. Hopefully these fragments will be useful to others.

    I created a header file (RadioStack.h) to define structures for the 7-segment displays and rotary encoders.

    // 7-segment display structure#define MAX_DISP   6
    typedef struct {
      byte digits;             // holds the number of digits of the display (1-MAX-DISP)
      byte dp;                 // position of decimal point (1-MAX_DISP), 0 = no decimal point
      char value[MAX_DISP+1];  // holds character ('0'-'9') for each digit as a NULL terminated string
      byte chip[MAX_DISP];     // used to select the required MAX72xx chip (currently 0 or 1)
      byte reg[MAX_DISP];      // used to select digit register on chip (1-8)
      char fsx[4];             // holds the string used by link2fs to update FSX, e.g. "A05"
      unsigned long minimum;   // minimum value allowed for the display
      unsigned long maximum;   // maximum value allowed for the display
    } disp7seg_t;
    // rotary encoder structure
    #define ENC_STATES    0x0F        // used to access bits 0-3, i.e. states, of the ‘flags’ structure
    #define ENC_FIRED     0x10        // used to access bit 4 (bits 5-7 are spare) of the ‘flags’ structure
    typedef struct {
      byte pinA;               // this pin must be associated with an interrupt
      byte pinB;                // this is the anti-clockwise pin (non-interrupt)
      byte interrupt;          // interrupt number associated with pin A
      void (*isr)(void);       // name of interrupt service routine
      int counter;             // used to store accumulated encoder movement between processing
      int stepvalue;           // defines the step value for each increment/decrement
      union {
        byte flags;            // use ( enc.flags & ENC_xxxxx ) to access elements
        struct {
          unsigned spare:3;    // reserved for future use
          unsigned isrFired:1; // signals that associated interrupt has been called
          unsigned states:4;   // 4 bits hold current/previous states (bit 3=old A, 2=old B, 1=new A, 0=new B)
        };                     // end of struct
      };                       // end of union
    } encoder_t;

    The 7-segment display and rotary encoder structures are initialised thus…

      dCOM1a    ... COM1 active  display
      dCOM1s    ... COM1 standby display
      dXPNDR    ... Transponder  display
    disp7seg_t dCOM1a = { 6, 3, "118750",     // number of digits, DP position and default value
                        { 0, 0, 0,    0, 0, 0 },  // which chip serves each digit (0-1)
                        { 1, 2, 3,    4, 5, 6 },  // which register serves each digit (1-8)
                        "A05",                    // FSX update code (SimConnect Input from link2fs)
                        118000, 135950 };         // minimum and maximum values
    disp7seg_t dCOM1s = { 6, 3, "118750",         // number of digits, DP position and default value
                        { 0, 0, 1,    1, 1, 1 },  // which chip serves each digit (0-1)
                        { 7, 8, 1,    2, 3, 4 },  // which register serves each digit (1-8)
                        "A62",                    // FSX update code (SimConnect Input from link2fs)
                        118000, 135950 };         // minimum and maximum values
    disp7seg_t dXPNDR = { 4, 0, "1200",           // number of digits, DP position and default value
                        { 1, 1, 1, 1 },           // which chip serves each digit (0-1)
                        { 5, 6, 7, 8 },           // which register serves each digit (1-8)
                        "A42",                    // FSX update code (SimConnect Input from link2fs)
                        0000, 7777 };             // minimum and maximum values
      eCOM1a    ...  COM1 frequency (Mhz) control
      eCOM1b    ...  COM1 frequency (kHz) control
      eHDG      ...  Heading bug control
    encoder_t eCOM1a  = { 20, 30, 3, isr_3, 0, 1000, 0 }; // A, B, int, isr, counter, step value, flags
    encoder_t eCOM1b  = { 21, 31, 2, isr_2, 0,   25, 0 };
    encoder_t eHDG    = { 19, 33, 4, isr_4, 0,    1, 0 };

    Rotary Encoder – Explanation of the Finite State Machine operation

      A 4-bit state machine (B0000-B1111) is used to interpret the rotational direction of encoders:
      i.e. anti-clockwise (CCW) or clockwise (CW)
      Uses grey code encoders which give the following output.  Note only pin A triggers the interrupt.  
    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 };

    Rotary Encoder - Interrupt Service Routine (example)

      Interrupt Service Routine for int 3, i.e. pin 20 (1st encoder)
      Both the isFired flag and counter should be reset by calling program ISR handling routine
    void isr_3 () {
      byte A = digitalRead(eCOM1a.pinA);          // Read pins
      byte B = digitalRead(eCOM1a.pinB);  
      eCOM1a.flags |= ENC_FIRED;                  // set interrupt fired flag
      byte states = eCOM1a.flags & ENC_STATES;    // get current states
      states <<= 1;                               // shift old AB into bits 1 & 2
      states |= (A & 0x01);                       // add current state for Pin A into bit 0
      states <<= 1;                               // shift pin A into bit 1 and old AB into bits 2 & 3
      states |= (B & 0x01);                       // add current state for Pin B into bit 0
      eCOM1a.flags &= ~ENC_STATES;                // clear existing encoder states
      eCOM1a.flags |= states;                     // save current states into encoder
      eCOM1a.counter += offsets[(eCOM1a.flags & ENC_STATES)]; // increment/decrement counter
    }  // end of isr_3()

    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
    //    …

    Rotary Encoder Handler (example)

      called when an COM encoder interrupt has been fired.
      uses a state machine to determine an offset which after multiplying by the (counter*step value)
      is used  to increment/decrement the associated counter.  The relevant 7-segment display is updated
      as is FSX via link2fs
    void serviceCOMInt(encoder_t* enc, disp7seg_t* disp) {
      long value = getDisplayValue(disp);              // get current display value
      value += (enc->stepvalue * enc->counter);        // increment/decrement value by step value (delta)
      enc->flags &= ~ENC_FIRED;                        // reset interrupt fired flag
      enc->counter = 0;                                // reset interrupt counter
      if (value > disp->maximum)  value = disp->minimum; // wrap value between minimum/maximum limits
      if (value < disp->minimum)  value = disp->maximum;    
      setDisplayValue(disp, value);                    // update displayed value (and FSX via link2fs)
    }  // end of serviceCOMInt()

    Determining the new 7-segment displayed value

    Having serviced the rotary encoder interrupt and determined the relevant display’s new value (taking into account minimum and maximum thresholds), the setDisplayValue() routine is called to update the 7-segment display.

      takes the passed parameter and converts it to individual numeric values for each digit
      it then stores them in the associated display structure byte array
      the 7-segment display is then updated using the updateMAX72xx() routine
      finally the resulting value is output to FSX (via link2fs).  NOTE: FSX only accepts 5 digits
    void setDisplayValue(disp7seg_t* disp, long value) {
      for (int i = disp->digits-1; i >= 0; i--) {        // for each digit segment
        byte numeral = (value % 10);                     // get the last digit (0-9)
        disp->value[i] = numeral + 48;                   // update display array
        if (disp->dp == i+1)                             // do we need a decimal point here?
          numeral += DP;                                 // if yes, set decimal point (bit 7)
          updateMAX72xx(disp->chip[i],                   // output digit to MAX72xx chip register
        value /= 10;                                     // remove last digit
      }  // end of for (i=0)                             // continue until no further digits exist
      Serial.print(disp->fsx);                           // update FSX via link2fs, However...
      if (disp->digits > 5) {                            // FSX only accepts 5 digits for frequencies...
        char val[MAX_DISP+1];                            // so define a temporary string storage area
        strCopy(&val[0], &disp->value[0]);               // copy display value into the temporary area
        val[disp->digits-1] = NULL;                      // remove last digit by overwriting it with NULL
        Serial.println(&val[0]);                         // output modified value to FSX via link2fs
      } else
        Serial.println(&disp->value[0]);                 // output un-modified value to FSX via link2fs
    }  // end of updateDisplayDigits()

    Updating the 7-segment display

      sends a byte of to the register ( = 0-7) on the MAX72xx chip ( = 0-1)
      Note: only two chips are used at present.  The final unit will require three MAX7221s so
      this routine will need modification to cater for the third chip.
    void updateMAX72xx (const byte chip, const byte reg, const byte data) {    
      digitalWrite (LOAD, LOW);                          // enable shift operation
      if (chip == 0) {                                   // if destined for chip  0
        SPI.transfer (MAX7219_REG_NOOP);                 // then output NULLs to chip 1...
        SPI.transfer (MAX7219_REG_NOOP);
        SPI.transfer (reg);                              // ...followed by register and data for chip 0
        SPI.transfer (data);
      } else {                                           // if destined for chip 1
        SPI.transfer (reg);                              // output register and data...
        SPI.transfer (data);        
        SPI.transfer (MAX7219_REG_NOOP);                 // followed by NULLS to chip 0
        SPI.transfer (MAX7219_REG_NOOP);
      }  // end of if (chip == 0)
      digitalWrite (LOAD, HIGH);                         // all finished, disable shift operation
    }  // end of updateMAX72xx()

    Arduino Code: Numeric Keypad

    Global defines and variables

      KeyPad ADC values and mappings
    #define KEYPAD_PIN A15                  // defines the analog pin used by the keypad
    #define KEY_MARGIN  15                  // used as + or - margin either side of defined value
    #define KEY_NONE KEY_MARGIN             // this is the idle keystate, i.e. no key pressed
    // these are the analogue values (0-1023) used to determine which key was pressed
    #define KEY_1       49               
    #define KEY_2       87
    #define KEY_3      129
    #define KEY_4      197
    #define KEY_5      309
    #define KEY_6      412
    #define KEY_7      543
    #define KEY_8      685
    #define KEY_9      780
    #define KEY_S      841              // '*'
    #define KEY_0      924
    #define KEY_H      959              // '#'
    unsigned long timenow = millis();   // used to time intervals between key presses
    unsigned long timethen = timenow;

    Initialising the keypad

      Initialises the analogue input pin for the keypad
    void initKeypad() {
      pinMode(KEYPAD_PIN, INPUT);                    // initialise keypad analog input pin
      digitalWrite(KEYPAD_PIN, LOW);                 // disable pullup resistor
    }  // end of initKeypad()

    Polling the keypad

    loop() {                      
    //    …
      char key = getKey();                                   // poll Keypad
      if (key != NULL)  updateTransponder(&dXPNDR, key);     // update the transponder display
    //    …
    }  // end of loop()
      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 is within range defined by + or - , 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()

    Reading values on Analogue-Digital Convertor (ADC) pins

    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 (
    ) and return it to the calling program.
       holds the "no key press" nominal value.   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
          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()

    Mapping the ADC value to a key

      map 'value' to a character key and return it, otherwise return NULL if no key pressed.
      Note that calling program should have already screened out the KEY_NONE condition,
    char mapKeypad(unsigned int value) {
      char key = NULL;
      if      ( btnPressed(value, KEY_1, KEY_MARGIN) )  {key = '1';}
      else if ( btnPressed(value, KEY_2, KEY_MARGIN) )  {key = '2';}  
      else if ( btnPressed(value, KEY_3, KEY_MARGIN) )  {key = '3';}  
      else if ( btnPressed(value, KEY_4, KEY_MARGIN) )  {key = '4';}  
      else if ( btnPressed(value, KEY_5, KEY_MARGIN) )  {key = '5';}  
      else if ( btnPressed(value, KEY_6, KEY_MARGIN) )  {key = '6';}  
      else if ( btnPressed(value, KEY_7, KEY_MARGIN) )  {key = '7';}  
      else if ( btnPressed(value, KEY_8, KEY_MARGIN) )  {key = '8';}  
      else if ( btnPressed(value, KEY_9, KEY_MARGIN) )  {key = '9';}  
      else if ( btnPressed(value, KEY_S, KEY_MARGIN) )  {key = '*';}  
      else if ( btnPressed(value, KEY_0, KEY_MARGIN) )  {key = '0';}  
      else if ( btnPressed(value, KEY_H, KEY_MARGIN) )  {key = '#';}  
      return key;
    }  // end of mapKeypad()

    Arduino Code: Latching Buttons

    Global defines and variables

      Each button operation is determined by comparing analogue values read via an analogue pin
      (BUTTON_PIN) to (BTN_X ± BTN_MARGIN).  Momentary buttons are used but a latching operation
      (i.e. on or off) is performed in software.  When button is latched on, its LED lights.
      LED operation is currently performed using an 8-bit Shift Register (M74HC959)
    // Shift Register used for LEDs
    const byte DS    = 45;               // Pin connected to data in pin  (DS - 14) of 74HC595
    const byte CLOCK = 47;               // Pin connected to clock pin (SH_CP - 11) of 74HC595
    const byte LATCH = 49;               // Pin connected to latch pin (ST_CP - 12) of 74HC595
    #define LED_ON   HIGH
    #define LED_OFF  LOW
    #define ON       true
    #define OFF      false
    #define BUTTON_PIN A14               // defines the analogue pin used by the buttons
    #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

    Initialise Shift Register (and button analogue input pin)

      Initialises the shift register and the analogue input pin for the buttons
    void initShiftRegister() {
      pinMode(DS, OUTPUT);                         // Initialise the Shift Register output pins
      pinMode(CLOCK, OUTPUT);
      pinMode(LATCH, OUTPUT);
      pinMode(BUTTON_PIN, INPUT_PULLUP);           // initialise analog input pin with pullup resistors
    }  // end of initShiftRegister()

    Polling the Buttons

    loop() {                      
    //    …
      unsigned int btn = checkButtons();       // poll buttons to see if one has just been pressed
      if (btn != NULL) {                       // if a button event has occured
        boolean state = ! buttonState(btn);    // toggle current button latch state
        processButton(btn, state);             // and perform the required action and update FSX
      }  // end of if (btn != 0)
    //    …
    }  // end of loop()
      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()

    Mapping the ADC value to a button

      map to a button bit position and return it, otherwise return NULL if no button recognised.
      Note that calling program should have already screened out the BTN_NONE condition,
    unsigned int mapButtons(unsigned int value) {
      unsigned int btn = 0x0000;
      if      ( btnPressed(value, BTN_01, BTN_MARGIN) )  {btn = BTN_COM1;}
      else if ( btnPressed(value, BTN_02, BTN_MARGIN) )  {btn = BTN_COM2;}  
      else if ( btnPressed(value, BTN_03, BTN_MARGIN) )  {btn = BTN_BOTH;}  
      else if ( btnPressed(value, BTN_04, BTN_MARGIN) )  {btn = BTN_NAV1;}  
      else if ( btnPressed(value, BTN_05, BTN_MARGIN) )  {btn = BTN_NAV2;}  
      else if ( btnPressed(value, BTN_06, BTN_MARGIN) )  {btn = BTN_MKR; }  
      else if ( btnPressed(value, BTN_07, BTN_MARGIN) )  {btn = BTN_DME; }  
      else if ( btnPressed(value, BTN_08, BTN_MARGIN) )  {btn = BTN_ADF; }  
      else if ( btnPressed(value, BTN_09, BTN_MARGIN) )  {btn = BTN_AP;  }  
      else if ( btnPressed(value, BTN_10, BTN_MARGIN) )  {btn = BTN_HDG; }  
      else if ( btnPressed(value, BTN_11, BTN_MARGIN) )  {btn = BTN_NAV; }  
      else if ( btnPressed(value, BTN_12, BTN_MARGIN) )  {btn = BTN_APR; }  
      else if ( btnPressed(value, BTN_13, BTN_MARGIN) )  {btn = BTN_REV; }  
      else if ( btnPressed(value, BTN_14, BTN_MARGIN) )  {btn = BTN_ALT; }  
      else if ( btnPressed(value, BTN_15, BTN_MARGIN) )  {btn = BTN_UP;  }  
      else if ( btnPressed(value, BTN_16, BTN_MARGIN) )  {btn = BTN_DN;  }  
      return btn;
    }  // end of mapButtons()

    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.

    Attached Images Attached Images

  9. #8
    10+ Posting Member
    Join Date
    Jan 2013
    Contribute If you enjoy reading the
    content here, click the below
    image to support MyCockpit site.
    Click Here To Contribute To Our Site

    Re: Radio Stack & Auto Pilot for FSX

    Radio Stack for Microsoft Flight Simulator X – version 2, part 2

    After taking a brief hiatus, work on the project has re-commenced, so here is an update on progress.

    Front Panel Design

    I felt my original front panel design (see part 1) could be aesthetically improved and reduced in size. Here is the final design which now measures 242mm x 175mm (as opposed to the original size of 264mm x 210mm).

    COM Radio Display Modules

    One of the early issues that needed resolving was how many digits should be displayed on the COM radios which in turn determined the number of LED driver chips required. The frequencies used for FSX COM radios range between 108.000 and 135.950MHz. However, as frequency channels are spaced either 25kHz (for COM) or 50kHz (for NAV) apart, FSX only displays two decimal places, and the third decimal place is inferred. For example, if the displayed frequency is 120.25 or 132.50, the inferred decimal is zero (i.e. 120.250 and 132.500 respectfully). Likewise, if the display shows 118.52 or 134.37, this actually represents 118.525 or 134.375MHz respectfully. I decided my design would also use five digits to minimise the footprint, leaving the inferred last decimal place to be handled in software. However, having five digits per COM radio display does not fit nicely with the outputs of the MAX7219/21 LED driver chips which each handle eight 7-segment digits. My initial printed circuit board (PCBs) designs based on driving five digits per display were overly complicated. Thus I eventually opted to make the most significant digit (which is always set to ‘1’ anyway!) hard-wired leaving the remaining four digits controlled by the driver chips. This resulted in a more elegant solution as each display pair (i.e. active and standby) only required a single LED driver chip, leading to a more modular design.

    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

    2 x display driver units (left) and 2 x radio display units (right) PCBs

    Here is the completed display unit. The two LEDs situated between the active and standby displays on each COM radio indicate which unit is under control of the rotary encoder used to change frequency. The connectors used to piggy-back the driver board have been super-glued onto the copper side of the PCB and their pins which protrude on this side are connected using fine wire through corresponding holes to pads once again on the copper side. The four mounting holes attach the complete piggy-backed unit to the front panel.

    2 x radio display unit schematic

    2 x LED driver unit schematic

    Buttons and Switches

    I originally intended to use a resistive ladder connected to a single analogue pin on the Arduino board to read all the switches and buttons which in all totalled 24. However, I found having so many buttons the separation between each value was too small to provide satisfactory operation. I briefly considered splitting the resistive ladders into three separate chains (thereby using three analogue pins) before abandoning this idea for a digital solution using three 8-bit parallel-in/serial-out (PISO) shift registers (74HC165). Having already made all the other PCBs for the project, it was easier to construct this relatively simple design on a small prototype board of which I already had a number lying around.

    3 x 8-bit PISO Shift Register Schematic

    This is the resulting board which measures 70mm x 50mm. As can be seen, it accommodates the three shift registers and the 24 female input connectors perfectly. I also decided to provide a number of +5v and ground connections pins (the two eight pin headers) to permit a common connection point for all the other modules. The 10kΩ resistors along the bottom are mainly connected to ground (i.e. pull-down) as the majority of buttons connect to +5v when pressed. However, the four encoder buttons connect to ground when pressed, thus the four leftmost resistors are configured as pull-ups. The three pin header connects to the Arduino board and is used to clock data (i.e. read the button/switch states).

    Attached Images Attached Images

  10. #9
    10+ Posting Member
    Join Date
    Jan 2013
    Contribute If you enjoy reading the
    content here, click the below
    image to support MyCockpit site.
    Click Here To Contribute To Our Site

    Re: Radio Stack & Auto Pilot for FSX

    Keypad and Transponder Module

    I wanted to avoid having to secure the transponder 4-digit display to the front panel with screws (i.e. similar to the COM radio display units), so decided to combine it with the keypad on a common PCB. Using pin headers I was able to keep the front of the 7-segment display unit level with the keypad. Once again I adopted a piggy-back design for the PCB.

    Keypad and Transponder Display Unit Schematic

    The keypad uses a small resistive ladder connected to an analogue pin to determine which button is pressed. The design (shown left on the schematic above) was obtained from The right hand side of the schematic consists of the single 4-digit transponder display and its driver unit. Once again I have used pin headers to connect the two boards together. Here are three images of this module. On the left, the transponder display and 7-pin header for the keypad are shown uppermost with the driver board below. As the transponder only uses four digits, the remaining four digits on the driver chip can be accessed as individual LEDs thereby providing the ability to drive 32 LEDs. These are used to display latch states of the buttons and connect via the 4x8 matrix of headers shown on the left of the lower image.

    Below are the two piggy-backed boards with the keypad connected (upper) and without the keypad connected (lower).


    Most of the buttons in this project have a latching action, i.e. each successive button press will toggle the latch state on then off, etc. Each latching button has a corresponding LED to show its current state. The LEDs are mounted on small strips of 3mm MDF with black heat-shrink over them to prevent light overspill. The heat-shrink wrap is glued to the MDF strips and cut flush with the front of the strip. The image below shows the six auto-pilot LEDs, (upper) and the eight switch panel LEDs (lower).

    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.

    Auto Pilot Module

    The Auto Pilot uses a 2 (rows) x 16 (characters) liquid crystal display (LCD) which is screwed to the front panel. Having soldered a female header to the LCD module this is used to make the connections to a small piece of veroboard via the 16-pin male header shown below. The small 10kΩ trim-pot is used to set the LCD contrast whilst the 220Ω resistor (top right) limits the backlight current.

    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.

    Putting it all together

    Having described how the individual modules have been fabricated, the following images show the radio stack with all the components connected together and working. The complete unit works off one Arduino Mega256 board and because all the LEDs are multiplexed via the MAX7219/21 chips, current draw is well within the capabilities of the USB connection of the Arduino so no separate power supply is needed.

    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

    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.

    In this image I have separated one of the two piggy backed dual radio modules to show the driver board and the connectors which join them together.

    The wiring behind the panel as it stands today.

    A final update will be posted once the project is completed with hopefully a video showing it in action. To be CONTINUED in the following post...
    Attached Images Attached Images
    Last edited by DLPublic; 10-09-2015 at 03:33 AM. Reason: added an image

  11. Thanks Morph thanked for this post
  12. #10
    10+ Posting Member
    Join Date
    Jan 2013
    Contribute If you enjoy reading the
    content here, click the below
    image to support MyCockpit site.
    Click Here To Contribute To Our Site

    Re: Radio Stack & Auto Pilot for FSX


  13. Thanks Morph, webliya, BushPilotWannabe thanked for this post
    Likes Morph, bizjet999, webliya liked this post