Using millis() to timeout/reset loop, after button triggered

Hello fellow coders and newbies! Hopefully this works out in everyones best interest!

I myself am most definitely a newbie and hoping for a little schooling in the right direction. I’ve done enough reading to alter this code I’ve found for sequencing buttons but am having trouble applying the millis() function into the code.

My goal (as best I can explain it) in english first:

  • 4 buttons triggered in a specific order turns on led and waits for reset button (wrong order resets waiting for another entry)
  • Once any of the 4 buttons are triggered, I would like “x seconds” of time to pass and then have the loop restart.

i.e. if one button is triggered and left for 10 seconds the program goes back to the top waiting for new entry. if 3 buttons are triggered and 10 seconds passes still go back to the top and wait for new entry.

//CONSTANTS
const int button1 = 2; //first button is on pin 2
const int button2 = 3; //second is on pin 3
const int button3 = 4; //third is pin 4
const int button4 = 5; //fourth is pin 5
const int button5 = 6; //reset button
const int LED[] = {14,15,16,17};
const int greenLed = 9; //green LED is pin 9
void checkEntered1(int button);

//VARIABLES
int code[] = {1,2,3,4}; //the desired code is entered in this array, separated by commas
int read[] = {0,0,0,0}; //array for each switch to only activate once per reset
int entered[5]; //create a new empty array for the code

// MILLIS
unsigned long previousMillis = 0;
unsigned long currentMillis = millis();
unsigned long period = 10000;

// SETUP
void setup(){ //run once at sketch startup
  Serial.begin(9600); //begin Serial

  pinMode(button1, INPUT_PULLUP); //button 1 is an input
  pinMode(button2, INPUT_PULLUP); //button 2 is an input
  pinMode(button3, INPUT_PULLUP); //button 3 is an input
  pinMode(button4, INPUT_PULLUP); //button 4 is an input
  pinMode(button5, INPUT_PULLUP); //reset button
  
  pinMode(greenLed, OUTPUT); // the green LED is an output
  
  for (int i = 0; i < 4;i++){ //work through numbers 0-3
    Serial.println(code[i]); //print each digit of the code
    Serial.println(entered[i]); //print each element of the entered[]
                                //array (this was for me to check that it 
                                //started at 0
    pinMode(LED[i],OUTPUT);
  }
}

//LOOP
void loop(){ //run repeatedly
  currentMillis = millis();
  
  if (digitalRead(button1) == LOW && read[0] !=1 ){ //if button1 is pressed
    read[0]=1;
    checkEntered1(1); //call checkEntered and pass it a 1
    previousMillis = currentMillis;
  }

  else if (digitalRead(button2) == LOW && read[1] !=1 ){ //if button2 is pressed
    read[1]=1;
    checkEntered1(2); //call checkEntered1 and pass it a 2
    previousMillis = currentMillis;
  }
  
  else if (digitalRead(button3) == LOW && read[2] !=1 ){ //if button3 is pressed
    read[2]=1;
    checkEntered1(3); //call checkEntered1 and pass it a 3
    previousMillis = currentMillis;
  }
  
  else if (digitalRead(button4) == LOW && read[3] !=1 ){ //if button4 is pressed
    read[3]=1;
    checkEntered1(4); //call checkEntered1 and pass it a 4
    previousMillis = currentMillis;
   
  }
  else if (digitalRead(button5) == LOW){
    reset(); // button inputs reset to be triggered again
    close_all(); //turn off all LED's
    digitalWrite(greenLed, LOW); // Turn on maglock
  }
  
  else if(currentMillis - previousMillis >= period);{
    reset();
    close_all;
    loop();
    previousMillis = currentMillis;
  }
}

void checkEntered1(int button){ //check the first element of the entered[] array
  digitalWrite(LED[button-1],HIGH);
  if (entered[0] != 0){ //if it is not a zero, i.e. it has already been inputted
    checkEntered2(button); //move on to checkEntered2, passing it "button"
  }
  
  else if(entered[0] == 0){ //if it is zero, i.e. if it hasn't been defined with a button yet
    entered[0] = button; //set the first element as the button that has been pressed
    Serial.print("1: ");Serial.println(entered[0]); //for debugging
  }
  
}

void checkEntered2(int button){ //check the second element of the entered[] array
  digitalWrite(LED[button-1],HIGH);
  if (entered[1] != 0){ //if it is not a zero, i.e. it has already been inputted
    checkEntered3(button); //move on to checkEntered3, passing it "button"
  }
  
  else if(entered[1] == 0){ //if it is zero, i.e. if it hasn't been defined with a button yet
    entered[1] = button; //set the second element as the button that has been pressed
    Serial.print("2: ");Serial.println(entered[1]); //for debugging
  }
  
}

void checkEntered3(int button){  //check the third element of the entered[] array
  digitalWrite(LED[button-1],HIGH);
  if (entered[2] != 0){ //if it is not a zero, i.e. it has already been inputted
    checkEntered4(button); //move on to checkEntered4, passing it "button"
  }
  
  else if (entered[2] == 0){ //if it is zero, i.e. if it hasn't been defined with a button yet
    entered[2] = button; //set the third element as the button that has been pressed
    Serial.print("3: ");Serial.println(entered[2]); //for debugging
  }
  
}

void checkEntered4(int button){  //check the third element of the entered[] array
  digitalWrite(LED[button-1],HIGH);
  if (entered[3] == 0){ //if it is zero, i.e. if it hasn't been defined with a button yet
    entered[3] = button; //set the third element as the button that has been pressed
    Serial.print("4: ");Serial.println(entered[3]); //for debugging
    delay(100); //allow time for processing
    compareCode(); //call the compareCode function
  }
}

void compareCode(){ //checks if the code entered is correct by comparing the code[] array with the entered[] array
  for (int i = 0; i<4;i++){ //these three lines are for debugging
    Serial.println(entered[i]);
  }
  if ((entered[0]==code[0]) && (entered[1]==code[1]) && (entered[2]==code[2]) && (entered[3]==code[3])){ //if all the elements of each array are equal
    digitalWrite(greenLed, HIGH); //turn the green LED on

    for (int i = 0; i < 5; i++){ //this next loop is for debugging
      entered[i] = 0;
      
    }
  
    loop(); //return to loop() (not really necessary)
  }
  
  else { //if you (or the intruder) get the code wrong
    
    for (int i = 0; i < 5; i++){ //this next loop is for debugging
      entered[i] = 0;
     
    }
    delay(1000);
    close_all();
    reset();
  }

}

void reset(){
    read[0]=0;
    read[1]=0;
    read[2]=0;
    read[3]=0;
}

void close_all(){
digitalWrite(LED[0],LOW);
digitalWrite(LED[1],LOW);
digitalWrite(LED[2],LOW);
digitalWrite(LED[3],LOW);

}

I realize that my millis() code is kind of scattered, I’m not exactly sure where to put it… (the reason for the post) I’ve copied and pasted and cut every which way but to no avail. Can someone lend a hand? Thank you so much in advanced for your efforts and time!

When one of the buttons is pressed, store the millis() value. That part is okay.
In the loop() check for a timeout. That part is also okay.
Store the buttons in an array. You also need that.

Is that timeout code running repeatedly with the 'period' interval when nothing is pressed ?
Therefor I prefer to add a 'bool' variable, to enable/disable the timeout.
I also prefer to run the timeout timer in the loop() without the 'else'.

bool enableTimeout;


// start timeout timer with button 1...4 is pressed
if( ...
{
  previousMillis = currentMillis;
  enableTimeout = true;


// stop timeout timer when button 5 is pressed
if( ...
{
  enableTimeout = false;


// check timeout timer
if( enableTimeout)
{
  if(currentMillis - previousMillis >= period)
  {
    ...
    enableTimeout = false;  // timeout timer stops itself when finished.

Since the timeout timer stops itself by making the 'bool' variable false, the 'previousMillis' value has no meaning after that. So you don't need a "previousMillis = currentMillis" in the timeout timer anymore.

The State Change Detection is used to turn the state of a button into a event for pressing or releasing. You could do that for every button, using an array for the buttons and an array for the last-button-states.

I think your sketch can benefit from the Bounce2 library. It has the State Change Detection already included.

The checkEntered() functions are confusing. That makes the program flow harder to understand. I have however not a good idea at the moment how to do that better :frowning:

This is a bug:

else if(currentMillis - previousMillis >= period);{

The ';' is an empty line within the condition. Because of the ';', the '{' and '}' after that have no meaning and are not part of the 'if' statement.

You have a major bug:
Never call loop() from within the loop() :stuck_out_tongue_closed_eyes:

Why are the button pin numbers not in an array?

const int button1 = 2; //first button is on pin 2
const int button2 = 3; //second is on pin 3
const int button3 = 4; //third is pin 4
const int button4 = 5; //fourth is pin 5
const int button5 = 6; //reset button

By giving each a different name you force yourself to write a lot of extra code.

Why so many ints to hold small values? Arduino variable type byte can hold 0 to 255.

I see millis() timing and delay() blocking mixed. What code did you start with?

Thank you so much Koepel for your quick response and knowledge! After playing with your suggestions for about 2 hours last night, I still have some quirks. The timeout isn’t occurring and the program is running as if I didn’t put the bool in there at all…

Do I have to define “enableTime” somewhere?
Also :-* :stuck_out_tongue_closed_eyes: noted… no loop within a loop!

//CONSTANTS
const int button1 = 2; //first button is on pin 2
const int button2 = 3; //second is on pin 3
const int button3 = 4; //third is pin 4
const int button4 = 5; //fourth is pin 5
const int button5 = 6; //reset button
const int LED[] = {14,15,16,17};
const int greenLed = 9; //green LED is pin 9
void checkEntered1(int button);
bool enableTimeout;

//VARIABLES
int code[] = {1,2,3,4}; //the desired code is entered in this array, separated by commas
int read[] = {0,0,0,0}; //array for each switch to only activate once per reset
int entered[5]; //create a new empty array for the code

// MILLIS
unsigned long previousMillis = 0;
unsigned long currentMillis = millis();
unsigned long period = 5000;

// SETUP
void setup(){ //run once at sketch startup
  Serial.begin(9600); //begin Serial

  pinMode(button1, INPUT_PULLUP); //button 1 is an input
  pinMode(button2, INPUT_PULLUP); //button 2 is an input
  pinMode(button3, INPUT_PULLUP); //button 3 is an input
  pinMode(button4, INPUT_PULLUP); //button 4 is an input
  pinMode(button5, INPUT_PULLUP); //reset button
  
  pinMode(greenLed, OUTPUT); // the green LED is an output
  
  for (int i = 0; i < 4;i++){ //work through numbers 0-3
    Serial.println(code[i]); //print each digit of the code
    Serial.println(entered[i]); //print each element of the entered[]
                                //array (this was for me to check that it 
                                //started at 0
    pinMode(LED[i],OUTPUT);
  }
}

//LOOP
void loop(){ //run repeatedly
  
  currentMillis = millis();

  
  if (digitalRead(button1) == LOW && read[0] !=1 ){ //if button1 is pressed
    read[0]=1;
    checkEntered1(1); //call checkEntered1 and pass it a 1
    previousMillis = currentMillis;
    enableTimeout = true;
  }

  if (digitalRead(button2) == LOW && read[1] !=1 ){ //if button2 is pressed
    read[1]=1;
    checkEntered1(2); //call checkEntered1 and pass it a 2
    previousMillis = currentMillis;
    enableTimeout = true;
  }
  
  if (digitalRead(button3) == LOW && read[2] !=1 ){ //if button3 is pressed
    read[2]=1;
    checkEntered1(3); //call checkEntered1 and pass it a 3
    previousMillis = currentMillis;
    enableTimeout = true;
  }
  
  if (digitalRead(button4) == LOW && read[3] !=1 ){ //if button4 is pressed
    read[3]=1;
    checkEntered1(4); //call checkEntered1 and pass it a 4
    previousMillis = currentMillis;
    enableTimeout = true;
  }
  
  if (digitalRead(button5) == LOW){
    reset(); // button inputs reset to be triggered again
    close_all(); //turn off all LED's
    digitalWrite(greenLed, LOW); // Turn on maglock
    enableTimeout = false;
  }
  
  if (enableTimeout){
  	if (currentMillis - previousMillis >= period){
      reset();
      close_all;
      enableTimeout = false;
    }
  }
}

I am not sure how to create an array for the buttons, you said I also need that… Was state change something required or something that would work nicely in this circumstance?

Thanks again for all your help!

Hello GoForSmoke and thank you for taking the time to reach out! Here is the code I started with… the buttons are not in an array because that’s the way it was and I just changed a few things here and there. Credit to Asim Zulfiqar and High Voltages at Push Button Combination Lock Using Arduino - Arduino Project Hub for the base code!

const int button1 = 2; //first button is on pin 8
const int button2 = 3; //second is on pin 9
const int button3 = 4; //third is pin 10
const int button4 = 5; //fourth is pin 11
const int button5 = 6; //third is pin 10
const int button6 = 7; //fourth is pin 11
const int LED[] = {14,15,16,17,18,19};

const int Red = 8; //red LED is on pin 4
const int greenLed = 9; //green LED is pin 12
void checkEntered1(int button);

int code[] = {6,5,5,4,3,2}; //the desired code is entered in this array,
                        //separated by commas

int entered[7]; //create a new empty array for the code entered by
                //the user (has 4 elements)

void setup(){ //run once at sketch startup
  Serial.begin(9600); //begin Serial

  pinMode(button1, INPUT_PULLUP); //button 1 is an input
  pinMode(button2, INPUT_PULLUP); //button 2 is an input
  pinMode(button3, INPUT_PULLUP); //button 3 is an input
  pinMode(button4, INPUT_PULLUP); //button 4 is an input
  pinMode(button5, INPUT_PULLUP); //button 3 is an input
  pinMode(button6, INPUT_PULLUP); //button 4 is an input

  pinMode(Red, OUTPUT); //the red LED is an output
  pinMode(greenLed, OUTPUT); // the green LED is an output
//  setupLights(); //run the setupLights routine
//  setupLights(); //run it again
 // delay(650); //delay (only for effect, :P not needed)
  digitalWrite(Red, LOW); //turn the red LED on
  for (int i = 0; i < 6;i++){ //work through numbers 0-3
    Serial.println(code[i]); //print each digit of the code
    Serial.println(entered[i]); //print each element of the entered[]
                                //array (this was for me to check that it 
                                //started at 0
    pinMode(LED[i],OUTPUT);
  }
}

void loop(){ //run repeatedly
  if (digitalRead(button1) == LOW){ //if button1 is pressed
    checkEntered1(1); //call checkEntered and pass it a 1
    
    delay(250);//wait, needed for correct functioning, otherwise
               //buttons are deemed to be pressed more than once
    
  }
  else if (digitalRead(button2) == LOW){ //if button2 is pressed
    checkEntered1(2); //call checkEntered1 and pass it a 2
    
    delay(250); //wait
    
  }
  else if (digitalRead(button3) == LOW){ //if button3 is pressed
    checkEntered1(3); //call checkEntered1 and pass it a 3
    
    delay(250); //wait
    
  }
  else if (digitalRead(button4) == LOW){ //if button4 is pressed
    checkEntered1(4); //call checkEntered1 and pass it a 4
    
    delay(250); //wait
    
  }
    else if (digitalRead(button5) == LOW){ //if button4 is pressed
    checkEntered1(5); //call checkEntered1 and pass it a 4
    
    delay(250); //wait
    
  }
    else if (digitalRead(button6) == LOW){ //if button4 is pressed
    checkEntered1(6); //call checkEntered1 and pass it a 4
    
    delay(250); //wait
    
  }
  

}

void checkEntered1(int button){ //check the first element of the entered[] array
  digitalWrite(LED[button-1],HIGH);
  if (entered[0] != 0){ //if it is not a zero, i.e. it has already been inputted
    checkEntered2(button); //move on to checkEntered2, passing it "button"
  }
  
  else if(entered[0] == 0){ //if it is zero, i.e. if it hasn't been defined with a button yet
    entered[0] = button; //set the first element as the button that has been pressed
    Serial.print("1: ");Serial.println(entered[0]); //for debugging
  }
  
}

void checkEntered2(int button){ //check the second element of the entered[] array
  digitalWrite(LED[button-1],HIGH);
  if (entered[1] != 0){ //if it is not a zero, i.e. it has already been inputted
    checkEntered3(button); //move on to checkEntered3, passing it "button"
  }
  
  else if(entered[1] == 0){ //if it is zero, i.e. if it hasn't been defined with a button yet
    entered[1] = button; //set the second element as the button that has been pressed
    Serial.print("2: ");Serial.println(entered[1]); //for debugging
  }
  
}

void checkEntered3(int button){  //check the third element of the entered[] array
  digitalWrite(LED[button-1],HIGH);
  if (entered[2] != 0){ //if it is not a zero, i.e. it has already been inputted
    checkEntered4(button); //move on to checkEntered4, passing it "button"
  }
  
  else if (entered[2] == 0){ //if it is zero, i.e. if it hasn't been defined with a button yet
    entered[2] = button; //set the third element as the button that has been pressed
    Serial.print("3: ");Serial.println(entered[2]); //for debugging
  }
  
}

void checkEntered4(int button){  //check the third element of the entered[] array
  digitalWrite(LED[button-1],HIGH);
  if (entered[3] != 0){ //if it is not a zero, i.e. it has already been inputted
    checkEntered5(button); //move on to checkEntered4, passing it "button"
  }
  
  else if (entered[3] == 0){ //if it is zero, i.e. if it hasn't been defined with a button yet
    entered[3] = button; //set the third element as the button that has been pressed
    Serial.print("4: ");Serial.println(entered[3]); //for debugging
  }
  
}


void checkEntered5(int button){  //check the third element of the entered[] array
  digitalWrite(LED[button-1],HIGH);
  if (entered[4] != 0){ //if it is not a zero, i.e. it has already been inputted
    checkEntered6(button); //move on to checkEntered4, passing it "button"
  }
  
  else if (entered[4] == 0){ //if it is zero, i.e. if it hasn't been defined with a button yet
    entered[4] = button; //set the third element as the button that has been pressed
    Serial.print("5: ");Serial.println(entered[4]); //for debugging
  }
  
}

void checkEntered6(int button){ //check the fourth element of the entered[] array
  digitalWrite(LED[button-1],HIGH);
  if (entered[5] == 0){ //if it is zero, i.e. if it hasn't been defined with a button yet
    entered[5] = button; //set the final element as the button that has been pressed
    Serial.print("6: ");Serial.println(entered[5]); //for debugging
    delay(100); //allow time for processing
    compareCode(); //call the compareCode function
  }
}

void compareCode(){ //checks if the code entered is correct by comparing the code[] array with the entered[] array
  for (int i = 0; i<6;i++){ //these three lines are for debugging
    Serial.println(entered[i]);
  }
  if ((entered[0]==code[0]) && (entered[1]==code[1]) && (entered[2]==code[2]) && (entered[3]==code[3]) && (entered[4]==code[4])&& (entered[5]==code[5])){ //if all the elements of each array are equal
    digitalWrite(Red, LOW); // turn the red LED off
    digitalWrite(greenLed, HIGH); //turn the green LED on
    delay(1000); //wait for a bit
    digitalWrite(greenLed, LOW); //turn the green LED off


    
    for (int i = 0; i < 7; i++){ //this next loop is for debugging
      entered[i] = 0;
      
    }
   
    loop(); //return to loop() (not really necessary)
  }
  
  else { //if you (or the intruder) get the code wrong
    
    digitalWrite(Red,HIGH);
    delay(1000);
    digitalWrite(Red,LOW);
    Serial.println("Red OFF");
    for (int i = 0; i < 7; i++){ //this next loop is for debugging
      entered[i] = 0;
     
    }
    
  }
  close_all();
}



void close_all(){
digitalWrite(LED[0],LOW);
digitalWrite(LED[1],LOW);
digitalWrite(LED[2],LOW);
digitalWrite(LED[3],LOW);
digitalWrite(LED[4],LOW);
digitalWrite(LED[5],LOW);
}

In this particular example, delay was preventing the buttons from bouncing but would also prevent others from being triggered as well. Also the buttons could be triggered more than once. In my particular instance, I am looking for each button only to have one entry per loop/timeout. I eliminated 2 buttons, got some assistance in creating a read array for the buttons, only allowing them to be hit once without the use of millis() or delay(). The final straw of this learning experience is implementing a timeout feature… which I have not been able to do on my own successfully :confused:

Thanks again for your insight!

You are using 4 variables to store the state (example: read[3]=1;)

But you can only be in one state at a time. You can never have the 1st and 4th buttons successfully pushed simultaneously, can you?

So your states are:

  1. Waiting for first button
  2. Waiting for second button
  3. Waiting for third button
  4. Waiting for 4th button.

If any of those "fails" and requires a reset, just set the state back to the first state. You don't need to reset the entire Arduino.

The "success" action only occurs if you are in the 4th state and get the correct 4th button. At that point, you would usually reset the state variable back to 1 and then run the function that unlocks the door or whatever. The action of unlocking the door isn't a state itself. It's a state transition in between state 4 and 1.

With the system you have, with 4 array positions recording 4 buttons, you have to "reset" all 4 of them to get back to the first state of nothing-pushed.

Hello MorganS and thank you for taking the time. I have read your comment 7 times and am still struggling to apply your knowledge. You are correct in saying the buttons cannot be triggered simultaneously but can be triggered in any order. The correct order gives success.

"If any of those "fails" and requires a reset, just set the state back to the first state. You don't need to reset the entire Arduino."

I believe thats what I am trying to do... but on a time limit. Please forgive my lack of understanding. The buttons can be triggered in any order, but after all 4 are triggered or "x" amount of time, cancel/reset all and start over. If correct, keep all led's on and yes, unlock the door until reset button is pushed.

Okay so your code needs to be time based.

Rough draft flow I would use would be something like this.

Store current millis
Check if sequence active
true,
check if stored millis - 5000 > sequence reset
True
sequence active false

Read buttons if stored millis - 20 > bounce variable
If button is pressed,
Check sequence active variable.
false,
set sequence active true
Set array pointer = 0
set sequence reset to stored millis.

Set bounce variable to stored millis.
store button pressed array(pointer),
increment pointer.
Check if pointer is = max
True
compare stored array to pass code array
True
Open Lock
False
Set sequence active false

ErebusZT:
In this particular example, delay was preventing the buttons from bouncing but would also prevent others from being triggered as well. Also the buttons could be triggered more than once. In my particular instance, I am looking for each button only to have one entry per loop/timeout. I eliminated 2 buttons, got some assistance in creating a read array for the buttons, only allowing them to be hit once without the use of millis() or delay(). The final straw of this learning experience is implementing a timeout feature… which I have not been able to do on my own successfully :confused:

Thanks again for your insight!

Look into the do many things at once lesson.

You can set up a task that only watches buttons and updates a status variable for each. I have libraries for that. You can write other tasks as a virtual machine, you’ll need less code and have a chance to get rid of your timing problems.

Not my site, very good lessons aimed at beginner level with the keys to tasking on Arduino.

  1. Gammon Forum : Electronics : Microprocessors : How to do multiple things at once ... like cook bacon and eggs
  2. Gammon Forum : Electronics : Microprocessors : How to process incoming serial data without blocking <<=== the State Machine part shows how to process data according to conditions

This part can be called a "single shot timer" or "timeout". It stops itself when it reaches the timeout.

Slumpert:
Check if sequence active
true,
check if stored millis - 5000 > sequence reset
True
sequence active false

The 'enable' or 'enableTimer' or 'sequence active' is a bool variable.

Two variables and a constant are needed for a "single shot timer" or "timeout".

unsigned long previousMillis;
const unsigned long interval = 4000;
bool enabled = false;

You can see it in a sketch here: Fun_with_millis/millis_single_delay.ino at master · Koepel/Fun_with_millis · GitHub.

ErebusZT:
The buttons can be triggered in any order, but after all 4 are triggered or "x" amount of time, cancel/reset all and start over. If correct, keep all led's on and yes, unlock the door until reset button is pushed.

OK, that's different to what I was expecting.

int code[] = {6, 5, 5, 4, 3, 2}; //the desired code is entered in this array,

So if button 5 must be pressed twice in the code but it doesn't matter when it's pressed, I could press 5,5,2,3,4,6 and it would unlock the door? Or is this part of the old code that you don't want?

Are there exactly 4 buttons or are there more than 4 buttons? Is a double-press ignored or is it a fail?