Debouncing multiplexed buttons

Hi there

I’m trying to debounce a group of buttons that are connected using a 4051 multiplexer. I tried using the Bounce library, but since ir relies on digitalRead() it seems like a no-go.

Here’s what I have so far:

byte MIDIChannel = 1;
const byte selPin[] = {2, 3, 4}; //Array of pins to be assigned to buttons
const byte btnCount = 8; //Number of buttons
const byte debounce = 5; //Debounce time
const byte analogPin = A0;
byte btnReading;
int muxChannel;
int previousstate[8];
int currentstate[8];
int btn[8]; 

int readMux(int muxChannel)
{
   // set the selector pins HIGH and LOW to match the binary value of channel
   for(int bit = 0; bit < 3; bit++)
   {
      int pin = selPin[bit]; // the pin wired to the multiplexer select bit
      int isBitSet =  bitRead(muxChannel, bit); // true if given bit set in channel
      digitalWrite(pin, isBitSet);
      //sets high/low threshold
      if (analogRead(analogPin) < 512) {
        btnReading = LOW;
      } else {  
        btnReading = HIGH;
      }
      
   }
   return btnReading;
}  
  
void setup() {
  
  pinMode(13, OUTPUT);
  digitalWrite(13, HIGH);
  
  for (byte thisPin = 0; thisPin < 3; thisPin++)  { //count through array and assign and name the pins
    pinMode(selPin[thisPin], OUTPUT);
  }
  
}
elapsedMillis msec = 0;
elapsedMillis bounceInterval = debounce;

void loop() {
  // set debounce time to 5ms, 
  if (bounceInterval = 5) {
  bounceInterval = 0;  
    for (byte i=0; i < 8; i++){
       currentstate[i] = readMux(i);
       if (currentstate[i] == previousstate[i]) {
         btn[i] = 1;     
       }
       previousstate[i] == currentstate[i];
    }
  
    if (msec >= 10) {   // Limit MIDI messages to 100 per second
      msec = 0;
      for (byte i = 0; i < btnCount; i++) {
        if (btn[i] == 1 && currentstate[i] != previousstate[i]) {
          usbMIDI.sendNoteOn(60 + i, 127, MIDIChannel);
        } 
        if (btn[i] == 0 && currentstate[i] != previousstate[i]) {
          usbMIDI.sendNoteOff(60 + i, 0, MIDIChannel);
        } 
      }
    }
  }
}

What I want to do is send a note on message on the rising edge, and a note off message on the falling edge, with no messages in between. I did try to modify the Bounce library, but I’m pretty inexperienced with this stuff.

Any help would be greatly appreciated.

I have these macros for general debouncing end edge detection:

//macro for detection of raising edge and debouncing
#define DRE(signal, state) (state=(state<<1)|(signal&1)&15)==7

//macro for detection of falling edge and debouncing
#define DFE(signal, state) (state=(state<<1)|(signal&1)&15)==8

You can use it your code like this (I had to change the usbMidi to normal midi in order to make it compile, also it only compiled, not tested):

#include <MIDI.h>

//macro for detection of raising edge and debouncing
#define DRE(signal, state) (state=(state<<1)|(signal&1)&15)==7

//macro for detection of falling edge and debouncing
#define DFE(signal, state) (state=(state<<1)|(signal&1)&15)==8

byte MIDIChannel = 1;
const byte selPin[] = {2, 3, 4}; //Array of pins to be assigned to buttons
const byte btnCount = 8; //Number of buttons
const byte analogPin = A0;
byte btnReading;
int muxChannel;
int btnState[btnCount];

int readMux(int muxChannel)
{
  // set the selector pins HIGH and LOW to match the binary value of channel
  for(int bit = 0; bit < 3; bit++){
    int pin = selPin[bit]; // the pin wired to the multiplexer select bit
    int isBitSet =  bitRead(muxChannel, bit); // true if given bit set in channel
    digitalWrite(pin, isBitSet);
    //sets high/low threshold
    if (analogRead(analogPin) < 512) btnReading = LOW;
    else btnReading = HIGH;
  }//for(bit)
  return btnReading;
}  
  
void setup(){
  pinMode(13, OUTPUT);
  MIDI.begin();
  digitalWrite(13, HIGH);
  for (byte thisPin = 0; thisPin < 3; thisPin++)  { //count through array and assign and name the pins
    pinMode(selPin[thisPin], OUTPUT);
  }//for(thisPin)
}//setup()

void loop() {
  for (byte i = 0; i < btnCount; i++){
    if(DRE(readMux(i), btnState[i])) MIDI.sendNoteOn(60 + i, 127, MIDIChannel);
    if(DFE(readMux(i), btnState[i])) MIDI.sendNoteOff(60 + i, 0, MIDIChannel);
  }//for(i) 
}//loop()

Thanks for your reply.

I checked the code and t partially works. Note on messages are sent through once, but unless you press another button you can't repeatedly press the button and send the same message. Note off messages aren't getting through at all, so I suspect that that's related.

I've tried to fix the problem myself, but I'm a little unclear on how the code works. I know it involves bit shifting, but I can't wrap my head around what those macros are actually doing.

Ok, i'll have a look.. since i dont have your mux i wasn't able to test the code.

The macros work like this: the state argument(which must be a variable) records the current and the last 3 reads by shifting one bit to the left at each read and bitwise anding with 15 (=0b1111).

  • If the value is 7(=0b0111) we have one raising edge followed by 3 consecutive 1's. That would qualify as a debounced raising edge
  • If the value is 8(=0b1000) we have one falling edge followed by 3 consecutive 0's. That would qualify as a debounced falling edge

.

Found it.
There was a set of parenthesis missing for some reason… sorry bout that

try these versions:

//macro for detection of raising edge and debouncing
#define DRE(signal, state) (state=((state<<1)|(signal&1))&15)==7

//macro for detection of falling edge and debouncing
#define DFE(signal, state) (state=((state<<1)|(signal&1))&15)==8

Those extra parentheses don’t seem to have done much. I’ve swapped the DFE and DRE calls around and now all the thing sends is noteOff messages, so there might be something odd going on with the execution order. Occasiaonlly a noteOn message gets through, but I can’t find any pattern to it.

Here’s my code:

//macro for detection of raising edge and debouncing
#define DRE(signal, state) (state=((state<<1)|(signal&1))&15)==8

//macro for detection of falling edge and debouncing
#define DFE(signal, state) (state=((state<<1)|(signal&1))&15)==7

byte MIDIChannel = 1;
const byte selPin[] = {2, 3, 4}; //Array of pins to be assigned to buttons
const byte btnCount = 8; //Number of buttons
const byte analogPin = A0;
int btnReading;
int muxChannel;
int btnState[btnCount];

byte readMux(byte muxChannel)
{
  // set the selector pins HIGH and LOW to match the binary value of channel
  for(int bit = 0; bit < 3; bit++){
    int pin = selPin[bit]; // the pin wired to the multiplexer select bit
    int isBitSet =  bitRead(muxChannel, bit); // true if given bit set in channel
    digitalWrite(pin, isBitSet);
    //sets high/low threshold
    if (analogRead(analogPin) < 700) btnReading = LOW;
    else btnReading = HIGH;
  }//for(bit)
  return btnReading;
}  
  
void setup() {
  
  pinMode(13, OUTPUT);
  digitalWrite(13, HIGH);  
  for (byte thisPin = 0; thisPin < 3; thisPin++)  { //count through array and assign and name the pins
    pinMode(selPin[thisPin], OUTPUT);
  }  
}

void loop() {
    for (byte i = 0; i < btnCount; i++){     
            
      if(DFE(readMux(i), btnState[i])) {
        usbMIDI.sendNoteOff(60 + i, 0, MIDIChannel);
      }
      
      if(DRE(readMux(i), btnState[i])) {
        usbMIDI.sendNoteOn(60 + i, 127, MIDIChannel);
      }
      
  }//for(i)
    // MIDI Controllers should discard incoming MIDI messages.
  while (usbMIDI.read()) {
    // ignore incoming messages
  }
}//loop()

Thanks for your explanation of the code by the way, that’s a really clever way of doing it.

My usual way of reading multiplexed buttons is to poll them at regular intervals (every few milliseconds) using either a task scheduler, a tick interrupt, or a poll function called from various places in the main loop (and the poll function does nothing if insufficient time has elapsed since the last call). I use a class to track the state of the push button. You can find the code at https://github.com/dc42/arduino.

I'm not sure if this will help you, but there's no reason why you can't digitalRead() from an input hooked up to a 4051 mux. It's an analog mux, but digital input is just a subset of analog input--you compare the voltage to a threshold and return HIGH or LOW, vs. reading the actual voltage with an ADC--so you can always run digital inputs through it if you want to. It might technically be better to use a shift register, but if a 4051 is what you've got on hand, you can use it.

What makes the buttons go High or Low? You have pullup resistors on the 4051 inputs., and a switch to gnd?

CrossRoads: What makes the buttons go High or Low? You have pullup resistors on the 4051 inputs., and a switch to gnd?

I've got pull down resistors on all of the buttons. The signal is relatively noisy on the analog pin, but defining a threshold of 512 gives me more than enough headroom.

joshuabardwell: I'm not sure if this will help you, but there's no reason why you can't digitalRead() from an input hooked up to a 4051 mux. It's an analog mux, but digital input is just a subset of analog input--you compare the voltage to a threshold and return HIGH or LOW, vs. reading the actual voltage with an ADC--so you can always run digital inputs through it if you want to. It might technically be better to use a shift register, but if a 4051 is what you've got on hand, you can use it.

I tried using digitalRead() but I couldn't get it working, even without a debounce. I've used an analogue pin and a 4051 because I'd like the option of mixing buttons and potentiometers on the same multiplexer. At this point I'm just testing different configurations of buttons and pots, I haven't settled on a final design.

dc42: My usual way of reading multiplexed buttons is to poll them at regular intervals (every few milliseconds) using either a task scheduler, a tick interrupt, or a poll function called from various places in the main loop (and the poll function does nothing if insufficient time has elapsed since the last call). I use a class to track the state of the push button. You can find the code at https://github.com/dc42/arduino.

Thanks for the link. The poll function seems like the best fit for this problem, I'll look into it and see what I can do.

Of course it has to with execution!!
Try this:

void loop() {
  for (byte i = 0; i < btnCount; i++){
    if(DRE(readMux(i), btnState[i])) MIDI.sendNoteOn(60 + i, 127, MIDIChannel);
    if(btnState[i] ==8) MIDI.sendNoteOff(60 + i, 0, MIDIChannel);
  }//for(i) 
}//loop()

nilton61:
Of course it has to with execution!!
Try this:

void loop() {

for (byte i = 0; i < btnCount; i++){
    if(DRE(readMux(i), btnState[i])) MIDI.sendNoteOn(60 + i, 127, MIDIChannel);
    if(btnState[i] ==8) MIDI.sendNoteOff(60 + i, 0, MIDIChannel);
  }//for(i)
}//loop()

Yup, that did the trick! Thanks so much for all the help and advice guys :slight_smile:

nilton61: The macros work like this: the state argument(which must be a variable) records the current and the last 3 reads by shifting one bit to the left at each read and bitwise anding with 15 (=0b1111).

  • If the value is 7(=0b0111) we have one raising edge followed by 3 consecutive 1's. That would qualify as a debounced raising edge
  • If the value is 8(=0b1000) we have one falling edge followed by 3 consecutive 0's. That would qualify as a debounced falling edge

.

Just hafta say -- that's pretty clever. :D

Thank you kindly. I just rewrote them to be even more general:

#define DEBOUNCE 4
#define DMASK ((1<<DEBOUNCE)-1)
#define DF (1<<(DEBOUNCE-1))
#define DR (DMASK-DF)

//macro for detection of raising edge and debouncing
#define DRE(signal, state) ((state=((state<<1)|(signal&1))&DMASK)==DR)

//macro for detection of falling edge and debouncing
#define DFE(signal, state) ((state=((state<<1)|(signal&1))&DMASK)==DF)

DEBOUNCE sets the number of reads required to get an edge (>=2)
If you are checking for both raising and falling edges the code should be.

if(DRE(digitalRead(pin), pinState)) //do something on raising edge
if(pinState == DF)//do soemthing on falling edge(same read)

I’ve just tried to modify the sketch to use a digital pin instead of an analogue one and I’ve hit a weird problem.

When pressed, button 1 is sending continuous note on data for button 5 and continuous note off date for itself. When released it sends a single note off message for button 5. Button 5 is doing the same, but in reverse.

I’ve tried it with multiple pins and it is the same no matter what I use. Colour me very confused indeed! Just as a sanity check, could someone please check my code to make sure I’ve not made some stupid mistake somewhere? I know it’s probably not worth fixing if I can just use an analogue pin, but I’m genuinely interested in the cause of this bug.

//macro for detection of raising edge and debouncing
#define DRE(signal, state) (state=((state<<1)|(signal&1))&15)==7

//macro for detection of falling edge and debouncing
#define DFE(signal, state) (state=((state<<1)|(signal&1))&15)==8

byte MIDIChannel = 1;
const byte selPin[] = {2, 3, 4}; //Array of pins to be assigned to buttons
const byte btnCount = 8; //Number of buttons
const byte muxPin = A0;
int btnReading;
int muxChannel;
int btnState[btnCount];

byte readMux(byte muxChannel)
{
  // set the selector pins HIGH and LOW to match the binary value of channel
  for(int bit = 0; bit < 3; bit++){
    int pin = selPin[bit]; // the pin wired to the multiplexer select bit
    int isBitSet =  bitRead(muxChannel, bit); // true if given bit set in channel
    digitalWrite(pin, isBitSet);
    //sets high/low threshold
    if (digitalRead(muxPin) == HIGH) btnReading = LOW;
    else btnReading = HIGH;
  }//for(bit)
  return btnReading;
}  
  
void setup() {
  
  Serial.begin(9600);
  pinMode(13, OUTPUT); 
  for (byte thisPin = 0; thisPin < 3; thisPin++)  { //count through array and assign and name the pins
    pinMode(A0, INPUT);
    pinMode(selPin[thisPin], OUTPUT);
  }  
}

void loop() {
    for (byte i = 0; i < btnCount; i++){     
            
      if(DRE(readMux(i), btnState[i])) {
        usbMIDI.sendNoteOn(60 + i, 127, MIDIChannel);
      }
      
      if(btnState[i] ==8) {
        usbMIDI.sendNoteOff(60 + i, 0, MIDIChannel);
      }
      
  }//for(i)
    // MIDI Controllers should discard incoming MIDI messages.
  while (usbMIDI.read()) {
    // ignore incoming messages
  }
}//loop()

Can you post the schematic of your multiplexer?

Here’s a schematic of the circuit.

Is that really correct? It looks like you are shorting the supply when pushing a button…
If you cut where indicated it will make more sense (i hope that is what you meant…)

Oops! Yes the schematic is wrong, there is no connection from VCC to the button. I modified the circuit to use pull ups instead of pull downs and I didn't properly modify the schematic.

If you use pullups you will have to connect the 10K resistor to ground instead of Vcc. (no offense, just want to make sure) I would skip the 100R resistors, i cant see what use they have. The 4051 has a Ron of about 300 Ohms (or so) anyway.