Delay issue (millis usage)

Hello everyone,

I have a small question. It's actually a very simple code, but I couldn't figure out one point.

There are 4 LEDs and 1 button. each one representing time. Like 5 minutes, 10 minutes, 15 minutes, 20 minutes. Each time you press the button, the relevant LED lights up and the time is selected. When the 2nd button is pressed, the relay will remain open for the selected time and will automatically close at the end of the time.

I'm using "delay" here. But since it stops the entire flow, I cannot stop the process by pressing a 3rd button (or pressing the 2nd button once more).

I tried using Millis, but it didn't work as I wanted. It does the first part of the condition, but the relay pin does not close at the end of the time.

Thanks in advance for your help

int ledPin[4] = {8, 9, 10, 11};
int Relay = 7;
int dk=5;   // Minute first value

unsigned long oldtime = 0;
unsigned long newtime;
unsigned long interval = 200;

const int buttonPin1 = 12;  // Min Select
const int buttonPin2 = 13;	// Start
const int buttonPin3 = 6;   // Stop Test

int buttonState1 = HIGH;
int buttonState2 = HIGH;
int buttonState3 = HIGH;

int pushCounter = 0;
int numberOfLED = 4;

void setup() {
  pinMode(Relay,OUTPUT);
  Serial.begin(9600);

  pinMode(buttonPin1, INPUT);
  pinMode(buttonPin2, INPUT);
  pinMode(buttonPin2, INPUT);
  
  for (int i = 0; i <= 4; i++) {
    pinMode(ledPin[i], OUTPUT);
  }
}



void loop() {
 
 newtime = millis();
 // Serial.println(millis());
   
  
  buttonState1 = digitalRead(buttonPin1);
  buttonState2 = digitalRead(buttonPin2);
  buttonState3 = digitalRead(buttonPin3);
  
  if (buttonState1 == LOW) {
    for (int i = 0; i < numberOfLED; i++) {
      
      if (pushCounter % numberOfLED == i) {
        digitalWrite(ledPin[i], HIGH);
        dk=i; // Min Select (5,10, 15, 20)
      }
      else {
        digitalWrite(ledPin[i], LOW);
      }
    }
    pushCounter++;
    delay(300);
  }
  
 //   Serial.println(newtime);
 //   Serial.println(oldtime);
  
  
// 5 min condition  
    
    if (dk==0 && buttonState2 == LOW) {
          Serial.println("5 min");

    	  digitalWrite(Relay,HIGH);         // Relay ON
		  delay(3000);                   	// Wait 5 min
		  digitalWrite(Relay,LOW);			// Relay OFF
 
    	  digitalWrite(ledPin[0], LOW);  	// 5 min led OFF
   		  pushCounter = 0;					// LED Count zero
    	  dk=5;
        }
          
// 10 min condition          
  
  if (dk==1 && buttonState2 == LOW) {
          Serial.println("10 min");

    	  digitalWrite(Relay,HIGH);         // Relay ON
		  delay(4000);                   	// Wait 10 min
		  digitalWrite(Relay,LOW);			// Relay OFF
 
    	  digitalWrite(ledPin[1], LOW);  	// 10 min led OFF
   		  pushCounter = 0;					// LED Count zero
    	  dk=5;
        }
  
// 15 min condition  
  
  if (dk==2 && buttonState2 == LOW) {
          Serial.println("15 min");

    	  digitalWrite(Relay,HIGH);         // Relay ON
		  delay(5000);                   	// Wait 15 min
		  digitalWrite(Relay,LOW);			// Relay OFF
 
    	  digitalWrite(ledPin[2], LOW);  	// 15 min led OFF
   		  pushCounter = 0;					// LED Count zero
    	  dk=5;
        }
  
  // 20 min condition  
  
  if (dk==3 && buttonState2 == LOW) {
          Serial.println("20 min");

    	  digitalWrite(Relay,HIGH);         // Relay ON
		  delay(6000);                   	// Wait 20 min
		  digitalWrite(Relay,LOW);			// Relay OFF
 
    	  digitalWrite(ledPin[3], LOW);  	// 20 min led OFF
   		  pushCounter = 0;					// LED Count zero
    	  dk=5;
          
        }
  

  
 // 3rd button stop testing
  
  if (buttonState3 == LOW) { 
      digitalWrite(Relay,LOW);
      digitalWrite(ledPin[0], LOW);  	
   	  pushCounter = 0;					
      dk=5;
         
  }
}

imho you should debounce the select button and start your sequence after the start button was pressed.

something like:

/* Timer with 4 LEDs and 3 Buttons
    https://forum.arduino.cc/t/delay-issue-millis-usage/1264070

   2024-05-25 by noiasca

   to be deleted 2024-09 
   https://wokwi.com/projects/398844668015443969
*/

class Button {                                // a simple class for buttons based on the "Debounce" example
    const uint8_t buttonPin;                  // the GPIO / pin for the button
    static constexpr byte debounceDelay = 30; // the debounce time; Static because we only need one value for all buttons
    const bool active;                        // is the pin active HIGH or active LOW (will also activate the pullups!)
    bool lastButtonState = HIGH;              // the previous reading from the input pin
    uint32_t lastDebounceTime = 0;            // the last time the output pin was toggled

  public:
    /**
       \brief constructor for a button

       The constructor takes the GPIO as parameter.
       If you omit the second parameter, the class will activate the internal pullup resistor
       and the button should connect to GND.
       If you set the second parameter to HIGH, the button is active HIGH.
       The button should connect to VCC.
       The internal pullups will not be used but you will need an external pulldown resistor.

       \param buttonPin the GPIO for the button
       \param active LOW (default) - if button connects to GND, HIGH if button connects to VCC
    */
    Button(uint8_t buttonPin, bool active = LOW) : buttonPin(buttonPin), active(active) {}

    /**
       \brief set the pin to the proper state

       Call this function in your setup().
       The pinMode will be set according to your constructor.
    */
    void begin() {
      if (active == LOW)
        pinMode(buttonPin, INPUT_PULLUP);
      else
        pinMode(buttonPin, INPUT);
    }

    /**
        \brief indicate if button was pressed since last call

        @return HIGH if button was pressed since last call - debounce
    */
    bool wasPressed() {
      bool buttonState = LOW;                                        // the current reading from the input pin
      byte reading = LOW;                                            // "translated" state of button LOW = released, HIGH = pressed, despite the electrical state of the input pint
      if (digitalRead(buttonPin) == active) reading = HIGH;          // if we are using INPUT_PULLUP we are checking invers to LOW Pin
      if ((millis()  - lastDebounceTime) > debounceDelay) { // If the switch changed, AFTER any pressing or noise
        if (reading != lastButtonState && lastButtonState == LOW) {  // If there was a change and and last state was LOW (= released)
          buttonState = HIGH;
        }
        lastDebounceTime = millis();
        lastButtonState = reading;
      }
      return buttonState;
    }
};

const uint8_t ledPin[4] = {8, 9, 10, 11};        // indicator LEDs
const uint8_t interval[4] = {5, 10, 15, 20};     // interval for each LED
const uint32_t factor = 1000;                    // 60 * 1000UL for minutes
const uint8_t relayPin = 7;                      // GPIO for the output
const uint8_t buttonPin1 = 12;                   // Min Select
const uint8_t buttonPin2 = 13;	                 // Start
const uint8_t buttonPin3 = 6;                    // Stop Test

enum State {IDLE, RUNNING} state;
int8_t presses = 0;                              // counts how often the select button was pressed
uint32_t previousMillis;                         // time management

Button selectBtn{buttonPin1};
Button startBtn{buttonPin2};
Button stopBtn{buttonPin3};

// switches some indicator LEDs on:
void indicate(byte count = state) {
  for (size_t i = 0; i < sizeof(ledPin) / sizeof(ledPin[0]); i++ ) {
    if (count >= i)
      digitalWrite(ledPin[i], HIGH);
    else
      digitalWrite(ledPin[i], LOW);
  }
}

void runFSM() {
  switch (state) {
    case IDLE :
      if (selectBtn.wasPressed()) {
        presses = ++presses % (sizeof(ledPin) / sizeof(ledPin[0]));
        indicate(presses);
        Serial.println(presses);
      }
      if (startBtn.wasPressed()) {
        Serial.print("start for "); Serial.print(interval[presses] * factor); Serial.println(" ms");
        state = RUNNING;
        previousMillis = millis();
        digitalWrite(relayPin, HIGH);
      }
      break;

    case RUNNING :
      if ((millis() - previousMillis > (interval[presses] * factor)) || stopBtn.wasPressed() ) {
        Serial.println("stop");
        state = IDLE;
        digitalWrite(relayPin, LOW);
        presses = 0;
        indicate(state);
      }
      // optional update indicator
      break;
  }
}

void setup() {
  Serial.begin(115200);
  selectBtn.begin();
  startBtn.begin();
  stopBtn.begin();
  pinMode(relayPin, OUTPUT);
  indicate(state);
}

void loop() {
  runFSM();
}

I would use a state machine, because I assume you will come up with new requirements when you got i working.

when you have started the 20 minute sequence, do you expect that after 5 minutes the 20minutes LED should be switched off and the 15 minute LED should be activated as indicator for the remaining time?

Hello cyberguy

Welcome to worldbest Arduino forum ever :wink:

What is the task of the sketch in real life?

Hi thanks for your reply. No if its 20 min selected 20 led will active 20 min then turn off with relay together. And so on ..thanks

Then I would assume my sketch does what you have written.

You mention millis()…
Can you show us your best code with millis?
We can move forward from that, so we can se what you understand.

I think that you have much to learn about handling buttons and button presses.

It is OK to have a very short delay (maybe 10 or 20 milliseconds) for the purpose of switch debouncing. Long delays cause you to risk missing button presses.

You need to keep track of when a button becomes pressed, rather than only when it is being pressed. If you are going to have a variable called buttonState1 to keep track of the current state of button 1, then you should probably also have a variable called something like oldButtonState1 to keep track of the previous state of button 1, and I would expect that near the end of your loop() you would have something like this:

  // remember old button states for next time through loop()
  oldButtonState1 = buttonState1;
  oldButtonState2 = buttonState2;
  oldButtonState3 = buttonState3;

You can tell that a button has become pressed when the old button state shows that it was not being pressed, but the new button state shows that it is being pressed.

this is a small boat, motor will run 5 or 10 or 15 or 20 min then stops. codes are working as I expected. But I want to stop manually if needed. Thanks

1 Like

Hi Thanks for advice, I will consider. Now codes are working as expected. Only I need to stop manually when needed thanks

Wow this is excatly what I wanted. I appriciate for your help. regards.

Yes. In fact, all could be done with a single button, it's all in how your code interprets the context. Consider:
1st button press, timer = 5min
If, within 5s, the button is pressed again, add 5 min to ghe timer

Do that until the button isn't pressed within 5s. Now, the button becomes a "clear timer" button.

If the timer runs down to 0, or the button is pressed, turn off outputs and begin monitoring for another start event.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.