Emulating a 3-bit multiplex on an Arduino

I am experimenting a bit on a project using the Arduino Leonardo/ATMEGA32U4 (midi control surface) and I need to connect 8 buttons to the Leonardo. However I am out of pins and I do not have any multiplexers around (and it takes some time to get some where I am right now).

However, I have many Arduino Nano's/ATMEGA328P lying around. So I was thinking that I could use one of these to temporarily emulate a 3-bit multiplexer until I get the real deal. I wrote a simple code

// Emulating a multiplexer 

// Button pins
const byte inputNum = 8;
const byte inputPins[] = {5, 6, 7, 8, 9, 10, 11, 12};

// Address pins
const byte outputNum = 3;
const byte addressPins[] = {A0, A1, A2};

// Mux output
const byte outputPin = A3;

byte address;
bool value;



void setup() {
  for( byte i = 0; i<inputNum; i++){
    pinMode(inputPins[i], INPUT);
  }

  for( byte i = 0; i<outputNum; i++){
    pinMode(addressPins[i], INPUT);
  }

  pinMode(outputPin, OUTPUT);
}




void loop() {
  // Read address
  bool a0 = digitalRead(addressPins[0]);
  bool a1 = digitalRead(addressPins[1]);
  bool a2 = digitalRead(addressPins[2]);

  // Convert to byte
  address = a0 + (a1 << 1) + (a2 << 2);

  // Read input pin = address
  value = digitalRead(inputPins[address]);

  digitalWrite(outputPin, value);
}

The Leonardo will then detect the buttons pushed by using the Nano as a multiplexer. There is however a problem, when a button is pushed, the Leonardo detects several buttons as being pushed.

Doing a bit of debugging, the problem seems to be what I feared from the get-go. Since both run at 16 MHz, the Nano might be too slow. Imagine that only button 000 has been pushed, this line is HIGH but all other lines LOW. When Leonardo scans it starts with 000, and Nano outputs HIGH, then Leonardo goes on to 001, but Nano is still outputting HIGH until it can read 001 button and then output LOW. But by this time Leonardo might have already read HIGH and gone on to 002.

I have a ESP32 and a FPGA (50MHz) that I could use as my temporary multiplexer, but they run at 3.3v and 1.2v logic, respectively, and I cannot interface them with Leonardo without a level converter (which I don't have).

Is there any easy solution to this issue?

Thanks for reading.

You can either add a delay on your master MC code after it sets address. Or implement some kind of handshaking using additional pin(s). However if you already have a bunch of nanos you can use one of them as a button processor (detection, de-bouncing etc). Then just send key code to your leonardo.

The only obvious thing I can see with your code is the button inputs are not defined as INPUT_PULLUP. Assuming they are normally open buttons wired between the pins and 0V then they need pull up resistors, otherwise you will get all sorts of random stuff.

Doing a bit of debugging, the problem seems to be what I feared from the start. Since both run at 16 MHz, the Nano might be too slow.

The Nano is not too slow, you are looking at this from the wrong end. What matters is how fast you need to scan the buttons, which is a snails pace compared to the speed of the Nano. Without checking I'd say a button press is going to be anything from 50ms to several seconds, so you need to scan the buttons every, say, 20ms. For 8 multiplexed buttons that is one button every 2.5ms. So, you need to arrange your scanning code to change the address output every 2.5ms, but as millis() does not give you half millisecond intervals maybe go with once every 2ms. However, to be really sure you need to change the address, wait, then check the result, so every even millisecond I suggest you change the address and every odd millisecond you read the pin state back.

Probably be OK if you went slower than that TBH, but the way to find out is to play.

Me I'd forget the multiplexing and just read the buttons and send the result over I2C or serial or whatever is available. 8 buttons, so that's 3 inputs for the button and 1 input to indicate that it's pressed.

Thanks a lot for the responses!

  1. I don't want to delay the master MC as the scanning of the buttons are done by a library (control surface), and I would have to hack it to make it work for this temporary situation. And I also want to avoid adding extra structure like handshaking or transfer information over I2C, as in the end I want to replace the arduino nanos with real multiplexers when they arrive.

  2. I am connecting the inputs to ground through an external pulldown resister, and the input goes high when I click the button.

But I just wanted to report that I found a solution for my above problem. The thing is that Arduino's digitalWrite and digitalRead functions are extremely slow and by instead using direct port manipulations one can make the multiplexer emulation many many times faster (wish I had an oscilloscope to check).
Therefore I replaced the code above with this much shorter and much faster (though more cryptic) code

// Emulating a multiplexer 
void setup() {
  cli();                      // disable all interrupts
                              // To avoid strange behavior at pin PD0/D0/Rx
  DDRD = B00000000;            // PortD (D0-D7) --> inputs, no pullup resistor
  DDRC = B00000000;            // Pin PC0-PC2 (A0-A2) --> Address inputs
  DDRB = B00000001;            // Pin PB0 (D8) ---> output
  PORTB = 0;      
}


void loop() {
  PORTB = PIND >> (PINC & B00000111);
}

The above code however requires that I put all buttons on port D (digital pins 0 to 7), but that is no problem for the "multiplexer emulator". (One just needs to disconnect the wires from pin 0 and pin 1 while uploading the program.)

Thanks for the update, glad you got it working.

4tnemele:
The thing is that Arduino's digitalWrite and digitalRead functions are extremely slow and by instead using direct port manipulations one can make the multiplexer emulation many many times faster (wish I had an oscilloscope to check).

For the record, pulsing a pin (digitalWrite high then low) creates a 3.36µs wide pulse while accessing the PORT register directly creates a pulse 0.125µs wide, 26 times faster.