2

I'm trying get a password system working that when you click 3 buttons in a certain order (1, 2, 3, 3, 1) then an LED will light up. But as I'm going to implement this into a larger code I can't just have the void loop() going forever to wait for it.

I think my issue is in the while loops and making it wait for one of the buttons to be pressed but I don't know how to get it to work.

thanks

#define greenLED A0
#define redLED A1

int button1 = 5;
int button2 = 4;
int button3 = 3;

void setup() {
  // put your setup code here, to run once:
  pinMode(A0, OUTPUT);
  pinMode(A1, OUTPUT);
  pinMode(button1, INPUT_PULLUP);
  pinMode(button2, INPUT_PULLUP);
  pinMode(button3, INPUT_PULLUP);
}

void loop() {
  // put your main code here, to run repeatedly:
  int total = 0;
  while(digitalRead(button1) == LOW && digitalRead(button2) == LOW && digitalRead(button3) == LOW){
    while(digitalRead(button1) == LOW && digitalRead(button2) == LOW && digitalRead(button3) == LOW){
      while(digitalRead(button1) == LOW && digitalRead(button2) == LOW && digitalRead(button3) == LOW){
        while(digitalRead(button1) == LOW && digitalRead(button2) == LOW && digitalRead(button3) == LOW){
          while(digitalRead(button1) == LOW && digitalRead(button2) == LOW && digitalRead(button3) == LOW){
            if(digitalRead(button1) == HIGH){
              total = total + 1;
            }
          }
          if(digitalRead(button2)==HIGH){
            total = total+1;
          }
        } 
        if(digitalRead(button3)==HIGH){
          total = total + 1;
        }
      }
      if(digitalRead(button3)==HIGH){
        total = total+1;
      }
    }
    if(digitalRead(button1) == HIGH){
      total = total+1;
    }
  }
  if(total == 5){
    digitalWrite(A0, HIGH);
    delay(2000);
    digitalWrite(A0, LOW);
  }else{
    digitalWrite(A1, HIGH);
    delay(2000);
    digitalWrite(A1, LOW);
  }
}
1
  • you need each button pressed to push something to a stack, then examine the stack for patterns; no waiting, no blocking, no nesting, no complexity. a String could work as a stack, and it's got built-in tools to find patterns.
    – dandavis
    Mar 18, 2019 at 20:28

4 Answers 4

2

Without programming everything out, use something like this:

  • Initialization:

    • Create a (global) variable of string ("12331") which is the code
    • Create a (global) variable of int that shows the number of correct numbers so far (initially 0)
  • In the while loop:

    • Check which button is pressed
    • If it is the correct button
    • increase the int variable
    • If that variable is 5 (meaning all numbers ok), light up the LED
    • Delay
    • Make the variable 0 again
    • Clear the LED
    • If the wrong button is pressed
    • If it is equal to the first value ('1')
      • Set the variable to 1 (since the wrong number is the first correct number)
    • Else
      • Set the variable to 0
0

For something like this, a state machine would probably be useful. Since this is software, we can skip the register assignment and use a state table directly.

The table would look as follows:
1. If button 1 is pressed, 2. Else 1.
2. If button 2 is pressed, 3. Else 1.
3. If button 3 is pressed, 4. Else 1.
4. Run task, go to 1.

The code would require you to add an integer or byte outside setup or loop. This would then contain the state number.

Inside loop, you can add the if statements for reading the button pins.

If one of the buttons is pressed (the if-statement fires), you can compare the state with the condition listed above. If the state and the correct condition match, set the state to the new state.

Otherwise, reset it to the initial state (0 is default if you do not explicitly change it when programming, and most things are 0-indexed. You can also use 1, but you have to remember to initialize it to 1 when declaring the state variable. i.e. byte state=1;)

The below sample code presumes that the buttons are connected between GND and the GPIO input pin on the Arduino. Since it's using the internal pullup resistors, no external ones are needed. The one minor issue is that it inverts the state of the buttons -- 0==pressed, 1==released (not pressed).

byte state = 0;
bool pressed=false;//used to prevent repeats

void setup(){
  pinMode(pin1,INPUT_PULLUP);
  //Repeat for other pins. Replace pin# with the number.
}

void loop(){
  if (digitalRead(pin1)==0 && !pressed){
    pressed=true;
    if (state==0){
      state=1; //go to next state.
    }
    else{
      state=0; //reset state. Needed even here since the button might be 
               //pressed later at the wrong time. Add 'else if (state==#)' 
               //sections if the button needs to be reused again in later states.
   }
   if (digitalRead(pin1) && pressed){
     pressed=false;
   }
   //Add more if statements here, following the pattern, for any more buttons you need.
}

Further improvements may involve getting independent press-detection variables (one for each button), but being only able to register one button press at a time is probably a good thing, actually.

0

You can split this into two different problems, and I suggest you track them one at a time.

The first problem is to detect the button presses. This is not as trivial as it sounds. For once, digitalRead() tells you whether the button is pressed, but that's not quite what you want. You want to detect the discrete press “events”. In other words, you have to remember the previous state of the button, and record a press event only when the state changes from HIGH to LOW. This is called “edge detection”. When doing this on mechanical buttons, you absolutely must do some kind of debouncing.

I will not elaborate on this topic, as there are a few Arduino libraries available for debouncing buttons that also do edge detection. Once you have sorted this out, each of your buttons will have a .fell() method that tells you whether that button has just been pressed. Then, inside loop(), you can do something like

char key = 0;  // key pressed by the user, 0 = none
if (button1.fell()) key = '1';
else if (button2.fell()) key = '2';
else if (button3.fell()) key = '3';
if (key) {  // some key has been pressed
    // process key...
}

The “process key...” part should initially be a simple Serial.write(c);. This way you can use the serial monitor to check that this part of the program is working properly. Only when this is the case you can move to the second part.

The second problem is to process the key presses in order to detect whether the correct code has been entered. There are various possible approaches. One of them is to store in an array the 5 last keys entered by the user, and compare that array with the correct code to know whether the entry was correct. This is not very elegant, as it will require storing more information than necessary, and shifting the array each time a new key is typed.

A more elegant solution is given by Michel Keijzers. Let me paraphrase it in C++. Inside loop():

if (key) {  // some key has been pressed
    if (check_code(key)) {  // correct code typed
        digitalWrite(LED, HIGH);
        delay(1000);
        digitalWrite(LED, LOW);
    }
}

where the logic for checking the correctness of the typed code is:

bool check_code(char key)
{
    const char code[] = "12331";
    const uint8_t length = sizeof code - 1;
    static uint8_t i = 0;  // number of correct characters so far

    if (key == code[i]) {  // correct key
        ++i;               // one more correct character
        if (i == length) {
            i = 0;         // reset
            return true;   // report entry is correct
        }
    } else {               // wrong key
        if (key == '1')
            i = 1;
        else
            i = 0;
    }
    return false;
}

Note that the part that handles a wrong key press is specific to that particular code. If you want the program to be generic, so that you can change the secret code just by changing the string code, it can get more complicated. For example, if the correct code is “12313” and the user has so far typed “1231”, he has so far 4 correct keys. Then,

  • if he types 1, he now has 1 correct key press
  • if he types 2, he has 2 correct keys (namely “12”)
  • if he types 3, he completed the code.

Handling this generically requires walking back the code string and searching for the longest possible match. Here is my take at it: replace the else part in the function above with:

// ...
} else {               // wrong key
    for (int j = i-1; j >= 0; j--) {
        if (key == code[j]) {
            int k;
            for (k = 0; k < j; k++) {
                if (code[k] != code[i-j+k])
                    break;
            }
            if (k == j) {  // found a partial match
                i = j + 1;
                return false;
            }
        }
    }
    i = 0;  // no partial match
}
// ...

Here j is the position, within the code string, where the key typed by the user may match.

0

As Edgar Bonet mentions in his answer, button debouncing is a critical component, and there are libraries available to simplify this part of the requirement. I used the Bounce2 library with three N.O. push button switches. The following sketch works, but it still needs one or two improvements.

One issue occurs if the user enters 4 button presses, then leaves and comes back in 1 hour. All they have to do press the last correct button in the sequence, then they are authenticated. This could easily be fixed by introducing a millis() based timer that starts on the first button press, and stops after 5 seconds, for example. This timer would reset the counter variable and the buttonsPressed[] byte array.

Perhaps a "enter" code button and/or a "cancel" button would be useful.

#include <Bounce2.h>

const byte sequenceLength = 5;
byte sequence[sequenceLength] = {1, 2, 3, 3, 1};

byte buttonsPressed[sequenceLength];
byte counter = 0;
const byte ledPinCodeAccepted = 2;
const byte ledPinCodeDenied = 3;
const byte button1Pin = 10;
const byte button2Pin = 11;
const byte button3Pin = 12;
const unsigned long debouncerInterval = 50;

Bounce button1 = Bounce();
Bounce button2 = Bounce();
Bounce button3 = Bounce();

void setup(){
  pinMode(ledPinCodeAccepted, OUTPUT);
  pinMode(ledPinCodeDenied, OUTPUT);
  button1.attach(button1Pin, INPUT_PULLUP);
  button1.interval(debouncerInterval);
  button2.attach(button2Pin, INPUT_PULLUP);
  button2.interval(debouncerInterval);
  button3.attach(button3Pin, INPUT_PULLUP);
  button3.interval(debouncerInterval);
}

void loop(){

  if(button1.update()){
    if(button1.read() == 0){
      buttonsPressed[counter] = 1;
      counter++;
    }
  }

  if(button2.update()){
    if(button2.read() == 0){
      buttonsPressed[counter] = 2;
      counter++;
    }
  }

  if(button3.update()){
    if(button3.read() == 0){
      buttonsPressed[counter] = 3;
      counter++;
    }
  }

  if(counter == sequenceLength){
    counter = 0;
    digitalWrite(ledPinCodeAccepted, LOW);
    digitalWrite(ledPinCodeDenied, LOW);
    for(byte i = 0; i < sequenceLength; i++){
      if(sequence[i] == buttonsPressed[i]){counter++;}
      else{break;}
    }
    if(counter == sequenceLength){
      digitalWrite(ledPinCodeAccepted, HIGH);
    }
    else{
      digitalWrite(ledPinCodeDenied, HIGH);
    }
    counter = 0;
  }
}
5
  • There is a small issue here: if the user types “1231” oops! Then “12331”, this program will not accept his input as valid. The expected behavior for any kind of keypad lock is to accept it. Mar 27, 2019 at 19:57
  • Perhaps a "enter" code button and/or a "cancel" button would be useful.
    – VE7JRO
    Mar 27, 2019 at 19:59
  • Keypad locks are a very common form of user interface. This ubiquity creates user expectations, including simplicity and ease of use. If you require the user to press a “cancel” button when he oops, you are violating this expectation and thus making a poor user experience. Mar 27, 2019 at 20:10
  • The OP says they are making a "password system" in the question. Is that the same as a Keypad Lock? I don't know. I don't even know what a "password system" is :) The enter/cancel buttons are very common here in Canada. Look at bank "cash" machines for example. The user is forced to press an enter button when they have completed entering their password. There is also a cancel button, and a "timeout" period where keypad inactivity causes your bank card to be ejected from the machine.
    – VE7JRO
    Mar 27, 2019 at 20:58
  • I don't know if the bank cash machine forces you to press the cancel button, then enter your passcode again, or whether you can just enter your passcode after incorrect digits and press enter.
    – VE7JRO
    Mar 27, 2019 at 20:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.