Having trouble with millis

I'm trying to make a jack and coke maker. I am very new to coding and struggling to learn, especially the millis function. I had ChatGPT write most of this code. When an option is selected pump1 turns on and stays on. I need the pumps to run for the specified time and then shut off. The option "Add Jack" works fine. The other 3 options are the problem. What is wrong here?

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

LiquidCrystal_I2C lcd(0x27, 20, 4);  // Adjust the I2C address to match your LCD

int upButtonPin = 2;       // Up button
int downButtonPin = 3;     // Down button
int selectButtonPin = 10;  // Select button
int cancelButtonPin = 9;   // Cancel button

int selectedOption = 0;    // Track the selected option

// Define pump control pins
int pump1Pin = 4;
int pump2Pin = 5;

unsigned long startTime;   // To store the start time for dispensing

enum DispensingState {
  NOT_DISPENSING,
  DISPENSING_PUMP1,
  DISPENSING_PUMP2,
};

DispensingState dispensingState = NOT_DISPENSING;

// Variables to store pump active times
unsigned long pump1ActiveTime;
unsigned long pump2ActiveTime;

void setup() {
  lcd.init();
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print(">Jack and Coke 1");
  lcd.setCursor(0, 1);
  lcd.print(" Jack and Coke 2");
  lcd.setCursor(0, 2);
  lcd.print(" Shot of Jack");
  lcd.setCursor(0, 3);
  lcd.print(" Add Jack");
  pinMode(upButtonPin, INPUT_PULLUP);
  pinMode(downButtonPin, INPUT_PULLUP);
  pinMode(selectButtonPin, INPUT_PULLUP);
  pinMode(cancelButtonPin, INPUT_PULLUP);
  pinMode(pump1Pin, OUTPUT);
  pinMode(pump2Pin, OUTPUT);
}

void loop() {
  // Read button presses and update the selected option
  if (digitalRead(upButtonPin) == HIGH) {
    selectedOption--;
    if (selectedOption < 0) selectedOption = 3;
    updateLCD();
    delay(200);  // Debounce delay
  }

  if (digitalRead(downButtonPin) == HIGH) {
    selectedOption++;
    if (selectedOption > 3) selectedOption = 0;
    updateLCD();
    delay(200);  // Debounce delay
  }

  // Implement the logic for dispensing the selected option here
  if (digitalRead(selectButtonPin) == HIGH) {
    dispense(selectedOption);
  }

  // Implement the logic for canceling dispensing here
  if (digitalRead(cancelButtonPin) == HIGH) {
    cancelDispensing();
  }
}

void updateLCD() {
  lcd.clear();
  lcd.setCursor(0, 0);
  switch (selectedOption) {
    case 0:
      lcd.setCursor(0, 0);
      lcd.print(">Jack and Coke 1");
      lcd.setCursor(0, 1);
      lcd.print(" Jack and Coke 2");
      lcd.setCursor(0, 2);
      lcd.print(" Shot of Jack");
      lcd.setCursor(0, 3);
      lcd.print(" Add Jack");
      break;
    case 1:
      lcd.setCursor(0, 0);
      lcd.print(" Jack and Coke 1");
      lcd.setCursor(0, 1);
      lcd.print(">Jack and Coke 2");
      lcd.setCursor(0, 2);
      lcd.print(" Shot of Jack");
      lcd.setCursor(0, 3);
      lcd.print(" Add Jack");
      break;
    case 2:
      lcd.setCursor(0, 0);
      lcd.print(" Jack and Coke 1");
      lcd.setCursor(0, 1);
      lcd.print(" Jack and Coke 2");
      lcd.setCursor(0, 2);
      lcd.print(">Shot of Jack");
      lcd.setCursor(0, 3);
      lcd.print(" Add Jack");
      break;
    case 3:
      lcd.setCursor(0, 0);
      lcd.print(" Jack and Coke 1");
      lcd.setCursor(0, 1);
      lcd.print(" Jack and Coke 2");
      lcd.setCursor(0, 2);
      lcd.print(" Shot of Jack");
      lcd.setCursor(0, 3);
      lcd.print(">Add Jack");
      break;
  }
}

void dispense(int option) {
  // Implement the logic to control the peristaltic pumps for dispensing here
  // Activate the peristaltic pump(s) to dispense the appropriate amounts of liquids
  // You might need to calculate the time or revolutions needed for the desired amount
  // Implement safety measures to stop dispensing when the desired amount is reached

  unsigned long currentTime = millis();

  switch (option) {
    case 0: // Jack and Coke 1
      if (dispensingState == NOT_DISPENSING) {
        lcd.clear();
        lcd.print("Pouring");
        startTime = currentTime;
        dispensingState = DISPENSING_PUMP1;
        pump1ActiveTime = 2000; // 2 seconds
      }
      if (dispensingState == DISPENSING_PUMP1 && (currentTime - startTime) < pump1ActiveTime) {
        digitalWrite(pump1Pin, HIGH);
      } else if (dispensingState == DISPENSING_PUMP1) {
        digitalWrite(pump1Pin, LOW);
        dispensingState = DISPENSING_PUMP2;
        startTime = currentTime;
        pump2ActiveTime = 4000; // 4 seconds
      }
      if (dispensingState == DISPENSING_PUMP2 && (currentTime - startTime) < pump2ActiveTime) {
        digitalWrite(pump2Pin, HIGH);
      } else if (dispensingState == DISPENSING_PUMP2) {
        digitalWrite(pump2Pin, LOW);
        dispensingState = NOT_DISPENSING;
        lcd.clear();
        lcd.print("ENJOY!!");
        delay(2000);
        updateLCD();
      }
      break;

    case 1: // Jack and Coke 2
      if (dispensingState == NOT_DISPENSING) {
        lcd.clear();
        lcd.print("Pouring");
        startTime = currentTime;
        dispensingState = DISPENSING_PUMP1;
        pump1ActiveTime = 4000; // 4 seconds
      }
      if (dispensingState == DISPENSING_PUMP1 && (currentTime - startTime) < pump1ActiveTime) {
        digitalWrite(pump1Pin, HIGH);
      } else if (dispensingState == DISPENSING_PUMP1) {
        digitalWrite(pump1Pin, LOW);
        dispensingState = DISPENSING_PUMP2;
        startTime = currentTime;
        pump2ActiveTime = 8000; // 8 seconds
      }
      if (dispensingState == DISPENSING_PUMP2 && (currentTime - startTime) < pump2ActiveTime) {
        digitalWrite(pump2Pin, HIGH);
      } else if (dispensingState == DISPENSING_PUMP2) {
        digitalWrite(pump2Pin, LOW);
        dispensingState = NOT_DISPENSING;
        lcd.clear();
        lcd.print("ENJOY!!");
        delay(2000);
        updateLCD();
      }
      break;

    case 2: // Shot of Jack
      if (dispensingState == NOT_DISPENSING) {
        lcd.clear();
        lcd.print("Pouring");
        startTime = currentTime;
        dispensingState = DISPENSING_PUMP1;
        pump1ActiveTime = 1000; // 1 second
      }
      if (dispensingState == DISPENSING_PUMP1 && (currentTime - startTime) < pump1ActiveTime) {
        digitalWrite(pump1Pin, HIGH);
      } else if (dispensingState == DISPENSING_PUMP1) {
        digitalWrite(pump1Pin, LOW);
        dispensingState = NOT_DISPENSING;
        lcd.clear();
        lcd.print("ENJOY!!");
        delay(2000);
        updateLCD();
      }
      break;

    case 3: // Add Jack
      if (dispensingState == NOT_DISPENSING) {
        lcd.clear();
        lcd.print("Pouring");
        digitalWrite(pump1Pin, HIGH);
        dispensingState = DISPENSING_PUMP1;
      }
      // Continuous dispensing while the button is held
      if (digitalRead(selectButtonPin) == LOW) {
        dispensingState = NOT_DISPENSING;
        digitalWrite(pump1Pin, LOW);
        lcd.clear();
        lcd.print("ENJOY!!");
        delay(2000);
        updateLCD();
      }
      break;
  }
}

void cancelDispensing() {
  if (dispensingState != NOT_DISPENSING) {
    // Stop both pumps and return to the main screen
    digitalWrite(pump1Pin, LOW);
    digitalWrite(pump2Pin, LOW);
    dispensingState = NOT_DISPENSING;
    lcd.clear();
    updateLCD();
  }
}

The code will work if I hold down he select button and stops if i release it

You might want to have a look at state change detection. You do not want to react on the fact that the pin is pressed but on the change from not-pressed to pressed.

This does not implement the state change detection; it's a modification on the end of your loop()

  // Implement the logic for dispensing the selected option here
  if (digitalRead(selectButtonPin) == HIGH)
  {
    pouring = true;
  }
  
  if(pouring == true)
  {
    dispense(selectedOption);
  }
  

  // Implement the logic for canceling dispensing here
  if (digitalRead(cancelButtonPin) == HIGH)
  {
    pouring = false;
  }
  
  if(pouring == false)
  {
    cancelDispensing();
  }

There is an additional variable pouring that you set when the select button is pressed and cleared when the cancel button is pressed. This variable must be global (declared before setup()).

In your dispense() function, you can clear the variable (set to false) when the dispensing is finished.

Without state change detection, you will have to release the select button before the pouring is complete else the process restarts from the beginning.

Not tested; this is just to give the idea.

Hello truck_driver

Welcome to the worldbest Arduino forum ever.

You have two options:

Either
Keep asking the robot until he/she/it gives you the correct result you expect. One answer could probably be 42.

Or
There's a trick to figuring out why something isn't working:

Use a logic analyzer to see what happens.
Put Serial.print statements at various places in the code to see the values of variables and determine whether they meet your expectations.

Have a nice day and enjoy coding in C++.

Are you willing to throw it all away and start again ?

There is a page about the State Change Detection: https://docs.arduino.cc/built-in-examples/digital/StateChangeDetection

If your sketch is in different states, then there is a good software solution for that. It is called a "Finite State Machine". That is a fancy name for simple code. Just let a variable 'state' control the functionality.
Tutorial: https://majenko.co.uk/blog/our-blog-1/the-finite-state-machine-26

In my opinion it is important that good code shows its structure and can be maintained. ChapGPT does not understand what it produces, and it can not understand the structure of the code. The sketch is your worst nightmare.

The structure could be:

void loop()
{
  // Gather all information.
  // read all buttons, do debouncing and state change detection.
  // Put the result in variables, so the rest of the sketch can use it.

  // Control the display. Keep the page of the screen in a variable.
  // If something needs to be done, set the 'state' to start the process.

  // Finite State Machine
  // Four different state machines is possible for each option.
  // Just one state machine is better, I think.
  // Combine millis timer with the state machine
  // To stop a process, check the variables from the buttons in each state.
  // Each state will be a separate piece of code for that job.
}

Called it:

Spaghetti code alá ChapGPT :sunglasses:

2 Likes

Try the sketch in Wokwi: Your Worst Nightmare - Wokwi ESP32, STM32, Arduino Simulator
I was not exaggerating when I called it your worst nightmare.

I know the code could be alot better, but the only thing wrong with this code is that it doesnt finish the code unless the button is held down for the 3 options. The buttons arent wired correctly in wokwi link. I have them wired the same way as shown in this link: https://docs.arduino.cc/built-in-examples/digital/Button

I'll have to try using the Finite State Machine tomorrow, assuming i can understand how to do it.

As far as I can see, you already have a finite state machine; e.g. this:

It's just not written as we usually see it.

Hello truck_driver

How to restart?

Take a piece of paper, a pencil and design, based on the IPO model, a program structure plan before start coding.

  • INPUT : read and debounce buttons

  • PROCESSING: analyze new button states - trigger LCD and pump action

  • OUT: execute triggered LCD update and pump actions

Identify am functions and their depencies. Now start with coding and testing, function by function.
At the end merge all tested function together to get the final sketch.
The basics for coding the project are to be found and may used in the IDE as examples.

You might start do design and code a simple and scalable button manager.

What is the meaning of scalable?

In general - Arrays and structs are your friends.
Don't duplicate code in your sketch. Write code once - use it multiple times.

Have a nice day and enjoy coding in C++.

1 Like

@truck_driver when I wrote if you are willing to throw all the code away and that the code is your worst nightmare, I was not just making a statement. I was trying to help you to get a working project.

If you understand your own code, then you can make changes in the future.

How about now ?
Are you willing to throw away the code generated by ChatGPT ?
If the code could be fixed, then we would have told you that.

Yes i an willing to start over. How should i restart?

The starting page for millis() is this: https://docs.arduino.cc/built-in-examples/digital/BlinkWithoutDelay

Then read the links for the Button and the State Change Detection.

Have you read the tutorial for the Finite State Machine ? That is the way to have different states in your sketch.

Have you told which Arduino board you use ?
For a ESP32, you could use multiple tasks, and millis() is not needed.

ESP32 indeed performs concurrent execution of multiple tasks; can UNO do it using millis() function?

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.