3

Due to a shortage of pins on a esp8266 in arduino, I need a way to detect a button where;

  momentary press runs snooze() 
  15 sec press runs conf_Desk() 
  30 sec press runs calibration()

the preconfig;

  int buttonPin = D7;
  pinMode( buttonPin , INPUT_PULLUP);

All while allowing the main loop to function.

If I trap an interrupt it stops cycling the loop(), a few millisec delays are OK but seconds of delay is too much.

The functions are already written I just can't seem to come up how to track and confirm the hold length to call the right function based on the right timing without stopping other process that must stay cycling.

cagdas
  • 1,634
  • 2
  • 14
  • 27
user1213320
  • 650
  • 1
  • 6
  • 13
  • Thank You all. you popped me out of a writers block of sorts and gave me some great feedback. -- next i'll put some timing into the code samples to see what speeds they function at. -- Again Thank You – user1213320 Jan 07 '17 at 21:37
  • 2
    Seeing that this question is solved but still lacks an accepted answer, I'll throw this in here.. on *stackexchange* thanks, when due, are usually given by *upvoting* good answers and choosing an *accepted answer* that solves your problem. *Just Saying* ;) – Patrick Trentin Jan 07 '17 at 23:45
  • It is not unusual to give a right answer and still get downvoted – Luiz Menezes Jan 08 '17 at 00:10

4 Answers4

5

Using an interrupt is, IMHO, overkill. Interrupts are made for when you need to reply to a stimulus quickly, and a button press is something slow. Unless your loop is blocking, thing that I highly discourage.

ADDITION: as Patrick pointed out in the comments, there is in fact another reason to use interrupts: sleep mode. In fact, if you want to go into sleep mode and wake with a button, you have to use interrupts to wake later. However usually you have to do something continuously and not only reply to the button inputs. If you can't go into sleep mode, using an interrupt for button detection is still overkill in my opinion.

So, if you properly designed your loop not to block, here is a brief part of code doing what I think you should implement:

uint8_t buttonState;
unsigned long lastPressTime;

void setup()
{
    ...
    buttonState = digitalRead(buttonPin);
    lastPressTime = 0;
}

void loop()
{
    uint8_t currRead = digitalRead(buttonPin);
    if (buttonState != currRead)
    { // Button transition
        buttonState = currRead;
        if (buttonState == LOW)
        { // Button pressed, start tracking
            lastPressTime = millis();
        }
        else
        { // Button released, check which function to launch
            if (lastPressTime < 100)
            {} // Discard (it is just a bounce)
            else if (lastPressTime < 15000)
                snooze();
            else if (lastPressTime < 30000)
                conf_Desk();
            else
                calibration();
        }
    }
    ...
}

Since you made three very distant intervals though, I think that this part better suits your needs:

if ((lastPressTime > 100) && (lastPressTime < 7000))
    snooze();
else if ((lastPressTime > 12000) && (lastPressTime < 20000))
    conf_Desk();
else if ((lastPressTime > 26000) && (lastPressTime < 40000))
    calibration();

So you define validity ranges, so if someone presses the button for 10 seconds nothing happens (this is useful because if someone presses the button for 14.9 seconds in the previous code it will trigger the snooze function).

frarugi87
  • 2,826
  • 1
  • 20
  • 41
  • 1
    why would you discourage blocking the loop? wouldn't interrupts allow for putting the *Arduino* to sleep, effectively blocking the loop, and wake on demand? – Patrick Trentin Jan 07 '17 at 10:42
  • @PatrickTrentin In fact I never used the sleep mode, since usually I can't go into sleep because I need to perform actions continuously (monitoring sensors, displaying output, counting the time passing ...), so I didn't even consider it. But you are right: interrupts can be used for button detection when you can go into sleep mode. So I added a note in the answer. Thank you! Anyway, I don't think the OP actually needs sleep mode (from the names maybe he is doing an alarm, and so he needs to perform actions continuously) – frarugi87 Jan 07 '17 at 23:23
  • and, BTW, with blocking loop I mean a loop that "blocks" the execution for some time not because of a sleep but because it has long operations. For instance the blink example has what I call a blocking loop, since it "blocks" the loop (and the whole program) execution for a long time (2s). The blink without delay, on the other hand, is a much better program, since it achieves the same result but the loop function is very quick, and so you can do a lot of other things in "parallel" – frarugi87 Jan 07 '17 at 23:27
  • 1
    I understand, and thank you for the explanation, I think it integrates you answer very well. – Patrick Trentin Jan 07 '17 at 23:42
3

i would use a simple state machine structure with two global vars to avoid complex nested logic:

int buttonDown = 0;
unsigned long buttonStart;

void loop(){
  int snapshot = digitalRead(buttonPin);

  if(!buttonDown && snapshot ){ //pressed, reset time
    buttonDown = 1; // no longer unpressed
    buttonStart = millis(); // when it was pressed
  }

  if(buttonDown && !snapshot ){ //released, count time
     buttonDown = 0; // no longer pressed
     int duration = millis() - buttonStart; // how long since pressed?

     // now the "event part"
     if(duration>30000) return calibration();
     if(duration>15000) return conf_Desk();
     snooze();
  }
  sleep(1); // or whatever
}
dandavis
  • 16,370
  • 5
  • 40
  • 36
2

Interrupt service routines should be a short as possible. You don't have to wait inside the ISR and suspend your main loop for seconds.

Just use two different ISRs for a rising and a falling edge. When the button is pressed, ISR1 starts a timer, when it is released ISR2 stop it and triggers whatever is necessary depending on the time passed.

Make sure your button is debounced.

https://www.arduino.cc/en/Reference/attachInterrupt

Piglet
  • 27,501
  • 3
  • 20
  • 43
1

Another way to do this is with a pointer-to-function based state machine. The advantage on this is that you can easily introduce more functionalities to your button (say, another function called at 45 seconds).

try this:

typedef void(*state)();

#define pressed (millis() - lastPressed)

void waitPress();
void momentPress();
void shortPress();
void longPress();

state State = waitPress;
unsigned long lastPressed;
int buttonState;
int buttonPin = 7;// or whathever pin you use

void snooze(){} // stubs for your functions
void conf_Desk(){}
void callibration(){}

void waitPress()
{
    if (buttonState == HIGH)
    {
        lastPressed = millis();
        State = momentPress;
        return;
    }
    else
        return;
}

void momentPress()
{
    if (buttonState == LOW)
    {
        snooze();
        State = waitPress;
        return;
    }
    if (pressed > 15000)
        State = shortPress;
        return;
    return;
}

void shortPress()
{
    if (buttonState == LOW)
    {
        conf_Desk();
        return;
    }
    if (pressed > 30000)
        State = longPress;
        return;
    return;
}

void longPress()
{
    if (buttonState == LOW)
    {
        callibration();
        return;
    }
    return;    
}

void loop() 
{
   buttonState = digitalRead(buttonPin);
   State();
}
Luiz Menezes
  • 749
  • 7
  • 16
  • I was curious about the downvote you received, so I read your answer and now I think it could be improved. I suggest you reading [this doc on button debouncing](https://www.arduino.cc/en/Tutorial/Debounce). Also, the tests `lastPressed > N` are incorrect because `lastPressed` equals `millis()`, and the latter returns the *absolute time* from *boot* and not the *delta time* from when the *button* started being *pushed*. Minor negs are *syntactic errors* and an *incoherent* use of *brackets*. Also, could you argue the choice of using *function pointers*? – Patrick Trentin Jan 08 '17 at 13:10
  • @PatrickTrentin Followed your advice. As it was the code would not even compile... I forgot that arduino does not accept recursive pointer to function declaration, unlike the compiler I use on POS machines that just cast the return to void*... This should work. – Luiz Menezes Jan 08 '17 at 14:56
  • it's still missing the debouncing, but it is seemingly better +1 – Patrick Trentin Jan 08 '17 at 15:25