Button Control Issue

I have this program working exactly the way I want, but now I am thinking it would be nice to show the kids how to go even further. Problem is, I am running into a wall. The program demonstrates how to add toggle functionality to momentary buttons. All good, but now I want to press two buttons at the same time to make a 4 button device do even more. (just simple momentary buttons).

So, I tried to put a delay(250) at the top of each of the four buttonXXX() functions. This way, there would be time to press the two buttons without the original program for each button starting inadvertently. Sounded logical to me! Well, apparently that is a bridge too far because if you add a delay to the top of those functions, you cause the program to execute all by itself on startup! Why would a delay cause the buttonXXX() to execute? What programming tricks do you use to make this sort of thing happen?

#include <Adafruit_NeoPixel.h>


const byte butnPin1 = 4;  
const byte butnPin2 = 7;             
const byte butnPin3 = 8;              
const byte butnPin4 = 12;            

volatile int btnValue1 = LOW;
volatile int btnValue2 = LOW;                  
volatile int btnValue3 = LOW;
volatile int btnValue4 = LOW;

volatile int btnPreValue1;          
volatile int btnPreValue2;    
volatile int btnPreValue3;    
volatile int btnPreValue4;   
unsigned long time = 0;    
long debounce = 200;  

/* LED Related Global Variables */
//const byte ledStrip7  = 3;       
const byte ledStrip16 = 5;    
const byte ledStrip24 = 6;     

bool btnOneOn = false;
bool btnTwoOn = false;     
bool btnThreeOn = false;      
bool btnFourOn = false;


// Adafruit_NeoPixel strip7  = Adafruit_NeoPixel( 7, ledStrip7 , NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip16 = Adafruit_NeoPixel(16, ledStrip16, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel strip24 = Adafruit_NeoPixel(24, ledStrip24, NEO_GRB + NEO_KHZ800);

void setup() {
  Serial.begin(115200);                          
  //strip7.begin();                                          
  //strip7.show();                                          
  strip16.begin();                                         
  strip16.show();                                         
  strip24.begin();                                        
  strip24.show();                                          
  pinMode(butnPin1, INPUT_PULLUP);
  pinMode(butnPin2, INPUT_PULLUP); 
  pinMode(butnPin3, INPUT_PULLUP); 
  pinMode(butnPin4, INPUT_PULLUP);  
  //pinMode(ledStrip7,  OUTPUT);
  pinMode(ledStrip16, OUTPUT);
  pinMode(ledStrip24, OUTPUT);                             
}

void loop() {  
  buttonOne();
  buttonTwo();                 
  buttonThree();                         
  buttonFour();
  buttonsOneTwo();
}

void buttonOne() {
  btnValue1 = digitalRead(butnPin1);
  /* Demonstrates how to use a momentary button to run a defined program where you start it and
    expect it to stop once its over (I.E. Push down once and let off right away).  */
  if ((btnValue1 == LOW) && (btnOneOn == false)) {
    btnOneOn = true;    
    for (int i = 0; i < 3; i++) {
      /* Function   Which Ring   R    G    B   Delay */
      colorWipe24(strip24.Color(100, 100, 100), 50);
      colorWipe24(strip24.Color(0, 0, 0), 1);
    }
    for (int i = 0; i < 5; i++) {
      reverseColorWipe24(strip24.Color(60, 60, 60), 10);
      reverseColorWipe24(strip24.Color(0, 0, 0), 1);
    }
    for (int i = 0; i < 2; i++) {
      theaterChase24(strip24.Color(60, 60, 60), 50);
    }
    colorWipe24(strip24.Color(127, 127, 127), 1);
    colorWipe24(strip24.Color(0, 0, 0), 1);
    btnOneOn = false;
  }
}

void buttonTwo() {
  btnValue2 = digitalRead(butnPin2);
  /* This one starts the program on button push and leaves it running until you press the button
    again. This emulates a toggle switch. */
  if (btnValue2 == HIGH && btnPreValue2 == LOW && millis() - time > debounce) {
    if (btnTwoOn == false) {
      btnTwoOn = true;  
      RGBLoop16();
      RGBLoop24();
      /* Function   Which Ring   R    G    B   Delay */
      colorWipe16(strip16.Color(127, 127, 127), 10);
      colorWipe24(strip24.Color(127, 127, 127), 10);
      colorWipe16(strip16.Color(0, 0, 0), 1);
      colorWipe24(strip24.Color(0, 0, 0), 1);
      reverseColorWipe16(strip16.Color(127, 127, 127), 10);
      reverseColorWipe24(strip24.Color(127, 127, 127), 10);
      reverseColorWipe16(strip16.Color(0, 0, 0), 1);
      reverseColorWipe24(strip24.Color(0, 0, 0), 1);
      theaterChase16(strip16.Color(127, 127, 127), 50);
      theaterChase24(strip24.Color(127, 127, 127), 50);
      colorWipe16(strip16.Color(60, 60, 60), 10);
      colorWipe24(strip24.Color(60, 60, 60), 10);
    }
    else if (btnTwoOn == true) {
      colorWipe16(strip16.Color(0, 0, 0), 10);
      colorWipe24(strip24.Color(0, 0, 0), 10);
      btnTwoOn = false;
    }
    time = millis();
  }
  btnPreValue2 = btnValue2;
}

void buttonThree() {
  btnValue3 = digitalRead(butnPin3);
  /* Demonstrates how to turn on/off in momentary mode using individual LEDs meaning the LEDs
    only light up as long as you push down the button. */
  if ((btnValue3 == LOW) && (btnThreeOn == false)) {
    btnThreeOn = true;                
    /*     Which Ring     LED#  R   G   B */
    strip16.setPixelColor(14, 127, 30, 0);   
    strip16.setPixelColor(1, 127, 127, 127);
    strip16.show();                        
    strip24.setPixelColor(22, 127, 20, 0);
    strip24.setPixelColor(1, 127, 127, 127);                            
    strip24.show();                       
  }
  else if ((btnValue3 == HIGH) && (btnThreeOn == true)) {
    strip16.setPixelColor(14, 0, 0, 0);                      
    strip16.setPixelColor(1, 0, 0, 0);
    strip16.show();
    strip24.setPixelColor(22, 0, 0, 0);                      
    strip24.setPixelColor(1, 0, 0, 0);
    strip24.show();
    strip16.setPixelColor(14, 0, 0, 0);                      
    strip16.setPixelColor(1, 0, 0, 0);
    strip24.setPixelColor(22, 0, 0, 0);                       
    strip24.setPixelColor(1, 0, 0, 0);
    btnThreeOn = false;
  }
}

void buttonFour() {
  btnValue4 = digitalRead(butnPin4);
  /* This one shows how to lite up any of the individual LEDS on a single pixel ring with
    whatever color you choose. This is based on an addressing system of 0-23 on a 24 LED
    ring, 0-15 on a 16 LED ring, and 0-6 on a 7 LED ring. It also incorporates the benefit
    of toggle action as in buttonTwo(). */
  if (btnValue4 == HIGH && btnPreValue4 == LOW && millis() - time > debounce) {
    if (btnFourOn == false) {            
      btnFourOn = true;
      //  which ring       led#  R   G   B
      strip16.setPixelColor(15, 127, 20, 0);                           
      strip16.setPixelColor(0, 127, 127, 127);                             
      strip16.show();                        
      strip24.setPixelColor(23, 127, 20, 0);                             
      strip24.setPixelColor(0, 127, 127, 127);                          
      strip24.show();                       
    }
    else if (btnFourOn == true) {
      strip16.setPixelColor(15, 0, 0, 0);                      
      strip16.setPixelColor(0, 0, 0, 0);
      strip16.show();
      strip24.setPixelColor(23, 0, 0, 0);                     
      strip24.setPixelColor(0, 0, 0, 0);
      strip24.show();
      strip16.setPixelColor(15, 0, 0, 0);                       
      strip16.setPixelColor(0, 0, 0, 0);
      strip24.setPixelColor(23, 0, 0, 0);                      
      strip24.setPixelColor(0, 0, 0, 0);
      btnFourOn = false;
    }
    time = millis();
  }
  btnPreValue4 = btnValue4;
}

void buttonsOneTwo() {
  /* This one requires you push button one and button two at the same time and hold them down.
    Letting off stops the program. */
  if (btnValue1 == LOW && btnValue2 == LOW) {
    Serial.println("test");
    //digitalWrite (ledPin1, HIGH);
  }
  else if (btnValue1 == HIGH && btnValue2 == HIGH) {    
  //    digitalWrite (ledPin1, LOW);
  }
}

You should read all of the buttons in one place only at the start of loop() and store their values in variables.

Then you can use the saved values to decide what to do, and which function to call.

Make sure to test for the two buttons first - otherwise you will implement the code for one of them rather than the code for both of them. And use IF/ELSE for the tests so that for any one iteration of loop() you only respond to one option.

...R

I thought that was already the case. All of the buttons ARE in one place at the start of the loop, all are stored in variables and each function is readily accessible. So as it is, you think if/else statements can get it done without a delay? I guess I am not following how that would look. Since you want the buttons original functions to be ready at the push of a button, how can you stop those functions from happening if you press two at the same time? (which isnt really "at the same time" since no-one will ever be able to exactly do that)

If the buttons were all on the same PORT, you could simply read the PORT status (maybe ten times quickly and "average" to debounce)

byte status = PINB

for example would return the states of the PortB pins (check a "pinout" diagram).

I used AdaFruits debounce code which works really well. If I remove the millis or time variable, it is obvious that its working great, but this is about using two buttons that are already claimed for other functions and trying to get more out of them by pressing two at the same time. I just wish I understood why I cant put ANY delays in the code at the top of the functions without it engaging the function! I have never seen that before. It must have something to do with the NeoPixel back end code. If anyone has any ideas how to make this happen, dont be bashful!

The structure of your program makes it very difficult to do what you want to do. You have designed a teaching demonstration of 4 different ways to read a button. You call four different functions in order, and only one of the functions becomes true when one if the 4 buttons is pressed.

The logical extension of your structure would be to have button5 and button6 and perform the compound conditional on them

if (btnValue5 == LOW && btnValue6 == LOW) {

If you put delay() at the start of buttonOne() and buttonTwo(), read both buttons in the function, and then use a compound conditional where only one is true, you might achieve what you want, but you will complicate the teaching demonstration. You will also be teaching blocking methods which the existing demonstration avoids.

for example

void buttonOne() {
delay(500);
  btnValue1 = digitalRead(butnPin1);
  btnValue2 = digitalRead(butnPin2);
  /* Demonstrates how to use a momentary button to run a defined program where you start it and
    expect it to stop once its over (I.E. Push down once and let off right away).  */
  if ((btnValue1 == LOW)&& (btnValue2 == HIGH) && (btnOneOn == false)) {
  // buttonOne code for leds

There are non blocking ways to determine if two buttons have been pressed in an interval of time, but you will need to restructure the way you have designed your program.

Deve: I thought that was already the case. All of the buttons ARE in one place at the start of the loop,

Not as far as I can see. Each button function seems to read a button.

There is no function called from loop() whose only purpose is to read buttons and save the values.

Have a look at Planning and Implementing a Program

...R

Robin, I read every word of your articles, because you would have to be crazy not to, but then I modify what I need to teach 8-9 year olds how to do Arduino projects. I feel I know how they think so this works VERY nicely for that. It is NOT about proper programming technique that adults could easily grasp. Thats what makes coming here so hard. I have a different angle I am approaching this at in order to get the most out of each child in a limited time.

Of course up until now when I want to do something like a two button solution. If anyone knows how to honor the concept I am trying to get across as evidenced by the way its programmed, and still pull this off, let me know. Cattledog, I feel you understand completely, so if you have any thoughts on how to do that, I am all ears. I also changed buttonFour() since last uploading the code…

void buttonFour() {
  btnValue4 = digitalRead(butnPin4);
  /* This one shows runs through 9 different programs with one button! Be patient and enjoy
    the show! */
  if (btnValue4 == LOW && btnPreValue4 == HIGH && millis() - time > debounce) {
    btnValue4 = digitalRead(butnPin4);
    if (btnValue4 == LOW) {
      showType++;                          // increment through each show by pushing the button
      if (showType > 9)                            // 9 cases, so greater than 9 resets to zero
        showType = 0;                                         // start at case 0 (which is off)
      startShow(showType);
    }
    time = millis();
  }
  btnPreValue4 = btnValue4;
}

void startShow(int btn4Show) {
  switch (btn4Show) {
    case 0:
      colorWipe16(strip16.Color         (0, 0, 0), 25);
      colorWipe24(strip24.Color         (0, 0, 0), 25);  // off
      break;
    case 1:
      colorWipe16(strip16.Color       (255, 0, 0), 25);
      reverseColorWipe24(strip24.Color(255, 0, 0), 25);  // Red
      break;
    case 2:
      reverseColorWipe16(strip16.Color(0, 255, 0), 25);
      colorWipe24(strip24.Color       (0, 255, 0), 25);  // Green
      break;
    case 3:
      colorWipe16(strip16.Color       (0, 0, 255), 25);
      reverseColorWipe24(strip24.Color(0, 0, 255), 25);  // Blue
      break;
    case 4:
      theaterChase16(strip16.Color   (127, 127, 127), 25); // White
      theaterChase24(strip24.Color   (127, 127, 127), 25); // White
      break;
    case 5:
      theaterChase16(strip16.Color       (127, 0, 0), 25); // Red
      theaterChase24(strip24.Color       (127, 0, 0), 25); // Red
      break;
    case 6:
      theaterChase16(strip16.Color       (0, 0, 127), 25); // Blue
      theaterChase24(strip24.Color       (0, 0, 127), 25); // Blue
      break;
    case 7:
      rainbow16(10);
      rainbow24(10);
      break;
    case 8:
      rainbowCycle16(10);
      rainbowCycle24(10);
      break;
    case 9:
      theaterChaseRainbow16(10);
      theaterChaseRainbow24(10);
      break;
  }
}

I have introduced Switch/Case in previous lessons so this will really help. Thanks to AdaFruits examples. The loop doesn’t HAVE to be totally barren of other code like it is, but I try to get them to understand to be minimal in their use of loop and how to organize similar things together. Admittedly some of my hesitation is because I am not a very good programmer in the first place.

I teach. "but then I modify what I need to teach 8-9 year olds how to do Arduino projects". Seems well over and above what they'd understand for a general cohort of that age.

Maybe The BBC micro bit or the like could be a nicer option with "code blocks" for them to understand?

no, these kids are extraordinary. They eat this stuff up. I only wish I could pick it up as fast as they do. We started a year ago with an uno and an led. We are working up to this:

http://devestechnet.com/NewTech/SuperSmartCar

That isn't in the cards for another year or so, but this light display really plays on what they have learned. Sometimes they even get bored and add things that actually work that I didn't think of. I am no teacher. Just retired and have time to help out, but it has been an amazing experience. My personal thoughts on teaching is, with the US being the 26th best education system in the world, we are selling our kids short by not giving them higher level content and seeing where they go with it. Not that it isn't a real challenge at times.

Deve: but then I modify what I need to teach 8-9 year olds how to do Arduino projects. I feel I know how they think so this works VERY nicely for that. It is NOT about proper programming technique that adults could easily grasp.

Let's agree to differ.

My opinion is that the youngsters will more easily understand "proper programming technique" than most adults.

And the alternative to "proper programming technique" is poor programming technique and I see no justification for teaching that.

...R

The way you structured button 1 is that you run a program the moment it is pressed. So you'd have to change that for starters to let it wait for maybe button 2 to come in as well.

There is more that you can do with the buttons: including adding a long press differentiation. I do that by recording the time you see a button pressed, then reacting upon release for a short press, or if it's pressed for a time longer than say 500 ms (1/2 second) it's considered a long press and acted upon (and of course record that you have seen a long press now, so you don't record also a short press when the button is released).

The latter can very easily be extended to double press: the moment one of the two buttons is declared "long pressed" you check whether the other is pressed as well, and if so, declare it a two-button press and handle it accordingly.

Same for short press: the moment you see one button released (within the short press period) check for the other button to be pressed as well.

Its all possible if you know what you are doing. Sadly I fumble with this stuff at this point. Everyone has to start somewhere, so I keep on. Meanwhile, I got the delays to work but not sure how to stop the primary button functions from going off after the delay.

void loop() {
  buttonOne();
  buttonTwo();                 // Keeping the loop nice and clean means being able to comment out
  buttonThree();                          // just the functions you want for testing purposes. :)
  buttonFour();
  buttonsOneTwo();
}

void buttonOne() {
  btnValue1 = digitalRead(butnPin1);
  /* Demonstrates how to use a momentary button to run a defined program where you start it and
    expect it to stop once its over (I.E. Push down once and let off right away). This one is
    best suited for long dazzling displays that you can walk away from and know that once its
    over, it will do that last thing you programmed it to do. (hopefully turned the LEDS off) */
  if ((btnValue1 == LOW) && (btnOneOn == false)) {
    delay(1000);  // to give enough time for the secondary function
    btnOneOn = true;                  // used to separate button 1's functions from other buttons
    for (int i = 0; i < 3; i++) {
      /* Function   Which Ring   R    G    B   Delay */
      colorWipe24(strip24.Color(100, 100, 100), 50);
      colorWipe24(strip24.Color(0, 0, 0), 1);
    }
    for (int i = 0; i < 5; i++) {
      reverseColorWipe24(strip24.Color(60, 60, 60), 10);
      reverseColorWipe24(strip24.Color(0, 0, 0), 1);
    }
    for (int i = 0; i < 2; i++) {
      theaterChase24(strip24.Color(60, 60, 60), 50);
    }
    colorWipe24(strip24.Color(127, 127, 127), 1);
    colorWipe24(strip24.Color(0, 0, 0), 1);
    btnOneOn = false;
  }
}

void buttonTwo() {
  btnValue2 = digitalRead(butnPin2);
  /* This one starts the program on button push and leaves it running until you press the button
    again. This emulates a toggle switch. With 47 LEDs, the current drain on the battery is
    pretty high, so be sure to turn it off when you are done! */
  if (btnValue2 == HIGH && btnPreValue2 == LOW && millis() - time > debounce) {
    delay(1000);  // to give enough time for the secondary function
    if (btnTwoOn == false) {
      btnTwoOn = true;               // used to separate button 2's functions from other buttons
      RGBLoop16();
      RGBLoop24();
      /* Function   Which Ring   R    G    B   Delay */
      colorWipe16(strip16.Color(127, 127, 127), 10);
      colorWipe24(strip24.Color(127, 127, 127), 10);
      colorWipe16(strip16.Color(0, 0, 0), 1);
      colorWipe24(strip24.Color(0, 0, 0), 1);
      reverseColorWipe16(strip16.Color(127, 127, 127), 10);
      reverseColorWipe24(strip24.Color(127, 127, 127), 10);
      reverseColorWipe16(strip16.Color(0, 0, 0), 1);
      reverseColorWipe24(strip24.Color(0, 0, 0), 1);
      theaterChase16(strip16.Color(127, 127, 127), 50);
      theaterChase24(strip24.Color(127, 127, 127), 50);
      colorWipe16(strip16.Color(60, 60, 60), 10);
      colorWipe24(strip24.Color(60, 60, 60), 10);
    }
    else if (btnTwoOn == true) {
      colorWipe16(strip16.Color(0, 0, 0), 10);
      colorWipe24(strip24.Color(0, 0, 0), 10);
      btnTwoOn = false;
    }
    time = millis();
  }
  btnPreValue2 = btnValue2;
}

void buttonsOneTwo() {
  /* This one requires you push button one and button two at the same time and hold them down.
    Letting off stops the program. Demonstrates how to do much more with only 4 buttons! */
  if (btnValue1 == LOW && btnValue2 == LOW) {
    Serial.println("test");
    //digitalWrite (ledPin1, HIGH);
  }
  else if (btnValue1 == HIGH && btnValue2 == HIGH) {
    //    digitalWrite (ledPin1, LOW);
  }
}

Deve: Its all possible if you know what you are doing. Sadly I fumble with this stuff at this point

The code in Reply #12 calls 5 separate functions, one after the other and each of those functions reads some button input(s). That is NOT the way to achieve what you want.

Make a new function that reads ALL the buttons and saves their values to variables and remove all the digitalRead()s from the existing functions.

Then create some code that checks the saved state of each button and, depending on which buttons were pressed, calls the appropriate function. For example

if (button2State == LOW) { // assumes LOW when pressed
   buttonTwo();
}

Your code in loop() might reduce to

void loop() {
  readAndSaveButtonStates();
  actOnButtonStates();
}

The code to call the existing functions would be in actOnButtonStates();

...R

So it is simply not possible to do what I want using the current configuration? I do not have a clue what it would look like to do what you are eluding to. Seems a little extreme to completely scrap what I have but its not for me, so that is what keeps me going. Okay, where do I even start? Put all the buttons states together in one place? I mean, I cant even get my head around what that would look like.

Deve:
Seems a little extreme to completely scrap what I have but its not for me,

I am not saying you need to scrap what you have. 90% of it will still be needed along with my suggestions.

This is a problem of logic and design, not a problem of programming.

Think of the problem like this

read button1 and save the value
read button2 and save the value
etc for all buttons

if the saved values for buttons 1 and 2 are both LOW then call the function buttonOneTwo()
else if the saved value for button1 is LOW call the function buttonOne()
else if the saved value for button2 is LOW call the function buttonTwo()
etc etc

And for example, your buttonOne() function would become

void buttonOne() {
  // btnValue1 = digitalRead(butnPin1); // <<=========DELETE THIS LINE
  /* Demonstrates how to use a momentary button to run a defined program where you start it and
    expect it to stop once its over (I.E. Push down once and let off right away). This one is
    best suited for long dazzling displays that you can walk away from and know that once its
    over, it will do that last thing you programmed it to do. (hopefully turned the LEDS off) */
  if ((btnValue1 == LOW) && (btnOneOn == false)) {
    delay(1000);  // to give enough time for the secondary function
    btnOneOn = true;                  // used to separate button 1's functions from other buttons
    for (int i = 0; i < 3; i++) {
      /* Function   Which Ring   R    G    B   Delay */
      colorWipe24(strip24.Color(100, 100, 100), 50);
      colorWipe24(strip24.Color(0, 0, 0), 1);
    }
    for (int i = 0; i < 5; i++) {
      reverseColorWipe24(strip24.Color(60, 60, 60), 10);
      reverseColorWipe24(strip24.Color(0, 0, 0), 1);
    }
    for (int i = 0; i < 2; i++) {
      theaterChase24(strip24.Color(60, 60, 60), 50);
    }
    colorWipe24(strip24.Color(127, 127, 127), 1);
    colorWipe24(strip24.Color(0, 0, 0), 1);
    btnOneOn = false;
  }
}

…R

read button1 and save the value
read button2 and save the value
etc for all buttons

if the saved values for buttons 1 and 2 are both LOW then call the function buttonOneTwo()
else if the saved value for button1 is LOW call the function buttonOne()
else if the saved value for button2 is LOW call the function buttonTwo()
etc etc

The problem is more complex than this. There is timing and logic required to determine if two buttons are pressed within a short window of time.

cattledog: The problem is more complex than this. There is timing and logic required to determine if two buttons are pressed within a short window of time.

Let's fall off that bridge when we come to it. I'm not yet convinced that it is necessary for this case. Start simple. And what I have suggested will provide a suitable base if more complexity is required.

...R