Here is a small piece of code that handles all of the above: It can read a number of different buttons, debounces them, and distinguishes between a short or a long press. Of course, you could also debounce the buttons by adding a small capacitor - but why would you add hardware when you can easily do it in code?
One contact of each button is connected to GND, and the other one to an Arduino pin which is specified in the initialization. In the initialization (the code before the "setup"), you specify the number of buttons used in your project, and the Arduino pins to which they are connected.
// -----------------------------------------------------------------
// --- push buttons: this example is made for five push buttons
const byte maxButton = 5; // number of buttons
const byte pinPushButton[maxButton] = {2,3,4,5,A2}; // the Arduino pins
In the "setup", you set the pinMode for the push button pins to "input" and enable the pull-up resistors for those pins.
// ------------------------------------------------------------------------
// --- Setup
// --------------------------------------------------------------------------
void setup() {
// --- initialize push button pins as inputs with pull-ups
for (byte i=0; i<maxButton; i++) {
pinMode(pinPushButton[i], INPUT_PULLUP);
}
}
In the following, I assume that your code is written without any longer delays, and your main loop is frequently executed. In my Arduino sketches, the main loop is usually executed more than 10,000 times per second. But it is not necessary to check the buttons that frequent, so I reduced the number of checks to 500 per second. In time intervals of 2 milliseconds I loop over all buttons, and, for each button, call the code to check if a short or a long push was detected. Then I am then executing the code that performs the corresponding actions.// --------------------------------------------------------------------------
// --- Main loop
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
void loop() {
static unsigned long previousTime = 0;
const byte timeInterval = 2; // pick a short time interval, you don't have to
// check the button with 10kHz
byte a[maxButton] = {}; // array to store the latest readings
// - check all buttons
if ((millis()-previousTime) > timeInterval) {
previousTime = millis();
for (byte i=0; i<maxButton; i++) {
a[i] = checkButtons(i);
}
if (a[0] == 1) [your code, when button 0 is short-pushed];
if (a[0] == 2) [your code, when button 0 is long-pushed];
if (a[1] == 1) [your code, when button 1 is short-pushed];
if (a[1] == 2) [your code, when button 1 is long-pushed];
if (a[2] == 1) [your code, when button 2 is short-pushed];
if (a[2] == 2) [your code, when button 2 is long-pushed];
if (a[3] == 1) [your code, when button 3 is short-pushed];
if (a[3] == 2) [your code, when button 3 is long-pushed];
if (a[4] == 1) [your code, when button 4 is short-pushed];
if (a[4] == 2) [your code, when button 4 is long-pushed];
}
}
That last item is the main one: the subroutine "checkButtons([button-number])" which, after debouncing the button, determines if it was push for a short time (<400ms long="" or="">400ms). The 400ms is hard-coded. It worked well for me (and my daughter's robot), but you may modify it according to your personal preferences. The subroutine returns zero if the button was not pressed, one in case of a short press, and two for a long press.
This is what the code does:
The "state" variables for all buttons are initialized as zero.
If it is detected that a button (that was not pressed before) was pressed, its "state" variable is set to 1, and the current time is stored in "previousTime".
If a button was pressed before (meaning its "state" is 1), the code waits 100ms (this takes care of the debouncing)
After 100ms, it checks if the button was released before 400ms expired - if yes, this was a short press.
After the 400ms expired (and a short press was not detected, it must have been a long press. In that case, the code waits until the button is released.
Afterwards, the code waits another 200ms before it is ready to accept any new presses.
400ms>
// -----------------------------------------------------------------
// ---------------------------------------------------------------------------------
// --- check one button for short or long push
// ---------------------------------------------------------------------------------
// returns: 0-none 1-short 2-long
//
byte checkButtons(byte buttonNo) {
const unsigned long timeDebounce = 100; // time to debounce
const unsigned long timeLong = 400; // minimum time for Long press
const unsigned long timeBreak = 200; // time interval after button release,
// before ready for next press
static byte state[maxButton] = {}; // this initializes all elements to zero
static unsigned long previousTime[maxButton] = {}; // this initializes all elements to zero
byte r = 0;
r = 0; // 0:not 1:short 2:long
if (state[buttonNo] == 0) { // --- no button has been pressed - check if
if (digitalRead(pinPushButton[buttonNo]) == LOW) {
previousTime[buttonNo] = millis();
state[buttonNo] = 1;
}
} else if (state[buttonNo] == 1) { // --- button was pressed - check for how long
if ( (millis()-previousTime[buttonNo]) > timeDebounce) {
if ( (millis()-previousTime[buttonNo]) < timeLong) {
if ( digitalRead(pinPushButton[buttonNo]) == HIGH) { // released -> short press
previousTime[buttonNo] = millis();
state[buttonNo] = 3;
r = 1;
}
} else { // it was a long press
state[buttonNo] = 2;
r = 2;
}
}
} else if (state[buttonNo] == 2) { // --- wait for long button press to end
if (digitalRead(pinPushButton[buttonNo]) == HIGH) {
previousTime[buttonNo] = millis();
state[buttonNo] = 3;
}
} else if (state[buttonNo] == 3) { // --- wait a little while after previous button press
if ( (millis()-previousTime[buttonNo]) > timeBreak) {
state[buttonNo] = 0;
}
}
return r;
}
That's it! The above code works nicely in a few of my recent projects. In a few cases it allowed me to use less buttons, and therefore fewer Arduino pins. In one case it helped me to add functionality to a project that was built using a single button.Note: Two typos in the code were fixed on July 10, 2018 - now it works correctly
2nd Note: Another bug was in the code that was fixed on July 23, 2018. Now it really works beautifully in my projects, distinguishing between short and long button pushes.
2 comments:
Hey, I just wanted to say fine work here. I needed a 'long press' solution and didn't want to reinvent the wheel. Tried a few but none came even close to the elegance and clean operation of yours.
Cheers.
Thank you for the compliment. I'm glad it worked for you.
Post a Comment