create a timed 3 state push button in arduino

1.3k views Asked by At

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.

4

There are 4 answers

0
Piglet On

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

4
frarugi87 On

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

0
dandavis On

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
}
3
Luiz Menezes On

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