16 Buttons with 2x 74HC4051, print only status change

Hello there,

I am starting to build a Arduino (or Teensy) based MIDI Controller. Before getting into the MIDI-area my first goal is to read out 16 Buttons and print a status change on the serial monitor. (To keep it simple, I started with only 8)

Thanks to @MicroBahner the first sketch without multiplexing works pretty fine, exactly the way I need it:

#include <Bounce2.h>

const byte buttonPins[] = { 4, 5, 6, 7, 8, 9, 10, 11 };
const byte buttonCnt = sizeof(buttonPins);
Bounce button[buttonCnt] ; 

void setup() {
  Serial.begin(9600);
  for ( byte i = 0; i < buttonCnt; i++ ) {
    button[i].attach( buttonPins[i], INPUT_PULLUP );
    button[i].interval(5);   // debounce time
  }
}

void loop() {
  for ( byte i = 0; i < buttonCnt; i++ )   button[i].update();

  for ( byte i = 0; i < buttonCnt; i++ ) {
    if (button[i].fallingEdge()) {
      Serial.print("Button "); Serial.print(i); Serial.println(" was pressed");
    }
    if (button[i].risingEdge()) {
      Serial.print("Button "); Serial.print(i); Serial.println(" was released");
    }
  }
}

But now, as I am trying to include a 74HC4051 multiplexer to reduce the involved pins (wiring see below), I need some help to translate the following code (taken from one of the examples from the "control surface" library:

/**
 * Written by Pieter P, 2019-08-08   
 * https://github.com/tttapa/Arduino-Helpers
 */

#include <Arduino_Helpers.h> // Include the Arduino Helpers library
#include <AH/Hardware/ExtendedInputOutput/AnalogMultiplex.hpp>

CD74HC4051 mux = {
   3,
   {0, 1, 2},
 };

void setup() {
  Serial.begin(115200);
  mux.begin();                  // Initialize multiplexer
  mux.pinMode(0, INPUT_PULLUP); // Set the pin mode (setting it for one pin of
                                // the multiplexers sets it for all of them)
}

void loop() {
  for (pin_t pin = 0; pin < mux.getLength(); ++pin) {
    Serial.print(mux.digitalRead(pin));
    Serial.print('\t');
  }
  Serial.println();
}

In this sketch, the serial monitor constantly prints all the button states. Can anyone show me how to change this, so it only prints a corresponding message, if the status of a button is changed? (Like in the first code example)

Thanks a lot
Philip

See the StateChangeDetection example in the IDE and put the previous states in an array for comparison with the current state then print a message if one of them changes

Thanks @UKHeliBob - I'll have a look at that example. Not sure if this may already exceed my coding skills..

With multiplexing many buttons the MoToButtons class of the MobaTools library may be worth a try. This class can manage up to 32 buttons in one instance. The 'raw state' of the buttons can be read by means of a callback function. This allows for arbitrary key arrangements. Even a matrix via I2C would be possible. Of course reading via HC4051 is also no problem.
Because the complete MobaTools library doesn't run on teensy yet, you have to copy the MoToButtons.h file into your sketch directory and include it from there. MoToButtons does not need any HW specific items and will therefor run on teensy too. MotoButtons.h contains the complete class definition, so you need nothing else.

Thanks @MicroBahner , you guys and this place are simply amazing. I'll try this

Crude but effective:

  unsigned statusNow = 0;
  static unsigned statusLastTime; // a place to save bits

  for (pin_t pin = 0; pin < mux.getLength(); ++pin) {
    Serial.print(mux.digitalRead(pin));
    bitWrite(statusNow, pin, digitalRead(pin)); // <- added
    Serial.print('\t');
  }
  // after collecting all the bits, compare them to the previous state
  if (statusLastTime != statusNow) {
    // something changed, do stuff
    statusLastTime = statusNow; // reset the status memory
  }

Do you have a pullup resistor in there somewhere so that a button press to Gnd can be distinguished from a high input?

I personally would have used 2 parallel in/serial out shift registers, and just read in the 2 bytes using SPI.transfer(), way faster than multiplexing thru 16 channels.
https://www.digikey.com/en/products/detail/texas-instruments/SN74HC165N/376966

digitalWrite (loadPin, LOW);
digitalWrite (loadPin, HIGH); // latch the data at the inputs
SPI.transfer(byte1);
SPI.transfer (byte2);

Have pullups on each input so they read high when button is not pressed.

if ((byte1==0xff) && (byte2 ==0xff))
{
// no button was pressed
buttonPressed = 0;
}
else {
buttonPressed = 1;
}
if (buttonPressed == 1){
// do something with the data
...
}

Yes, that would be much more effektive. And if you use MotoButtons, you get the data perfect bit coded for direct transfer to Motobuttons.

I started with an Arduino Uno and moved now to Teensy 4.0 for the next steps - due to the easy USB MIDI handling. That has internal pullups

Thanks again everyone - I might need a moment though to go through all your kind suggestions...

Control Surface comes with a Button class that does roughly the same as the Bounce library, and it supports multiplexers out of the box:

#include <Control_Surface.h>

CD74HC4051 mux {
  3,
  {0, 1, 2},
};

// Convert the array of pins of the multiplexer to an 
// array of debounced button objects
Array<Button, 8> buttons = copyAs<Button>(mux.pins());

void setup() {
  Serial.begin(115200);
  mux.begin();
  for (Button &button : buttons)
    button.begin();
}

void loop() {
  uint8_t i = 0;
  for (Button &button : buttons) {
    switch (button.update()) {
      case Button::Pressed: break;
      case Button::Released: break;
      case Button::Falling: Serial << "Button " << i << " was pressed" << endl; break;
      case Button::Rising: Serial << "Button " << i << " was released" << endl; break;
    }
    ++i;
  }
}

However, none of this is necessary if you just want some buttons that send MIDI messages when pressed/released:

#include <Control_Surface.h>

USBMIDI_Interface midi;

CD74HC4051 mux {
  3,
  {0, 1, 2},
};

NoteButton midibuttons[] {
  {mux.pin(0), 0}, // pin, MIDI address (note number, in this example)
  {mux.pin(1), 1},
  {mux.pin(2), 2},
  {mux.pin(3), 3},
  {mux.pin(4), 4},
  {mux.pin(5), 5},
  {mux.pin(6), 6},
  {mux.pin(7), 7},
};

void setup() {
  Control_Surface.begin();
}

void loop() {
  Control_Surface.loop();
}
1 Like

Hi, @PieterP !
Yep, this is what I asked for, so I'll mark this thread as solved. However, actually my goal is HUI protocol. So instead of sending one MIDI note per pressing / releasing a button, I'd like to send two cc messages for each single action. As I am still a beginner, I need some time to figure that out..

Right before I saw your kind response I was skipping through the control surface examples.. quite extensive

Thanks again

In that case, you'll probably want to look at these examples:

And you probably already came across this page, but I'll link to it again just in case: I FREAKIN FOUND THE HUI DOCS!!!!! - Cockos Incorporated Forums

Yes indeed - which was the exact moment when I ordered my ArduinoUno starter kit :laughing: and there we are. Still impressed and optimistic. Will have a close look at your examples

Hi again,

after browsing through the various examples from @PieterP 's Control Surface library I now stuck with the one called "Toggle-LEDs", because shift registers for LEDs were prepared (next steps..) But somehow I can't get it to send cc messages for pressing and releasing the buttons. I assume there is something wrong in the if statement? (see below) Help is very much appreciated.

Both statements on their own ("Falling", "Rising") seem to give the right results, but I need both...

Sincerely, Philip

/**
   Most of this sketch taken from Example "Toggle-LEDs" by PieterP, 2018-08-28
*/

#include <Arduino_Helpers.h> // Include the Arduino Helpers library.

#include <AH/Containers/ArrayHelpers.hpp>
#include <AH/Hardware/Button.hpp>
#include <AH/Hardware/ExtendedInputOutput/AnalogMultiplex.hpp>
#include <AH/Hardware/ExtendedInputOutput/SPIShiftRegisterOut.hpp>
const int channel = 1;
const int port = 2;

CD74HC4051 mux = {
  3,
  {0, 1, 2},
};

SPIShiftRegisterOut<mux.length()> sreg = {SS, MSBFIRST};

auto buttons = generateIncrementalArray<Button, mux.length()>(mux.pin(0));

void setup() {
  mux.begin();
  sreg.begin();
  for (Button &button : buttons)
    button.begin();
}

void loop() {
  for (uint8_t i = 0; i < mux.length(); ++i) {
    if (buttons[i].update() == Button::Falling) {
      usbMIDI.sendControlChange(0x0F, i, channel);
      usbMIDI.sendControlChange(0x2F, (0x40 + port), channel);
    }
    if (buttons[i].update() == Button::Rising) {
      usbMIDI.sendControlChange(0x0F, i, channel);
      usbMIDI.sendControlChange(0x2F, port, channel);
    }
  }
}

You're calling buttons[i].update() twice, so you're losing edges. Call Button::update() once, then use Button::getState() to get the state without updating it first.

Thanks @PieterP again, the 16 buttons are successfully sending now. I'll move over to the LEDs, maybe with new help requests. On a different thread though.

Thanks again also to everyone who contributed