Delay turning LED off after button press

Hello all, This is my first ever project and i'm still very new to programming. I have a basic setup of 4 buttons, 4 relays and 4 LED's. When button 1 is press it activates relay1 and LED 1. Pressing button 2 would deactivate relay 1 and LED 1 and activate relay 2 and LED 2 etc.

The code below is doing just that, but now I would like to introduce a function that after a time delay the LED (whichever is on) would switch off. For testing purposes this can be set to 5 sec but for the project i would like 10 mins.

This is completely out of my scope and would like some help, please.

#define LED_PIN1 6
#define LED_PIN2 7
#define LED_PIN3 12


int relay1 = 11; //Amp CH1 relay
int relay2 = 10; //Amp CH2 relay
int relay3 = 9; //Amp CH3 relay
int relay4 = 8; //Amp CH4 relay
int button1 = 2; //CH1 Button
int button2 = 3; //CH2 Button
int button3 = 4; //CH3 Button
int button4 = 5; //CH4 Button

void setup()
{
// Initialize digital output pins for relays:
pinMode(relay1, OUTPUT);
pinMode(relay2, OUTPUT);
pinMode(relay3, OUTPUT);
pinMode(relay4, OUTPUT);

// Initialize digital inputs with internal pullup resistors:
pinMode(button1, INPUT_PULLUP);
pinMode(button2, INPUT_PULLUP);
pinMode(button3, INPUT_PULLUP);
pinMode(button4, INPUT_PULLUP);

//Set the initial startup to OFF:
digitalWrite(relay1, HIGH);
digitalWrite(relay2, HIGH);
digitalWrite(relay3, HIGH);
digitalWrite(relay4, HIGH);

// Initialize LED output pin:
pinMode(LED_PIN1, OUTPUT);
pinMode(LED_PIN2, OUTPUT);
pinMode(LED_PIN3, OUTPUT);
}

void loop()
{
//Button 1 Enable
  bool button1State ;
  button1State = digitalRead(button1);
  if(button1State == LOW)
  {
    digitalWrite(relay1, LOW);
    digitalWrite(relay2, HIGH);
    digitalWrite(relay3, HIGH);
    digitalWrite(relay4, HIGH);
    digitalWrite(LED_PIN1, HIGH);
    digitalWrite(LED_PIN2, LOW);
    digitalWrite(LED_PIN3, LOW);
  }
//Button 2 Enable
  bool button2State ;
  button2State = digitalRead(button2);
  if(button2State == LOW)
  {
    digitalWrite(relay2, LOW);
    digitalWrite(relay1, HIGH);
    digitalWrite(relay3, HIGH);
    digitalWrite(relay4, HIGH);
    digitalWrite(LED_PIN1, LOW);
    digitalWrite(LED_PIN2, HIGH);
    digitalWrite(LED_PIN3, LOW);
  }
//Button 3 Enable
  bool button3State ;
  button3State = digitalRead(button3);
  if(button3State == LOW)
  {
    digitalWrite(relay3, LOW);
    digitalWrite(relay1, HIGH);
    digitalWrite(relay2, HIGH);
    digitalWrite(relay4, HIGH);
    digitalWrite(LED_PIN1, LOW);
    digitalWrite(LED_PIN2, LOW);
    digitalWrite(LED_PIN3, HIGH);
  }
//Button 4 Enable
  bool button4State ;
  button4State = digitalRead(button4);
  if(button4State == LOW)
  {
    digitalWrite(relay4, HIGH);
    digitalWrite(relay1, HIGH);
    digitalWrite(relay2, HIGH);
    digitalWrite(relay3, HIGH);
    digitalWrite(LED_PIN1, LOW);
    digitalWrite(LED_PIN2, LOW);
    digitalWrite(LED_PIN3, LOW);
  }
}

I know this is probably written poorly and could be done with a few lines of coding, so after the problem has been solved, is there a better way of writing the above code?

Thank you for your help.

Here is a demo-code for a so called mono-flop.

A Monoflop is a functionality that does

  • wait for an input-signal
  • if input-signal is detected optional wait some time before the main action is done
  • do the main action (switch on LED)
  • keep LED switched on for a certain time
  • switch off LED
  • optional stay "silent" if a new input-pulse comes in and ignore input-pulses until a certain time is over
const byte triggerInputPin  = 3;
const byte triggerIsActive = LOW; // LOW o HIGH

const byte PinToSwitch     = 12;
const byte OutPutOn  = HIGH;
// the attention-mark ! is the not-operator which inverts the value
// !true = false  !false = true     !HIGH = LOW    !LOW = HIGH
const byte OutPutOff = !OutPutOn; 

// set InitialWaitTime to 0 if you don't want a delay between triggering and switching output
unsigned long InitialWaitTime = 2000; // set to zero if you don't want an initial wait
unsigned long ActiveTime = 3000;
unsigned long LockedTime = 10000; // set to zero if you don't want a lock-time

unsigned long MonoFlopTimer; // variable used for non-blocking timing

// constants of the state-machine
const byte sm_Idling      = 0;
const byte sm_InitialWait = 1;
const byte sm_Activated   = 2;
const byte sm_Locked      = 3;

byte MonoFlopState = sm_Idling; // state-variable of the state-machine


void PrintFileNameDateTime() {
  Serial.println( F("Code running comes from file ") );
  Serial.println( F(__FILE__) );
  Serial.print( F("  compiled ") );
  Serial.print( F(__DATE__) );
  Serial.print( F(" ") );
  Serial.println( F(__TIME__) );
}


// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - startOfPeriod >= TimePeriod ) {
    // more time than TimePeriod has elapsed since last time if-condition was true
    startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    return true;
  }
  else return false;            // actual TimePeriod is NOT yet over
}

unsigned long MyTestTimer = 0;                   // Timer-variables MUST be of type unsigned long
const byte    OnBoard_LED = 13;


void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
  static unsigned long MyBlinkTimer;
  pinMode(IO_Pin, OUTPUT);

  if ( TimePeriodIsOver(MyBlinkTimer, BlinkPeriod) ) {
    digitalWrite(IO_Pin, !digitalRead(IO_Pin) );
  }
}


void setup() {
  Serial.begin(115200);
  Serial.println("Setup-Start");
  PrintFileNameDateTime();
  pinMode(triggerInputPin,INPUT_PULLUP); // INPUT_PULLUP
  digitalWrite(PinToSwitch,OutPutOff);
  pinMode(PinToSwitch,OUTPUT);
}


void MonoFlopStateMachine() {

  switch (MonoFlopState) {

    case sm_Idling:
      // check if trigger-input reads triggering signal
      if (digitalRead(triggerInputPin) == triggerIsActive) {
        MonoFlopTimer = millis();       // make snapshot of time
        MonoFlopState = sm_InitialWait; // set next state of state-machine
        Serial.println( F("trigger-signal detected start inital wait") );
      }
      break; // IMMIDIATELY jump down to END OF SWITCH

    case sm_InitialWait:
      // wait until initial waittime has passed by
      if ( TimePeriodIsOver(MonoFlopTimer,InitialWaitTime) ) {
        // if waittime HAS passed by
        MonoFlopTimer = millis();           // make new snapshot of time
        digitalWrite(PinToSwitch,OutPutOn); // switch IO-Pin to activated state
        MonoFlopState = sm_Activated;       // set next state of state-machine
        Serial.println( F("InitialWaitTime is over switching output to ACTIVE") );
      }
      break; // IMMIDIATELY jump down to END OF SWITCH

    case sm_Activated:
      // check if time the IO-pin shall be active is over
      if ( TimePeriodIsOver(MonoFlopTimer,ActiveTime) ){
        // if activetime of IO-pin IS over
        MonoFlopTimer = millis();             // make new snapshot of time
        digitalWrite(PinToSwitch,OutPutOff);  // switch IO-pin to DE-activated state
        MonoFlopState = sm_Locked;            // set next state of state-machine
        Serial.println( F("Activetime is over switching output to IN-active") );
        Serial.println( F("and starting locktime") );
      }
      break; // IMMIDIATELY jump down to END OF SWITCH

    case sm_Locked:
      // check if time the logic shall be locked against too early re-triggering is over
      if ( TimePeriodIsOver(MonoFlopTimer,LockedTime) ){
        // if locked time IS over
        Serial.println( F("locktime is over change state to idling") );
        MonoFlopState = sm_Idling; // set state-machine to idle-mode
      }
      break; // IMMIDIATELY jump down to END OF SWITCH
  } // END OF SWITCH
}


void loop() {
  BlinkHeartBeatLED(OnBoard_LED, 250);

  // run down code of state-machine over and over again
  // all the logic for reading in sensor-signal and switching output ON/OFF 
  // is inside the function
  MonoFlopStateMachine(); 
}

This goes beyond what you have asked for.
This demo-code shows two important concepts

  1. non-blocking timing
  2. state-machine

here are a tutorials for both

best regards Stefan

look this over
i'm not sure what you're doing with the LEDs

#undef MyHW
#ifdef MyHW
# include "sim.hh"

const byte PinBut [] = { A1, A2, A3 };
const byte PinRly [] = { 10, 11, 13 };

#else
const byte PinBut [] = { 2, 3, 4, 5 };
const byte PinRly [] = { 11, 10, 9, 8 };
// const byte PinLed [] = { 6, 7, 12 };
#endif

unsigned long msecPeriod;
unsigned long msecLst;

enum { Off = HIGH, On = LOW };

// -----------------------------------------------------------------------------
void
rlysOff (void)
{
    for (unsigned n = 0; n < sizeof(PinBut); n++)
        digitalWrite (PinRly [n], Off);
}

// -----------------------------------------------------------------------------
void loop()
{
    // check if timer enabled and turn off LEDs if expired
    unsigned long msec = millis ();
    if (msecPeriod && msec - msecLst >= msecPeriod)  {
        msecPeriod = 0;
        rlysOff ();
    }

    // check for button press
    for (unsigned n = 0; n < sizeof(PinBut); n++)  {
        if (LOW == digitalRead (PinBut [n]))  {
            rlysOff ();
            digitalWrite (PinRly [n], On);

            msecPeriod = 3000;
            msecLst    = msec;
        }
    }
}

// -----------------------------------------------------------------------------
void setup()
{
    Serial.begin (9600);

    for (unsigned n = 0; n < sizeof(PinBut); n++)  {
        pinMode (PinBut [n], INPUT_PULLUP);
        pinMode (PinRly [n], OUTPUT);
        digitalWrite (PinRly [n], Off);
    }
}

AKA re-triggerable or non-retriggerable monostable multivibrator. :slight_smile:

1 Like

@rickkap428 If all this seem a little too complicated just say so. We should be able to make this understandable for a newcommer.

If you don't mind freezing the system for 10 minutes, you can code your function with only a few lines, like:

digitalWrite(relay, HIGH); // turn relay on
delay(1000L * 60 * 10); // wait 10 minutes
digitalWrite(relay, LOW); // turn relay off

(just an example)

The bullet list in reply #2 explains in very clear language the functionality that you requested. It's a good starting point to stimulate your thinking processes.

Yes this has gone over my head.

From the examples that some of you have written, i was hoping to keep the code that i have as this seems to be working fine. I just wanted to add the function of the selected LED turning off after 10 mins (for the test 5 secs).

I still want to be able to press other buttons so the delay() function wouldn't work for me.

You should at least learn how to use millis for timing See this and study the Non-blocking section
https://www.norwegiancreations.com/2017/09/arduino-tutorial-using-millis-instead-of-delay/

Hello rickkap428

Do you have experience with programming in C++?

The task can easily be realised with an object.
A structured array contains all the information, such as the pin addresses for the I/O devices, as well as the information for the timing.
A single service takes care of this information and initiates the intended action.
The structured array makes the sketch scalable until all I/O pins are used up without having to adapt the code for the service.
It is cool stuff, isn´t it?

Have a nice day and enjoy coding in C++.

1 Like

Hi,

No i don't have any programming experience. I only started leaning this about a month ago and as you can see from the above coding, it's probably not the best written.

As for millis i will have a look at it but again i think this is going to be too complicated for me to do.

Regards

It's not too complicated. You only need to add 3 lines of code to make it work using millis.

the code is very redundant so any modifications need to be made in multiple places. using arrays can both minimize the code (logic) while at the same time expand it to easily handle more I/O

can you post what you're suggesting?

wouldn't a timer need to be restarted after each of the 4 button reads?

Hello rickkap428

I would like to publish a sketch, but the Advocatus for beginners does not like to see beginners being made too smart at the beginning.

1 Like

Yes, let's all see what @rickkap428 comes up with. He won't learn if he doesn't even try.

i'm trying to imagine what 3 lines of code can be added to fix the problem. i'd hate to waste an afternoon trying

@rickkap428

There were already lot of hints in this thread.
Basically you describe a Monoflop ("should switch off")
Furthermore you describe a "Radio Button" used in older days for switching bands or frequencies where one button releases another button ("Pressing button 2 would deactivate relay 1 ").

A Monoflop consists of an input and an output.
Furthermore you will need some time management to release a button by time.

Well combine the Monoflop with an interlocking/release method and you are ready.

/*
  Interlocking Monoflop

  https://forum.arduino.cc/t/delay-turning-led-off-after-button-press/1105635/
  2023-03-23 by noiasca
  to be deleted: 2023-06
*/

void release(); // only a prototype - just the signature of a function implemented later in the code

// a class for one LED one button
class Monoflop {
    const uint8_t buttonPin;  // the input GPIO, active LOW
    const uint8_t relayPin;   // the output GPIO, active 
    uint32_t previousMillis = 0; // time management
    bool state = false;       // active or inactive
  public:
    Monoflop(uint8_t buttonPin, uint8_t relayPin) : buttonPin{buttonPin}, relayPin{relayPin}
    {}

    void begin() {                         // to be called in setup()
      pinMode(buttonPin, INPUT_PULLUP);
      pinMode(relayPin, OUTPUT);
    }

    void off() {                           // handles switching off 
      digitalWrite(relayPin, LOW);
      state = false;
    }

    void update(uint32_t currentMillis = millis()) {   // to be called in loop()
      uint8_t buttonState = digitalRead(buttonPin);
      if (buttonState == LOW) {
        release();
        previousMillis = currentMillis;
        digitalWrite(relayPin, HIGH);
        state = true;
      }
      if (state && currentMillis-previousMillis > 1000) {
        off();
      }
    }
};

//create 3 indicators (each with one button and one relay/LED)
Monoflop monoflop[] {
  {A0, 12},  // buttonPin, relayPin
  {A1, 11},
  {A2, 10},
};

void release(){    // release all others
  for (auto &i : monoflop) i.off();
}

void setup() {
  for (auto &i : monoflop) i.begin();
}

void loop() {
  for (auto &i : monoflop) i.update();
}

Just to play around:

A single line in setup() and loop() - I don't think it will get much shorter :wink:
P.S.: I know it's not the shortest nor the cleanest code. But if you follow that idea I think we can elaborate to improve it even more.

P.SS.: When ever you try to start to name variables 1, 2, 3 it will be far better if you just use an array. I prepared 3 Monoflops only - you should be able to add a 4th Monoflop with one single line.

Do you a problem with the help offered here?

Thank you for your help, this has certainly made a lot of sense and with the visual simulation it really helps.

One question, is it easy to add the 4 relays to the coding to be able to turn on and remain on when a button is pressed?

what do you mean by that?
Currently the Monoflop is "retriggerable", it will start the timer over and over again as long as you press the button. OK, the call of the release() in case of the button press is not very clean, but if you just call the release() with a parameter of the current outputPin you can use that information to omit the switch off of this current pin. Not really an issue.

or do you mean something different?

Please run again the simulation in wokwi and tell ...