Running multiple buttons

I am a very newb and have done some small projects but I took on a large one now. I have a bunch of BASIC programming background as well as HTML and CSS.

I need to run 8 buttons and on pressing they have to start a sequence. The buttons will have to be pressed multiple times and depending on the current sequence it will run another sequence. The sequences will be pretty easy, it's the same sequence that just runs at different times.

I am trying to figure out the best way to implement the buttons; a standard if then loop, switch case, button state, ect.

Here is a copy of what I have so far, mostly just defining the variables and constants. Wondering where to go from here.

// this constant won't change:
const int  buttonMC1 = A0;    // Main Box Button for Chute 1
const int  buttonMC2 = A1;    // Main Box Button for Chute 2
const int  buttonMC3 = A2;    // Main Box Button for Chute 3
const int  buttonMC4 = A3;    // Main Box Button for Chute 4
const int  buttonAC1 = 12;    // Actor 1 Box Button for Chute 1
const int  buttonAC2 = 13;    // Actor 1 Box Button for Chute 2
const int  buttonAC3 = A4;    // Actor 2 Box Button for Chute 3
const int  buttonAC4 = A5;    // Actor 2 Box Button for Chute 4
const int ledC1 = 8;       // LED for Chute 1
const int ledC2 = 9;       // LED for Chute 2
const int ledC3 = 10;       // LED for Chute 3
const int ledC4 = 11;       // LED for Chute 4

// Variables will change:
int buttonPushCounter = 0;   // counter for the number of button presses
int buttonState = 0;         // current state of the button
int lastButtonState = 0;     // previous state of the button

void setup() {
  // initialize the button pin as an input:
  pinMode(buttonMC1, INPUT);
  pinMode(buttonMC2, INPUT);
  pinMode(buttonMC3, INPUT);
  pinMode(buttonMC4, INPUT);
  pinMode(buttonAC1, INPUT);
  pinMode(buttonAC2, INPUT);
  pinMode(buttonAC3, INPUT);
  pinMode(buttonAC4, INPUT);
  // initialize the LED as an output:
  pinMode(ledC1, OUTPUT);
  pinMode(ledC2, OUTPUT);
  pinMode(ledC3, OUTPUT);
  pinMode(ledC4, OUTPUT);
  // initialize serial communication:
  Serial.begin(9600);
}

So ?
What is your question ?

Where do I go from here. To use multiple buttons what is the best way to write the program. Do I use button state, or just a simple if. .then statement with loops, or something I haven't found yet.

morte615:
I need to run 8 buttons and on pressing they have to start a sequence. The buttons will have to be pressed multiple times and depending on the current sequence it will run another sequence. The sequences will be pretty easy, it’s the same sequence that just runs at different times.

Hi… no, sorry, I don’t get it… can you give a couple of examples? Will a button have to be pressed multiple times before anything happens? By “different times” do you mean different speeds?

With 8 buttons, it strikes me your sketch is very quickly going to get very long and repetitive. Suggest you might want to put your button pin numbers in an array and deal with them in loops. Your sketch above becomes:

const int  buttonPin [8] = { A0, A1, A2, A3, 12, 13, A4, A5 }; 
const int ledC1 = 8;       // LED for Chute 1
const int ledC2 = 9;       // LED for Chute 2
const int ledC3 = 10;       // LED for Chute 3
const int ledC4 = 11;       // LED for Chute 4

// Variables will change:
int buttonPushCounter = 0;   // counter for the number of button presses
int buttonState = 0;         // current state of the button
int lastButtonState = 0;     // previous state of the button

void setup() {
  // initialize the button pin as an input:
  for (byte i=0; i<8; i++) {
    pinMode(buttonPin[i], INPUT);
  }
  // initialize the LED as an output:
  pinMode(ledC1, OUTPUT);
  pinMode(ledC2, OUTPUT);
  pinMode(ledC3, OUTPUT);
  pinMode(ledC4, OUTPUT);
  // initialize serial communication:
  Serial.begin(9600);
}

See helping already :slight_smile:

Here is a sample of the logic that it has to do.
Arduino Logic:

  1. 4 momentary switches on front panel with an indicator light above each. The indicator lights are Red and controlled from an Arduino.
  2. Upon pressing a button the first sequence is started.
  3. Chute 1 is pressed
  4. The Chute 1 light turns on, on the Main Control Panel
  5. The Chute 1 light turns on, on the Remote Control Panel
  6. The Chute 1 video player is given power
  7. Chute 2 is pressed
  8. The Chute 2 light turns on, on the Main Control Panel
  9. The Chute 2 light turns on, on the Remote Control Panel
  10. The Chute 2 video player is given power
  11. Chute 3 is pressed
  12. The Chute 3 light turns on, on the Main Control Panel
  13. The Chute 3 light turns on, on the Remote Control Panel
  14. The Chute 3 video player is given power
  15. Chute 4 is pressed
  16. The Chute 4 light turns on, on the Main Control Panel
  17. The Chute 4 light turns on, on the Remote Control Panel
  18. The Chute 4 video player is given power
  19. Once the chute button is pressed on the remote panel the second sequence is started.
  20. Chute 1 is pressed
  21. The Chute 1 strobe lights turn on
  22. Chute 2 is pressed
  23. The Chute 2 strobe lights turn on
  24. Chute 3 is pressed
  25. The Chute 3 strobe lights turn on
  26. Chute 4 is pressed
  27. The Chute 4 strobe lights turn on
  28. Once the chute button is pressed a second time on the remote panel the third sequence is started.
  29. Chute 1 is pressed
  30. The light turns Off on the Chute 1 Main Control Panel
  31. The light turns Off on the Chute 1 Remote Control Panel
  32. Power is removed from the Chute 1 video player
  33. Power is removed from the Chute 1 Strobe Lights
  34. Chute 2 is pressed
  35. The light turns Off on the Chute 2 Main Control Panel
  36. The light turns Off on the Chute 2 Remote Control Panel
  37. Power is removed from the Chute 2 video player
  38. Power is removed from the Chute 2 Strobe Lights
  39. Chute 3 is pressed
  40. The light turns Off on the Chute 3 Main Control Panel
  41. The light turns Off on the Chute 3 Remote Control Panel
  42. Power is removed from the Chute 3 video player
  43. Power is removed from the Chute 2 Strobe Lights
  44. Chute 4 is pressed
  45. The light turns Off on the Chute 4 Main Control Panel
  46. The light turns Off on the Chute 4 Remote Control Panel
  47. Power is removed from the Chute 4 video player
  48. Power is removed from the Chute 2 Strobe Lights

It is very repetitive but with the timing differences it is, I think the best way to do it. :smiley:

The outputs are all LED's or Relay's. I have hardware all built just not wired up so the pins can move around if I have to :slight_smile:

That made my head spin!

What do the numbers before each item in the list signify? The sequence starts 1, 2, 1, 1, 2, 3, 2, 1, 2, 3, 3....

What are these "timing differences" you mention?

Sorry the list was originally in ordered list format, and moving to the forum it lost that :slight_smile:

Each "chute" is controlled independently. The same sequence will run in each but will be started and advanced at different intervals. That's what I mean by timing.

Let's see if this makes more sense, very simplified:

  • Button 1 is pressed. Sequence 1 starts in Chute 1
  • Button 2 is pressed. Sequence 1 starts in Chute 2
    ....
  • Button 5 is pressed. Sequence 2 starts in Chute 1
  • Button 5 is pressed again. Sequence 3 starts in Chute 1

That is a small sample of what I am wanting to do. This will continue through 4 "chutes" and with 2 buttons per "chute"

The Chute is a physical location. A hallway that the lighting and video sequence will be running in as triggered.

Once a sequence has started do you want it to be able to be overridden? That is during a sequence do you want pressing an other button to be able to start another sequence?
It is vital that you know this now as it will affect the structure of the whole code.

Focussing on the original question of the buttons, you will need to detect state changes in the buttons (ie has the button been pressed since it was last checked, as opposed to simply is it pressed right now) and debounce them too (buttons bounce when you press them, leading to multiple unwanted presses if you don't debounce). Will a single user be pressing buttons at a time or could there be multiple users pressing them simultaneously?

Here is a piece of code on which I am presently working, which arranges repetitive timing and button debouncing. It uses two (at present) functions to correspondingly perform the timing and debounce for whichever operation you need, and you can simply define as many timers and buttons as you need. Arguably, you could factor multiple buttons further with arrays though that is most useful so far as the operations corresponding to each button are essentially identical.

You may care to try it out as is with a couple of buttons and lights, and then work up from there.

// Blink without "delay()" - multi!

const int led1Pin =  13;    // LED pin number
const int led2Pin =  10;
const int led3Pin =  11;
const int button1 =  4;
int led1State = LOW;        // initialise the LED
int led2State = LOW;
int led3State = LOW;
char bstate1 = 0;
unsigned long count1 = 0;   // will store last time LED was updated
unsigned long count2 = 0;
unsigned long count3 = 0;
unsigned long bcount1 = 0; // button debounce timer.  Replicate as necessary.

// Have we completed the specified interval since last confirmed event?
// "marker" chooses which counter to check 
boolean timeout(unsigned long *marker, unsigned long interval) {
  if (millis() - *marker >= interval) { 
    *marker += interval;    // move on ready for next interval
    return true;       
  } 
  else return false;
}

// Deal with a button read; true if button pressed and debounced is a new event
// Uses reading of button input, debounce store, state store and debounce interval.
boolean butndown(char button, unsigned long *marker, char *butnstate, unsigned long interval) {
  switch (*butnstate) {               // Odd states if was pressed, >= 2 if debounce in progress
  case 0: // Button up so far, 
    if (button == HIGH) return false; // Nothing happening!
    else { 
      *butnstate = 2;                 // record that is now pressed
      *marker = millis();             // note when was pressed
      return false;                   // and move on
    }

  case 1: // Button down so far, 
    if (button == LOW) return false; // Nothing happening!
    else { 
      *butnstate = 3;                 // record that is now released
      *marker = millis();             // note when was released
      return false;                   // and move on
    }

  case 2: // Button was up, now down.
    if (button == HIGH) {
      *butnstate = 0;                 // no, not debounced; revert the state
      return false;                   // False alarm!
    }
    else { 
      if (millis() - *marker >= interval) {
        *butnstate = 1;               // jackpot!  update the state
        return true;                  // because we have the desired event!
      }
      else 
        return false;                 // not done yet; just move on
    }

  case 3: // Button was down, now up.
    if (button == LOW) {
      *butnstate = 1;                 // no, not debounced; revert the state
      return false;                   // False alarm!
    }
    else { 
      if (millis() - *marker >= interval) {
        *butnstate = 0;               // Debounced; update the state
        return false;                 // but it is not the event we want
      }
      else 
        return false;                 // not done yet; just move on
    }
  default:                            // Error; recover anyway
    {  
      *butnstate = 0;
      return false;                   // Definitely false!
    }
  }
}

void setup() {
  pinMode(led1Pin, OUTPUT);      
  pinMode(led2Pin, OUTPUT);      
  pinMode(led3Pin, OUTPUT);      
  pinMode(button1, INPUT);      
  digitalWrite(button1,HIGH);        // internal pullup all versions
}

void loop() {
  // Toggle LED if button debounced
  if (butndown(digitalRead(button1), &bcount1, &bstate1, 10UL )) {
    if (led1State == LOW) {
      led1State = HIGH;
    }
    else {
      led1State = LOW; 
    } 
    digitalWrite(led1Pin, led1State);
  } 

  // Act if the latter time (ms) has now passed on this particular counter,
  if (timeout(&count2, 300UL )) {
    if (led2State == LOW) {
      led2State = HIGH;
    }
    else {
      led2State = LOW; 
    } 
    digitalWrite(led2Pin, led2State);
  } 

  if (timeout(&count3, 77UL )) {
    if (led3State == LOW) {
      led3State = HIGH;
    }
    else {
      led3State = LOW; 
    } 
    digitalWrite(led3Pin, led3State);
  } 
}

I think if you explain what your machine is supposed to do it would help everyone.

You introduced the word "chute" without explaining it. (Some James Bond mechanism for getting rid of the baddies?)

...R

Was trying to keep it simple, guess I kept it too simple :~

The system is a control system for a haunted house. 3 control boxes with LEDs and Buttons, and one main control box housing the Arduino and an 8 relay card. For these purposes assume it's all in one box. The other boxes will be connected with CAT 5 cable being used as long extensions (No RS232 or Ethernet communication, just wire.) Here is what is supposed to happen once the system starts

  • Guests will enter 1 of 4 chutes (hallways). Once they enter, the actor at the door will push the corresponding button on his control panel. This starts sequence 1 in the first chute. (video plays) And actor 1 will start loading another chute
  • Once the video is finished a second actor, in view of the video, will push the corresponding chute button on his controller. This activates sequence two. (Strobe light turns on in chute, LED's on control panels blink) He goes and scares the guests into the house
  • Once the guests are out of the chute the actor returns to his control panel and pushes the same button once again. This activates sequence 3. (Resets video player, turns off strobe light, turns off LED's on control panels
  • And the whole thing starts over again.

And all of this is also taking place in the other 3 chutes also!

I have the hardware side of it figured out (that's my strong suit) just need some help with the programming. Doing the sequences I think I can figure out, but I am not sure how to setup and register the button pushes. I can figure it out with a single button, but I will have 8 total. I am already going to take the idea from earlier of putting them in an array, but how do I call said button when needed? There may also be an occasion where I have to have 2 buttons pressed at the same time, I hope to avoid that but it may happen when so many people are running sections at once.

Once the sequence starts, it will finish rapidly. Though the relays will stay in their current state, the next sequence will change the relay position. (Mud as clear, that last sentence was {he says in his best Yoda voice})

I think I answered all the questions from before, if anyone has any more please feel free to ask. Or if you have any suggestions that's great also :smiley:

This is what I have code wise so far:

const int  buttonPin [8] = { A0, A1, A2, A3, 12, 13, A4, A5 }; //Buttons
const int relay120 [4] = { 1, 3, 5, 7 }; // 120VAC Relays
const int relay12 [4] = { 0, 2, 4, 6 }; //12VDC Rekays
const int ledc [4] = { 8, 9, 10, 11 }; //Chute LEDs
int ledState = LOW;             // ledState used to set the LED
long previousMillis = 0;        // will store last time LED was updated
long interval = 1000;           // interval at which to blink (milliseconds)
int buttonPushCounter = 0;   // counter for the number of button presses
int buttonState = 0;         // current state of the button
int lastButtonState = 0;     // previous state of the buttocledn

void setup() {
  // initialize the button pin as an input:
  for (byte i=0; i<8; i++) {
    pinMode(buttonPin[i], INPUT);
  }
  // initialize the 120VAC relay pins as outputs:
  for (byte d=0; d<4; d++) {
    pinMode(relay120[d], OUTPUT);
  }
  // initialize the 12VDC relay pins as outputs:
  for (byte a=0; a<4; a++) {
    pinMode(relay12[a], OUTPUT);
  }
  // initialize the LEDs as outputs:
  for (byte l=0; l<4; l++) {
    pinMode(ledc [l], OUTPUT);
  }
  // initialize serial communication:
  Serial.begin(9600);
}
void loop() {
  
  
}
void sequence1() {
  int led=ledc [0]; //Just here for testing
  int relayDC=relay12 [0]; //will be set by button press sequence
  digitalWrite(led, HIGH); // Turn LED ON
  digitalWrite(relayDC, HIGH); // Turn 12VDC video player ON
}
void sequence2() {
  int relayAC=relay120 [0]; // Just here for testing
  int led=ledc [0]; //will be set by button press sequence
  digitalWrite(relayAC, HIGH); // Turn 120VAC Strobe Light ON
   
   unsigned long currentMillis = millis(); //Blink the LED
 
   if(currentMillis - previousMillis > interval) {
    // save the last time you blinked the LED 
    previousMillis = currentMillis;   

    // if the LED is off turn it on and vice-versa:
    if (ledState == LOW)
      ledState = HIGH;
    else
      ledState = LOW;

    // set the LED with the ledState of the variable:
    digitalWrite(led, ledState);
  }
}

void sequence3() {
  int relayDC=relay12 [0]; // Just here for testing
  int relayAC=relay120 [0]; //will be set by button press sequence
  int led=ledc [0]; //Same as above, will change
  digitalWrite(relayAC, LOW); //Turn 120VAC Strobe Light OFF
  digitalWrite(relayDC, LOW); //Turn 12VDC video player OFF
  digitalWrite(led, LOW); //Turn LED OFF
}

This sets up my subroutines and such, it verified ok but haven't tested it with hardware yet. I still need to figure out the multiple button pushes to get started testing. I plan on having the LED and Relay variables set in the sequence with the button pushes. So when they punch a button it will set the variable depending on which button was pressed.

So any suggestions on the button push portion?

I think things are getting clearer now.

Questions:

  1. Each chute has 2 buttons. Are they, or could they be, the same button from the Arduino's point of view? Button 1 starts the sequence, button 2 changes the sequence. Any reason why either button could not do either job? If not, just wire them up in parallel to the same Arduino pin.
  2. "Sequence 3" is really just off/normal state?

With all those actors pressing buttons at any time, the debouncing will need to be per-button I think. So you will need an array for the current button states (pressed or not pressed), an array for the button debouncing (unsigned long to record value of millis at point button gets pressed), an array of states for each chute (off/sequence 1/sequence 2).

You can implement the flashing of leds easily by using bitRead (millis (), 9).

That sequence 2 is blocking, that is once it is enterd you will not be able to do anything until it is finished.
The whole of your code needs to be structured as a state machine, using the technique you have in sequence 2 but at the top level of the loop function.

Think of creating a very simple function that just "operates" the relays and leds - a bit like this

void actionFunction() {
digitalWrite(ledx, ledxState);
// etc etc
digitalWrite(relayY, relayYstate);
//etc etc
}

The point is that it is a function with no "intelligence" whatever - the intelligence is in the state variables. (And, of course if there are several leds and relays their pins and states could be stored in an array.) The simplest thing is to create the state variables as global variables.

Now, elsewhere you will have the decision making code that changes the values of the relevant state variables - perhaps when a switch is triggered or when a time elapses. I find it much easier to "see" my logic when I organize things like this.

I would keep each switch on a separate pin so that there is no possibility of one switch setting a state and another switch on the same pin unsetting the state. If each switch is separate then there probably won't be any need for debouncing code - two or three quick pulses from a switch won't have any different effect from a single pulse - the first pulse will change the state and that will be that.

...R

PaulRB:
Questions:

  1. Each chute has 2 buttons. Are they, or could they be, the same button from the Arduino's point of view? Button 1 starts the sequence, button 2 changes the sequence. Any reason why either button could not do either job? If not, just wire them up in parallel to the same Arduino pin.
  2. "Sequence 3" is really just off/normal state?
  1. Hmm yeah I don't see any reason they can't be the same button, that would save on pins and may make things simplier. Though I could see it cause problems if the first actor (who loads them) hits it by accident when loading another chute, but that's not something I can really do anything about.
  2. Yes, "Sequence 3" is just a back to normal state.

PaulRB:
With all those actors pressing buttons at any time, the debouncing will need to be per-button I think. So you will need an array for the current button states (pressed or not pressed), an array for the button debouncing (unsigned long to record value of millis at point button gets pressed), an array of states for each chute (off/sequence 1/sequence 2).

I don't understand this. How would I implement an array for the button debouncing. Doing an array for the state I understand, just put a flag value in the array and read and write it as needed, but how would I put a debounce in an array?

PaulRB:
You can implement the flashing of leds easily by using bitRead (millis (), 9)

i don't understand how this should blink the LED. If I am reading the state, what causes the LED to turn off and on?

Grumpy_Mike:
That sequence 2 is blocking, that is once it is enterd you will not be able to do anything until it is finished.
The whole of your code needs to be structured as a state machine, using the technique you have in sequence 2 but at the top level of the loop function.

I don't understand this really. Sequence 2 is just like the others except it has a blink section for the led.

Robin2:
Think of creating a very simple function that just "operates" the relays and leds - a bit like this

void actionFunction() {
digitalWrite(ledx, ledxState);
// etc etc
digitalWrite(relayY, relayYstate);
//etc etc
}

The point is that it is a function with no "intelligence" whatever - the intelligence is in the state variables. (And, of course if there are several leds and relays their pins and states could be stored in an array.) The simplest thing is to create the state variables as global variables.

Now, elsewhere you will have the decision making code that changes the values of the relevant state variables - perhaps when a switch is triggered or when a time elapses. I find it much easier to "see" my logic when I organize things like this.

I would keep each switch on a separate pin so that there is no possibility of one switch setting a state and another switch on the same pin unsetting the state. If each switch is separate then there probably won't be any need for debouncing code - two or three quick pulses from a switch won't have any different effect from a single pulse - the first pulse will change the state and that will be that.

...R

This is probably the closest to what I already have. Each of the Sequences are separate functions that just operate the LED's and Relays. All the variables will be set in the actual loop section (once I get that figured out) and will be set determined on which switch gets pushed. Right now I have all the switches separate, and I think that might be the way to go just to be on the safe side.
Though I do think I still need debounce because there are parts where I want the same button to change the state, though there will be anywhere from 30 seconds to 2 min in between these changes.

Thanks everyone on the suggestions I am taking them in mind, but I still don't understand how to implement the button pushes.

but I still don't understand how to implement the button pushes.

I told you in reply #14.
See this page:-
http://www.thebox.myzen.co.uk/Tutorial/State_Machine.html

Each time round the loop you check for a button press, that is a change from not pressed last time to pressed this time. Once you see a press you mark that sequence as active. ( Using a Boolean variable )
Then in the next part of the loop you look to see what sequences are active. Each one will have it's own state variable. You then call the function associated with that action and it will either return immediately because it is not time to go on the next state or, if it is time, it will do what it needs to do, like switch on or off some relay or LED. Then it will change the state variable and then set the time when the next action is to occur. If it has finally finished all the actions then it will clear the Boolean variable indicating it is an active task.

morte615:
How would I implement an array for the button debouncing.

Debouncing is just about ignoring any further changes from a button for a short period (e.g. 50ms) after you have detected the initial change. The array would just contain an unsigned long, for each button, which would be used to save the value of millis() at the moment the initial button change was detected. If any subsequent changes are picked up, your sketch can compare the value stored in the array for that button against the value of millis() now. If the difference is less than the debounce period, ignore the button change.

morte615:

PaulRB:
You can implement the flashing of leds easily by using bitRead (millis (), 9)

i don't understand how this should blink the LED. If I am reading the state, what causes the LED to turn off and on?

Its a way of avoiding keeping the state of the LED and having to keep checking the time and updating the state. The millis() function returns a value that gets updated every 1ms. So bit 9 of the result will get updated every 256ms. If you use that to set the LED, it will flash roughly twice per second. Like this:

digitalWrite(ledPin, bitRead(millis(), 9));

Its less flexible than keeping the LED state and checking when its time to change the state. You only have the choice of flashing approximately every 2s, 1s, 0.5s, 0.25s etc. and the on/off periods will always be equal.

morte615:
Thanks everyone on the suggestions I am taking them in mind, but I still don't understand how to implement the button pushes.

The button pushes (once a change from not-pressed to pressed has been detected and debounced) should be used by your sketch to change the state of that chute. The "master" button will always set the state to sequence 1 (if it is not already). The "remote" button will change state from sequence 1 to sequence 2 or from sequence 2 to normal/off.

Now I get it, thanks! :slight_smile:

I will look into it but that seems like just what I am looking for, YAY!!