Hold button for x seconds, leds turn off HELP

So I have the following program:

byte led1 = 8;
byte led2 = 9;
byte led3 = 10;

byte knop = 5;
byte var=0;
bool status = 0;
void setup()
{
  
  pinMode(led3, OUTPUT);
  pinMode(led2, OUTPUT);
  pinMode(led1, OUTPUT);
}

void loop()
{
   status = digitalRead(knop);
  if(status ==HIGH){
  	var +=1;
    delay(1000);
  }
  
  
  switch (var) {
  case 0:
    digitalWrite(led1,HIGH);
    digitalWrite(led2,LOW);
    digitalWrite(led3,LOW);
    break;
  case 1:
   digitalWrite(led2,HIGH);
    digitalWrite(led1,LOW);
    digitalWrite(led3,LOW);
    break;
  case 2:
   digitalWrite(led3,HIGH);
    digitalWrite(led1,LOW);
    digitalWrite(led2,LOW);
    break;
    case 3: 
    digitalWrite(led3,LOW);
    digitalWrite(led1,LOW);
    digitalWrite(led2,LOW);
    break;
  default:
    var = 0;
}
  
}

I can't for the life of me figure out how to fit a program into this that will enable me to turn off all LEDs by holding a button for 5 seconds. Is there anyone that could help?

Record the time (using millis()) when the switch (which is missing a pinMode) becomes pressed (take a look at the state change detection example in the IDE) and if it is still pressed after 5000 milliseconds, do your thing.

If you're open to using a library to do the lifting for you... I wrote myself a simple button library a while back where you can set the timeout before it returns a long press.

If you used it, your code could look something like this:

#include "KTS_Button.h"
#define NUM_LEDS 3
#define BTN_PIN 5
#define LED1_PIN 8
#define LED2_PIN 9
#define LED3_PIN 10

KTS_Button button(BTN_PIN);
byte leds[NUM_LEDS] = { LED1_PIN, LED2_PIN, LED3_PIN };
byte activeLed = NUM_LEDS - 1;

void cycleActiveLed() {
  for (byte i = 0; i < 3; i++)
    digitalWrite(leds[i], LOW);
  digitalWrite(leds[++activeLed %= 3], HIGH);
}

void turnOffLeds() {
  for (byte i = 0; i < 3; i++)
    digitalWrite(leds[i], LOW);
  activeLed = NUM_LEDS - 1;
}

void setup() {
  for (byte i = 0; i < 3; i++)
    pinMode(leds[i], OUTPUT);
  button.setLongPressTimeout(5000);
}

void loop() {
  switch (button.read()) {
    case SINGLE_PRESS:
      cycleActiveLed();
      break;
    case LONG_PRESS:
      turnOffLeds();
      break;
  }
}

I appreciate there could be a line or two that aren't so beginner-friendly.

This is the KTS_Button.h file: (likely better ways of making a button library but it does the job for me for now)

#ifndef KTS_BUTTON_H
#define KTS_BUTTON_H

enum ActionType {
  NOTHING = 0,
  SINGLE_PRESS,
  LONG_PRESS,
  DOUBLE_PRESS,
  TRIPLE_PRESS,
};

class KTS_Button {

  // Private Variables
  private:
    byte inputPin;
    byte currentState = HIGH;
    byte lastState = HIGH;
    unsigned long currentTime;
    unsigned long timeCapture;
    unsigned long debounceLength;
    unsigned long longPressTimeout;
    unsigned long multiPressTimeout;
    bool longPressEnabled = true;
    byte pressCount;
    void (*btnFuncPtr)(void);

  // Constructor
  public:

  KTS_Button(int pin, unsigned long dbLength = 5, unsigned long lpTimeout = 350, unsigned long mtTimeout = 150)
  : inputPin(pin), 
  debounceLength(dbLength), 
  longPressTimeout(lpTimeout),
  multiPressTimeout(mtTimeout)
    { pinMode(inputPin, INPUT_PULLUP); }


  // Public Modifiers
  void setLongPressEnabled(bool newState) { longPressEnabled = newState; }
  void setDebounceLength(unsigned long newDBLength) { debounceLength = newDBLength; }
  void setLongPressTimeout(unsigned long newLPTimeout) { longPressTimeout = newLPTimeout; }
  void setButtonFunction(void (*func)(void)) { btnFuncPtr = func; }

  // Public Functions
  void execute(){ btnFuncPtr();} 

  ActionType read() {
    updateButton();
    if (buttonHasChanged()) {
      if (buttonIsDown()) captureTime();
      else if (timeCapture && hasBeenDebounced()) {
        timeCapture = 0; 
        return SINGLE_PRESS;
      }
      else timeCapture = 0;
    }
    if (longPressEnabled && timeCapture && aboveLPTimeout()) {
      timeCapture = 0;
      return LONG_PRESS;
    }
    return NOTHING;
  } 

  ActionType readMultiPress() {
    updateButton();
    if (buttonHasChanged()) { 
      if (buttonIsDown()) captureTime();
      else setLongPressEnabled(true);
      if (hasBeenDebounced()) {
        if (belowLPTimeout()) { pressCount++; } 
      }
    }
    if (longPressEnabled && buttonIsDown() && aboveLPTimeout()) {
      pressCount = timeCapture = 0;
      setLongPressEnabled(false);
      return LONG_PRESS;
    }
    if (aboveDPTimeout()) {
      switch (pressCount) {
        case 1: pressCount = timeCapture = 0; return SINGLE_PRESS; break;
        case 2: pressCount = timeCapture = 0; return DOUBLE_PRESS; break;
        case 3: pressCount = timeCapture = 0; return TRIPLE_PRESS; break;
        default: pressCount = 0; break;
      }
    }
    return NOTHING;
  }

  private:

  // Private Functions
    bool buttonIsDown() { return currentState == LOW; }
    bool buttonHasChanged() { return currentState != lastState; }
    void updateStates() { lastState = currentState; } 
    bool hasBeenDebounced() { return currentTime - timeCapture > debounceLength; }
    bool aboveLPTimeout() { return currentTime - timeCapture > longPressTimeout; }
    bool belowLPTimeout() { return currentTime - timeCapture < longPressTimeout; }
    bool aboveDPTimeout() { return currentTime - timeCapture > multiPressTimeout; }
    void captureTime() { timeCapture = currentTime; }
    
    void updateButton() {
      if (buttonHasChanged()) updateStates();
      currentTime = millis();
      currentState = digitalRead(inputPin);
    }
}; 

#endif

Maybe something like this:

const byte knop = 5;

const byte led1 = 8;
const byte led2 = 9;
const byte led3 = 10;

byte var = 0; // Next step is 1
unsigned long LastOffTime = 0;

void setup()
{
  pinMode(knop, INPUT);
  pinMode(led1, OUTPUT);
  pinMode(led2, OUTPUT);
  pinMode(led3, OUTPUT);

  digitalWrite(led1, LOW);
  digitalWrite(led2, LOW);
  digitalWrite(led3, LOW);
}

void loop()
{
  int state = digitalRead(knop);

  if (state == LOW)
    LastOffTime = millis();

  if (state == HIGH)
  {
    // HIGH for more than 5 seconds?
    if (millis() - LastOffTime > 5000)
    {
      // Yes. Turn off the lights
      digitalWrite(led1, LOW);
      digitalWrite(led2, LOW);
      digitalWrite(led3, LOW);
      var = 0; // Next step is 1
    }
    else
    {
      // No, Less than 5 seconds. Take a step.
      var += 1;

      switch (var)
      {
        case 1:
          digitalWrite(led1, HIGH);
          digitalWrite(led2, LOW);
          digitalWrite(led3, LOW);
          break;

        case 2:
          digitalWrite(led1, LOW);
          digitalWrite(led2, HIGH);
          digitalWrite(led3, LOW);
          break;

        case 3:
          digitalWrite(led1, LOW);
          digitalWrite(led2, LOW);
          digitalWrite(led3, HIGH);
          break;

        case 4:
          digitalWrite(led1, LOW);
          digitalWrite(led2, LOW);
          digitalWrite(led3, LOW);
          var = 0;  // Next step goes to 1
          break;
      }
      delay(1000);
    }
  }
}

Just a side note: you can save yourself some typing if you have a function with 3 arguments take care of the switching and you just invoke it with the configuration you want.

void handle_lights (const bool a, const bool b, const bool c)
{
    digitalWrite (led1, a);
    digitalWrite (led2, b);
    digitalWrite (led3, c);

    return;
}

And then:

case 0:
    handle_lights (1,0,0);
    break;
    // and so on

For the button polling function, this is an approach you can take:

  1. When the button is pressed, take a timestamp
  2. When the button is released, return the elapsed time (current millis() minus the timestamp you took)

Then, have a function update the state of the lights according to the return value you got from the button polling function:

  1. If it's less than 50 ms or so, do nothing: it's probably a bouncing artifact
  2. If it's more than 5000 ms, go into state 4
  3. Else (short, but legit press) cycle states

Fixed.

This seems to be doing a good job of what I'm looking for, however, when holding down the button the LED's continue to change in the order in what they light up. How could I fix this? I'd like for them not to change when I hold down the button, only once when I click on it.

I'll try this when I've ordered a functioning LED strip, as i'm currently using TinkerCAD to simulate the design and I don't think I can add extern libraries

What do you exactly mean by this? I'm a beginner in Arduino and don't know that much yet haha, sorry :sweat_smile:

StateChangeDetection

I also started on Tinkercad. Give 'Wokwi' a shot, you might prefer it, as I did.

Added the INPUT_PULLUP but it doesn't seem to work?

Will look into it, thanks for the recommendation

What does "this" refer to? The function for the LEDs? The algorithm for reading the button?

Are you familiar with functions returning values instead of void? If you can make a function return how long the button was pressed, you are halfway there.

Actually I understand what you had typed now, I've had school classes for arduino for 2 years but I haven't seen that type of function though. I think I've only used it once to make an interrupt.

Because that is what your original code did. If I understand correctly, you want to:

  1. Start with the lights out.
  2. If you press then release the button in less than 5 seconds, light the next LED.
  3. If you press and then hold the button for more than 5 seconds, start over with the lights out.

Is that correct?

I have a long-press example that might help:

const byte ButtonPin = 2;
const unsigned long DebounceTime = 30;
const unsigned long LongPressInterval = 5000;

boolean ButtonWasPressed = false;
boolean ButtonBeingTimed = false;
unsigned long ButtonStateChangeTime = 0; // Debounce timer

void setup()
{
  pinMode (ButtonPin, INPUT_PULLUP);  // Button between Pin and Ground
}

void loop()
{
  checkButton();
}

void checkButton()
{
  unsigned long currentTime = millis();

  boolean buttonIsPressed = digitalRead(ButtonPin) == LOW;  // Active LOW

  // Check for button state change and do debounce
  if (buttonIsPressed != ButtonWasPressed &&
      currentTime  -  ButtonStateChangeTime > DebounceTime)
  {
    // Button state has changed
    ButtonWasPressed = buttonIsPressed;

    if (ButtonWasPressed)
    {
      ButtonBeingTimed = true;
    }
    else
    {
      // Button was just released
      ButtonBeingTimed = false;
      if (currentTime - ButtonStateChangeTime < LongPressInterval)
      {
        // Short Press
      }
    }

    ButtonStateChangeTime = currentTime;
  }

  if (ButtonBeingTimed && ButtonWasPressed)
  {
    // Button is still pressed
    if (currentTime - ButtonStateChangeTime >= LongPressInterval)
    {
      ButtonBeingTimed = false;
      // Long Press
    }
  }
}

Yes, that is exactly what I'd like to have. How would I go about putting it in the existing code?

Where the example says:
// Short Press
Put the code to light up 'the next LED'.

Where the example says:
// Long Press
Put the code to turn off the LEDs and set 'var' back to the beginning.

I'm extremely sorry but I can't seem to get it to work haha, could I have some help?

const byte ButtonPin = 5;
const byte led1 = 8;
const byte led2 = 9;
const byte led3 = 10;

byte var = 0; // Next step is 1

const unsigned long DebounceTime = 30;
const unsigned long LongPressInterval = 5000;

boolean ButtonWasPressed = false;
boolean ButtonBeingTimed = false;
unsigned long ButtonStateChangeTime = 0; // Debounce timer

void setup()
{
  pinMode (ButtonPin, INPUT_PULLUP);  // Button between Pin and Ground
  pinMode(led1, OUTPUT);
  pinMode(led2, OUTPUT);
  pinMode(led3, OUTPUT);

  digitalWrite(led1, LOW);
  digitalWrite(led2, LOW);
  digitalWrite(led3, LOW);
}

void loop()
{
  checkButton();
}

void checkButton()
{
  unsigned long currentTime = millis();

  boolean buttonIsPressed = digitalRead(ButtonPin) == LOW;  // Active LOW

  // Check for button state change and do debounce
  if (buttonIsPressed != ButtonWasPressed &&
      currentTime  -  ButtonStateChangeTime > DebounceTime)
  {
    // Button state has changed
    ButtonWasPressed = buttonIsPressed;

    if (ButtonWasPressed)
    {
      ButtonBeingTimed = true;
    }
    else
    {
      // Button was just released
      ButtonBeingTimed = false;
      if (currentTime - ButtonStateChangeTime < LongPressInterval)
      {
        // Short Press

        var += 1;

        switch (var)
        {
          case 1:
            digitalWrite(led1, HIGH);
            digitalWrite(led2, LOW);
            digitalWrite(led3, LOW);
            break;

          case 2:
            digitalWrite(led1, LOW);
            digitalWrite(led2, HIGH);
            digitalWrite(led3, LOW);
            break;

          case 3:
            digitalWrite(led1, LOW);
            digitalWrite(led2, LOW);
            digitalWrite(led3, HIGH);
            break;

          case 4:
            digitalWrite(led1, LOW);
            digitalWrite(led2, LOW);
            digitalWrite(led3, LOW);
            var = 0;  // Next step goes to 1
            break;
        }
      }
    }

    ButtonStateChangeTime = currentTime;
  }

  if (ButtonBeingTimed && ButtonWasPressed)
  {
    // Button is still pressed
    if (currentTime - ButtonStateChangeTime >= LongPressInterval)
    {
      ButtonBeingTimed = false;
      // Long Press
      // Turn off the lights
      digitalWrite(led1, LOW);
      digitalWrite(led2, LOW);
      digitalWrite(led3, LOW);
      var = 0; // Next step is 1
    }
  }
}