MUX-DEMUX: CD4051 Parlor Tricks

Sorry model train fans, this is more electronics. I wanted to document how I intend to get 16 input/output signals from an 8-channel multiplexer chip using only 5 pins on the Arduino micro-controller. This will take both some hardware tricks as well as some software shenanigans that I should understand better once I try and explain it to you.

I started with the tutorial at http://www.arduino.cc/playground/Learning/4051. The concept is that using just three digital pins you can address one of eight channels at a time (which can be input or output, analog or digital). In total, you use four pins (three signal and one common) to get 8 I/O. That is a good deal and fairly straight forward.

It also occurred to me, that in a manner similar to what I had seen done with multiplexed displays, that by adding an additional common pin I could drive current in either direction and double the number of I/Os. That is not so straight forward, so let me try and explain.
If you consider the pair of LEDs in this example to be a "bank", and you want to light LED_0, you need to set PIN 17 to HIGH, and PIN 18 to LOW. (The pin numbers are confusing, but they match the later example so bare with me). To light LED_1, you just reverse the PINS. The diode nature of LEDs keeps the current from flowing the opposite direction keeping the other one off.
This gets a little more complicated if you want to do an analog read on a CdS light sensor. First, you need to add a diode to each sensor to control the flow. Second, since you are reading values, you need to pull the inputs high or low to keep them from floating. Being a lazy person, I'm going to pull them high using the internal pull-up resistors. To read CdS_0, you set PIN 17 mode to OUTPUT and set it to LOW. This makes it the ground. Then you set PIN 18 mode to INPUT and set it to HIGH to engage the pull-up resistor. Now you are set to do a read on PIN 18 (a.k.a. analog pin 4). To access the other sensor, just switch the modes and outputs.

Alright, if you are still with me, now all we have to do is multiply that by eight. The eight channels are accessed by addressing the 3 select pins (digital pins 14, 15, & 16) as follows:

Channel / Pin 16 / Pin 15 / Pin 14
0 - 0 0 0
1 - 0 0 1
2 - 0 1 0
3 - 0 1 1
4 - 1 0 0
5 - 1 0 1
6 - 1 1 0
7 - 1 1 1

Hey, that looks like a decimal to binary chart. Imagine that.

O.K., now to the breadboarded "proof of concept". I've split it into 4 banks of LEDs (8 total), and 4 banks of CdS cells (8 total), for a grand total of 16 I/O on 5 pins. Before you say, "wait, you can do that with a 4067 chip," let me just say that the 4051 was all I had handy, and that this trick will also work on 4067 if you want to get 32 I/O out of six pins. That is quite the math. Double your pins for the price of one!

I did the schematics and breadboard with a cool program called Fritzing. It will also help you layout a PCB board, but I don't need to go that far with this (I've gone too far already).


Here is the pudding:


I hoped this helped you. It did me.

Here is the Arduino code (I'm not sure why the formatting gets spread out:

/*
 * Example of getting 16 i/o from 5 pins using a CD4051
 *
 * Based on tutorial and code by david c. and tomek n.* for k3 / malmö högskola
 * http://www.arduino.cc/playground/Learning/4051?action=sourceblock&ref=1
 */


int selPin[] = { 14, 15, 16 }; // select pins on 4051 (analog A0, A1, A2)
int commonPin[] = { 17, 18};   // common in/out pins (analog A3, A4)
int led[] = {LOW, LOW, LOW, LOW, LOW, LOW, LOW, LOW };  // stores eight LED states
int CdSVal[] = { 0, 0, 0, 0 }; // store last CdS readings
int cnt = 0;  // main loop counter
int persistDelay = 100; // LED ontime in microseconds


void setup(){
  Serial.begin(9600);  // serial comms for troubleshooting (always)
  for(int pin = 0; pin < 3; pin++){ // setup select pins
    pinMode(selPin[pin], OUTPUT);
  }
}


void loop(){
  flashLEDs();
  if (cnt == 0){
    for(int x; x < 8; x++){
      led[x] = random(2);
    }
  }
  cnt++;
  if (cnt > 100) { cnt = 0; }
}


void flashLEDs() {
  for(int pin = 0; pin < 2; pin++) {  // set common pins low
    pinMode(commonPin[pin], OUTPUT);
    digitalWrite(commonPin[pin], LOW);
  }
  for (int bank = 0; bank < 4; bank++) {
    for(int pin = 0; pin < 3; pin++) { // parse out select pin bits
      int signal = (bank >> pin) & 1;  // shift  & bitwise compare
      digitalWrite(selPin[pin], signal);
    }
    if (led[bank * 2]){        // first LED
      digitalWrite(commonPin[0], HIGH);  // turn common on
      delayMicroseconds(persistDelay);   // leave led lit
      digitalWrite(commonPin[0], LOW);   // turn common off
    }
    if (led[bank * 2 + 1]){     // repeat for second LED
      digitalWrite(commonPin[1], HIGH);
      delayMicroseconds(persistDelay);
      digitalWrite(commonPin[1], LOW);
    }
  }
}

6 comments:

  1. the way to double the i/o ports by using diodes isjust genius

    ReplyDelete
  2. Hi,

    I am using EVK1100. I need to read voltages from Voltage divider, since EVK1100 has only 5 adc inputs and i have 8-10 sensor outputs i need to read on EVK1100. Can i use the method above to read the voltages on EVK1100 or is there any alternative method.

    Kuamr

    ReplyDelete
  3. What diodes are you using? I was checking starter packages for the arduino and the included a 1N4007, but Jameco Electronics sells 3 types.

    -@ RCTFIER STND RECVRY O/J,RHS DIP8
    -@ DIODE, 1N4007, DO-41, (10),GPP 1A 1000V
    -@ DIODE,SIL REC,1N4007,1A,1000V PRV (10)

    Are any of these similar to what you used?


    P.S. Any chance you could upload the arduino code for this setup?

    ReplyDelete
  4. Can you post your code here please... Thank you very much.

    ReplyDelete
  5. Added dusty code to the post. Use at your own risk.

    ReplyDelete
  6. Hi Ken this is just great and easy to follow, thanks. I was wondering what diodes you used? I downloaded the Fritzing package but its missing a few parts. Once again great post.

    ReplyDelete