Thursday, May 14, 2009

Click for A, Press and Hold for B

I planned to add a time setting mode to the alarm clock later, as gravy-- thought I wouldn't need one since the clock chip keeps time-- but with the board not yet built, I keep accidentally disconnecting the DS1307's battery and needing to reset it with a special sketch.

That's a pain, so I'm adding it to the PPAC program next, and to keep the interface simple-- jog/shuttle and a button-- a press and hold event seems appropriate for switching into an otherwise hidden mode, like shutting down a MacBook by holding down the button instead of just clicking it.

Despite a few posted examples on debouncing a button press (explicitly or with a library), I couldn't find any press+hold examples, so I took a stab at it and got a test sketch working pretty quickly with a button and two indicator LEDs. The code is posted below, as a comment.

16 comments:

  1. /* Click and Press+Hold Test Sketch
    By Jeff Saltzman
    To keep input interfaces simple, I want to use a single button to:
    1) click (fast press and release) for regular button use, and
    2) press and hold to enter a configuration mode.
    */

    #define buttonPin 19 // analog input pin to use as a digital input
    #define ledPin1 9 // digital output pin for LED 1 indicator
    #define ledPin2 8 // digital output pin for LED 2 indicator

    #define debounce 20 // ms debounce period to prevent flickering when pressing or releasing the button
    #define holdTime 2000 // ms hold period: how long to wait for press+hold event

    // Button variables
    int buttonVal = 0; // value read from button
    int buttonLast = 0; // buffered value of the button's previous state
    long btnDnTime; // time the button was pressed down
    long btnUpTime; // time the button was released
    boolean ignoreUp = false; // whether to ignore the button release because the click+hold was triggered

    // LED variables
    boolean ledVal1 = false; // state of LED 1
    boolean ledVal2 = false; // state of LED 2


    //=================================================


    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);

    }


    //=================================================


    void loop()
    {

    // Read the state of the button
    buttonVal = digitalRead(buttonPin);

    // Test for button pressed and store the down time
    if (buttonVal == LOW && buttonLast == HIGH && (millis() - btnUpTime) > long(debounce))
    {
    btnDnTime = millis();
    }

    // Test for button release and store the up time
    if (buttonVal == HIGH && buttonLast == LOW && (millis() - btnDnTime) > long(debounce))
    {
    if (ignoreUp == false) event1();
    else ignoreUp = false;
    btnUpTime = millis();
    }

    // Test for button held down for longer than the hold time
    if (buttonVal == LOW && (millis() - btnDnTime) > long(holdTime))
    {
    event2();
    ignoreUp = true;
    btnDnTime = millis();
    }

    buttonLast = buttonVal;

    }


    //=================================================
    // Events to trigger by click and press+hold

    void event1()
    {
    ledVal1 = !ledVal1;
    digitalWrite(ledPin1, ledVal1);
    }

    void event2()
    {
    ledVal2 = !ledVal2;
    digitalWrite(ledPin2, ledVal2);
    }

    ReplyDelete
    Replies
    1. how do you have it wired. i tried on my arduino uno and i get nothing.

      Delete
    2. Hi! I tried to modify your code to work in a different way: press-on (led1) / hold-off (led1)... i need only to on and off the same output but doesn\t work like i want.

      Delete
  2. very, very nice! I think a lot of 'duino with LCD's just got nested menus because of you!

    ReplyDelete
  3. Gotta say that's a great idea. Seems so simply obvious to do that now that you've stated it. :-)

    I think that's begging to become a compliment to AlphaBeta's Button abstraction layer: http://www.arduino.cc/playground/Code/Button.

    We could start coding all that in two lines like this, and get down to the fun stuff:

    Button pressMe = button(2, PULLUP);
    if (pressMe.held(1000)) someFunction();

    ReplyDelete
  4. I used something like this in a project I was working on. Though I think this could be compacted further. If you use a simple counter 'while' or 'for' loop, you can have the led turn on, or the function occur right when the threshold between a 'press' and a 'hold' is crossed.

    Attach this function to an interrupt pin

    int ButtonCheck(){
    count = 0;
    while(digitalRead(But)==HIGH){
    delay(10);
    count++;
    }
    if (count > 100){ /button held
    return 2; /button held
    else if count > 0
    return 1; /button pressed
    }
    }

    ReplyDelete
  5. What about a double press?

    ReplyDelete
  6. I'm working on a similar code but with a sensor instead. I've been trying for days to convert the code but no luck.
    Here's the code I have for the sensor to turn off a single led.
    void loop() {
    val = analogRead(LDR);

    if (val2 < 28)
    {digitalWrite(ledPin, HIGH);
    //delay(3000);
    } else {
    digitalWrite(ledPin, LOW); // turn the ledPin off
    }
    //delay(1000);
    }

    Thanks, 12 year old from Toronto

    ReplyDelete
  7. You are awesome, this code is flawless.

    ReplyDelete
  8. i try this code with keys, but i get ever a 'g' and one second later a 'h'.
    i try click, double click long click. always the same --> gh

    complete code:
    http://pastebin.com/hmSEJgDN

    ReplyDelete
    Replies
    1. Sorry I don't have time to debug your code but maybe somebody else does.

      Delete
  9. Your code is amazingly stupendous!!! I will forever owe you!

    ReplyDelete
  10. Thanks Jeff, you're a legend for posting this! Believe it or not, I stumbled upon this post just now as I was coming to the final stages of my first ever electronics project which I named "JeffBot". As much as I'd love to say I'd named it after you as gratitude to this post... it was actually due to it being a physical soundboard inspired by the meme quote "My name is Jeff" (by Channing Tatum in the movie 22 Jump Street).

    Using an LM386-1 audio amp and an Atmega328p chip (coded on Arduino Uno but later moved to PCB to save on space and costs) I was limited in the amount of buttons I could have. With digital pins 0 and 1 reserved for serial rx and tx, digital 9 as audio input and digital 10 as chip select (for SD card) I was left with 13 pins total for buttons (digital 2, 3, 4, 5, 6, 7, 8 + analog 0, 1, 2, 3, 4, 5). Incorporating your code (tweaked a little for my needs) I now have 24 sounds instead of 12 (I went with 12 buttons instead of 13 for design reasons) which is more than enough, especially as I can have multiple 128Mb SD cards available each with 24 sounds that I can quickly pop in and out!

    I can't paste the code as it goes over the comment character limit... I'll try in a second comment in case anyone is interested or would like to try to adopt it for their own project at all.

    ReplyDelete
  11. Very good implementation. I modified for use with a bunch of buttons in a resistor ladder, for those who need it:

    #include

    #define debounce 20 // ms debounce period to prevent flickering when pressing or releasing the button
    #define holdTime 2000 // ms hold period: how long to wait for press+hold event

    #define NO_BTN 0
    #define A1_BTN 1
    #define A2_BTN 2
    #define F1_BTN 3
    #define F2_BTN 4
    #define F3_BTN 5
    #define F4_BTN 6
    #define F5_BTN 7
    #define DI_BTN 8
    #define LF_BTN 9
    #define RT_BTN 10
    #define SB_BTN 11
    #define PW_BTN 12
    #define BB_BTN 13

    // Button variables
    int buttonVal = 0; // value read from button
    int buttonLast = 0; // buffered value of the button's previous state
    long btnDnTime; // time the button was pressed down
    long btnUpTime; // time the button was released
    boolean ignoreUp = false; // whether to ignore the button release because the click+hold was triggered

    void setup() {
    Serial.begin(115200);
    }

    void loop() {
    buttonVal = matrixRead();

    // Test for button pressed and store the down time
    if (buttonVal != NO_BTN && buttonLast == NO_BTN && (millis() - btnUpTime) > long(debounce)) {
    btnDnTime = millis();
    }
    // Test for button release and store the up time
    if (buttonVal == NO_BTN && buttonLast != NO_BTN && (millis() - btnDnTime) > long(debounce)) {
    if (ignoreUp == false) buttonPress(buttonLast);
    else ignoreUp = false;
    btnUpTime = millis();
    }
    // Test for button held down for longer than the hold time
    if (buttonVal != NO_BTN && (millis() - btnDnTime) > long(holdTime)) {
    buttonHold(buttonLast);
    ignoreUp = true;
    btnDnTime = millis();
    }
    buttonLast = buttonVal;
    }

    void buttonPress(int btn) {
    printf("Received press event for %d\n", btn);
    }

    void buttonHold(int btn) {
    printf("Received hold event for %d\n", btn);
    }

    int matrixRead() {
    int aVal = analogRead(A1);
    int bVal = analogRead(A2);
    int rtn = NO_BTN;
    if (aVal < 1000) {
    if (aVal < 40 ) rtn = A1_BTN;
    else if (aVal < 100) rtn = A2_BTN;
    else if (aVal < 200) rtn = F1_BTN;
    else if (aVal < 300) rtn = F2_BTN;
    else if (aVal < 400) rtn = F3_BTN;
    else if (aVal < 500) rtn = F4_BTN;
    else rtn = F5_BTN;
    }
    if (bVal < 1000) {
    if (bVal < 40 ) rtn = DI_BTN;
    else if (bVal < 100) rtn = LF_BTN;
    else if (bVal < 200) rtn = RT_BTN;
    else if (bVal < 300) rtn = SB_BTN;
    else if (bVal < 400) rtn = PW_BTN;
    else rtn = BB_BTN;
    }
    return rtn;
    }

    ReplyDelete