6 push-buttons, they must be pressed in a certain order to power an LED

Hey, guys!
First forum post, somewhat of a noob in this.
So I banged my head with this for a while now and I hit problems every time.

This is what I want:
I have 6 push-buttons on my breadboard, each wired to an LED. Pins 2 through 7 are INPUT pins (the buttons), pins 8 through 13 are OUTPUT pins (the LEDs). I need to press the buttons in a certain order (7-6-5-4-3-2) for all the LEDs to light up (each one connected to the corresponding button press), but if I deviate from the correct pushing order in any way, the situation resets, all the LEDs turn off and I have to start all over again. So for example, if I push 7, the 13 LED lights up, then I push 6, the 12 LED lights up and then I push 2, which is a mistake and all the LEDs turn off and I have to start from the 7 again.

The biggest problem I ran into was that I don’t know how NOT to listen to a button push from a certain button. Like, I listen for the button 7 push with digitalRead and check if it is HIGH to start the sequence and then it needs to be listening for the button 6, but it still either registers the 7, which is a mistake, or it registers nothing, which is also a mistake and it can’t continue. I need to listen to the 7, and then when the 7 is pushed, ignore it until any other button is pushed. I tried setting a variable to the value ”1” when a button is pushed and stay that way, but it doesn’t work. Here’s my code:

//init the switches
int switch1 = 0;
int switch2 = 0;
int switch3 = 0;
int switch4 = 0;
int switch5 = 0;
int switch6 = 0;

//init the LEDs
int led1 = 0;
int led2 = 0;
int led3 = 0;
int led4 = 0;
int led5 = 0;
int led6 = 0;
int reset = 1;

void setup() {
Serial.begin(9600);
pinMode(2, INPUT);
pinMode(3, INPUT);
pinMode(4, INPUT);
pinMode(5, INPUT);
pinMode(6, INPUT);
pinMode(7, INPUT);
pinMode(8, OUTPUT);
pinMode(9, OUTPUT);
pinMode(10, OUTPUT);
pinMode(11, OUTPUT);
pinMode(12, OUTPUT);
pinMode(13, OUTPUT);
for (int leds = 8; leds < 14; leds++) {
digitalWrite(leds, LOW); //make sure all the LEDs are off
}
}

void loop() {
if (reset == 1) { led1 = 0; led2 = 0; led3 = 0; led4 = 0; led5 = 0; led6 = 0; reset = 0; } //in case of mistake

switch1 = digitalRead(7);
switch2 = digitalRead(6);
switch3 = digitalRead(5);
switch4 = digitalRead(4);
switch5 = digitalRead(3);
switch6 = digitalRead(2); //the listenings

if (led1 == 1) { digitalWrite(13, HIGH); }
if (led2 == 1) { digitalWrite(12, HIGH); }
if (led3 == 1) { digitalWrite(11, HIGH); }
if (led4 == 1) { digitalWrite(10, HIGH); }
if (led5 == 1) { digitalWrite(9, HIGH); }
if (led6 == 1) { digitalWrite(8, HIGH); } //check for boolean, light up LED

if (switch1 == HIGH) {
led1 = 1;
} else { reset = 1; }
if (switch2 == HIGH && led1 == 1) {
led1 = 0; led2 = 1;
} else { reset = 1; }
if (switch3 == HIGH && led2 == 1) {
led1 = 0; led2 = 0; led3 = 1;
} else { reset = 1; }
if (switc4 == HIGH && led3 == 1) {
led1 = 0; led2 = 0; led3 = 0; led4 = 1;
} else { reset = 1; }
if (switch5 == HIGH && led4 == 1) {
led1 = 0; led2 = 0; led3 = 0; led4 = 0; led5 = 1;
} else { reset = 1; }
if (switch6 == HIGH && led5 == 1) {
led1 = 0; led2 = 0; led3 = 0; led4 = 0; led5 = 0; led6 = 1;
} else { reset = 1; }
Serial.print(led1); Serial.print(led2); Serial.print(led3); Serial.print(led4); Serial.print(led5); Serial.println(led6); //status check in the form of ”100000” or ”111000”, which LEDs it considers lit
if (led6 == 1) {
Serial.println(“Sequence correct!”);
}
}

Where am I going wrong?
Thanks!

I did a similar thing recently by having a unique state for each step along the path.

So booleans a b c d e f for each of the steps, initialised false.

Then at the start, you read only the 7 pin, and if you get a 7 then u make a true. (The other pins are "ignored" since you use ifs and only read 6 if a is true, which it's not, 5 if b is true which it's not....)

Then, you have an if (a) ..... which is now true.... you listen only for the 6 pin.

If you get a 6, you make b true, since the only way you got there was a 7 then a 6...

And so on.

I walked that path myself, but it breaks my requirements. Initially, indeed, I only have to listen for button 7, because that’s where it starts, but then, I have to listen to all buttons EXCEPT the one that was last pushed. If I press 7, then 6 and then 7 again, it needs to reset, it’s still a mistake. If I exclude each button with each step, it would still work on 7-6-2-7-5-2-6-3-5-6-4-2-3-2, for example. It shouldn’t.

(maybe it’s a useful mention, it’s for a puzzle in an escape-room game and the reason it’s important for me is that it needs to be error-proof, the players need the correct order, so any mistake they make, they should have to start over)

Oh right sorry... mine wasn't concerned with resetting if an error was made, it just didn't progress.

Let me have a think....

The furthest I got was managing to complete the sequence, but the downside was that I had to KEEP the buttons pressed. Meaning that I pressed and held button 7, then pressed 6 and held it, then 5 and so on.

I thought about using a switch-case statement, but I couldn't fit it to my needs because I need cascading the conditions each below the previous one. The switch-case treats all conditions equally.

Then I thought about cascading the ”if”s, each checking for conditions inside the previous one, but also didn't work. This is where I had to keep the buttons pressed.

Btw, thanks a lot for your time, I appreciate it!

I think in each state, eg "a" you have to listen in turn for each pin.

Let's say we start in the state of "idle" and we got to "a" with a 7.

Then (very pseudocode):

if (a) if 6, "a"= false, "b"= true if (a) if 7 or 5 or 4 etc... "a"=false, "idle"=true

etc etc

You mean explicitly document each case? Like step 1, listen for all, if 6 or 5 or 4 or 3 or 2 -> wrong, if 7 -> right, move on listen for all again, if 7 or 5 or 4 or... -> wrong if if 6 -> right, move on and so on?

What would be the general case, then? How do I make it remember the last button push while listening for the next pushes? That's what I'm having trouble with, making it advance through the steps, because no matter what I try, I have to keep the buttons pushed for it to continue...

I'll give what you say a try, though. I might pull a breakthrough, who knows. :D Thanks again!

if (no leds lit and button 7 pressed) {light led 1} ELSE if (led 1 lit and button 6 pressed) {light led 2} ELSE if (led 2 lit and button 5 pressed) {light led 3} ... ELSE {turn off all leds}

...right?

EDIT: Oop, you'll have to filter the case where multiple buttons are pressed at once. Just filter that up front. If you get more than one input, turn all the LEDs off.

No you need a boolean state, which in effect is the memory, since you can only be in a certain state if you advanced legitimately from the state before.

So in state_idle (which is a boolean initialsed true), you only listen for a 7. If you get a 7, you set state_idle false and state_a true. The fact state_a is true, effectively is you "remembering" that you got a 7 from state_idle.

Then in state_a, you only get to state_b with a 6, effectively remembering the 76 sequence.

manor_royal: Then in state_a, you only get to state_b with a 6, effectively remembering the 76 sequence.

I just came up with the idea of using functions, combined with your idea. A different function for each state, triggered when the previous state is completed. And in the ”else” part, call the first function again. I'll try and come back with my results.

jaholmes: ...right?

I went through what you're saying and it seems so simple, but the problem is that when I go with this, it insists on me keeping the buttons pressed. I need to press and release and the system should remember what I pressed and add it to whatever I pressed before, given that I pressed the correct buttons so far. And this combined with the reset stuff makes me throw myself out the window.

I edited my prior response. He said he wanted the LEDs to go dark on a mistake, so there's no need to remember the sequence. Logically, it's just a state machine where each state has one edge forward and five edges back to the first. I'm pretty sure this is accomplished with my edited post.

vladpostolache: keeping the buttons pressed

Right. As you say, you need to check for an up-to-down transition, not simply whether the button is currently down. If a button hasn't changed from up to down, your code shouldn't do anything else.

jaholmes: As you say, you need to check for an up-to-down transition, not simply whether the button is currently down. If a button hasn't changed from up to down, your code shouldn't do anything else.

That would be lovely. Basically, it would only register the actual press, not the value the press returns. But how do I do that?

vladpostolache: That would be lovely. Basically, it would only register the actual press, not the value the press returns. But how do I do that?

More variables! :) You'll need someplace to store the previous states of the buttons so that you can do a before/after comparison. You'll also probably want to debounce the buttons. Search 'debounce' for numerous strategies.

(Optional reading ahead...)

One neat thing to do here (though certainly NOT required) would be to build a function that gathers the states of all the buttons into a single byte, where each button's state corresponds to a single bit position, 0-5, with a '1' meaning 'down' and a '0' meaning 'up.' (The other way around works fine too.) Then you'd basically have:

byte buttonsThatWereDownLastTime = 0;

loop()
{
  byte buttonsThatAreDownNow = GetButtonStates();
  byte buttonsThatWereJustPressed = buttonsThatAreDownNow & ~buttonsThatWereDownLastTime;

  //
  // Do stuff based on buttonsThatWereJustPressed
  //

  buttonsThatWereDownLastTime = buttonsThatAreDownNow;
}

This makes things pretty convenient. buttonsThatWereJustPressed then has 64 possible configurations (there are six interesting bits, so 2^6), but you only need to think about a few:

  • buttonsThatWereJustPressed is 0 (i.e. no bits are set, so nothing was pressed and you do nothing)
  • buttonsThatWereJustPressed is 1, 2, 4, 8, 16, or 32 (i.e. exactly one '1' bit in positions 0-5). According to what LEDs you have currently lit, you'd be expecting one of these specific values.
  • anything else (multiple buttons were pressed, which is invalid--go back to the beginning)

I like to tune in. I'm looking for exactly the same system, but connected to a relay board where i can use 220v lights instead of led's. I have had some tries at arduino but its to hard for me. Because i need it for a company, i'm even willing to pay someone to finish this code in the way i need.

Tip: This type of game looks a lot like 'Simon Says' but i would like it with max 10 levels. (reply sequence of 1, if correct reply sequence of 2, etc.)

@vladpostolache Perhaps we can work together? Where's your escaperoom situated?

Please contact me if you can help me!!!

Santepeter85: @vladpostolache Perhaps we can work together? Where's your escaperoom situated?

Hey, Peter! :) I'll happily share whatever way I find to solve this problem, really! No need for anything. But the whole project I'm building is way more complicated and I don't want to reveal more, especially because escape-room games are supposed to be a secret until you play them. :D The description I gave is the bare principle involved. I'm in Romania, btw.

jaholmes: More variables! :)

Yeah, just what I love, more variables. :)) Hm, that byte stuff sounds interesting. I thought about something like that before, add the powers of 2 to an integer, which makes it easy to identify which buttons were pressed judging by the value. I don't know how I'd add a value to the 4th bit of the byte though, but I'll look it up. Also, I found a tutorial that describes how to catch the button press event, rather than the value given by the push, so I hope that's the solution I need. I just got home and I'm really excited. I'll report back.

@Santepeter85, stay tuned. :D

I started working on this but ran out of time, and very busy with paying work now. Hate when that happens. (Not really, have to pay the rent...)

Will report when I have some time.

I do realise now though you need to catch that it's a new press, since if you go into a new state with the button that took you there still pressed (and you will, since the finger is so slow) that will be seen as an illegal out-of-sequence exit press from the new state and cause an unintended reset.

The use of arrays for the pins is sensible for more than a couple of pins.

Storing states as bits in bytes is a very good idea.

I did it! Here’s the code:

int btnPin[] = { 0,7,6,5,4,3,2 };
int ledPin[] = { 0,13,12,11,10,9,8 };
int buttonState[] = { 0,0,0,0,0,0,0 };
int lastButtonState[] = { 0,0,0,0,0,0,0 };
int led = 0;
int btn = 0;
int i = 0;
int ctrl = 0;
int lastBtn = 0;

void setup() {
  Serial.begin(9600);
  Serial.println("Starting");
  for (led = 8; led <= 13; led++) { pinMode(led, OUTPUT); }
  for (btn = 2; btn <= 7;  btn++) { pinMode(btn, INPUT);  }
}

void loop() {
  for (i = 1; i <= 6; i++) {
    if (wasBtnPushed(i) > 0) {
      if (wasBtnPushed(i) != lastBtn) {
        Serial.print("last_btn_"); Serial.print(lastBtn); Serial.print("   curr_btn_"); Serial.print(i); Serial.print("   initial_ctrl_"); Serial.print(ctrl); 
        if (ctrl == 0 && wasBtnPushed(i) == 1) { ctrl = 1; digitalWrite(ledPin[i], HIGH); }
        if (ctrl == 1 && wasBtnPushed(i) == 2) { ctrl = 2; digitalWrite(ledPin[i], HIGH);}
        if (ctrl == 2 && wasBtnPushed(i) == 3) { ctrl = 3; digitalWrite(ledPin[i], HIGH);}
        if (ctrl == 3 && wasBtnPushed(i) == 4) { ctrl = 4; digitalWrite(ledPin[i], HIGH);}
        if (ctrl == 4 && wasBtnPushed(i) == 5) { ctrl = 5; digitalWrite(ledPin[i], HIGH);}
        if (ctrl == 5 && wasBtnPushed(i) == 6) { ctrl = 6; digitalWrite(ledPin[i], HIGH);}
       
        if (ctrl == 0 && wasBtnPushed(i) == 2) { reset(); }
        if (ctrl == 0 && wasBtnPushed(i) == 3) { reset(); }
        if (ctrl == 0 && wasBtnPushed(i) == 4) { reset(); }
        if (ctrl == 0 && wasBtnPushed(i) == 5) { reset(); }
        if (ctrl == 0 && wasBtnPushed(i) == 6) { reset(); }
  
        if (ctrl == 1 && wasBtnPushed(i) == 3) { reset(); }
        if (ctrl == 1 && wasBtnPushed(i) == 4) { reset(); }
        if (ctrl == 1 && wasBtnPushed(i) == 5) { reset(); }
        if (ctrl == 1 && wasBtnPushed(i) == 6) { reset(); }
  
        if (ctrl == 2 && wasBtnPushed(i) == 1) { reset(); }
        if (ctrl == 2 && wasBtnPushed(i) == 4) { reset(); }
        if (ctrl == 2 && wasBtnPushed(i) == 5) { reset(); }
        if (ctrl == 2 && wasBtnPushed(i) == 6) { reset(); }
  
        if (ctrl == 3 && wasBtnPushed(i) == 1) { reset(); }
        if (ctrl == 3 && wasBtnPushed(i) == 2) { reset(); }
        if (ctrl == 3 && wasBtnPushed(i) == 5) { reset(); }
        if (ctrl == 3 && wasBtnPushed(i) == 6) { reset(); }
  
        if (ctrl == 4 && wasBtnPushed(i) == 1) { reset(); }
        if (ctrl == 4 && wasBtnPushed(i) == 2) { reset(); }
        if (ctrl == 4 && wasBtnPushed(i) == 3) { reset(); }
        if (ctrl == 4 && wasBtnPushed(i) == 6) { reset(); }
  
        if (ctrl == 5 && wasBtnPushed(i) == 1) { reset(); }
        if (ctrl == 5 && wasBtnPushed(i) == 2) { reset(); }
        if (ctrl == 5 && wasBtnPushed(i) == 3) { reset(); }
        if (ctrl == 5 && wasBtnPushed(i) == 4) { reset(); }
        
        Serial.print("   ctrl_"); Serial.println(ctrl);
        lastBtn = wasBtnPushed(i);
        delay(100);
      }
    if (ctrl == 6) { Serial.print("Correct!"); delay(2000); reset(); }
    }
  }
}

void reset() {
  for(led = 1; led <= 6; led++) {
    digitalWrite(ledPin[led], LOW);
  }
  Serial.print("   has_reset");
  ctrl = 0;
  lastBtn = 0;
}

int wasBtnPushed(int x) {
  buttonState[x] = digitalRead(btnPin[x]);
    if (buttonState[x] != lastButtonState[x]) {
      if (buttonState[x] == HIGH) { return x; }
    }
  lastButtonState[x] = buttonState[x];
}

A few comments, now.

The use of arrays is something sent from heaven. It enabled me to iterate through the LEDs or buttons with a for loop, instead of writing actions for each LED or button. I couldn’t have done it with regular variables. For ease of use, though, I had to insert a ”never used” value of ”0” as the first value of the array, because the system counts the values starting from 0 rather than 1. So this way, I knew that ledPin[3] is actually the 3rd LED and not the 4th.

Then, external functions were also wonderful, besides the fact that it makes the code look A LOT more organized, the fact that I could write a function and call it with an argument and make it return a value made everything much more manageable.

No matter what I tried, I couldn’t get it to work without writing a line for every mistake in the button presses. Where I documented the errors for each value of the variables, I would’ve loved something like ”if ctrl is 2 and the button that was just pushed is ANY OTHER than 3, reset”. I think this is because the system still registers a few button pushes with each button, rather than only one, so it resets every time because it expects the next button and considers an error if it finds the same one being pushed. Because I have only 6 buttons, it wasn’t hard to write a line for every case, but what if I had 100 buttons? If you could recommend another way, I might learn something more in the process, so thanks in advance. :slight_smile:

As you can see, I had to make an exception for when you keep the same button pressed (for my project, the physical environment makes it obsolete, I don’t need this case because it’s never gonna happen, so I have to allow the case when the button is being kept pushed), but I have no idea how to modify my code so the system would reset if the buttons were pushed like ”1, 2, 3, 3”, for example. Again, ideas? :smiley:

Thanks for all your time, guys! This was my first kinda complex project and I had a blast getting it through.