Close

The GNS 430 GPS

A project log for Piper Arrow III Sim Cockpit

I‘m building an instrument panel of a Piper Arrow III for use with flight sims (X-plane in my case)

zaggoZaggo 05/01/2024 at 11:461 Comment

The first device I built was the GNS 430 GPS unit. Here's the finished thing:

I wanted this to have with backlit buttons and real dual rotary encoders. After searching the web for a while it became clear, that I had to design this myself.

The screen content comes directly from the flight sim. But since I'm currently running the sim on a Macbook Pro, and thus do not have an option to connect too many external displays, I use AirManager for grabbing the GPS' display content directly from XPlane and sending it via a network connection to a Raspberry Pi 4. This works surprisingly well, even over wifi. Unfortunately the Raspberry doesn't have enough GPIO pins to also drive all the buttons and encoders. I originally planned to build two GNS 430 units anyway (as there are two of them in the simulator) and it's no problem for the Raspberry Pi 4 with its 2 HDMI connectors (and the AirManager/AirPlayer combo) to provide the display content for both of them. So I decided to use a separate Arduino Mega 2560 for interfacing with all the hardware buttons and encoders. To my pleasant surprise, it's even possible to connect the Arduino(s) directly to the Rasperry Pi and setup AirManager/AirPlayer in such a way, that all information is transported via the netrwork connection. This greatly reduces the need for cables between the dashboard and the computer. In fact, there is actually 0 (zero) cables between the main computer and the dashboard, since everything is working great so far with just a WiFi connection between the Mac and the Raspberry Pi. The only cable going to the dashboard is a power cable!

Here's the current wiring schematic of the GNS 430:

I did draw the whole design in Fusion. I was able to use some parts (like the rotary encoders) from GrabCAD, but most parts (including the backlit push buttons and the LCD) I drew myself.

After a few failed test prints of the tiny push button covers with my > 10 years old LulzBot TAZ 6 it was clear, that a FDM printer wouldn't cut it (at least mine). So after more than 15 years of FDM printing, I decided that it would be time to give resin printers a try and bought a Elegoo Mars 3 Pro.

Using a resin printer after years and years of FDM printing is maybe a story for a different time, but I finally managed do successfully print first push buttons!

I printed these with clear resin and airbrushed them black afterwards. Finally I sanded off the black coating from the embossed text and voila: A backlit push button:

I ordered most of the hardware for cheap from Ali Express (rotary encoders and a bag of 100 pcs "6x6x7mm through hole reset micro push button tactile momentary switch with led (white)"), and a 4 inch IPS display with HDMI port from Amazon (not so cheap).

The white text on the frontpanel and the encoder knobs is also 3D printed, embossed text. But this time the part was printed with black resin and the embossed text later painted white (with a white paint pen).

And here's the thing, all cabled up, after assembly:

On the software side, I wrote a LUA script for my GNS 430 in AirManager (I'm new to AirManager and also LUA, so please let me know if I can optimize the code!).

The LCD-Content is fetched from XPlane and send to the AirPlayer instance on the Raspberry Pi with this simple command:

video_stream_id = video_stream_add("xpl/GNS430_1", 0, 0, 800, 480)

All the buttons and encoders are then mapped by a script, running on the Arduino Mega:

------------------
-- GPS PushButtons
------------------

cvol_event = 0

function cvol_pressed()
  print("cvol_pressed")
  xpl_command("sim/GPS/g430n1_cvol", "BEGIN")
  
  local event = cvol_event
  timer_start(2000, function(count)
    if event == cvol_event then
        print("Shutdown")
        shut_down("SYSTEM") 
    end
  end)
end

function cvol_released()
   cvol_event = cvol_event + 1
   -- print("cvol_released, cvol_down is now "..cvol_down)
   xpl_command("sim/GPS/g430n1_cvol", "END")
end

hw_button_add("CVol", cvol_pressed, cvol_released)

function vvol_pressed()
  print("vvol_pressed")
  xpl_command("sim/GPS/g430n1_vvol", "BEGIN")
end

function vvol_released()
  xpl_command("sim/GPS/g430n1_vvol", "END")
end

hw_button_add("VVol", vvol_pressed, vvol_released)

function comff_pressed()
  print("comff_pressed")
  xpl_command("sim/GPS/g430n1_com_ff", "BEGIN")
end

function comff_released()
  xpl_command("sim/GPS/g430n1_com_ff", "END")
end

hw_button_add("Com FF", comff_pressed, comff_released)

function navff_pressed()
  print("navff_pressed")
  xpl_command("sim/GPS/g430n1_nav_ff", "BEGIN")
end

function navff_released()
  xpl_command("sim/GPS/g430n1_nav_ff", "END")
end

hw_button_add("Nav FF", navff_pressed, navff_released)

function nav_com_tog_pressed()
  print("nav_com_tog_pressed")
  xpl_command("sim/GPS/g430n1_nav_com_tog", "BEGIN")
end

function nav_com_tog_released()
  xpl_command("sim/GPS/g430n1_nav_com_tog", "END")
end

hw_button_add("NavCom Toggle", nav_com_tog_pressed, nav_com_tog_released)

function cdi_pressed()
  print("cdi_pressed")
  xpl_command("sim/GPS/g430n1_cdi", "BEGIN")
end

function cdi_released()
  xpl_command("sim/GPS/g430n1_cdi", "END")
end

hw_button_add("CDI", cdi_pressed, cdi_released)

function obs_pressed()
  print("obs_pressed")
  xpl_command("sim/GPS/g430n1_obs", "BEGIN")
end

function obs_released()
  xpl_command("sim/GPS/g430n1_obs", "END")
end

hw_button_add("OBS", obs_pressed, obs_released)

function msg_pressed()
  print("msg_pressed")
  xpl_command("sim/GPS/g430n1_msg", "BEGIN")
end

function msg_released()
  xpl_command("sim/GPS/g430n1_msg", "END")
end

hw_button_add("MSG", msg_pressed, msg_released)

function fpl_pressed()
  print("fpl_pressed")
  xpl_command("sim/GPS/g430n1_fpl", "BEGIN")
end

function fpl_released()
  xpl_command("sim/GPS/g430n1_fpl", "END")
end

hw_button_add("FPL", fpl_pressed, fpl_released)

function proc_pressed()
  print("proc_pressed")
  xpl_command("sim/GPS/g430n1_proc", "BEGIN")
end

function proc_released()
  xpl_command("sim/GPS/g430n1_proc", "END")
end

hw_button_add("PROC", proc_pressed, proc_released)

function rng_dn_pressed()
  print("rng_dn_pressed")
  xpl_command("sim/GPS/g430n1_zoom_out", "BEGIN")
end

function rng_dn_released()
  xpl_command("sim/GPS/g430n1_zoom_out", "END")
end

hw_button_add("RNG Down", rng_dn_pressed, rng_dn_released)

function rng_up_pressed()
  print("rng_up_pressed")
  xpl_command("sim/GPS/g430n1_zoom_in", "BEGIN")
end

function rng_up_released()
  xpl_command("sim/GPS/g430n1_zoom_in", "END")
end

hw_button_add("RNG Up", rng_up_pressed, rng_up_released)

function direct_pressed()
  print("direct_pressed")
  xpl_command("sim/GPS/g430n1_direct", "BEGIN")
end

function direct_released()
  xpl_command("sim/GPS/g430n1_direct", "END")
end

hw_button_add("DIRECT", direct_pressed, direct_released)

function menu_pressed()
  print("menu_pressed")
  xpl_command("sim/GPS/g430n1_menu", "BEGIN")
end

function menu_released()
  xpl_command("sim/GPS/g430n1_menu", "END")
end

hw_button_add("MENU", menu_pressed, menu_released)

function clr_pressed()
  print("clr_pressed")
  xpl_command("sim/GPS/g430n1_clr", "BEGIN")
end

function clr_released()
  xpl_command("sim/GPS/g430n1_clr", "END")
end

hw_button_add("CLR",clr_pressed, clr_released)

function ent_pressed()
  print("ent_pressed")
  xpl_command("sim/GPS/g430n1_ent", "BEGIN")
end

function ent_released()
  xpl_command("sim/GPS/g430n1_ent", "END")
end

hw_button_add("ENT", ent_pressed, ent_released)

function cursor_pressed()
  print("cursor_pressed")
  xpl_command("sim/GPS/g430n1_cursor", "BEGIN")
end

function cursor_released()
  xpl_command("sim/GPS/g430n1_cursor", "END")
end

hw_button_add("CRSR", cursor_pressed, cursor_released)

-- Create a new rotary encoder
function page_change(direction)
  if direction == 1 then
    print("The page turned clockwise")
    xpl_command("sim/GPS/g430n1_page_up", "ONCE")
  end

  if direction == -1 then
    print("The page turned counterclockwise")
    xpl_command("sim/GPS/g430n1_page_dn", "ONCE")
  end
end

hw_dial_add("PAGE", page_change)

-- Create a new rotary encoder
function chapter_change(direction)
  if direction == 1 then
    print("The chapter turned clockwise")
    xpl_command("sim/GPS/g430n1_chapter_up", "ONCE")
  end

  if direction == -1 then
    print("The chapter turned counterclockwise")
    xpl_command("sim/GPS/g430n1_chapter_dn", "ONCE")
  end
end

hw_dial_add("CHAPTER", chapter_change)

-- Create a new rotary encoder
function cv_fine_change(direction)
  if direction == 1 then
    print("The cv_fine turned clockwise")
    xpl_command("sim/GPS/g430n1_fine_up", "ONCE")
  end

  if direction == -1 then
    print("The cv_fine turned counterclockwise")
    xpl_command("sim/GPS/g430n1_fine_down", "ONCE")
  end
end

hw_dial_add("CV_FINE", cv_fine_change)

-- Create a new rotary encoder
function cv_coarse_change(direction)
  if direction == 1 then
    print("The cv_coarse turned clockwise")
    xpl_command("sim/GPS/g430n1_coarse_up", "ONCE")
  end

  if direction == -1 then
    print("The cv_coarse turned counterclockwise")
    xpl_command("sim/GPS/g430n1_coarse_down", "ONCE")
  end
end

hw_dial_add("CV_COARSE", cv_coarse_change)

-- Create a new rotary encoder
function cvol_change(direction)
  if direction == 1 then
    print("The cvol turned clockwise")
    xpl_command("sim/GPS/g430n1_cvol_up", "ONCE")
  end

  if direction == -1 then
    print("The cvol turned counterclockwise")
    xpl_command("sim/GPS/g430n1_cvol_dn", "ONCE")
  end
end

hw_dial_add("C_VOL", cvol_change)

-- Create a new rotary encoder
function vvol_change(direction)
  if direction == 1 then
    print("The vvol turned clockwise")
    xpl_command("sim/GPS/g430n1_vvol_up", "ONCE")
  end

  if direction == -1 then
    print("The vvol turned counterclockwise")
    xpl_command("sim/GPS/g430n1_vvol_dn", "ONCE")
  end
end

hw_dial_add("V_VOL", vvol_change)

 Maybe the most fancy feature on this code is right at the beginning, where I managed to translate a "long press" on the "Pwr VOL/SQ" encoder button  to a system shutdown of the Raspberry Pi. That way, I'm able to correctly shut down the Raspberry without the need for a SSH shell or just cutting the power and hoping for the best.

Discussions

Felix wrote 05/29/2024 at 01:56 point

Absolutely fantastic writeup mate, looks like an outstanding final product too. So outstanding that I would love to give it a crack and make one myself, if you would be willing to share the 3D printer files :D

  Are you sure? yes | no