Tuesday, October 13, 2009
4-Way Button: Click, Double-Click, Hold, Long Hold
How many ways can a button be clicked? [Anonymous] commented asking about double-clicking after my "Click for A, Press and Hold for B" post, and I finally got around to it, adding a long hold function too. Last night I tidied up the code and debugged obscure outlier cases, and the result is a simple 4-way button function which can report click, double-click, hold, and long hold events.
When used with a rotary input (pot, rotary encoder, jog-shuttle, etc), complex branching menus are easy to implement. Note that the long hold event always triggers a "normal" hold event first, while single- and double-click are completely independent (as well as being independent from the hold events).
I've posted code in the first two comments-- the tabs of my test sketch, with the second comment containing the checkButton() function. This seems like a good candidate for my first proper library; I would appreciate any help in getting this example sketch converted into an easily instantiated object, like AlphaBeta's Button library.
Subscribe to:
Post Comments (Atom)
/* 4-Way Button: Click, Double-Click, Press+Hold, and Press+Long-Hold Test Sketch
ReplyDeleteBy Jeff Saltzman
Oct. 13, 2009
To keep a physical interface as simple as possible, this sketch demonstrates generating four output events from a single push-button.
1) Click: rapid press and release
2) Double-Click: two clicks in quick succession
3) Press and Hold: holding the button down
4) Long Press and Hold: holding the button down for a long time
*/
#define buttonPin 19 // analog input pin to use as a digital input
#define ledPin1 17 // digital output pin for LED 1
#define ledPin2 16 // digital output pin for LED 2
#define ledPin3 15 // digital output pin for LED 3
#define ledPin4 14 // digital output pin for LED 4
// LED variables
boolean ledVal1 = false; // state of LED 1
boolean ledVal2 = false; // state of LED 2
boolean ledVal3 = false; // state of LED 3
boolean ledVal4 = false; // state of LED 4
//=================================================
void setup()
{
// Set button input pin
pinMode(buttonPin, INPUT);
digitalWrite(buttonPin, HIGH );
// Set LED output pins
pinMode(ledPin1, OUTPUT);
digitalWrite(ledPin1, ledVal1);
pinMode(ledPin2, OUTPUT);
digitalWrite(ledPin2, ledVal2);
pinMode(ledPin3, OUTPUT);
digitalWrite(ledPin3, ledVal3);
pinMode(ledPin4, OUTPUT);
digitalWrite(ledPin4, ledVal4);
}
void loop()
{
// Get button event and act accordingly
int b = checkButton();
if (b == 1) clickEvent();
if (b == 2) doubleClickEvent();
if (b == 3) holdEvent();
if (b == 4) longHoldEvent();
}
//=================================================
// Events to trigger by click and press+hold
void clickEvent() {
ledVal1 = !ledVal1;
digitalWrite(ledPin1, ledVal1);
}
void doubleClickEvent() {
ledVal2 = !ledVal2;
digitalWrite(ledPin2, ledVal2);
}
void holdEvent() {
ledVal3 = !ledVal3;
digitalWrite(ledPin3, ledVal3);
}
void longHoldEvent() {
ledVal4 = !ledVal4;
digitalWrite(ledPin4, ledVal4);
}
/*
ReplyDeleteMULTI-CLICK: One Button, Multiple Events
Oct 12, 2009
Run checkButton() to retrieve a button event:
Click
Double-Click
Hold
Long Hold
*/
// Button timing variables
int debounce = 20; // ms debounce period to prevent flickering when pressing or releasing the button
int DCgap = 250; // max ms between clicks for a double click event
int holdTime = 2000; // ms hold period: how long to wait for press+hold event
int longHoldTime = 5000; // ms long hold period: how long to wait for press+hold event
// Other button variables
boolean buttonVal = HIGH; // value read from button
boolean buttonLast = HIGH; // buffered value of the button's previous state
boolean DCwaiting = false; // whether we're waiting for a double click (down)
boolean DConUp = false; // whether to register a double click on next release, or whether to wait and click
boolean singleOK = true; // whether it's OK to do a single click
long downTime = -1; // time the button was pressed down
long upTime = -1; // time the button was released
boolean ignoreUp = false; // whether to ignore the button release because the click+hold was triggered
boolean waitForUp = false; // when held, whether to wait for the up event
boolean holdEventPast = false; // whether or not the hold event happened already
boolean longHoldEventPast = false;// whether or not the long hold event happened already
int checkButton()
{
int event = 0;
// Read the state of the button
buttonVal = digitalRead(buttonPin);
// Button pressed down
if (buttonVal == LOW && buttonLast == HIGH && (millis() - upTime) > debounce) {
downTime = millis();
ignoreUp = false;
waitForUp = false;
singleOK = true;
holdEventPast = false;
longHoldEventPast = false;
if ((millis()-upTime) < DCgap && DConUp == false && DCwaiting == true) DConUp = true;
else DConUp = false;
DCwaiting = false;
}
// Button released
else if (buttonVal == HIGH && buttonLast == LOW && (millis() - downTime) > debounce) {
if (not ignoreUp) {
upTime = millis();
if (DConUp == false) DCwaiting = true;
else {
event = 2;
DConUp = false;
DCwaiting = false;
singleOK = false;
}
}
}
// Test for normal click event: DCgap expired
if ( buttonVal == HIGH && (millis()-upTime) >= DCgap && DCwaiting == true && DConUp == false && singleOK == true) {
event = 1;
DCwaiting = false;
}
// Test for hold
if (buttonVal == LOW && (millis() - downTime) >= holdTime) {
// Trigger "normal" hold
if (not holdEventPast) {
event = 3;
waitForUp = true;
ignoreUp = true;
DConUp = false;
DCwaiting = false;
//downTime = millis();
holdEventPast = true;
}
// Trigger "long" hold
if ((millis() - downTime) >= longHoldTime) {
if (not longHoldEventPast) {
event = 4;
longHoldEventPast = true;
}
}
}
buttonLast = buttonVal;
return event;
}
Hi Jeff,
ReplyDeleteVery clever button setup,
I am right now re-writing a code to incorporate your Button code arrangement with and LCD.
If you want I can give you a snippet of my code
I'have been using..so far. Let me know....
Thanks again.
Frank..
Australia
framul@gmail.com
Hi Jeff,
ReplyDeleteI applied the class to your code for my private usage. If you want to publish please contact me.
Regards,
Nadir
catalkn@hotmail.com
I would love to have this as a class - yes please!
ReplyDeleteHi James,
ReplyDeleteI posted the class source to Jeff. I think that Jeff has right to publish.
Regards,
Nadir
Thanks, Nadir! James, I will try to publish it during the next week, but will add back the long hold since I need it for a project I'm working on, and it will give me some experience in editing the class structure. For now, using the multi-click code, and previously setting up buttonPin (set to HIGH for the internal pullup) should work fine.
ReplyDeleteAny news on this class? :)
ReplyDeleteJames
James,
ReplyDeletePM to me, I can sent it to you.
Nadir
catalkn@hotmail.com
Very cool code there!
ReplyDeleteI get a feeling I'm missing something about how to use it (I am a newb). I pasted the top two comments into a sketch and it works, but your mention of "tabs" and "function" (IE: "I've posted code in the first two comments-- the tabs of my test sketch, with the second comment containing the checkButton() function.") make me think this is supposed to be in two parts, with the first part "calling" to the second part? Do I save the second comment (The "function") in a seperate text file or something?
It works just pasted in, but would be a little easier that way.
Thanks again, for both the code and letting me ask dumb questions, LOL. The overall goal is to make an RGB lightsaber LED controller, and this will let us do nested menus right on the device.. how cool is that?!
TroyO
troyollom@hotmail.com
I'd love to have a class version of this code.
ReplyDeleteIs that possible? It looks above like it's been created, but I don't see where it may be available.
Ron
RonCraig007@yahoo.com
I've tried for two days to get a push and hold functionality in my latest project. I guess I have to accept that I am more mechanical than coding oriented. I wish I had found your code sooner. I want to know how easily I could get my hands on the nice clean arduino library if there is one?
ReplyDeleteYour code is very nice. I've used it in a Sous Vide project here to great success. Thanks a bunch for sharing!
ReplyDeletehello,
ReplyDeleteI translated your code into a library available at: http://pastebin.com/gQLTrHVF
Thank you for your work
Jeremy
http://lightuino-stairlight.blogspot.com
i had to make a few changes (update it?) to make the code work. main changes:
Delete1. REMOVED: #include "WProgram.h" (no longer required).
2. CHANGED: bpUP TO bp.
3. ADDED: event listeners for ALL four types of presses in the sample code.
4. CHANGED: time intervals for single, double, long clicks and for long press. these feel more intuitive to me.
5. CHANGED: event OnLongPress is raised ONLY after the button is released. it's now "on long press AND release." this feels more intuitive. code is tested and at http://pastebin.com/87cCn6h9.
@mahesh Found your code for the four types of button presses for the Arduino.
DeleteAny idea how many buttons I could add? I'd like to use a Mega 250 with 15-20 input buttons where each button has four functions.
Wondering if you expanded your code for more than one button?
sir jeff, do you have a separate snippet for the long press and hold? i'm really a newbie, i'm trying to extract but i'm confused with the mixed codes. thanks in advance. :)
ReplyDeleteI really hope you can help me, because I'm stumped here!
ReplyDeleteI'm using your code above (thank you1), whittled down to detect just a single or double click, as that's all I need. It works perfectly.
However, when I replace the button with a magnetic reed switch, it only *ever* detects a single click. And it's really frustrating!
I've tried increasing the debounce time to 500ms and the DCgap to 2000ms, but to no avail. I can still do a (slow) double-click using a button, but when I use the reed switch, it's a single click every time.
Any idea what might be causing the problem?
What does a reed switch do really? If it's just a switch it should work fine, I've used this with different types of buttons. Debouncing it for very long like 500ms is too much though. I'd get a handle on what the reed switch is doing compared to a "regular" button. Good luck!
DeleteSome further info: according to http://reed-switch-info.com/ the bounce time for a typical reed switch is around 2ms, so it appears the bounce is irrelevant here.
DeleteWhich means... aargh, I have no idea what's going on!
Using the reed switch to just turn an LED on and off works exactly like using a button.
Using the reed switch with the above code triggers a single click as expected, exactly like using a button.
But when I try a double click, it works perfectly with a button, and fails every time with a reed switch (triggering a single-click event instead).
:-((
A reed switch is just a switch that's activated by a magnet. When a magnet comes near, it opens the switch; when the magnet goes away, it closes again (or vice versa, depending on the reed switch). They're useful for detecting doors closing, either to turn on a light, or to set off a burglar alarn.
ReplyDeleteAs far as I've ever known, it's just a switch exactly like any other — it can break or make a circuit — so I'm really confused about what's going on here.
The reason I tried the outlandishly long debounce time is that literally the only thing I could think of was that just maybe the switch was prone to a lot of bounce, and this might be why the single/double click detection wasn't working, but I'm sure it can't be bouncing for 500ms or more, as you would actually see LEDs flickering at that point.
I'm still bemused, so any other suggestions will be most welcome.
This is still driving me nuts, but in the interest of not clogging up Jeff's blog with long tangential comments, I've put a summary of the problem here:
ReplyDeletehttp://bit.ly/Lye6Aq
If any readers have nothing better to do with half an hour and feel like diving into some (actually fairly simple) code to hunt down a bug, I'd be eternally grateful. I can trade you language services (put your app/site into French/Esperanto) or buy you dinner or something. :o) Please!
Jeff ,
DeleteThis is very impressive and I am trying to incorporate it into a project I am working on with 4 button keypads . The embedded functions will help do the things that I need without adding more physical buttons. Thanks for sharing !
Tim,
Once a magnet reaches the threshold of attraction it will pull the reed making contact. To demo reed operation connect the switch to continuity function of your meter. Note how close a switch has to be to make contact , then start pulling the magnet away slowly and you will find the gap is typically larger for break. This is why a reed switch is great for security applications .
Bob D
Hi, i'm new to arduino and programming.
ReplyDeleteI'm trying to control only 2 leds with a button.
control one of them with a single click and the other one with the double click.
i tried modifying this code but didnt work for me.
Will i only need to change the variables and the void setup, or are there changes that would have to be done to the void loop as well??
Sorry without seeing your circuit it's hard to debug, but the sketch worked as shown so I'd try to get it working 1:1 before modifying anything!
DeleteThanks Jeff.
ReplyDeleteAbout the problems with the reed switch: Use a Hall effect switch. Not a linear Hall effect sensor but a switch.
OK, got it working. But how would I make ledPin3 only come on after the time alloted and as long as I am pushing the button. Does that make sense?
ReplyDeleteMy plan is to use this to start my truck with just one button. One click turns on my ACC, two clicks turns on the IGN, the long click will engage the starter, so I need that one to come on as usual but only stay on as long as I am pushing the button.
Thanks for your time.
Scott b.
hI,
ReplyDeleteI just modified it for myself, but [newb] now i would like the same for a second button?
could you help??
// Read the state of the button
buttonVal = digitalRead(buttonPin);
// Button pressed down
if (buttonVal == LOW && buttonLast == HIGH && (millis() - upTime) > debounce) {
downTime = millis();
ignoreUp = false;
waitForUp = false;
singleOK = true;
holdEventPast = false;
longHoldEventPast = false;
if ((millis()-upTime) < DCgap && DConUp == false && DCwaiting == true) DConUp = true;
else DConUp = false;
DCwaiting = false;
}
// Button released
else if (buttonVal == HIGH && buttonLast == LOW && (millis() - downTime) > debounce) {
if (not ignoreUp) {
upTime = millis();
if (DConUp == false) DCwaiting = true;
else {
event = 2;
DConUp = false;
DCwaiting = false;
singleOK = false;
}
}
}
// Test for normal click event: DCgap expired
if ( buttonVal == HIGH && (millis()-upTime) >= DCgap && DCwaiting == true && DConUp == false && singleOK == true) {
event = 1;
DCwaiting = false;
}
// Test for hold
if (buttonVal == LOW && (millis() - downTime) >= holdTime) {
// Trigger "normal" hold
if (not holdEventPast) {
event = 3;
waitForUp = true;
ignoreUp = true;
DConUp = false;
DCwaiting = false;
//downTime = millis();
holdEventPast = true;
}
// Trigger "long" hold
if ((millis() - downTime) >= longHoldTime) {
if (not longHoldEventPast) {
event = 4;
longHoldEventPast = true;
}
}
}
buttonLast = buttonVal;
return event;
Thank you very much for your code, this was exactly what I was looking for.
ReplyDeleteGreat Work!
-X
Hi Jeff,
ReplyDeletebeen wanting to minimize my projects by removing my 4 microswitches to now just 1. Looking forward to implementing your code. Thanks!
Hi. Great Work.
ReplyDeleteI do have a question.
I can see how your code works for a single button or even several buttons, but each connected to it's Arduino pin.
Is there a way to make this work with 8 buttons connected to a Multiplexer (so all being read from one single AnalogRead)? That would be so great!!!
Thanks!
=)
Of course! It all depends on how you read the button. You could wrap it up a bunch of ways, but this is where it would help to make a library for your specific application... which is up to you ;)
DeleteSome of my comments:
ReplyDelete1, it looks like the variable: waitForUp is an unused variable
2, DCwaiting is actually a variable waiting for the second press down edge, right? (In double click mode)
3, DConUp is actually a variable waiting for the second press release edge, right? (In double click mode)
Both of the two variable was used in checking double click mode, so I suggest change their names:
DCwaiting -> waitForNextDown
DConUp -> waitForNextUp
Thanks.
BTW: I think you can use a enum instead of numbers, like:
ReplyDeleteenum KeyEvent
{
Undefined = 0;
SingleClick = 1;
DoubleClick = 2;
PressHold = 3;
LongPressHold = 4;
};
So, lets step away from multiple outputs and look at the following scenario.
ReplyDeleteOne button, single click blinks led 3 times
1000 millis hold blinks led until stopped by a second switch? Can you help? Cause I'm about to snap trying to figure it out.
Hi Adam, interesting problem, sounds very doable! Seems like you have a clear spec, so what's the problem exactly? I wonder if you're using the dreaded delay() to make the LED blink: your sketch can't detect a button state change if you're in the delay(). Keeping track of timing and looping your sketch in the KHz range makes simple work of catching any change in UI element states.
DeleteHello! I'm doing a project with the Arduino MEGA that would work great with this code but can't get it to compile. Are there libraries I need to import? How does it know those functions: checkButton(), clickEvent(), etc...?
ReplyDeleteOk, this is very nice, almost exactly what I need! Thanks! It's a GOOD start! But I need to make it work for multiple switches, and I'm total nube with C. I made 4 buttons be read from an array of the pins they connect to, and sent both the array index and the resulting initial pin read to your function, but I begin to realize that I probably have to make things like 'buttonLast', and maybe many more, into arrays so that the proper flags will be stored to be properly tested against. Have you or anyone else worked up multi-button code? (And what do I do to get a name showing here instead of Anonymous?)
ReplyDeleteWhy this sketch doesn't compile today?
ReplyDeleteWhat has changed in newest Arduino world?
hello after some trial and error , it works fine now, for control of a RGB light, even on a small attiny 85chip :)
ReplyDeletethis little program has really made my day, good work! :)
hello, is it possible to write the complete code?
ReplyDeletei get some errors like ... was not declared in this scope
i need this example for 2 rotary encoder. thx
Sorry but I can't help troubleshoot your code! If you reconstruct my setup you should be able to gain some insight into the example.
DeleteHello
ReplyDeleteI need help.
I'm doing a project and need to have 6 buttons with these four functions , but when I try to make the addition of the 5 buttons , the relay stops working.
none of the buttons work
Am I doing something wrong ??
I'm not having error message when compiling the code.
Sorry I can't help troubleshoot your code or circuit, but maybe you can find help in the comments above or in Arduino forums. Good luck and happy hacking!
Deletehello, I want to make one button for 4 digit 7 segment with shift register, press one for change digit ones ( count up) , press two for change digit tens (count up), press three for change digit hundreds( count up),press four for change digit thousands (count up) ,i am newbie with code arduino.tanks before
ReplyDeleteSounds cool! What's your question? I can't help much anyway, but I can say this:
DeleteDiagram your circuit,
Simplify your code,
Test one piece of code and hardware at a time.
Good luck and happy hacking!
Hi Salsa, i use your sketch and is very good.
ReplyDeleteI'm a newbie in arduino and i try without succesful to make a mod in your sketch.
I need to separate holdevent and longholdevent:
if i hold for >1000 but <3000 event 3
if i hold for >3000 only event 4
i want to chose with time hold event 4 without make also event 3...
Please help me....
Bye
Sorry anon but the way it's currently structured, it hits the hold before the long hold. I'm sure if you made the events triggered when the button comes up (instead of while it's being held down), this would not be a big deal. You're new-- this is a perfect little project for you ;)
DeleteHi Salsa,
ReplyDeleteAmazing Code. I am trying to incorporate your code with a RGB LED. I have got it to work quite nicely adding some other features to it.
At this current time, I am trying to add midi to this sketch and I am wondering is it possible to work correctly. I have got a single midi message to go across to Ableton Live and was just wondering is it possible to add multiple midi messages? Just a newb question in general.
Thanks,
Kind regards,
Anthony
One improvement (to an already very cool project) is to add a Schmitt trigger to debounce the PB instead of the debounce delay in software. I find that the Schmitt makes the tap and double tap very crisp and sure-footed. But, that's must my personal preference for "debouncing" all switches I have problems with.
ReplyDeleteThanks for the great sketch.
hi. Its is a great program and is saving me so much time.
ReplyDeleteI am trying to use analog pulse(pulse width 10ms) as a signal instead of momentary push button. i only need single and double click functionality.
Could someone pls share the program with just those two functions, sorry i am unable to reduce the code myself as i do not know programming much !!
thanks in advance.
I still use this, so great
ReplyDelete