interrupt push button hold

Hello Everyone!

Im attempting to add a hold setting to my push button. The button works just fine but I am having a hard time writing an effective hold setting.

I want it to do one thing when pressed quickly and another after being held for a few seconds.

I am using the interrupt FALLING setting.

This is the interrupt function I have right now.

void push1()
{
static unsigned long last_interrupt_time = 0;
unsigned long interrupt_time = millis();
  if (interrupt_time - last_interrupt_time > 400 && interrupt_time - last_interrupt_time < 2000){
buttonChange1++;
    if (buttonChange1>=5){buttonChange1 = 1;}
  }
  else if (interrupt_time - last_interrupt_time > 3000) 
  {
    Serial.print("HELD");
  }
  last_interrupt_time = interrupt_time;
  EEPROM.write(address0,0);
  EEPROM.write(address0, buttonChange1);
  
      Serial.print("PUSHED button 1 ==");
    Serial.println(buttonChange1);
}

I can see that it sometimes prints HELD but not in a predictable way. Also it will usually ++ the buttonChange.

Any ideas on what I am doing wrong?

Any ideas on what I am doing wrong?

You didn't post your complete code.
You can't use Serial.print within an interrupt routine.
Why use an interrupt for a pushbutton?

First, don't use interrupts. There's much more important jobs that interrupts should be doing.

Your main loop can look at the button state regularly during the execution of the loop. So long as the loop never goes more than about 100 milliseconds between digitalRead(), then it should be indistinguishable from 'instant'.

Then just record the time where you first found that it was pressed, check that it didn't bounce back immediately, record the time you're sure that it is pressed. Then look at that time regularly if it's still pressed. If it exceeds your threshold, you have a "long press" successfully recognised.

...oh, and use constants for the intervals. Don't type numbers like 400 and 2000 into your code. Put them at the top where they can be seen and edited in one place. Give them each a meaningful name.

Hiya rexhex,

Like MorganS and dlloyd said, I do this by polling the pin, rather than waiting for an interrupt:

#define button            9
unsigned long previousTenthSecondMillis = 0L;
long tenthSecond = 100UL;
byte buttonStillDown = 0;

void setup()
{
  pinMode(button, INPUT_PULLUP); 
  Serial.begin(9600);
}

void loop()
{
  // do this every 100ms
  if (millis() - previousTenthSecondMillis >= tenthSecond) {

    // check the button (pin 9 --> button --> gnd)
    if (!digitalRead(button)) {
      if ((buttonStillDown++ > 0 ) && (buttonStillDown < 3)) {

        // do the single click thing
        Serial.println("Clicked");
 
      } else {
        if (buttonStillDown > 10 ) {

          // do the held down thing
          Serial.println("Held");

        }
      }
    } else {
      buttonStillDown = 0;
    }

    previousTenthSecondMillis += tenthSecond;
  }
  
}

It works fine, best if you don't do anything too time consuming during the inner parts where the Serial.println statements are. If you hold the button down, it repeatedly runs the 'held' part, for 25.5 seconds. Then the byte variable, buttonStillDown rolls over, and it goes back to clicked mode. Seems unlikely that you'd hold the button that long, but if so, use an int or even long type.

I strongly advise against writing to EEPROM in an interrupt routine. There is a limit to the number of writes you can do, you know.

dlloyd, I did not post the full code because its SOOO long. I can write it in a smaller form for this function if you would like. I figured this was enough information. Also, why cant I use Serial.print in an interrupt? It has been working thus far.

Morgan, you are right they should be constants but its easier to manipulate there right now. I dont want to keep scrolling up and down to change it.

Im using a teensy 3.2 processing audio with FFT and the FastLED library. There are many settings in this code for the lights and I found the interrupt was the best way to get accurate presses anywhere in the code. So far it has not affected any of my code. I keep hearing not to use interrupts for the button but have found this to work the best so far with my application.

I tried what you recommended by writing this with no luck.

void push1()
{
static unsigned long last_interrupt_time = 0;
unsigned long interrupt_time = millis();
  if (interrupt_time - last_interrupt_time > 400)
  {  
    buttonHeld = 0;
   if (interrupt_time - last_interrupt_time > 3000) 
  {
    buttonHeld = 1;
    Serial.print("HELD");
    //do something
  }
  if (buttonHeld == 0)
  {
    buttonChange1++;
    if (buttonChange1>=5){buttonChange1 = 1;}
  }
  }
  
  last_interrupt_time = interrupt_time;
  EEPROM.write(address0,0);
  EEPROM.write(address0, buttonChange1);
  
      Serial.print("PUSHED button 1 ==");
    Serial.println(buttonChange1);
}

ChrisTenone, Thank you for your response and example code. Im going to try polling again my previous approach was maybe not the best. I will make a function modeled off of what you provided. This will then be called in EVERY loop possible in the code to ensure when the button is pressed the chip sees this.. I hope no one pushes it for longer then 25 seconds but if they do... they deserve the roll over. I like that you used byte instead of int. I need to practice using smaller values when applicable.

Nick Gammon, I thought I took care of the limit by first clearing it with 0. After cleared I tell it the new value. Thank you for your response. Today is the first time I have used EEPROM. I am very happy with the results :slight_smile:

Well an erased EEPROM cell isn't zero. It's 255.

Don't try to erase it yourself. Just write the correct value. Your code will double the wear on the EEPROM.

In fact, it will make it much worse than double, as you can usually have an EEPROM write first check if it's writing a different value and skip the erase-then-write if the value is identical.

rexhex:
...ChrisTenone, Thank you for your response and example code. Im going to try polling again my previous approach was maybe not the best. I will make a function modeled off of what you provided. This will then be called in EVERY loop possible in the code to ensure when the button is pressed the chip sees this.. I hope no one pushes it for longer then 25 seconds but if they do... they deserve the roll over. I like that you used byte instead of int. I need to practice using smaller values when applicable.

Big numbers intimidate me. :wink:

When you say "This will be called in EVERY loop possible ..." beware. If you call it too often, the click time becomes too short, and you double-click too much. It's ok if your click just does one thing, but if it toggles, it makes a mess. The timing is built into the code - just put it at the top level of your loop() routine, and it'll run as intended.

Nick Gammon, I thought I took care of the limit by first clearing it with 0. After cleared I tell it the new value. Thank you for your response. Today is the first time I have used EEPROM. I am very happy with the results :slight_smile:

The 'limit' is a hardware thing. the EEPROM poops out after 100,000 (or so) writes. If you write in a loop, this happens rather quickly, then it doesn't work until you get another chip.

If you want to check if a button is pressed continuously for some period of time this code should work - call it regularly from loop()

btnVal = digitalRead(btnPin);
if (btnVal == HIGH) {    // assumes btn is LOW when pressed
   lastBtnPressMillis = millis();   // btn not pressed so reset clock
}
if (millis() - lastBtnPressMillis > = interval) {
   // button has been pressed for longer than interval
}

There is a neat piece of code in this link if you need to detect different button presses

...R

rexhex:
Also, why cant I use Serial.print in an interrupt? It has been working thus far.

Serial.print puts things into an output buffer, and waits for interrupts to empty it. If the buffer is full it waits for an interrupt to remove one byte, so it can insert a new one. Interrupts are off inside an ISR. It may work for a while (until the buffer fills up) then it will hang for ever.

Thank you all for your advice and support. I just got home from work and am going to work on the button and I have an idea on how to clean up the EEPROM. Scary to think that after a while of use it will stop working. The Teensy has a lot of space so I am going to cycle through and remember current location while using update. This should give me 100,000 x .length times to write to EEPROM before theoretically pooping out.

No, fix your algorithm. If the thing was going to exceed the number of writes in a few seconds, then a few hundred thousand seconds is not long - like a few days.

I dont think I explained my idea properly.

The only time anything gets stored to EEPROM is when a button is pressed. This is to change a 'light show' setting. I dont think it will be pressed too often, though it will be pressed. So when a button is pressed it will store the position of the button in EEPROM so if it turns off we can access the same light setting when turned back on. Next time the button is pressed it stores the position in the next EEPROM location. This goes on until reaching the end of EEPROM locations in which it will start all over.

I still have not gotten to this part so I cant upload the code.

rexhex:
I dont think I explained my idea properly.

The only time anything gets stored to EEPROM is when a button is pressed. This is to change a 'light show' setting. I dont think it will be pressed too often, though it will be pressed. So when a button is pressed it will store the position of the button in EEPROM so if it turns off we can access the same light setting when turned back on. Next time the button is pressed it stores the position in the next EEPROM location. This goes on until reaching the end of EEPROM locations in which it will start all over.

I still have not gotten to this part so I cant upload the code.

When you cold boot, how will you know which EEPROM location is the active one? :slight_smile:

Haha Im going to use EEPROM! Thats the plan.

rexhex:
Haha Im going to use EEPROM! Thats the plan.

But won't the location that you use to store it, wear out? You see where I'm going with this? :wink:

Yes I do see what you are saying. Without thinking about it much I was planning on having it move too. But obviously that wont work because what will find that location.... Shoot.

Any ideas?

Well, if you can be reasonably sure that the button won't be pushed more than 100,000 times during the lifetime of the device, then just use one single EEPROM location. Don't make it any more complex than it has to be. (Just make sure you're not writing to the EEPROM on every single button bounce.)

Actually that 100,000 figure is pretty conservative. You can find reports on the internet of people who intentionally tried to wear out an Arduino EEPROM and they got much much higher numbers.

Alright guys... I have been working on this for about 7 hours straight. I had never thought it would take this long.

I started off with the code that Robin linked to by Jeff Saltzman. Im pretty sure I did everything right but I had the LOW vs. HIGH backwards for my set up... So I tried many different things before finally just trying out that code by its self.

After looking at the pure code by Jeff Saltzman I saw that my LOW was to his HIGH and so it was switched then all rewritten again just now. I have the click and hold click working for my code without interrupts. It laggs slightly more than the interrupt but not enough to be a problem. (Thank you Robin for that link!!!)

I had no time to think about the EEPROM issue.

MorganS, your post just came through before I posted this. Thats great to hear. I am concerned about longevity. I dont know how long this will be used honestly. Could just be a year but maybe it will be 10!!!

Also a strange thing is a function for a light setting that just cycles through the color wheel has an error. It will play an FFT setting on the first pixel. Sometimes it will play the FFT setting on the last pixel.. Its not written to do this. Very odd.

WOO HOO hold button is working!!! :o