multiple buttons with lots of functions question

ok so i'm working on a project where i want 4 foot switches and need them to have more then 4 functions.

what i need

button 1 normal press = func1
button 2 normal press = func2
button 3 normal press = func3
button 4 normal press = func4

button 1 long hold = func5
button 2 long hold = func6
button 3 long hold = func7
button 4 long hold = func8

buttons 1and2 pressed together = func9
buttons 2and3 pressed together = func10
buttons 3and4 pressed together = func11

ok so i'm pretty sure i have the normal press and long hold figured out. i'm really struggling how to do the pressed together. i really cant figure out the debounce delay part.

i had an idea to use an analog input for all 4 buttons. the thinking is that i should be able to get a different analog read when 2 buttons are pressed together. again i will need to some how record the value of that analog read for a short period of time to make sure i capture both buttons pressed. anyone have some suggestions for me? i'm a noob so some sort of code, so i can see would help alot.

thanks

digitalread is what you need for buttons, not analogread.

for two (or more) buttons pressed together, just detect the first button press (edge detection), start a timer of say 50ms, and after the timer expires, read what other buttons are also pressed (level detection).

See this for edge detection:

and for level detection, just do (for button pins 2,3,4,etc):

if (digitalRead(buttonPin2) == HIGH)

and then post ALL your code and we can make suggestions for you.

i'm sorry about that, when i said analog read i meant analog read. my thinking is to use one analog pin for all 4 buttons.

An issue is that when a pedal is pressed, you can't know at the time it is pressed whether the operator is going to hold it, or maybe going to press another pedal.

There are two ways to address this.

First, you can introduce a delay before you do the normal press function to give the operator a chance to do the two-buttton press.

The other way is to always do the normal press function, but to design your interface in such a way that this is always ok. This is how mouse clicks work. A single click selects, and a double click does an action, and it's always ok for the select to happen before an action does.

Oh, a third way is to have a special pedal that works like a shift key, or an auto-holding shift key. you have functions a, b, c, d, and another pedal which you press first to make the pedals go A, B, C, D (uppercase). This is called having an interface that is modal. It can be helpful to have an indicator LED so that the operator knows what's going to happen when they press the b pedal - 'b' or 'B'.

It might be helpful to get away from the arduino and to work it out on paper with some sort of diagram. What you are trying to do isn't tricky because arduino, it's tricky because its tricky no matter what you are actually programming on.

You can't use analogRead to detect multiple buttons. Look at the schematic in the first reply in arduino - What resistors to use to read several buttons with a single analog pin - Electrical Engineering Stack Exchange.

If you press e.g. the 'right' button, the voltage on the analog input will always be 0V, regardless if other buttons are pressed or not.

janicefamily:
i'm sorry about that, when i said analog read i meant analog read. my thinking is to use one analog pin for all 4 buttons.

Yeah, that's not going to work.

arduinodlb:
Yeah, that's not going to work.

It can work with a set of differently-valued resistors for each button. But with four buttons, 16 possible values, you need better than 6% tolerance to be able to read what combination of switches is pressed.

sterretje:
You can't use analogRead to detect multiple buttons.

I think you can, but not with the StackExchange diagram.

If you arrange the switches so they bypass a resistor (rather than connecting to GND) the effect of pressing a button is to change the resistance in the low leg of the voltage divider. If you press 2 buttons it changes the resistance to a different value. The trick (which takes a bit of care) is to pick resistor values so that no two options give the same result.

However I think the whole affair will get very messy if the OP tries to combine different button timings with a series of analogRead()s for multiple buttons - not impossible, just complex.

With 4 buttons there are 16 options without needing to use different lengths of button-press. Or one button could be used as a"shift" button.

...R

thank you for all the responses, i have yet to work out the resistor values, but there will only be 7 different button presses, it mite be good for you to know that this whole thing is a midi foot controller. so it will be really hard to lets say press button 1 and button 3 at the same time with one foot. because of that the shift thing is out of the question. it needs to work with using one foot.

but back to my original post, is there a way to "record" a analog pin for lets say 50 ms and then send the most constant # to a button int.

i will try to break this down,

lets say button 1 reads (100)

lets say button 2 reads (300 )

and lets say both 1 and 2 read (2oo)

so lets say i try and press both together and i get the reading of 100 or 300 first real quick then it goes to the 200 that i want. what would be the best way to get that 200? just a sort delay right after the the analog read? or some sort record button state? can i do the record thing? if so how would i go about it?

as for the people that are saying you cant do it, i'm really confused on why you say that? it seems to me it is totally possible. if all the resistors are in parallel, when 2 are engaged you will get a different reading. i realize i didn't post a schematic of how i'm thinking about wiring it, but please if your going to tell me it cant be done, tell me why so i can weigh my options. i'm not a pro but i know it technically can be done. now if its a good idea we shall see ::slight_smile:

i'm waiting for my foot switches to come in so as soon as they do ill start messing around with it!

janicefamily:
just a sort delay right after the the analog read?

Just to be clear, forget completely about the delay() function. It will have no role in what you want to achieve. use millis() to manage timing as illustrated in Serial Input Basics

I think there are a few issues you need to take into account.

The scenario you presented (in which B1 is pressed slightly before B2 but you want to know if both are pressed) requires your code to wait a while after first detecting a button to allow time for another button to be pressed. That is straightforward. But you must wait the same time to confirm that a single button is a single button.

If, in addition to that, you want to distinguish short presses from long presses I think you will begin to see the complexity.

You need to be sure that there will ALWAYS be a period of no buttons pressed in between button presses in order to know when to start the clock on the period for checking for two buttons.

You have not said why you want all the buttons on one analog pin? Why not use digital I/O? You can use the analaog pins for digital I/O if you are short of pins.

...R

janicefamily:
i'm waiting for my foot switches to come in so as soon as they do ill start messing around with it!

So, I implemented a similar thing, using four switches, short and long button presses executing different functions. Unfortunately, if you don't understand pointers, you may have a difficult time absorbing the code.

I worked it out breadboarded as attached, maybe you will find it useful.

I didn't implement the simultaneous presses, that's ratcheting things up a ways from this example.

const byte buttons[] = {6, 7, 8, 9};
const byte NUMBER_OF_BUTTONS = sizeof(buttons) / sizeof(buttons[0]);


int lastButtonState[NUMBER_OF_BUTTONS];
boolean condition [NUMBER_OF_BUTTONS];
unsigned long buttonStart[NUMBER_OF_BUTTONS];

void(*myShortFunction[NUMBER_OF_BUTTONS])(void);  // array of pointers to functions

void setup() 
{
  myShortFunction[0] = myShortFunction0;  // assign the function name (below) to the pointer
  myShortFunction[1] = myShortFunction1;
  myShortFunction[2] = myShortFunction2;
  myShortFunction[3] = myShortFunction3;
  
  Serial.begin(9600);

  for (int i = 0; i < NUMBER_OF_BUTTONS ; i++)
  {
    pinMode (buttons[i], INPUT_PULLUP);
  }

  Serial.println("Starting...");
}

void loop() 
{
  for (int i = 0; i < NUMBER_OF_BUTTONS; i++)
  {
    int buttonPress = buttonCheck(i);
    if (buttonPress == 1)
    {
      doSomethingShortWith(i);
      //myShortFunction[i]();        // <<<<< or just call the short function directly like this
    }
    else if (buttonPress == 2)
    {
      doSomethingLongWith(i);
      //myLongFunction[i]();        // <<<<< extending the example here
    }
    else
    {
      // nothing to do here
    }
  }
}
//
int buttonCheck(int buttonNumber) 
{
  int buttonState = digitalRead(buttons[buttonNumber]);
  if (buttonState == LOW && lastButtonState[buttonNumber] == HIGH && (millis() - buttonStart[buttonNumber])  > 50) // a new press not a bounce
  {
    Serial.print("Button ");
    Serial.print(buttonNumber);
    Serial.println(" depressed");
    buttonStart[buttonNumber] = millis();
    condition[buttonNumber] = true;
    lastButtonState[buttonNumber] = buttonState;
    return 0;
  }
  else if ((condition[buttonNumber]) && buttonState == HIGH && lastButtonState[buttonNumber] == LOW)
  {
    if (millis() - buttonStart[buttonNumber] < 1000) //short press
    {
      lastButtonState[buttonNumber] = buttonState;
      condition[buttonNumber] = false;
      return 1;
    }
    else // long press
    {
      lastButtonState[buttonNumber] = buttonState;
      condition[buttonNumber] = false;
      return 2;
    }
  }
  else
  {
    lastButtonState[buttonNumber] = buttonState;
    return 0;
  }
}

void doSomethingShortWith(int buttonNumber)
{
  Serial.print(F("Short Press on: "));
  Serial.println(buttonNumber);
  myShortFunction[buttonNumber]();  // <<<< Run the unique function for the press
}

void doSomethingLongWith(int buttonNumber)
{
  Serial.print(F("Long Press on: "));
  Serial.println(buttonNumber);
  Serial.println();
  //myLongFunction[buttonNumber](); //<<<< you can do the same thing with an array of long press functions
}

void myShortFunction0()  // <<<<< defining every function
{
  Serial.println("short function zero running");
}

void myShortFunction1()
{
  Serial.println("short function one running");
}

void myShortFunction2()
{
  Serial.println("short function two running");
}

void myShortFunction3()
{
  Serial.println("short function three running");
}

janicefamily:
as for the people that are saying you cant do it, i'm really confused on why you say that? it seems to me it is totally possible. if all the resistors are in parallel, when 2 are engaged you will get a different reading. i realize i didn't post a schematic of how i'm thinking about wiring it, but please if your going to tell me it cant be done, tell me why so i can weigh my options. i'm not a pro but i know it technically can be done. now if its a good idea we shall see ::slight_smile:

It's exactly because you haven't posted a schematic that you don't know why it can't be done (at the very least, not in any reliable fashion). There are two basic problems:

  1. analog is well, analog. You won't get the same reading every time. So you need to have enough space for the readings to vary without looking like another value.

  2. 7 different button presses. And you need to be able to tell when any 1 or 2 is pressed. Which means each of the buttons and button combinations needs to present a different value to your pin. That's 7 (single button) + 6 + 5 + 4 + 3 + 2 + 1 (two buttons together) = 28 different values. That means 0.178V per step, if evenly spaced = 3.6% precision, best case.

So, let's assume a voltage divider where the top resistor is shared, and the bottom resistor is different for each switch.

For our first example, let's choose resistors incrementing in a binary fashion. For our test scenario, we'll choose 100 Ohms for the smallest resistor on the button side, giving us 100, 200, 400, 800, 1600, 3200 and 6400 for our resistors. And we'll choose the middle value, 800 ohms for the top resistor to make an even spread, with the middle button being a 50:50 voltage divider. For 5V, that would give us a range of 0.555V to 4.444V for a single button press. But if we press say buttons 1 and 7, the effective resistance is 98.46 ohms, giving us 0.548V, only 0.007V or 0.15% precision difference from our single button press.

But, parallel resistors don't combine like series resistors, so we don't need to increment in a binary fashion (i.e.:, doubling each time). Let's try linear incrementing as a ballpark best-case figure test. So, we'll select lower resistances of 100,200,300,400,500,600,700. And we choose the middle value, 400 ohms, for the upper resistor. working out the resistances, we see that when 1 and 6 are pressed together, we have 85 ohms in the bottom. When 1 and 7 are pressed together, the bottom parallel resistance is 87.5 ohms, giving us a 2.5 ohm difference. The voltage seen at the pin will be 0.876V with 1+6, and 0.897V with 1+7, a 0.4% voltage difference. Even with 1+4 vs 1+5 (more likely to represent a best-case resistance network instead of using the worst case linear version), the difference is 0.833V vs 0.862V = 0.6%.

As said above, why are you trying to do this with just 1 pin?

thank you bulldog for that. and just so every on knows i'm not set on using an analog pin. the real reason why i was thinking about doing it, is because i couldn't figure out how to add in the code to add the 2 buttons at once to the long and sort press code, really just the denounce timing is messing with me. i thought that if i could use the analog pin to tell the program if 1 or 2 buttons are pressed then i could really just use a code like this to handle debouncing and long press.

does anyone have code that will handle sort, long and multiple button presses? its really that darn double press that is my problem. just remember because this is controlled with one foot the shift thing won't work, also because of that i should never run into the button 1 and 3 being pressed at the same time.

but i'm open to using digital pins or analog.

Bulldog i'm not following your math for the 3.6%, are you saying 7 physical buttons? i'm talking about having 4 physical buttons (my first post lays it out)

this is what i was using as a reference when i was trying to use digital pins. just couldn't figure out how to add the 2 button press

void loop() {

//Record roughly the tenths of seconds the button in being held down
while (digitalRead(buttonPin) == LOW ){

delay(100); //if you want more resolution, lower this number
pressLength_milliSeconds = pressLength_milliSeconds + 100;

//display how long button is has been held
Serial.print("ms = ");
Serial.println(pressLength_milliSeconds);

}//close while

//Different if-else conditions are triggered based on the length of the button press
//Start with the longest time option first

//Option 2 - Execute the second option if the button is held for the correct amount of time
if (pressLength_milliSeconds >= optionTwo_milliSeconds){

digitalWrite(ledPin_Option_2, HIGH);

Normal vs long press, these are both to be determined upon release, so as long as the button is held down nothing happens is that correct?

janicefamily:
as for the people that are saying you cant do it, i'm really confused on why you say that? it seems to me it is totally possible. if all the resistors are in parallel, when 2 are engaged you will get a different reading. i realize i didn't post a schematic of how i'm thinking about wiring it, but please if your going to tell me it cant be done, tell me why so i can weigh my options. i'm not a pro but i know it technically can be done. now if its a good idea we shall see ::slight_smile:

I said it could not be done; maybe to absolute but I gave the schematic that I based it on.

After Robin2's correction, I tried that with an R/2R resistor network and ended with a sequence 1/31, 1/29, 1/27 .. 1/3 and 1.1. So 161mV, 172mV, 185mV, .. 1666mV and 5000mV. The difference in values in the first steps is probably too small to be detected reliably.

Next I did fiddle with parallel resistors. 1k/2k/4k/8k with switches to 5V, other side connected via 1k to ground and analog in. The smallest voltage difference to detect between two combinations is 130mV and I think that that is feasible; only used one button or combination of two buttons.

janicefamily:
does anyone have code that will handle sort, long

Now that digital I/O is on the table you may like to look at this Post which has a neat system for different lengths of button presses.

...R

janicefamily:
Bulldog i'm not following your math for the 3.6%, are you saying 7 physical buttons? i'm talking about having 4 physical buttons (my first post lays it out)

I'm not sure what you mean here but we are using four physical buttons in my example; connected as:

const byte buttons[] = {6, 7, 8, 9};

janicefamily:
...your math for the 3.6%,...

can you clarify?

janicefamily:
thank you for all the responses, i have yet to work out the resistor values, but there will only be 7 different button presses, it mite be good for you to know that this whole thing is a midi foot controller. so it will be really hard to lets say press button 1 and button 3 at the same time with one foot. because of that the shift thing is out of the question. it needs to work with using one foot.

Would it be possible to use some kind of optical feedback? Such as an RGB-LED, lighting in different colors?

In that case you could use one of the foot switches to select the function set. And instead of 4 buttons, I'd talk about that you have:

  • 1 "select switch"
  • 3 "function switches"

By pressing the "select switch", you switch the function sets, let's say:

  • function set "green"
  • function set "yellow"
  • function set "red"
  • function set "blue"
    Active function set is indicated with an RGB LED, and each press of the "select switch" will advance the "function set" by one color.

In total you would have those 12 functions:

  • green-1
  • green-2
  • green-3
  • yellow-1
  • yellow-2
  • yellow-3
  • red-1
  • red-2
  • red-3
  • blue-1
  • blue-2
  • blue-3
    = 4*3 = 12 functions in total with using four switches

Then you would have never ever any delay in selecting one of the functions, and you can start the functions as fast as you can press the pedals to select one of the 12 functions.
If you need just 9 or 6 different functions instead of 12, you could easily reduce the number of color sets in the programming logic.

janicefamily:
thank you bulldog for that. and just so every on knows i'm not set on using an analog pin. the real reason why i was thinking about doing it, is because i couldn't figure out how to add in the code to add the 2 buttons at once to the long and sort press code, really just the denounce timing is messing with me. i thought that if i could use the analog pin to tell the program if 1 or 2 buttons are pressed then i could really just use a code like this to handle debouncing and long press.

you have the same debounce problem whether the buttons share a pin or use different pins. The debounce problem is caused by two things:

  1. pressing mechanical buttons can electrically bounce
  2. humans cannot press two buttons at the exact same instant.

but i'm open to using digital pins or analog.

great. Use digital.

The below function might be what you're looking for.

It uses a byte (pedalstatus) to store the status of the four pedals. The low nibble is used to store information about short and multiple pedal presses and the high nibble is used to store information about long pedal presses.

Two timings are used, one to detect long pedal presses and one to detect short and multiple pedal presses.

The code returns 0 if no pedal is pressed or if pedals were pressed and we're waiting for the timing(s) to lapse.

If any pedal is pressed, the timing is started.
Next the individual pedals are checked and if pressed the corresponding bit in the low nibble of pedalstatus is set to indicate a short press.
If the timing for a long press has lapsed, the code compares each pedal's current input against the corresponding bit in the low nibble in pedalstatus and if they match and no other pedals we're pressed in the 1 second, the corresponding bit in the high nibble in pedalstatus is set. If a long press is found (value of pedalstatus >=16), timing is stopped and the pedal status is returned.
If the total timing has lapsed, timing is stopped and the pedal staus is returned.

// pedal pin definitions
#define PEDAL01 4
#define PEDAL02 5
#define PEDAL03 6
#define PEDAL04 7
// LED pin definitions; just for fun as I had an RGB led on the breadboard
#define LEDRED     9
#define LEDGREEN  10
#define LEDBLUE   11

// constants for short and long presses
#define PEDAL01SHORT   1
#define PEDAL02SHORT   2
#define PEDAL03SHORT   4
#define PEDAL04SHORT   8
#define PEDAL01LONG   16
#define PEDAL02LONG   32
#define PEDAL03LONG   64
#define PEDAL04LONG  128

// five seconds pedal delay to move foot from one pedal to another
#define PEDALDELAY 5000
// one second delay to detect long press
#define LONGDELAY 1000
// pedal debounce delay
#define DEBOUNCEDELAY 100



/*************************************************
   checkPedals
   check the status of the pedals

   returns
   0 if no pedals pressed or if we're waiting after a pedal was pressed
   number if we've waited for a given period after a pedal was activated
*************************************************/
byte checkPedals()
{
  // start of wait timing
  static unsigned long startTime = 0;
  // indicate if check for long press was done
  static bool fLongchecked = false;
  // status of pedals; use static so we can remember
  static byte pedalstatus = 0;

  // get status of pedals
  byte pedals[4];
  pedals[0] = digitalRead(PEDAL01);
  pedals[1] = digitalRead(PEDAL02);
  pedals[2] = digitalRead(PEDAL03);
  pedals[3] = digitalRead(PEDAL04);

  // if any pedal pressed
  if (pedals[0] == LOW ||
      pedals[1] == LOW ||
      pedals[2] == LOW ||
      pedals[3] == LOW)
  {
    // if wait time not started
    if (startTime == 0)
    {
      // reset pedal status
      pedalstatus = 0;
      // set start time
      startTime = millis();
      // debug
      Serial.println(F("Timing started"));
    }
  }

  // set the status of the pedals if pedal is pressed; assume short till proven differently
  if (pedals[0] == LOW)
    pedalstatus |= PEDAL01SHORT;
  if (pedals[1] == LOW)
    pedalstatus |= PEDAL02SHORT;
  if (pedals[2] == LOW)
    pedalstatus |= PEDAL03SHORT;
  if (pedals[3] == LOW)
    pedalstatus |= PEDAL04SHORT;

  // if delay has lapsed to detect long press
  if (fLongchecked == false && startTime != 0 && millis() - startTime > LONGDELAY)
  {
    // if pedal 01 was low and still is low and no other pedals pressed
    if (pedals[0] == LOW && (pedalstatus & PEDAL01SHORT) == PEDAL01SHORT &&
        (pedalstatus & PEDAL02SHORT) != PEDAL02SHORT &&
        (pedalstatus & PEDAL03SHORT) != PEDAL03SHORT &&
        (pedalstatus & PEDAL04SHORT) != PEDAL04SHORT)
      pedalstatus |= PEDAL01LONG;
    // if pedal 01 was low and still is low and no other pedals pressed
    if (pedals[1] == LOW && (pedalstatus & PEDAL02SHORT) == PEDAL02SHORT &&
        (pedalstatus & PEDAL01SHORT) != PEDAL01SHORT &&
        (pedalstatus & PEDAL03SHORT) != PEDAL03SHORT &&
        (pedalstatus & PEDAL04SHORT) != PEDAL04SHORT)
      pedalstatus |= PEDAL02LONG;
    // if pedal 03 was low and still is low and no other pedals pressed
    if (pedals[2] == LOW && (pedalstatus & PEDAL03SHORT) == PEDAL03SHORT &&
        (pedalstatus & PEDAL01SHORT) != PEDAL01SHORT &&
        (pedalstatus & PEDAL02SHORT) != PEDAL02SHORT &&
        (pedalstatus & PEDAL04SHORT) != PEDAL04SHORT)
      pedalstatus |= PEDAL03LONG;
    // if pedal 04 was low and still is low and no other pedals pressed
    if (pedals[3] == LOW && (pedalstatus & PEDAL04SHORT) == PEDAL04SHORT &&
        (pedalstatus & PEDAL01SHORT) != PEDAL01SHORT &&
        (pedalstatus & PEDAL02SHORT) != PEDAL02SHORT &&
        (pedalstatus & PEDAL03SHORT) != PEDAL03SHORT)
      pedalstatus |= PEDAL04LONG;

    // if we have a long press
    if (pedalstatus >= 16)
    {
      // reset start time
      startTime = 0;
      // debug
      Serial.println(F("Long delay lapsed"));
      Serial.print(F("Pedals: 0x"));
      Serial.println(pedalstatus, HEX);
      // return the pedal status
      return pedalstatus;
    }

    // indicate we did the check for long press
    fLongchecked = true;
  }

  // if total wait time over
  if (startTime != 0 && millis() - startTime > PEDALDELAY)
  {
    // reset start time
    startTime = 0;
    // reset longcheck indication
    fLongchecked = false;
    // debug
    Serial.println(F("Pedal delay lapsed"));
    Serial.print(F("Pedals: 0x"));
    Serial.println(pedalstatus, HEX);
    // return the pedal status
    return pedalstatus;
  }

  // indicate that we're still waiting
  return 0;
}

The above code does not use a special debounce. The long timings and the fact that we only act on a LOW level should cater for this.

The code also does not check if pedals are released. So it would be possible that consecutive calls first detect a long press followed by a short press. The below code performs a check if pedals are released.

/*************************************************
   checkPedalsreleased
   check if pedals are released

   returns
   false if one or more pedals are pressed
   true if no pedals are pressed after a short delay
*************************************************/
bool checkPedalsreleased()
{
  static unsigned long startTime = 0;

  // if any pedal low
  if (digitalRead(PEDAL01) == LOW ||
      digitalRead(PEDAL02) == LOW ||
      digitalRead(PEDAL03) == LOW ||
      digitalRead(PEDAL04) == LOW)
  {
    // reset start time
    startTime = 0;
    // indicate not released
    return false;
  }
  else
  {
    // if start time not set
    if (startTime == 0)
    {
      // start timing
      startTime = millis();
      // indicate not released (yet)
      return false;
    }
    // if delay lapsed
    if (millis() - startTime > DEBOUNCEDELAY)
    {
      // reset start time
      startTime = 0;
      // indicated released
      return true;
    }
  }
}

It is more than likely possible to reduce the size by using some extra functions and constants.