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);
}
}
-
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– dandavis2019年03月18日 20:28:54 +00:00Commented Mar 18, 2019 at 20:28
4 Answers 4
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
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.
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.
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;
}
}
-
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.Edgar Bonet– Edgar Bonet2019年03月27日 19:57:49 +00:00Commented Mar 27, 2019 at 19:57
-
Perhaps a "enter" code button and/or a "cancel" button would be useful.VE7JRO– VE7JRO2019年03月27日 19:59:10 +00:00Commented 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.Edgar Bonet– Edgar Bonet2019年03月27日 20:10:42 +00:00Commented 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– VE7JRO2019年03月27日 20:58:43 +00:00Commented 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– VE7JRO2019年03月27日 20:59:08 +00:00Commented Mar 27, 2019 at 20:59