Stuck in While Loop Button Change State read issue

My program is not exiting one while loop in a perticular fuction of my code. For some reason it appears that the digitalread on the button pin is not being called. I am totally at a loss as to what might/would/could be the culprit here, because if I where to create a new sketch with the code from the function with this while loop the code works properly.

The while loop that this issue is located in is the function Dispense which is just above my main loop

*Edit: I removed a digialread for buttonstate when oritionally posting the code cuz of several serialprint commands. I have put it back in.

 /*
Soda-Pop Dispenser Machine V 1.8

By:
Geoffrey Andreychuk

July 30th, 2022

*/

// Including additional required libraries:

#include <LiquidCrystal_I2C.h>
#include <EncoderButton.h>'
    
#include <Wire.h>



EncoderButton eb1(2, 3, 4); // Declaring the Rotary Encoder & Button along with the Assigned Pins used Encoder+button: EncoderButton(byte encoderPin1, byte encoderPin2, byte switchPin);

LiquidCrystal_I2C lcd(0x27, 20, 4);  // set the LCD address to 0x27 for a 20 chars and 4 line display


int ButtonPin = 7; // pin that the push button is assigned to to dispense the drink
int ButtonState = 1; // the state of the button that the user presses to dispense drink
int Solenoid = 0;  // soleenoid to open the valve to dispence beverage
int Pump = 0; // to turn on or off the pump to dispense syrup
bool IsClicked = false;


int DrinkPointer = 5;       // a variable to point to the current position in the Drink List
int TotalNumDrink = 10;     // variable to hold the value of Drink Options
int DrinkSelection = 0;    // variable to point to the users selected drink option to make the drink
int Increment;              // variable to hold a + for CW  and - for a CCW turn of Rotary Encoder

unsigned long IdleTime = 15000;       // a timer fuction to be used to clear any selected options and return program to main selection screen
unsigned long EventInterval = 12000;
unsigned long PreviousTime = 0;
unsigned long CurrentTime;

char *DrinkChoices[] = {"0.", " Dr. Pepper", "Grape Crush", "Orange Crush", "Cream Soda", " Iced Tea", "Soda Water", "   Coke", "Diet Coke", "Root Beer", "  Sprite"};  


void setup() 
{

//Pump A
#define inA 22

//Pump B
#define inB 23

//Pump C
#define inC 24

//Pump D
#define inD 25

//Pump E
#define inE 26

//Pump F
#define inF 27

//Pump G
#define inG 28

//Pump H
#define inH 29

//Relays to control Solenoid Valves 13
#define Sol1 32
#define Sol2 33
#define Sol3 34
 
  //Set all pump and Solenoid pins to outputs
  pinMode(inA, OUTPUT);
  pinMode(inB, OUTPUT);
  pinMode(inC, OUTPUT);
  pinMode(inD, OUTPUT);
  pinMode(inE, OUTPUT);
  pinMode(inF, OUTPUT);
  pinMode(inG, OUTPUT);
  pinMode(inH, OUTPUT);
  pinMode(Sol1, OUTPUT);
  pinMode(Sol2, OUTPUT);
  pinMode(Sol3, OUTPUT);
//Sets button pin as input  
  pinMode(ButtonPin, INPUT_PULLUP);

  
  Serial.begin(9600);
  lcd.init();  // initialize the display
  lcd.backlight();
  IdleMenu();

  //Link the event(s) to your function
  eb1.setClickHandler(onEb1Clicked);
  eb1.setEncoderHandler(onEb1Encoder);
  eb1.setIdleHandler(onEb1Idle);
  CurrentTime = millis ();
}

void onEb1Encoder(EncoderButton& eb) 
{
}

void ResetorIdle()
{
  IsClicked = false;
  DrinkSelection = 4; // To ensure that nothing is accidently dispensed till a selection is actually made
  Pump = 0; // Sets the enable pin for the previously selected pump to null
  Solenoid = 0; // Sets the previously selected solenoid valve back to null
}


void onEb1Clicked(EncoderButton& eb) 
{ 
  DrinkSelection = DrinkPointer;  // to ensure that the selected drink is not changed till either drink is poured or idle timer timeout is reached
  IsClicked = true;
  PlaceGlass();
  PourDrink();
  ResetorIdle();
}

void onEb1Idle(EncoderButton& eb)
{
//  ResetorIdle();
//  IdleMenu();
}

void IdleMenu()  // Prints the idle screen message to the display
{
  lcd.clear();
  lcd.setCursor(0, 0);  // Sets the cursor to the first row
  lcd.print("Drink Selection Menu");  // set the cursor to  line 1 which  is the second row
  lcd.setCursor(2, 1);
  lcd.print("Rotate Knob Left");  // print to the second line
  lcd.setCursor(2, 2);
  lcd.print("or Right & Press");  // print to the fourth line
  lcd.setCursor(0, 3);
  lcd.print("to make Selection!!");
}

void SelectionMenu() 
{
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("<-----------------");
  lcd.setCursor(5, 1);
  lcd.print(DrinkChoices[DrinkPointer]);
  lcd.setCursor(3, 2);
  lcd.print("---------------->");
  lcd.setCursor(0, 3);
  lcd.print("Press knob to Select");
}

void UpdatePointer()
{ 
  Increment = (eb1.position());
  DrinkPointer = DrinkPointer + Increment;
  eb1.resetPosition();
  if (DrinkPointer > TotalNumDrink) // Catches a positive increment overturn and wrap around
  {
    DrinkPointer = 1;
    return;
  }
  if (DrinkPointer < 1) // Catches a negative increment overturn and wrap around
  {
    DrinkPointer = TotalNumDrink;
    return;
  }
}

     
void PlaceGlass() // Function to tell the user which side to place glass
{ // cleans up the screen before letting user know where to put glass
  lcd.clear();
  lcd.setCursor(4, 0);
  lcd.print("Place Glass");
  lcd.setCursor(2, 1);
  if (DrinkSelection >= 1 && DrinkSelection <= 7)  
  {
      lcd.print("Right --------->");
  }	
  else if (DrinkSelection > 7 && DrinkSelection <= 10) 
  {
    lcd.print("<---------- Left");
  }
  lcd.setCursor(1,2); 
  lcd.print("Press & Hold Button");	
  lcd.setCursor(0,3); 
  lcd.print("to dispense Pop");
}

void ErrorMessage()
{
  lcd.clear();
  lcd.setCursor(6,0);
  lcd.print("Error!!");
  lcd.setCursor(0,1);
  lcd.print("Invalid Selection");
  lcd.setCursor(0,2);
  lcd.print("Or Out of Syrup");
  lcd.setCursor(2,3);
  lcd.print("Sorry, Not Sorry!!");
}
void PourDrink()
{
  switch (DrinkSelection)
  {
    case 0:
      ErrorMessage();
     break;
    
    case 1: // Selection Dr. Pepper
      Pump = inA;
      Solenoid = Sol1;
      Dispense();
    break;

    case 2: // Selection Grape Crush
      Pump = inB;
      Solenoid = Sol1;
      Dispense();
    break;

    case 3: // Selection Orange Crush
      Pump = inC;
      Solenoid = Sol1;
      Dispense();
    break;

    case 4: // Selection Cream Soda
      Pump = inD;
      Solenoid = Sol1;
      Dispense();
    break;

    case 5: // Selection Iced Tea
      Pump = 0;
      Solenoid = Sol3;
      Dispense();
    break;

    case 6: // Selection Soda Water
      Pump = 0;
      Solenoid = Sol1;
      Dispense();
    break;

    case 7: // Selection Coke
     // Pump = inE;
     // Solenoid = Sol2;
     // Dispense();     
      ErrorMessage();
    break;

    case 8: // Selection Diet Coke
   //   Pump = inF;
   //   Solenoid = Sol2;
   //   Dispense();
      ErrorMessage();
    break;

    case 9: // Selection Sprite
    //  Pump = inG;
    //  Solenoid = Sol2;
    //  Dispense();
      ErrorMessage();
    break;

    case 10: // Selection Root Beer
   //   Pump = inH;
   //  Solenoid = Sol2;
   //   Dispense();
      ErrorMessage();
    break;
  }
}

void Dispense() 
{
  ButtonState = digitalRead(ButtonPin);
  while (IsClicked == true)
  {
    if (ButtonState == 0)
    {
       while (ButtonState == 0)
      {
        digitalWrite(Pump, 1); // turn on pump
        digitalWrite(Solenoid, 1); // turn on Solenoid
        ButtonState = digitalRead(ButtonPin); //This appears to be not getting checked!!!!!!
      } 
      IsClicked = false;
      break;
    }
  }
}

void loop() 
{
  CurrentTime = millis();
  eb1.update();
  if (eb1.position() != 0)
 {
    UpdatePointer();
    SelectionMenu();
    PreviousTime = CurrentTime;
    
  }
  if (CurrentTime - PreviousTime >= IdleTime)
  {
    ResetorIdle();
    IdleMenu();
    PreviousTime = CurrentTime;
  }
}

Have you printed what the variables are doing and also “I am here” to find out where you are stuck?

void Dispense()
{
  while (IsClicked == true)
  {
    if (ButtonState == 0)
    {
      while (ButtonState == 0)
      {

What will the value of ButtonState be on entry to the Dispense() function ?

I was checking the state of both buttonstate along with isclicked with several serialprint statements.

I get the following

09:38:01.548 -> -----First Loop-------
09:38:01.548 -> Button is: 1
09:38:01.588 -> IsClicked is: 1
09:38:01.588 -> -----First Loop-------
09:38:01.628 -> Button is: 1
09:38:01.628 -> IsClicked is: 1
09:38:01.668 -> -----If Statement-------
09:38:01.668 -> Button is: 0
09:38:01.708 -> IsClicked is: 1
09:38:01.708 -> -----Second  Loop-------
09:38:01.748 -> Button is: 0
09:38:01.748 -> IsClicked is: 1
09:38:01.788 -> -----Second  Loop-------
09:38:01.788 -> Button is: 0
09:38:01.828 -> IsClicked is: 1
09:38:01.828 -> -----Second  Loop-------

it keeps going returning the outputs from the second loop even after the button is released.

ButtonState enters the function as 1, and then goes to 0 when pressed. I tried to check to confirm with serialprint statements, and got the following results and remains printing output for the second loop until I reset the arduino

09:38:01.548 -> -----First Loop-------
09:38:01.548 -> Button is: 1
09:38:01.588 -> IsClicked is: 1
09:38:01.588 -> -----First Loop-------
09:38:01.628 -> Button is: 1
09:38:01.628 -> IsClicked is: 1
09:38:01.668 -> -----If Statement-------
09:38:01.668 -> Button is: 0
09:38:01.708 -> IsClicked is: 1
09:38:01.708 -> -----Second  Loop-------
09:38:01.748 -> Button is: 0
09:38:01.748 -> IsClicked is: 1
09:38:01.788 -> -----Second  Loop-------
09:38:01.788 -> Button is: 0
09:38:01.828 -> IsClicked is: 1
09:38:01.828 -> -----Second  Loop-------

If button state is 1 on entry, your dispense function will loop forever, as you observe.

Edit: Which, on reflection is what @UKHeliBob was hinting at.

Why would that be??

the first loop is set by isclicked == true, then if buttonstate == 0, do the next while loop while buttonstate is still 0.. then when buttonstate is 1 upon release, it should continue the rest of the previous if statement and set isclicked to false, which should exit the while loop, along with there being a break command after as well.

I used this to try and see what was going on and with this very simplified sketch the code works correctly.

#include <LiquidCrystal_I2C.h>


LiquidCrystal_I2C lcd(0x27, 20, 4);  // set the LCD address to 0x27 for a 20 chars and 4 line display

int ButtonPin = 33; // pin that the push button is assigned to to dispense the drink
int ButtonState = 1; // the state of the button that the user presses to dispense drink
int Solenoid = 0;  // soleenoid to open the valve to dispence beverage
int Pump = 0; // to turn on or off the pump to dispense syrup


void setup() {

//Pump A
#define inA 22
#define Sol1 32


pinMode(inA, OUTPUT);
pinMode(Sol1, OUTPUT);
pinMode(ButtonPin, INPUT_PULLUP);
 
  Pump = inA;
  Solenoid  = Sol1;
  Serial.begin(9600);
 
}

void loop()
{
  while (ButtonState == 0)
  {
    Serial.print("Button is: pressed");
    Serial.println(ButtonState);
    digitalWrite(Pump, 1); // turn on pump
    digitalWrite(Solenoid, 1); // turn on Solenoid
     ButtonState = digitalRead(ButtonPin);
      }
    }

When dispense starts, if IsClicked is true the outer while loop starts running. If ButtonState is 1, the if is false and nothing inside it runs. So the button is never read and IsClicked remains true forever, causing an infinite loop.

sorry I deleted a digitalread when removing the serialprints to the forum

to error check I had the code as follows to see what was happening

void Dispense() 
{
  while (IsClicked == true)
  {
    Serial.println("-----First Loop-------");
    Serial.print("Button is: ");
    Serial.println(ButtonState);
    Serial.print("IsClicked is: ");
    Serial.println(IsClicked);
    ButtonState = digitalRead(ButtonPin);
    if (ButtonState == 0)
    {
       Serial.println("-----If Statement-------");
       Serial.print("Button is: ");
       Serial.println(ButtonState);
       Serial.print("IsClicked is: ");
       Serial.println(IsClicked);
       while (ButtonState == 0)
      {
        Serial.println("-----Second  Loop-------");
        Serial.print("Button is: ");
        Serial.println(ButtonState);
        Serial.print("IsClicked is: ");
        Serial.println(IsClicked);
        digitalWrite(Pump, 1); // turn on pump
        digitalWrite(Solenoid, 1); // turn on Solenoid
        ButtonState = digitalRead(ButtonPin); //This appears to be not getting checked!!!!!!
      } 
      IsClicked = false;
      break;
    }
  }
}

when cleaning up my code I removed a digitalread for the button state.. I just posted the code that I was trying to see what was going on with all the serialprints still.. Gotta love the save as to see what I had changee

I'd add some prints. What are solenoid & pump & buttonPin set to? Print something before & after the digitalRead too.

Solenoid is a variable which takes the value of either Sol1, Sol2, or Sol3, while Pump is a variable for the value of inA - inH, Buttonpin is the pin # that is assigned to the button. Buttonpin is currently set to pin 7, and the following values for inA-inH are set as follows in my code

Edit: I did have this being called within the second loop, which is how I was able to confim that I was stuck in this part of the loop as the prints would keep going until I reset the arduino
Serial.println("-----Second Loop-------");
Serial.print("Button is: ");
Serial.println(ButtonState);
Serial.print("IsClicked is: ");
Serial.println(IsClicked);

*note thdis code is not the whole program, just what was asked about from wildbill


int ButtonPin = 7; // pin that the push button is assigned to to dispense the drink
int ButtonState = 1; // the state of the button that the user presses to dispense drink
int Solenoid = 0;  // soleenoid to open the valve to dispence beverage
int Pump = 0; // to turn on or off the pump to dispense syrup
bool IsClicked = false;


int DrinkPointer = 5;       // a variable to point to the current position in the Drink List
int TotalNumDrink = 10;     // variable to hold the value of Drink Options
int DrinkSelection = 0;    // variable to point to the users selected drink option to make the drink
int Increment;              // variable to hold a + for CW  and - for a CCW turn of Rotary Encoder

unsigned long IdleTime = 15000;       // a timer fuction to be used to clear any selected options and return program to main selection screen
unsigned long EventInterval = 12000;
unsigned long PreviousTime = 0;
unsigned long CurrentTime;

char *DrinkChoices[] = {"0.", " Dr. Pepper", "Grape Crush", "Orange Crush", "Cream Soda", " Iced Tea", "Soda Water", "   Coke", "Diet Coke", "Root Beer", "  Sprite"};  


void setup() 
{

//Pump A
#define inA 22

//Pump B
#define inB 23

//Pump C
#define inC 24

//Pump D
#define inD 25

//Pump E
#define inE 26

//Pump F
#define inF 27

//Pump G
#define inG 28

//Pump H
#define inH 29

//Relays to control Solenoid Valves 13
#define Sol1 32
#define Sol2 33
#define Sol3 34
 
  //Set all pump and Solenoid pins to outputs
  pinMode(inA, OUTPUT);
  pinMode(inB, OUTPUT);
  pinMode(inC, OUTPUT);
  pinMode(inD, OUTPUT);
  pinMode(inE, OUTPUT);
  pinMode(inF, OUTPUT);
  pinMode(inG, OUTPUT);
  pinMode(inH, OUTPUT);
  pinMode(Sol1, OUTPUT);
  pinMode(Sol2, OUTPUT);
  pinMode(Sol3, OUTPUT);
//Sets button pin as input  
  pinMode(ButtonPin, INPUT_PULLUP);

I meant, what are those three things actually set to in second loop. Something odd is happening there and printing that information may provide a clue what it is.

I am turning on a pump via a l298n motor driver to pump pop syrup, and turning on a relay via a relay board to dispense the liquid base, to ultimately dispense pop when a push button is pressed.

However like I have also stated, the loop that is giving me problems functions correctly if I put the code, along with only the required variables needed for a pump pin, solenoid relay, etc. So my code should technically work, but I am stumped as to why it is not.

What additional information might you like me to provide you to try and resolve this problem???

As written, the code in dispense looks as though it should work. The first digitalRead of the button must have worked because the prints for the second loop appear. But apparently, the second one doesn't.

If I were debugging this, I'd probably comment out the two digitalWrites. I'd also print the value of buttonPin to verify that it hasn't been changed somehow. Better yet, I'd make it const and still print it.

consider - code demonstrates checking or multiple button presses and toggling LED associated with each button

// check multiple buttons and toggle LEDs

enum { Off = HIGH, On = LOW };

byte pinsLed [] = { 10, 11, 12 };
byte pinsBut [] = { A1, A2, A3 };
#define N_BUT   sizeof(pinsBut)

byte butState [N_BUT];

// -----------------------------------------------------------------------------
int
chkButtons ()
{
    for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
        byte but = digitalRead (pinsBut [n]);

        if (butState [n] != but)  {
            butState [n] = but;

            delay (10);     // debounce

            if (On == but)
                return n;
        }
    }
    return -1;
}

// -----------------------------------------------------------------------------
void
loop ()
{
    switch (chkButtons ())  {
    case 2:
        digitalWrite (pinsLed [2], ! digitalRead (pinsLed [2]));
        break;

    case 1:
        digitalWrite (pinsLed [1], ! digitalRead (pinsLed [1]));
        break;

    case 0:
        digitalWrite (pinsLed [0], ! digitalRead (pinsLed [0]));
        break;
    }
}

// -----------------------------------------------------------------------------
void
setup ()
{
    Serial.begin (9600);

    for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
        pinMode (pinsBut [n], INPUT_PULLUP);
        butState [n] = digitalRead (pinsBut [n]);
    }

    for (unsigned n = 0; n < sizeof(pinsLed); n++)  {
        digitalWrite (pinsLed [n], Off);
        pinMode      (pinsLed [n], OUTPUT);
    }
}

requested description

define pins at two sets of arrays making pins accessible with an index

check each button pin if pressed (change of state and LOW) and return index or return -1 if no button is pressed

"switch" on the return value from chkButtons(). could have been simpler since there's a 1-to-1 correspondence between the buttons pins and led pins array. for each button index that is returned, toggle an LED pin by reading its value and setting the pin to the inverse ("!") of the value that is read.

there is no case for "-1", so it is ignored

configure each LED pin as an output and initialize it to be Off, which on my hardware is HIGH. by defining and specifying Off, it is clear what i expect the pin state to be

can you please explain to me what exactly this code is supposed to be doing or do? I am at a loss. I am fairly new to arduino

It’s best you ask direct questions.

example:
What is happening here ?


enum { Off = HIGH, On = LOW };

Goes to show you the benefit of adding comments to sketches.
:wink:

Work through code line by line. Google the terms and then, if you don’t understand the purpose ask specific questions about what you don’t understand.

The enum just makes it simple to read the code when you can write on rather than low going forward and off rather than high
https://playground.arduino.cc/Code/Enum/

The next bit is arrays (very important concept)