Using my GPIO expander boards, I could now assemble a stack of eight MCP23017s which would give me 16 x 8 = 128 inputs. This may sound like a lot, but when I counted the number of stuff I would need to connect for the 737 Overhead, it was quite limiting. So I spent a few weeks researching on options to increase the number of GPIOs further.

Given that my toggle switches are only used for inputs, one common solution, is to multiplex a number of GPIOs together in a Matrix configuration. This is solution is commonly used for keypads and keyboards, where a large number of inputs is required. A fantastic article for anyone designing a matrix input, written by Michał Trybus, can be found here. Essentially you arrange your GPIOs into rows and columns as so:

The GPIOs on the "columns" are set as "inputs", and the GPIO's on the rows are set as "output". The MCP23017's internal pull-ups are activated in the input columns, which means that the input will show HIGH if the switch is open. All the row outputs are also set to HIGH by default. As mentioned in the article, a diode is needed on each switch to prevent the flow of current around switches when multiple switches are activated,allowing you to "see" the correct situation.

Software to Scan the Matrix

Next, software is executed to perform a scan from row 0 to the highest. It sets the output of the row to be scanned to LOW, and then looks across each of the columns to see which ones are LOW. Although there are many switches connected to a single column, only the one at the intersection of the scanned row will be able to report LOW (if the switch is closed). So the software collects the state of all the columns in that row, and then raises that row back to HIGH and sets the next row to LOW, and repeats the column scan. In this way, the entire matrix will be covered.

You can build a matrix from any number of rows and columns, but optimally you want to divide them equally to get a square., e.g. if you had 8 GPIOs, the following combinatinos are possible:

7 x 1 = 7
6 x 2 = 12
5 x 3 = 15
4 x 4 = 16 (best combination)

In my case, I decided to dedicate two MCP23017's for the matrix, with one acting as the rows and another acting as the columns. With each MCP23017 featuring 16 GPIOs, I could get a matrix of 16x16=256 switches. It's advantageous for me to use a whole MCP23017 for each side because I can access or set all 16 pins of a MCP23017 in one single i2C transaction.

Here's what the scanning code looks like:

void InterfaceMatrixInput::pollLoop () {

// initialize the input MCP23017
inputMCP->setGPIOModeAll(0xFFFF); // all input
inputMCP->setGPIOPullUpAll(0xFFFF); // enable pull-up on all pins

// initialize the output MCP23017
outputMCP->setGPIOModeAll(0x0000); // all output

int outputPin = 0;

int prevData [16];
bool first = true;

while (threadState == InterfaceMatrixInput::THREADSTATE::RUNNING) {

// set only the output pin we want to 0, rest as 1
int mask = ~(1 << outputPin);

// wait a bit for things to settle down

// now read all the input pins
int data = inputMCP->readAll();

if (!first) {
if (prevData[outputPin] != data) {
// this row is different
int diff = (prevData[outputPin] ^ data) & 0xFFFF;
int mask = 1;
for (int r = 0; r < 16; r++) {
if ((diff & mask) != 0) {
bool state = (data & mask) != 0;
// intersection of outputCol and row
cout << "i" << hex << r << "o" << outputPin << " " << (state ? "N/C" : "CONN")
<< endl;
mask = mask << 1;

prevData[outputPin] = data;

// cycle to the next output pin
outputPin += 1;
if (outputPin > 15) {
outputPin = 0;
first = false;


threadState = InterfaceMatrixInput::THREADSTATE::STOPPED;

It just highlights whenever any of the toggle switches changes state -- later I will add code to communicate with the flight sim.

(Note: Work-In-Progress full source code can be found at;a=summary.)

Distributing the Matrix

On my 737 Overhead Panel I have many subpanels, and I want to be able to remove each subpanel individually. Therefore, any wiring to a subpanel must be on a connector so that it can be taken off. This makes wiring it up something of a concern, as for example the engine starter panel at the bottom alone would need about 60 wires running to it.

(In hindsight ... i should have designed my overhead panel as one big piece, and made it possible to remove the entire overhead panel from the standing mount for maintenance. That way I would make one large PCB with all the controlling circuits, and just run wires to each individual toggle switch or component, without needing to be concerned with removal of each subpanel).

I decided to take advantage of the Matrix to help simplify my wiring, by creating a submatrix of 4x4 on a smaller Printed Circuit Board, with help from EasyEDA again. A 4x4 matrix requires just 8 wires to drive it, and I can use a flat ribbon cable for that, to keep the wiring neat. Having a PCB at each subpanel also makes it easy for me to wire in the required diodes.

Here's the PCB I made for each subpanel (with a 20 sen coin to show how small it is):

I just had to solder regular PCB Headers and also 1N4148 signalling diodes into the appropriate through-hole pads.

Here's how I connect the submatrix boards to the GPIO Expanders and raspberry Pi.

If you recall, my GPIO Expanders have 4 pins connected to each GPIO -- this makes it easy to connect multiple wires to the same GPIO. In wiring the submatrix boards, I have to ensure that each board goes to a different row and column. It's ok to combine rows and columns, but no two boards should occupy the same row and same column.

Here's what it looks like in real life, on my test bench:

And this is the other end, wired to the toggle switches:

What if that's not enough? Well I can always dedicate two more GPIO Expanders and create a 2nd matrix. 😎

Note: Submatrix PCB design can be found here.

Mounting the Submatrix Board To Each Subpanel

In the case of the ignition panel above, I just screwed the submatrix board into the MDF backplate.

For other panels, where I didn't have a backplate, and the toggle switches themselves got in the way, I used standoffs to raise the submatrix board above the backs of the toggle switches. Then I made some countersunk holes in small PVC board squares to be used as "feet", screwed them to the standoffs, and finally hot-glued the PVC board "feet" to the back foam-core surface of each subpanel.

Special Case: Connecting The Matrix Input to a Selector Switch

The Rotary Switch raises a problem with the Matrix Input circuit. Unlike regular toggle switches, most Rotary Switches share a common pin on one side of the switch:

Because the matrix input detects a connection between a combination of input and output lines, it is not normally possible to use it with a 12-way Rotary Switch. You can get away with it however, if each of the inputs share a common output line, which means you have to carefully select which position is used.

In my submatrix board, each rotary switch must be connected into the following groups of swith positions:

This works for the ignition switches, wipers, pressurisation mode, and cross feed rotary switches. But what about the electric metering panel, where there are 8 selections? I will post a solution for that later. 8-)