Tutorial 40. Multiplexers

How to fit many signals into few pins

One of MEAP's greatest weaknesses when building it out into a larger system is the limited number of GPIO pins left over for us to work with. It would be great if we could add dozens more buttons, knobs etc. but we simply wouldn't have enough pins to connect them all! In this tutorial we will explore multiplexers which provide us with a way to connect multiple signals into on GPIO pin.

A multiplexer (sometimes referred to as a mux) is an electronic switch with several inputs and one output. The multiplexers on MEAP are LV4051's, which are 8-to-1 multiplexers, meaning they have eight inputs and one output. The output can access all eight of this inputs but only one at a time, like an elevator stopping on a floor. We use a set of binary address pins on the multiplexer to select which one of these inputs gets connected to the output. On the 4051, because we have eight inputs, we have three address inputs (A, B, and C) which each represent one bit of a 3-bit unsigned integer. A 3-bit number can count from zero up to seven, allowing us to select each one of our inputs.

fig 1. diagram showing how address pins connect a multiplexer's output to a specific input

With this switch, we are now able to connect 8 devices (buttons, potentiometers, etc) to just one GPIO pin. There are a few issues to consider though:

  1. We can only read these inputs one at a time, which will increase the complexity of our code a little bit. If we want to read all 8 inputs we need to first select the input we want, read the GPIO pin we want, and then repeat this process until we have read all eight pins. The easiest way to do this is using a for loop which will be demonstrated below.
  2. Going back to our elevator analogy, once you press the button of the floor you want to go to, you don't immediately teleport there; it takes a little bit of time for the elevator to move from one floor to another. The same is true of multiplexers! If you select an input using your address pins and immediately try to read the output, you may find that you get unexpected results. This is because you haven't given signal enough time to pass through the multiplexer. The propagation delay of a multiplexer is the amount of time it takes between receiving a change of address, and properly connecting that input to the output. The length of this delay is typically in the nanoseconds range, but can sometimes approach one microsecond. On slower microcontrollers like an arduino uno this is not always a problem but on a faster processor like the ESP32, it can easily cause problems if we aren't careful!
  3. Though we are connecting 8 signals to a single data pin, we need to remember that we also need 3 address pins, so in all it will take us 4 pins to hook up one 8-to-1 multiplexer. The good news is that in many cases we can re-use these address pins, so if already have one multiplexer hooked up, we only need one more pin to hook up a second one! The address pins used for MEAP's internal multiplexers are broken out to the pins on the circuit board labelled A, B and C, so if we want to add more multiplexers we can do just that!

Using MEAP's built-in multiplexer:

MEAP has one built-in multiplexer you can use to connect more analog (continuously variable like a potentiometer) or digital (on/off like a button) signals without adding any extra circuitry. All you need to do is solder the data signal of whatever device you are connecting to the 2x4 grid of pins to the right of your MEAP's DIP switches as shown below. The output of this multiplexer is connected to GPIO pin 11.

._ ._
|0|/1\
|_|\_/
._ ._
/3\/2\
\_/\_/
._ ._
/6\/4\
\_/\_/
._ ._
/5\/7\
\_/\_/

Connecting extra multiplexers to MEAP:

If you need even more inputs, you can add more multiplexers but you will need a little bit of external circuitry. We basically need a little circuit board that we can solder the multiplexer and its inputs (plus any interfacing circuitry like pullup resistors for buttons) onto. I recommend using a protoboard like this one, however a solderless breadboard could work in a pinch, it will just be less reliable.

Once you have your protoboard, you should solder your multiplexer into it and then begin connecting its pins to MEAP. For a multiplexer, I would recommend using a CD4051 which works the same as the LV4051 but comes in a DIP package which makes it easier to solder into a protoboard by hand. I recommend looking over the chip's datasheet to fully understand how it works, but the important thing to grab from there is the pinout. This tells us what each of the chip's pins do.

fig 2. pinout of CD4051

Beyond that we have a few power pins. VEE and VSS can be connected to ground


Writing code to read your multiplexer:

In most cases, code for reading inputs can live in updateControl(). The following method for reading multiplexer inputs is by no means the most efficient, but it will get the job done.

In this example, I will assume we have connected a multiplexer with buttons at its inputs to pin 15 on MEAP and a second multiplexer with potentiometers at its inputs to pin 16. If you are using MEAP's built-in multiplexer, it can be treated the same way, it is just always connected to pin 11.

First, let's create some global variables to store the data from our multiplexers. We'll create an array of 8 integers for each multiplexer, which will represent the signal at each of their eight inputs.

int mux1_vals[8] = {0, 0, 0, 0, 0, 0, 0, 0};
int mux2_vals[8] = {0, 0, 0, 0, 0, 0, 0, 0};

Next, in setup(), we need to tell MEAP that we will be using our GPIO pins as inputs rather than outputs.

pinMode(15, INPUT);
pinMode(16, INPUT);

Next, in updateControl() we will cycle through all 8 multiplexer channels and read the data at each stop. Buttons are digital (either 3.3v or GND), while potentiometers are analog (continuously varying between 3.3v and ground), so we will be using digitalRead() for our first multiplexer and analogRead() for our second.

for (int i = 0; i < 8; i++) {
  meap.setMuxChannel(i);
  meap.setMuxChannel(i);
  meap.setMuxChannel(i);
  mux1_vals[i] = digitalRead(15);
  mux2_vals[i] = analogRead(16);
}

meap.setMuxChannel() sets the A, B, and C channel select pins to the correct values to connect the specified channel (0-7) to the multiplexer's output. We call it three times to allow the signal ample time to propagate through the multiplexer before reading it so we get a more reliable result (refer to the propagation delay note above).