Connecting the 7373 FMC CDU to X-Plane via a Raspberry Pi

To connect the 737 FMC CDU PCB board to my X-Plane flight simulator, I used a Raspberry Pi 2. Almost any Pi model will do, provided it has the full 40-pin GPIO connector (i.e. not the early model A and B), networking, and video output to connect to the 5" display.

The Pi talks to the FMC CDU PCB I made earlier using GPIOs. I needed 9 (cols) + 8 (rows) + 5 (LEDs) GPIOs which came up to a total of 22. Many of the GPIO pins on the Raspberry Pi serve dual-purposes, and so you must make sure to:

  • not enable SPI
  • not enable I2C
  • not enable TTL Serial Port
  • not enable PWM

A freshly installed Raspbian usually comes with these features disabled.

For ease of wiring I bought a Female-Female Jumper Cable strip and just tore out how many wires I needed.


I also printed a bracket so I could mount the Raspberry Pi on the underside of the FMC CDU PCB using the existing bolts.


The first cable connects to "P1 - Cols" and was wired as follows:

Colour
P1 - Cols
Raspberry Pi (physical/wiringPi/BCM)
Black
1
8 15 14
White
2
10 16 15
Gray
3
12 1 18
Purple
4
16 4 23
Blue
5
18 5 24
Green
6
22 6 25
Yellow
7
24 10 8
Orange
8
26 11 7
Red
9
36 27 16
Not connected
10
n/a


The second cable is connected to "P2 - Rows" is wired as below:


Colour
P2 - Rows
Raspberry Pi (physical/wiringPi/BCM)
Gray
1
40 29 21
Purple
2
3 8 2
Blue
3
5 9 3
Green
4
7 7 4
Yellow
5
11 0 17
Orange
6
13 2 27
Red
7
15 3 22
Brown
8
19 12 10

The third cable is for the "P3 - LEDs" and is wired as below:

Colour
P3 - LEDs
Raspberry Pi (physical/wiringPi/BCM)
White
1 (VIN)
4 (5V)
Black
2 (GND)
6 (GND)
Brown
3 (EXEC)
21 13 9
Red
4 (MSG)
23 14 11
Orange
5 (DSPY)
29 21 5
Yellow
6 (OFST)
31 22 6
Green
N7 (FAIL)
33 23 13
N/A
8 (GPIOA)
N/A
N/A
9 (GPIOB)
N/A
Here are some photos of the wiring connections on the FMC CDU end:



And this is on the Pi's GPIO connector end:




Software Architecture

I divided the controller software into the following modules:



(green denotes a module that runs in its own thread)

ExtPlane Client

This module deals with communicating with the flight simulator, via the ExtPlane Plugin installed on the server. On module startup it attempts to connect to the server repeatedly. Once connected, it submits subscription requests for the aircraft type, and also calls FMCList's connectEvent() method.

Whenever a dataref's value changes in the flight simulator, the ExtPlane Client receives an update. If the update is a change of aircraft type, the new aircraft name is remembered. Otherwise, the dataref is passed to FMCList's receiveData() method for further processing.

FMC plugins can notify ExtPlaneClient if they have determined themselves to be the active FMC, via the setActiveFMC() method. FMCList will call the previous FMC's deactivate() method, followed by the new FMC's activate() method.

If the communications with the server is lost, the ExtPlane client calls the FMCList's disconnect_event(). It then resumes attempting to connect to the server.

FMCList

This class manages a set of FMC modules, which allow the behaviour of the CDU to change depending on which FMC is current active in the flight simulator. Currently there is support only for x737FMC. A "dummy" FMC, zeroFMC handles the CDU's behaviour when there is no active FMC.

FMCList connectEvent() method is called whenever ExtPlane Client establishes a new connection to the server. It calls the same method in each of the registered FMC modules. The FMC modules are expected to subscribe to whatever datarefs they require to determine if they are "active".

All dataref updates are passed down to each of the FMC modules. If they receive a dataref indicating that they are active, FMCList's setActiveFMC is called. This calls the previous FMC's deactivate() method followed by the new FMC's activate() method.

When activate()'d an FMC is expected to subscribe to all the dataref in needs to fully implement he CDU's behaviour. Typically this means subscribing to all the screen datarefs. Keypress datarefs also need to be subscribed to (ExtPlane doesn't let you set the value of a dataref unless you are subscribed to it). The FMC module is expected to translate screen datarefs updates and call the Screen module's queueLineUpdate() method to draw on the screen.

The active FMC module also receives keypress events from the KeypadScanner module. It is expected to translate the row and col from the keypad into dataref writes and submit them to the flight sim by calling ExtPlaneClient's sendLine() method.

The active FMC module also monitors indicator datarefs. If these are received, the LEDs::setLED method is called to activate the appropriate LED.

Screen

This module handles drawing on the screen. It divides the screen into interleaved tall and short lines of 25 columns. The calculateDimensions() method determines the X and Y offsets for each character position.

The Screen module makes use of SDL2 to draw on the screen, using a TrueType font. Because of the monospaced nature of the display, I rendered and cached each character as a separate texture, making it easy to draw individual characters any any fixed position.

The Screen module has its own thread, centered around the mainLoop() method. It listens for SDL events, such as SDL_QUIT, which triggers the program to exit (used when developing on a desktop PC).

It also listens for a registered user event called SDLUserEvent::LineUpdate, which is used by other modules to pass the screen module a line update. When a line update is received, the Screen module calls the renderLineAsTexture() method, which draws that particular line as a texture. The Screen module keeps a cache of the current texture for each of the 15 lines.

If the Screen module detects that the screen has changed but no update has been received for 10 milliseconds, it will then call CompositeLineTexturesToScreenTexture() to redraw the screen. This was implemented to prevent a flutter of refreshes if each of the 15 lines are changed, as would occur when the FMC switches to a new page.

Keypad Scanner

The Keypad Scanner module is responsible for detecting keypresses. It runs it's own thread around mainLoop().

During each iteration, it performs a matrix scan to determine which buttons are pressed. It determines this by setting each row's output to LOW in turn, while checking the status of all the inputs. Because of the pull-up resistor on each input, each input will report as HIGH unless the key is pressed where that input meets the current row (as current will flow to the row output pin set to LOW). If nothing was pressed previously, we consider this keypress as a new keypress event and notify the FMS module of a new keypress via the FMCList::keyPressEvent() method. If something was previously pressed but in this scan nothing was found, we declare a new keyrelease event and notify the FMS module.

Toggling LEDs On and Off

The FMS module subscribes to the X-Plane datarefs for each LED indicator. Whenever these datarefs change state, the FMS receives the new state from the ExtPlane plugin in the flight simulator. It then calls the relevant method in the LEDs module, to activate or deactivate that LED.



Current Status

Current source code can be found at http://home.abubakar.net/gitpub/?p=Remote737FMCCDU2;a=tree. Some parts might be lagging behind this note, some might be ahead. At the moment, x737FMC works and I am working on implementing the zeroFMC -- I want a screen on power up that will let you change the sim's IP address so the FMC can run totally standalone. After that I will look at adding support for X-FMC and some ability to select which FMC to talk to.

Do drop my a note if you're interested in building and running this -- I'll clean things up and make it "friendlier".


Update 2018-01-17

Quite a bit has changed since I wrote this post 6 months ago. The highlights: