2

I'm working on using a GUI in Processing to control some LED rope from Adafruit, and using this multiplexer.

I've gotten the following Arduino code working the way I want to cycle through the various strands of LED rope and turn them on:

#include <SoftwareSerial.h>
 
//Mux1 control pins
int s0 = 8;
int s1 = 9;
int s2 = 10;
int s3 = 11;
 
//Mux2 control pins
int s4 = 12;
int s5 = 13;
int s6 = 14;
int s7 = 15;
 
 
//Mux in "SIG" pin
int SIG_pin = 3;
 
//Voltage vlaue to write to the LEDs
int testValue = 255;
 
 
void setup(){
  Serial.begin(9600);
 
 
  pinMode(s0, OUTPUT); 
  pinMode(s1, OUTPUT); 
  pinMode(s2, OUTPUT); 
  pinMode(s3, OUTPUT); 
  pinMode(s4, OUTPUT); 
  pinMode(s5, OUTPUT); 
  pinMode(s6, OUTPUT); 
  pinMode(s7, OUTPUT); 
  pinMode(SIG_pin, OUTPUT);
 
  digitalWrite(s0, LOW);
  digitalWrite(s1, LOW);
  digitalWrite(s2, LOW);
  digitalWrite(s3, LOW);
  digitalWrite(s4, LOW);
  digitalWrite(s5, LOW);
  digitalWrite(s6, LOW);
  digitalWrite(s7, LOW);
}
 
 
void loop(){
 
  for(int i = 0; i <= 19 ; i ++){
    Serial.println(i);
    testValue = 255;
    writeMux(i);    
    delay(500);
//    testValue = 0;
//    writeMux(i);    
 
  }
 
}
 
 
int writeMux(int channel){
  int controlPin[] = {s0, s1, s2, s3, s4, s5, s6, s7};
 
  int muxChannel[20][8]={
    //Mux 1 
    {0,0,0,0,0,0,0,0}, //channel 0
    {1,0,0,0,0,0,0,0}, //channel 1
    {0,1,0,0,0,0,0,0}, //channel 2
    {1,1,0,0,0,0,0,0}, //channel 3
    {0,0,1,0,0,0,0,0}, //channel 4
    {1,0,1,0,0,0,0,0}, //channel 5
    {0,1,1,0,0,0,0,0}, //channel 6
    {1,1,1,0,0,0,0,0}, //channel 7
    {0,0,0,1,0,0,0,0}, //channel 8
    {1,0,0,1,0,0,0,0}, //channel 9
 
    //Max 2
    {1,1,1,1,0,0,0,0}, //channel 0
    {1,1,1,1,1,0,0,0}, //channel 1
    {1,1,1,1,0,1,0,0}, //channel 2
    {1,1,1,1,1,1,0,0}, //channel 3
    {1,1,1,1,0,0,1,0}, //channel 4
    {1,1,1,1,1,0,1,0}, //channel 5
    {1,1,1,1,0,1,1,0}, //channel 6
    {1,1,1,1,1,1,1,0}, //channel 7
    {1,1,1,1,0,0,0,1}, //channel 8
    {1,1,1,1,1,0,0,1}, //channel 9
  }; 
 
 
  //loop through the 4 sig
  for(int i = 0; i < 8; i ++){
    digitalWrite(controlPin[i], muxChannel[channel][i]);
//    Serial.println("CHANNEL IS ");
//    Serial.println(muxChannel[channel][i]);
 
//    Serial.println("Control pin IS ");
//    Serial.println(controlPin[i]);
  }
 
 
 
  //write the value at the SIG pin
  analogWrite(SIG_pin, testValue);  
 
//    Serial.println("Test value IS ");
//    Serial.println(testValue);
 
 
 
  //return the value
  return 0;
}

I'm working on converting this Arduino code to a Processing sketch so I can control the lights with a GUI. I've run the Standard Firmata sketch on my Uno before running my Processing sketch. However, nothing seems to happen...I know that my circuit is working (since it worked for the Arduino sketch), and most of my Processing code should be correct. Logging shows the sketch is trying to write the values I want. Anyone have any ideas? Below is the relevant part of the Processing sketch:

import cc.arduino.*;
import processing.serial.*;
 
int [] muxChannel;
 
//Mux1 control pins
int s0 = 8;
int s1 = 9;
int s2 = 10;
int s3 = 11;
 
//Mux2 control pins
int s4 = 12;
int s5 = 13;
int s6 = 14;
int s7 = 15;
 
//Mux in "SIG" pin
int SIG_pin = 3;
 
//Voltage vlaue to write to the LEDs
int testValue = 255;
 
 
void setup() {
  size(700,400);
  noStroke();
  cp5 = new ControlP5(this);
  println(Arduino.list());
 
  //// Set up Arduino and pin output modes
  arduino = new Arduino(this, Arduino.list()[1], 96000);
 
  arduino.pinMode(s0, Arduino.OUTPUT); 
  arduino.pinMode(s1, Arduino.OUTPUT); 
  arduino.pinMode(s2, Arduino.OUTPUT); 
  arduino.pinMode(s3, Arduino.OUTPUT); 
  arduino.pinMode(s4, Arduino.OUTPUT); 
  arduino.pinMode(s5, Arduino.OUTPUT); 
  arduino.pinMode(s6, Arduino.OUTPUT); 
  arduino.pinMode(s7, Arduino.OUTPUT); 
  arduino.pinMode(SIG_pin, Arduino.OUTPUT);
 
  arduino.digitalWrite(s0, Arduino.LOW);
  arduino.digitalWrite(s1, Arduino.LOW);
  arduino.digitalWrite(s2, Arduino.LOW);
  arduino.digitalWrite(s3, Arduino.LOW);
  arduino.digitalWrite(s4, Arduino.LOW);
  arduino.digitalWrite(s5, Arduino.LOW);
  arduino.digitalWrite(s6, Arduino.LOW);
  arduino.digitalWrite(s7, Arduino.LOW);
}
 
void draw() {
  background(bgColor);
 
    for(int i = 0; i <= 19 ; i ++){
    testValue = 255;
    writeMux(i);    
    delay(500); 
  }
}
 
int writeMux(int channel){
  int controlPin[] = {s0, s1, s2, s3};
 
  final int[][] muxChannels = {
    //Mux 1 
    {0,0,0,0,0,0,0,0}, //channel 0
    {1,0,0,0,0,0,0,0}, //channel 1
    {0,1,0,0,0,0,0,0}, //channel 2
    {1,1,0,0,0,0,0,0}, //channel 3
    {0,0,1,0,0,0,0,0}, //channel 4
    {1,0,1,0,0,0,0,0}, //channel 5
    {0,1,1,0,0,0,0,0}, //channel 6
    {1,1,1,0,0,0,0,0}, //channel 7
    {0,0,0,1,0,0,0,0}, //channel 8
    {1,0,0,1,0,0,0,0}, //channel 9
 
    //Max 2
    {1,1,1,1,0,0,0,0}, //channel 0
    {1,1,1,1,1,0,0,0}, //channel 1
    {1,1,1,1,0,1,0,0}, //channel 2
    {1,1,1,1,1,1,0,0}, //channel 3
    {1,1,1,1,0,0,1,0}, //channel 4
    {1,1,1,1,1,0,1,0}, //channel 5
    {1,1,1,1,0,1,1,0}, //channel 6
    {1,1,1,1,1,1,1,0}, //channel 7
    {1,1,1,1,0,0,0,1}, //channel 8
    {1,1,1,1,1,0,0,1}, //channel 9  
    };
 
  //loop through the 4 sig
  for(int i = 0; i < 4; i ++){
    arduino.digitalWrite(controlPin[i], muxChannels[channel][i]);
  }
 
  //write the value at the SIG pin
  arduino.analogWrite(SIG_pin, testValue);  
 
  int val = 0;
  return val;
}

EDIT: I put the project and a little explainer on GitHub to show what this type of project can or can't be useful for here: https://github.com/narner/Analog-LED-Multiplexing-Example

  • 2
    It makes no sense to have that stupid muxChannels[][] array, which wastes 128 bytes of RAM. Comments in the bildr.org webpage you referenced pointed out that it's unnecessary, but the webpage author had a lame excuse for not fixing it. Instead of for(int i = 0; i < 4; i ++) arduino.digitalWrite(controlPin[i], muxChannels[channel][i]);, say for(int i=0; i<4; ++i) arduino.digitalWrite(controlPin[i], (channel>>i)&1); or alternately, PORTB = (PORTB & 0xF0)|channel; – James Waldby - jwpat7 Apr 2 '17 at 19:38
  • Hey @jwpat7, I think I follow what you're saying there. I tried your suggestion; and it works for the lights connected to the first multiplexer, but nothing seems to happen with the lights on the second one. – narner Apr 4 '17 at 20:46
  • I didn't see code for the second multiplexer, and don't know what channel numbers you will use for it, so didn't say how to treat it. But if its list of control pins is in controlM2[] and the channels are 0 to 9, try for(int i=0; i<4; ++i) if (firstMux()) arduino.digitalWrite(controlPin[i], (channel>>i)&1); else arduino.digitalWrite(controlM2[i], (channel>>i)&1); or you could say for(int i=0; i<4; ++i) arduino.digitalWrite( firstMux()? controlPin[i] : controlM2[i], (channel>>i)&1); where firstMux() is true for the first mux but not the second – James Waldby - jwpat7 Apr 4 '17 at 21:07
  • Comments are for clarifying the question. Please post answers as answers. – Nick Gammon Apr 9 '17 at 7:27
5

You're using a 16-channel analog multiplexer to talk to your devices (the LED ropes) via a single pin on your Arduino. "Multiplexer" is just a fancy name for a switch; "analog" means that it's switching analog connections, basically, wires that can have any voltage on them, as opposed to digital connections that carry only logic high (usually +5 volts) or low (0 volt) signals.

The first place to start is the datasheet. From the product link, we can see that the chip we're using is a CD74HC4067. Their datasheet links don't work, but searching in Octopart for just "74HC4067" eventally led me to an actual datasheet. These datasheets are the key source for information on the parts you're using and are always worth reading through. Even if you don't understand everything in them, they'll usually have the information you need, and reading them gets easier with practice.

Section 5 has a functional diagram (Fig. 1) which shows the important connections.

  • Z is what you'll hook up to an I/O pin on your Arduino. When the chip is enabled, Z will be connected to one of the Y connections.
  • Y0 through Y15 are what you'll hook up to your individual devices; one (and only one) of these at a time will be connected to Z, connecting the device to your Arduino I/O pin. (The other Y connections will be in a high impedance state.)
  • Ē is the "enable" signal, the bar over top indicating that it's "active low." That is to say, when you ground (0 V) Ē the mux will be active and the selected Y will be connected to Z. When you hold Ē high (5 V) the mux will be inactive and all the Ys and Z will be disconnected (high impedance again).
  • S0 through S3 are the select inputs to the mux; they determine which Y is connected to Z (when the device is active) in a way we'll show below.

Depending on the project, you may not want to switch the connection between the Z and Y pins (by changing S0-S3) while the devices connected to the Ys are active so that you don't accidentally get a a little bit of signal meant for one device going to another. The steps to do this would be as follows:

  1. You start by having Z connected to, say, Y0, with the signal you want to send to Y0 going through Z.
  2. Disable the device by bringing Ē high. Now Y0 (and all other Ys) are disconnected.
  3. Change the value on S0-S3 to select, e.g., Y1.
  4. Change the signal you're sending to Z to the correct signal for Y1.
  5. Bring Ē low to enable the device and now Z will be connected to Y1.

In your project you likely don't need to worry about being this careful with your switching so long as you change S0-S3 and Z as closely together as possible, so I'm going to simplify the code I show here by leaving out the enabling and disabling step; you can just wire Ē to ground for the moment.

The key to the switch is what you send on S0-S3 to select which Y is connected to Z. This input is just a binary number, which you'll need to understand to use it effectively. Following is a short summary, but if you don't understand it completely (or the C code later on), there are plenty of tutorials on the web that can give you further help.

Counting in binary is just like counting in tens except you have only the numbers 0 and 1. So instead of counting 0, 1, 2, ..., 9, 10, 11 you count 0, 1, 10, 11, 100, 101, ... (which represent the numbers 0, 1, 2, 3, 4, 5, ... in decimal). The S0 input on the mux is the least signifcant bit (the one furthest right), the S1 in put the second least significant bit (second from the right) and so on.

So you need to hook up four digital ouput pins from your Arduino to the S0 through S3 inputs on the mux and then manipulate those pins to send the appropriate number to the mux to select your chosen Y that you want to connect to X.

The following program, which counts sequentially from 0000 (0) through 1111 (15) on the four output pins; shows you how to do this. It would be a good idea to build up a small circuit on a breadboard with four LEDs (remember to use a 330 Ω or so resistor in series with the LED) that you can hook to your four pins so you can see visually what your program is doing as you experiment.

We start by declaring some constants for the pins:

// Multiplexer control pins are assumed to be sequentially
// assigned from `mux_base` onward.
//
const int mux_base = 8;
const int mux_len = 4;                  // 2^4 values, 0 through 15
const int mux_max = (1 << mux_len) - 1;

mux_base is the first pin of the four sequental pins you'll hook up to the S0-S3 (or the LEDs); I use pin 8 here but you can use pretty much any pin you like. mux_len is the number of bits in the mux selector input; S0, S1, S2, S3 is 4 bits, so this determines that I'm using pins 8, 9, 10 and 11 on the Arduino to control the mux.

mux_max is purely a calculation (from the previous two constants) of the highest value you can select on the mux; in this case it's 15. The details of how I caculate it are a bit beyond the scope of this answer, but it's worth working out yourself how that C code works from what you learn later in the code and can find out elsewhere on the web.

We need, as usual, to set up our chosen pins as digital outputs; setup_mux() does just that:

void setup_mux() {
    for (int pin = mux_base; pin < mux_base+mux_len; pin++) {
        pinMode(pin, OUTPUT);
        digitalWrite(pin, LOW);
    }
}

We start with the first selector pin and loop through to the last one, for each one setting it to output mode and setting the current value to LOW so that we have known starting conditions.

For the selection of the Y to connect to Z on the multiplexer, it's convenient to have a function that takes an int indicating that Y and sets our selector pins to the correct values for this. Thus, set_mux(value) below. This is probably the most complex part of this answer, and bears detailed study.

void set_mux(int value) {
    for (int offset = 0; offset < mux_len; offset++) {
        int bit = (value >> offset) & 1;
        digitalWrite(mux_base + offset, bit ? HIGH : LOW);
    }
}
  1. The first line loops through our mux_len (4 from above) selector pins; each of these will have to be set to the correct HIGH or LOW value for the desired Y selection.
  2. The second line determines the binary bit from value for the particular selector pin we're setting in this iteration of the loop. We do this by doing a binary shift of the value to the right, and then clearing all bits except the lowest one, leaving us with a final value for bit of 1 or 0. For example, if we pass 14 to the function, which is 1110 in binary, when the offset is 2 in the loop we'll use the >> operator to shift that right two bits, through 0111 to 0011. (With each shift the right bit drops off and a 0 is added at the left.) Doing a logcal AND (the & operator) of 0011 with 0001 keeps only the bits that are set in both, leaving us with 0001. (Only the far right bit is set in both.)
  3. We then need to set the output pin to HIGH if bit is 1 or LOW if bit is 0; the ternary operator (condition ? when-true : when-false) provides a concise way of doing this.

That's our core "library" code, if you will. In this simple example the only setup we need to do is to call setup_mux():

void setup() {
    setup_mux();
}

Our main program, for demonstration, will count upwards in binary on the selector outputs, delaying a half second between each count so you can see your LEDs blink:

int counter = 0;

void loop() {
    set_mux(counter);
    delay(500);
    counter++;
    if (counter > mux_max)
        counter = 0;
}

Once you understand this example, you have everything you need to start using your multiplexer with something more sophisticated than just counting.

One last important thing to emphasize: the mux allows you to use just one signal I/O (plus the four selector outputs) on your Arduino, but that signal I/O is connected to only one device at a time. This won't work if you must always be sending a signal to several devices at the same time, which it appears might be the case from your other question. That said, if there's any time at which you're sending 0 V to a device, that is time you could be spending sending a non-0 V signal to another device.

For example, holding Z at +5 V and cycling the signal outputs through Y0-Y3 quickly will give you what's effectively a +5 V PWM with 25% duty cycle on Y0-Y3. In some cases, this technique can be useful.

Oh, one additional note: this example controls just one multiplexer; you can easily control two with two sets of selector pins, but this starts to use up a lot of pins. Instead, you may wish to have one set of selector pins on the Arduino hooked up to both your muliplexors (and one I/O pin hooked up to both Zs) and use two more Arduino pins to control the Ē on each mux, having only one mux enabled at a time. This will save you three pins, at the cost of some complexity, but it's a good exercise to see if you really understand multiplexing technique since it's a straightforward extension of what you're doing with a single mux.

  • This is such an awesome answer - thank you for your time and input; this helped me a lot. Thank you!!! – narner Apr 10 '17 at 22:56
  • 1
    I'm glad to hear it! The research to do this answer helped me learn, too, so thanks for the question! – Curt J. Sampson Apr 10 '17 at 23:35
  • Note that, if you don't mind direct port access (which is non-portable), set_mux() can be simplified to PORTB = PORTB & ~mux_max | value;. If you don't use pins 12 and 13, it can simplified further to PORTB = value;. – Edgar Bonet Apr 11 '17 at 7:52
  • I put the project and a little explainer on GitHub to show what this type of project can or can't be useful for here: github.com/narner/Analog-LED-Multiplexing-Example – narner Apr 12 '17 at 15:58
4
+50

After reading your github page, I came to realize that:

  1. you do not require full color control, but only setting the strips to full red or full blue
  2. you are using MOSFETs for driving them.

Given these conditions, I believe the project may be feasible with your current setup. The trick is that MOSFETs have some gate capacitance: if you set the gate either HIGH or LOW, and then disconnect it, it will keep that state for some time, until the charge stored in the gate leaks. This means you do not need to address each MOSFET continuously: you can just periodically “refresh” them, and let the gate capacitance “remember” the state between refreshes. This is similar to how DRAM memory works.

I wrote the following code based on this idea. Note that I did not test it, as I do not have the required hardware. It uses a timer interrupt firing every 1024 µs to cycle through the ten used mux channels: each channel gets then refreshed every 10.24 ms. It also uses direct port access instead of digitalWrite(): not only it's way faster (and you want to be fast in an ISR) but, more importantly, it allows changing several outputs simultaneously. This is important in order to avoid glitches.

/*
 * Patterns to send to the multiplexers. bit_patterns[mux][chann] is the
 * pattern to send to the multiplexer `mux' in order to update the
 * channel `chann'. Bits 0..3 hold the channel number, bit 4 is the
 * data. The data bits are all initialized to zero.
 */
volatile uint8_t bit_patterns[2][10] = {
    { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 },
    { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
};

/*
 * Periodically cycle through the 10 used multiplexer channels, updating
 * the MOSFETs. The TIMER0_COMPA interrupt has to be enabled for this to
 * automatically run every 1024 microseconds.
 */
ISR(TIMER0_COMPA_vect)
{
    static uint8_t i;            // channel index
    PORTB = bit_patterns[0][i];  // update MUX 0
    PORTC = bit_patterns[1][i];  // update MUX 1
    if (++i >= 10) i = 0;        // cycle channels
}

void setup()
{
    DDRB   =  0x1f;         // set PB0..PB4 as outputs
    DDRC   =  0x1f;         // set PC0..PC4 as outputs
    TIMSK0 |= _BV(OCIE0A);  // enable TIMER0_COMPA interrupt
}

This code relies on Timer 0 being in the standard Arduino configuration, where it loops every 1024 µs. It also needs a specific wiring, where each multiplexer is connected to a single AVR port, as follows:

Arduino   multiplexers
----------------------
   8      S0 of MUX 0
       ...
  11      S3 of MUX 0
  12      SIG of MUX 0
  A0      S0 of MUX 1
       ...
  A3      S3 of MUX 1
  A4      SIG of MUX 1

In order to change the strip colors, you just have to update the stored bit patterns:

/*
 * On each of PORTB and PORTC, bits 0..3 are wired to the address select
 * pins of a multiplexer, whereas bit 4 goes to the SIG pin of the same
 * multiplexer.
 */
const uint8_t SIG_BIT = 0x10;

/* These are the only available colors. */
typedef enum { BLACK, RED, BLUE, MAGENTA } color_t;

/* Set the color of a strip. Strips are numbered from 0 to 9. */
void set_color(uint8_t strip, color_t color)
{
    /* Find the relevant multiplexer and channels. */
    uint8_t mux, red_ch, blue_ch;
    if (strip < 5) {
        mux = 0;
        red_ch = 2 * strip;
    } else {
        mux = 1;
        red_ch = 2 * (strip - 5);
    }
    blue_ch = red_ch + 1;

    /* Update the bit patterns. */
    if (color == RED || color == MAGENTA)
        bit_patterns[mux][red_ch] |= SIG_BIT;
    else
        bit_patterns[mux][red_ch] &= ~SIG_BIT;
    if (color == BLUE || color == MAGENTA)
        bit_patterns[mux][blue_ch] |= SIG_BIT;
    else
        bit_patterns[mux][blue_ch] &= ~SIG_BIT;
}

The function set_color() assumes the following connections between the multiplexers and the strips:

MUX  channel    strip  color
----------------------------
 0      C0        0     red
 0      C1        0     blue
 0      C2        1     red
 0      C3        1     blue
            ...
 0      C8        4     red
 0      C9        4     blue
 1      C0        5     red
 1      C1        5     blue
            ...
 1      C8        9     red
 1      C9        9     blue

Hardware considerations

All this scheme may or may not work depending on some hardware factors such as the gate capacitance of your MOSFETs, the leakage current of the multiplexers when in high-impedance and the timing characteristics of the same multiplexers. If the gate capacitance is too weak to hold the value for 9 ms, you may want to add capacitors in parallel, maybe something in the 100 pF range. If you add more than that, you may also want to put resistors in series with pins 12 and A4 of the Arduino, in order to protect them from the inrush current. But then you may also want to slow down the scanning of the channels, which would require configuring a timer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.