One button, press and press+hold analog input

Hello, I’m having some difficulty trying to get a button to perform 2 separate commands.
Project is 5 switches all going through resistors, but I need only ONE to have a press, along
with a press+hold function. The code posted is some nice analog debounce code i’ve found on
the net, merged with my own code for performing the commands. I’ve been working on this for a few
days and most examples i’ve seen using millis() use led’s, and digital inputs/outputs, so the
translation is confusing for me…i’m a hardware guy, not software. I’d appreciate any advise.

[/int getButton()        
 { 
 
  int i, z, sum ;
  int button;

  sum = 0;
  for (i=0; i < 4; i++)
  {
     sum += analogRead(A0);        // will have 5 buttons through resistors on one input
  }
  z = sum / 4;
  if (z > 650 && z < 690) button = 0;             //aplication requires rest at 3 volts//                               
  else if (z > 840 && z < 860)  button = 1;       // analog input range for a button                                    
  else if (z > 970 && z < 1010) button = 2;       //will have 5 total buttons
              
  else button = 0;
  return button;
 
 }


 void loop(void)
{
 int button, button2, pressed_button;  
  button = getButton();
  if (button != old_button)
  {
      delay(50);                   // debounce
      button2 = getButton();

      if (button == button2)
      {
         old_button = button;
         pressed_button = button;
         Serial.println(pressed_button); 
         if (pressed_button == 1) 
           {
             ble.print("AT+BleHidControlKey=");   
             ble.println("VOLUME+");             
             
           }

         if (pressed_button == 2)
            {
              ble.print("AT+BleHidControlKey=");
              ble.println("VOLUME-");  
            }
       }           
   }             
}code]
      delay(50);                   // debounce

Yes, the old reliable stick-your-head-in-the-sand method of debouncing.

Well, I guess it kind of works. You could say with a pretty high confidence that if it's the same before and after 50ms then it's no longer bouncing and it's probably not detecting button 1 on the way over to visit button 2.

So when button == old_button, don't you think that might be the place to check if this button was held down? When you first detected this as a "new" button, you should remember the time on the millis() clock when you detected that. Then keep glancing at the clock while the button is held down.

Thanks for the reply. So after "old button= new button", I should use millis() and if statements
to record button press time? Just trying to wrap my head around this..

Presently, the code works perfectly for single button presses. On serial monitor, a button press registers only once, even if held down, then registers a "button 0" only when released. This is fine, and is actually what is best. Project involves a car steering wheel control buttons to control an Android phone over bluetooth emulating an HID keyboard...I'd like to use one button on my wheel for a "play/pause" single press, and hold it down for some other random functionality(quite a few HID codes work!)

Is the loop the best place to do this, or would the getbutton function be better? If there is a better or
even simple way to do the above as described, then that's fine too. Basically, can I do what I want
with the code as written? Still tyring to learn, and honestly have a hard time understanding anything
other than simple beginner code..Appreciate the help.

Either or both of these links may give you some ideas
Button pressed for period of time
Four button click types

...R

Thanks for the links. In your example, this is where I start to get confused. Most every example I find for button presses are using digital high/ low and then turning something on/off. Since I'm using an analog input, I dont get how to assign switch states for using millis (). Also, my application needs to see around 3 volts as a no button pushed, rest state. This is also the higest voltage recorded from all 5 buttons I'll be using. Thanks for any insight.

Presumably some value for analogRead() indicates that a particular button is pressed. Just treat that the same as if the button was detected with digitalRead().

On the one hand you might have

if (digitalRead( buttonPin ) == LOW) {
   // do stuff;
}

and on the other hand

if (analogRead( buttonPin ) > NNN) {
   // do stuff
}

...R

That part I understand, but after that, confusion. Most millis () with button examples i find, they are toggling a button state with a boolean eg "ledState= !ledState, then performing a digitalWrite to an led, using the new ledState. Since I’ll have a total of 6 different analog range values, with 3 volts value at rest, I dont understand how to go about it. Maybe I don’t need to, I just don’t know. The code i posted works so well for single button presses, but since it’s not all mine, I’m just wondering if I need to scrap it and try something completely differnt…Again, thanks for your time and patience.

Matt1995:
That part I understand, but after that, confusion. Most millis () with button examples i find, they are toggling a button state with a boolean eg "ledState= !ledState, then performing a digitalWrite to an led, using the new ledState.

What do you want to do instead of toggling an LED?

The program logic will probably be very similar. Think of it this way. If there is someone upstairs who can hear me but can't see me. Suppose I shout OK and he does something. We could have arranged for him to do any of several things such as open a window; turn off a light; dance; wash the floor. But the logic of shouting OK to start the thing is exactly the same.

If you need to do several different things then you just need variables to keep track of the state of each of them.

Have a look at Planning and Implementing a Program and especially how the code is organized into separate single purpose functions. You can think of the functions as equivalent to the tasks open-window, dance etc. And you can arrange for your code to call the appropriate function when the relevant button press is detected.

...R

Thank you. In the code I posted, and what I’m trying to do, is take 6 different analog input ranges, compare ranges to account for any slight voltage fluctuations, then assign a button number to each. In the loop, the code recognizes and maps each button number to two lines of code(the two “ble.print”) lines to send the media command eg “volume up, vol down, next track”. I’m confused about the toggle part, as I dont need to toggle anything on or off, just send one command. Button state cant be toggled “high or low”, as its analog.So I want to take one of those analog input ranges, and have it map to TWO different commands, depending on if just pressed and released, or pressed and held down. Also, when no buttons are pressed, it needs to rest at 3 volts, which is around 640 analog (not sure specific, but eg). Hopefully this makes sense.

EDIT…

Would it be good to create 2 variables for each compared analog input value, for each button? Example,

In the code, "z "= a compared input range

Then assign a “high” and “low” to each imput range

Eg … if( z <670 && z > 630 ) button1Off= LOW
if (z < 840 && z >800) button1On= HIGH

Then use this with millis () code, and THEN assign a button number? This would have to be done in the “getbutton” function, not loop…does that matter? This is all i can think of at the moment…right track? Not sure if if the HIGH and LOW variables are correct since I’m not reference a digital output…just a command.

Then assign a "high" and "low" to each imput range

Eg ..     if( z <670 && z > 630 ) button1Off= LOW

Do you think that way? Most people don’t. The usual way to test that a value is in range is to compare the value to the low end first and then to the high end:

    if(z > 630 && z < 670)

A particular switch is pressed, or it isn’t. That the switch is connected through an resistor ladder and an analog pin isn’t relevant.

If the switch being pressed this time is not the same switch that was being pressed last time, a change has occurred. THAT is what you want to record the time of. The change from a switch being pressed to no switch being pressed is important, too.

I’m not sure how you want to interpret the buttons. If you are treating them as momentary press buttons then I would set all the button values to 0 before the analogRead() and then set the appropriate ones to 1 depending on what is pressed.

However things would be a little more complicated if you want one press to turn on a buttonState and a subsequent press of the same button to turn it off.

By the way if you test the values in sequence the code can be simplified to

if (x < 50) {

}
else if (x < 100) {

}
else if (x < 150 {

}
// etc

…R

The code as posted works how I want it with simple button presses (momentary). When a button is pressed, weather held or not, sends a button number 1 for example and executes a volume up command, ONLY ONCE, then after button is released, the code sends a "button = 0" command, which is declared in the "getbutton" function with z= the analog range of no buttons pressed.

I want to take only one button analog input range, and map that to TWO "button=" numbers in the getbutton function. So if i press and release a button, i want this to eventually = "button = 1". Then if i press and hold the same button, have this equal "button=2. Then i can reference these button numbers in the loop to perform 2 separate commands(as posted in my loop code). Hopefully this makes sense..

Hopefully this makes sense..

Not entirely.

You need a function that returns the number of the button being pressed. When the value returned by that function is not the same as the value returned last time, you need to record when the current switch became pressed, if the current switch is not whatever corresponds to no switch.

When the current switch is not the same as the previous switch, you can determine how long it has been since the current switch became pressed. That is how you distinguish between a short and long press.

Something vaguely like this:

int prevNumber = 0;
int currNumber = 0;

unsigned long differentSwitchBecamePressed = 0;

void loop()
{
   currNumber = getButton(); // stupid name for function that determines which SWITCH is pressed
   if(currNumber != prevNumber)
   {
      // The current switch is not the same as the previous switch
      if(currNumber == 0)
      {
         // The switch that was pressed no longer is
         unsigned long holdTime = millis() - differentSwitchBecamePressed;
         if(holdTime >= longHoldTime)
         {
            // Switch was held down for a while
         }
         else
         {
            // Switch was pressed and released "quickly"
         }
      }
      else
      {
         // A different switch became pressed
         differentSwitchBecamePressed = millis();
      }
   }
   prevNumber = currNUmber;
}

Matt1995:
So if i press and release a button, i want this to eventually = "button = 1". Then if i press and hold the same button, have this equal "button=2.

Then the code I linked to in Reply #3 will be suitable.

...R

Thanks guys. I'll see what I can come up with and post it.I'll have to digest this info a bit.

I think the other posters are dragging you backwards here. You already have a perfectly good function to read the analog input and return which button is pressed. You already have an adequate method of debouncing and you already have the initial press detected separately from the holding-it-down state.

Those 3 things are usually very difficult to get across to Arduino beginners, so most of the advice so far is addressing those 3 problems.

You have a large if() statement which detects that the button being pressed is a new button. Add an else to it for the case when the button is pressed but it’s not a new button. My attempt is below. This just looks at one button, but I think you can set it up easily enough for your other buttons.

int getButton()
{

  int i, z, sum ;
  int button;

  sum = 0;
  for (i = 0; i < 4; i++)
  {
    sum += analogRead(A0);        // will have 5 buttons through resistors on one input
  }
  z = sum / 4;
  if (z > 650 && z < 690) button = 0;             //aplication requires rest at 3 volts//
  else if (z > 840 && z < 860)  button = 1;       // analog input range for a button
  else if (z > 970 && z < 1010) button = 2;       //will have 5 total buttons

  else button = 0;
  return button;

}


void loop(void)
{
  int button, button2, pressed_button;
  static unsigned long buttonPressTime;
  static boolean buttonWasHeldDown = false;
  const unsigned long BUTTON_LONG_PRESS = 2000; //milliseconds - the duration of a 'long' press
  button = getButton();
  if (button != old_button)
  {
    delay(50);                   // debounce
    button2 = getButton();

    if (button == button2)
    {
      old_button = button;
      pressed_button = button;
      Serial.println(pressed_button);
      if (pressed_button == 1)
      {
        ble.print("AT+BleHidControlKey=");
        ble.println("VOLUME+");

      }

      if (pressed_button == 2)
      {
        ble.print("AT+BleHidControlKey=");
        ble.println("VOLUME-");
      }
    }
    buttonWasHeldDown = false;
    buttonPressTime = millis(); //record when the button was initially pressed
  } else {
    //the button is not a new button - is it held down long enough?
    //Well, first we are only interested in one or two buttons that have the long-press function
    if(button == 1) 
    {
      if(!buttonWasHeldDown) 
      {
        //so far, we have not detected it as a held-down button
        if(millis() - buttonPressTime > BUTTON_LONG_PRESS)
        {
          buttonWasHeldDown = true; //set this so we don't do the held-down action more than once
          Serial.println("button was held down");
          //do the held-down action
        }        
      }
    }
  }
}

MorganS, thank you for your reply. Before I read your post, I actually had finally thought about exactly
what your explaining..the present code is already doing most everything. I think what is confusing me
is that in all the example code I can find and study, they use "LOW" and "HIGH" for detecting the
button press. Are these low/high designations just logic toggles, or is it actually detecting the 0-5v input range??? Can I use an integer, like "0" as HIGH and "1" as LOW? Also, is the code looking for a "constant stream" of data from the button? OR is it just looking
for a state change? If all it needs is the "state change", then the current code I posted already does this
for each button. In serial monitor, if I press the first button, it reports a button "1". It will ONLY report a
button "0" when the button is released. It doesn't keep scrolling eg"1111111111111111" if i hold the button down.
If it did this,
then I could potentially, lets say increase the volume extremely quickly if the button was held down. I dont want this(i dont want it to act like a TV remote that if you hold the button down, it increments volume). I just want a long press for one button to perform an additional task, like "next track".

I'll see if I can merge your code example and make something happen. I greatly appreciate the help
here. It's been days trying to figure this out..wife is getting annoyed at all the time spent lol.

Are these low/high designations just logic toggles, or is it actually detecting the 0-5v input range??? Can I use an integer, like "0" as HIGH and "1" as LOW?

HIGH and LOW are just names, with values of 1 and 0, respectively. They represent a pin voltage of 5V or 0V, respectively (for 5V Arduinos).

Using the names is preferred, to make it clear that you are comparing pin states and that you know what the possible states are.

Since you don't actually have pin states, you have a different problem.

Matt1995:
in all the example code I can find and study, they use "LOW" and "HIGH" for detecting the
button press.

I had hoped that my examples in Reply #5 would have shown you that digitalRead() and analogRead() can be used in a way that is equivalent to each other.

In the Arduino LOW is defined as 0 and HIGH as 1. And digitalRead() returns a value that is either 0 (LOW) or 1 (HIGH).

What your code does when it detects a HIGH or a LOW or an analog reading above a certain value is up to you.

...R

Robin2:
In the Arduino LOW is defined as 0 and HIGH as 1. And digitalRead() returns a value that is either 0 (LOW) or 1 (HIGH).

What your code does when it detects a HIGH or a LOW or an analog reading above a certain value is up to you.

…R

Ok, I didn’t realize this, that now makes much more sense, at least for digitalRead().
In the link you posted, here are some snippets from that code:

boolean buttonVal = HIGH; // value read from button
boolean buttonLast = HIGH; // buffered value of the button’s previous state

buttonVal = digitalRead(buttonPin);
// Button pressed down
if (buttonVal == LOW && buttonLast == HIGH && (millis() - upTime) > debounce)

Now knowing that my input is analog, and button “off” is not “0” (i cannot pullup the pin to 5v for
button off), how do I go about translating these statements? For each HIGH and LOW statement, do I
have to designate an analog range? eg if(buttonVal > 600 && buttonVal <700) ? If that is the case, what do I declare for the 2 boolean variables? Seems like there should be a better way to detect a press, so you dont
have to type out each “range” everytime a comparison needs to be made…which is what MorganS I believe is hinting at…currently a button number is generated when a button is pressed, then a “0” gets returned
when it is released.