How do I exit a loop with button instead of delay?

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

RTC_DS1307 rtc;
LiquidCrystal_I2C lcd(0x27, 20, 4);

char *pszMenuItems[] =
{
  "clock",
  "MenuItem2",
  "MenuItem3",
  "MenuItem4"
};

void action1(void);
void action2(void);
void action3(void);
void action4(void);

typedef void(*myFunc)();
myFunc Functions[4] =
{
  &action1,
  &action2,
  &action3,
  &action4
};

int upButton = 10;
int downButton = 11;
int selectButton = 12;
int menu = 1;

void setup()
{
  Serial.begin(9600);
  lcd.init();
  lcd.backlight();
  pinMode(upButton, INPUT_PULLUP);
  pinMode(downButton, INPUT_PULLUP);
  pinMode(selectButton, INPUT_PULLUP);
  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    while (1);
  }
  if (!rtc.isrunning()) {
    Serial.println("RTC lost power, lets set the time!");
  // Comment out below lines once you set the date & time.
    // Following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(__DATE__, __TIME__));
  }
  updateMenu();
}

void loop()
{
  static unsigned long
    timeButtonRead = 0;
  static byte
    lastupButton = 0xff,
    lastselButton = 0xff,
    lastdownButton = 0xff;
  byte
    currButton;
  unsigned long
    timeNow;

  //check buttons every 75mS
  timeNow = millis();
  if (timeNow - timeButtonRead < 75)
    return;
  timeButtonRead = timeNow;

  //read the button...
  currButton = digitalRead(downButton);
  //...if it's state is not the same as last...
  if (currButton != lastdownButton)
  {
    //...save the new state
    lastdownButton = currButton;
    //if it's low indicating it has been pressed...
    if (currButton == LOW)
    {
      //process action; in this case, if menu is greater than 1...
      if (menu > 1)
      {
        //...decrement it               
        menu--;
        //and update the menu
        updateMenu();

      }//if

    }//if

  }//if

  //same comments for downbutton apply for up and select
  currButton = digitalRead(upButton);
  if (currButton != lastupButton)
  {
    lastupButton = currButton;
    if (currButton == LOW)
    {
      if (menu < 4)
      {
        menu++;
        updateMenu();

      }//if

    }//if

  }//if

  currButton = digitalRead(selectButton);
  if (currButton != lastselButton)
  {
    lastselButton = currButton;
    if (currButton == LOW)
    {
      //call the function pointed to by menu in the Functions[] array
      Functions[menu - 1]();
      //when it returns, update the menu
      updateMenu();

    }//if

  }//if

}//loop

void updateMenu()
{
  lcd.clear();
  Serial.print("\n\n\n\n");
  for (int i = 0; i < 4; i++)
  {
    lcd.setCursor(0, i);
    //if menu line is selected, print a '>', otherwise a space
    if (i == (menu - 1))
    {
      Serial.print(">");
      lcd.print(">");
    }
    else
    {
      Serial.print(" ");
      lcd.print(" ");

    }//else

    //print the text of the line item
    Serial.println(pszMenuItems[i]);
    lcd.print(pszMenuItems[i]);

  }//for

}//updateMenu

void action1()
{
  while(true){
    DateTime now = rtc.now(); //Attaches now to action1 scope
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Current Date & Time: ");
    lcd.setCursor(0, 1);
    lcd.print(now.year(), DEC);
    lcd.print('/');
    lcd.print(now.month(), DEC);
    lcd.print('/');
    lcd .print(now.day(), DEC);
    lcd.print(" ");
    lcd.print(now.hour(), DEC);
    lcd.print(':');
    lcd.print(now.minute(), DEC);
    lcd.print(':');
    lcd.print(now.second(), DEC);
    delay(1500)
  }
}
//action1

void action2()
{
  lcd.clear();
  lcd.print(">Executing #2");
  Serial.println(">Executing #2");
  delay(1500);
}//action2

void action3()
{
  lcd.clear();
  lcd.print(">Executing #3");
  Serial.println(">Executing #3");
  delay(1500);
}//action3

void action4()
{
  lcd.clear();
  lcd.print(">Executing #4");
  Serial.println(">Executing #4");
  delay(1500);
}//action4

Instead of using delay to exit the function pointers, how can I exit the loop by pressing select once?
I tried while(!digitalRead(selectButton) == LOW){
delay(1500)} and even return; in place of delay

I even tried using a if loop instead with no luck, the screen exits but re enters the loop super fast while trying any of the buttons.

I would like to have a button press exit the loop and would prefer to add a 4th button as a exit button so I can work on variables in the menu without delay blocking input. :o

You obviously know about the ‘blink without delay’ technique but you are using delay(1500).

Why?

while(digitalRead(downButton)!= HIGH) {
    delay(1000);

replacing the delay function, does stay in the function pointer, and exits if the upButton is pressed. The time stays frozen though unfotunately.

I can't really explain why I understand some concepts and not others, just trying to figure this out. Maybe a switch case should be used instead of the function pointers?

I suspect the first step towards understanding is recognizing that delay() does not exit a loop. If anything, the purpose of delay() is to not-exit.

If you want a responsive program you should not use delay() because it blocks the Arduino from doing other things. If you need time intervals have a look at how millis() is used to manage timing without blocking in Several things at a time.

And see Using millis() for timing. A beginners guide if you need more explanation.

...R

Robin2:
I suspect the first step towards understanding is recognizing that delay() does not exit a loop. If anything, the purpose of delay() is to not-exit.

If you want a responsive program you should not use delay() because it blocks the Arduino from doing other things. If you need time intervals have a look at how millis() is used to manage timing without blocking in Several things at a time.

And see Using millis() for timing. A beginners guide if you need more explanation.

...R

I agree that's why I am trying to find a replacement to delay to exit out of the function pointers.
I don't need time intervals, just a replacement to delay to exit back to the main loop

keyboardcowboy:
I agree that's why I am trying to find a replacement to delay to exit out of the function pointers

A function pointer is just that; a pointer.

You can return from a function anytime you like.

Im just lost at the moment, I thought I had a well working menu that would be simple to exit by changing out delay for updatemenu(), but it never seems to be that easy. I thought a simple if or while would do.

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

RTC_DS1307 rtc;
LiquidCrystal_I2C lcd(0x27, 20, 4);

char *pszMenuItems[] =
{
  "clock",
  "MenuItem2",
  "MenuItem3",
  "MenuItem4"
};

void action1(void);
void action2(void);
void action3(void);
void action4(void);

typedef void(*myFunc)();
myFunc Functions[4] =
{
  &action1,
  &action2,
  &action3,
  &action4
};

int upButton = 10;
int downButton = 11;
int selectButton = 12;
int menu = 1;

void setup()
{
  Serial.begin(9600);
  lcd.init();
  lcd.backlight();
  pinMode(upButton, INPUT_PULLUP);
  pinMode(downButton, INPUT_PULLUP);
  pinMode(selectButton, INPUT_PULLUP);
  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    while (1);
  }
  if (!rtc.isrunning()) {
    Serial.println("RTC lost power, lets set the time!");
  // Comment out below lines once you set the date & time.
    // Following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(__DATE__, __TIME__));
  }
  updateMenu();
}

void loop()
{
  static unsigned long
    timeButtonRead = 0;
  static byte
    lastupButton = 0xff,
    lastselButton = 0xff,
    lastdownButton = 0xff;
  byte
    currButton;
  unsigned long
    timeNow;

  //check buttons every 75mS
  timeNow = millis();
  if (timeNow - timeButtonRead < 75)
    return;
  timeButtonRead = timeNow;

  //read the button...
  currButton = digitalRead(downButton);
  //...if it's state is not the same as last...
  if (currButton != lastdownButton)
  {
    //...save the new state
    lastdownButton = currButton;
    //if it's low indicating it has been pressed...
    if (currButton == LOW)
    {
      //process action; in this case, if menu is greater than 1...
      if (menu > 1)
      {
        //...decrement it               
        menu--;
        //and update the menu
        updateMenu();

      }//if

    }//if

  }//if

  //same comments for downbutton apply for up and select
  currButton = digitalRead(upButton);
  if (currButton != lastupButton)
  {
    lastupButton = currButton;
    if (currButton == LOW)
    {
      if (menu < 4)
      {
        menu++;
        updateMenu();

      }//if

    }//if

  }//if

  currButton = digitalRead(selectButton);
  if (currButton != lastselButton)
  {
    lastselButton = currButton;
    if (currButton == LOW)
    {
      //call the function pointed to by menu in the Functions[] array
      Functions[menu - 1]();
      //when it returns, update the menu
      updateMenu();

    }//if

  }//if

}//loop

void updateMenu()
{
  lcd.clear();
  Serial.print("\n\n\n\n");
  for (int i = 0; i < 4; i++)
  {
    lcd.setCursor(0, i);
    //if menu line is selected, print a '>', otherwise a space
    if (i == (menu - 1))
    {
      Serial.print(">");
      lcd.print(">");
    }
    else
    {
      Serial.print(" ");
      lcd.print(" ");

    }//else

    //print the text of the line item
    Serial.println(pszMenuItems[i]);
    lcd.print(pszMenuItems[i]);

  }//for

}//updateMenu

void action1()
{
  while(1){
  DateTime now = rtc.now(); //Attaches now to action1 scope
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Current Date & Time: ");
  lcd.setCursor(0, 1);
  lcd.print(now.year(), DEC);
  lcd.print('/');
  lcd.print(now.month(), DEC);
  lcd.print('/');
  lcd .print(now.day(), DEC);
  lcd.print(" ");
  lcd.print(now.hour(), DEC);
  lcd.print(':');
  lcd.print(now.minute(), DEC);
  lcd.print(':');
  lcd.print(now.second(), DEC);
  if (digitalRead(downButton) == LOW) {
    delay(1000);
  }
  else {
    break;
  }
  }
}//action1

void action2()
{
  lcd.clear();
  lcd.print(">Executing #2");
  Serial.println(">Executing #2");
  delay(1500);
}//action2

void action3()
{
  lcd.clear();
  lcd.print(">Executing #3");
  Serial.println(">Executing #3");
  delay(1500);
}//action3

void action4()
{
  lcd.clear();
  lcd.print(">Executing #4");
  Serial.println(">Executing #4");
  delay(1500);
}//action4

This seems to work I think so far

If someone can improve or simplify my code somehow anyhow i'd appreciate it. It seems we are at the same learning level. Had the hope that someone might have at least some good experience with programming concepts in C, and would be a easy solution due to only one line needing to be replaced. This is why I try to occupy myself with other things then programming due to not grasping some concepts, widespread ignorance, and a poor knowledge base that would require books to supplement with adequate information to complete such tasks.

I’ll help. I know my stuff. But you have to let me know what you want to have happen. Right now you keep using terms that you obviously don’t understand. Instead of trying to sound smart and use the lingo, keep it dumb and just explain what the code is supposed to do. Like, this should turn on my blender for five seconds every half an hour or something like that. But leave out all the technical terms that you don’t quite understand like function pointer.

If you can clearly communicate what the problem is then it will be easier to help.

keyboardcowboy:
Instead of using delay to exit the function pointers, how can I exit the loop by pressing select once?

Generally speaking - don't use a loop. Use a state machine. It's a different way of coding: "given the state that I am currently in, and the state of my inputs, what should I do right now?" Usually, the answer is "nothing". Sometimes the answer is "oh wow, the current time has changed. I need to update the LCD", or "oh wow, the button is now pressed - I need to stop updating the LCD and go do something else".

Delta_G:
I’ll help. I know my stuff. But you have to let me know what you want to have happen. Right now you keep using terms that you obviously don’t understand. Instead of trying to sound smart and use the lingo, keep it dumb and just explain what the code is supposed to do. Like, this should turn on my blender for five seconds every half an hour or something like that. But leave out all the technical terms that you don’t quite understand like function pointer.

If you can clearly communicate what the problem is then it will be easier to help.

Thank you very much, I am doing my best to learn at the same time.

I just basically want a menu system I can edit, understand, and make further sub menu's if would like to.

I am making a Environment controller/Smart Garden/Food Computer, well trying my best.

I have my 8 relay controlled powerstrip that I made following the Arduilay write-up
3 DHT22 Temp,Hum 1 DHT11 sensors
light sensor
esp01(Undecided if I want to tie this in yet)
SDCard reader
ds1307 rtc
20x4 lcd
3 push buttons but would gladly add a 4th for a back button

I first started by writing code for the temp sensors which I saved for when I got a well functioning menu.
After which I discovered I should start by writing the menu.
Just received a ds1307rtc today after finding millis() isn't reliable at keeping time, so I can add the ability of datalogging date/time, temp, humidity, if lights are on or off, or other device through the powered relays.

Id like to have a menu item named lights that I could hit the select button on, enter the sub menu, have the ability to toggle on and off by pressing select, n toggle on automatic lights on a certain number of hours.

A water menu that can be entered by hitting select, enter sub menu, turn on or off, have ability to set to automatic watering till a certain humidity is hit

Soil moisture menu, turn on n off automatic watering according to humidity percentage like above

Heating menu for a heating pad that can be turned on and off, automatic on and off according to temp.

SDCard menu to turn datalogging off and on to a sdcard

I have 8 outlets to work with that any device can be plugged into, I was thinking of being able to set what pin/relay # in each sub menu by using a array to go through each plug relay. Which I tested in my current menu by telling it to turn on relay 1 in menuitem1.

I'd just like to have a menu I can enter sub menu's, add menu item's, and go back to previous menu so I can finish my layout and label everything so can get down to the code inside each menu like I am currently attempting.

I hope this is descriptive enough, any questions feel free to ask, I appreciate the knowledge available and am eager to learn more.

I've got a great menu library.

https://forum.arduino.cc/index.php?topic=353045.0

Part of your issue is here:

void action1()
{
  while(true){

Your action includes an infinite loop. So once you choose menu option 1 you're toast. You're just going round and round this loop printing the time.

Instead, set a variable to hold the "mode" that you're in. Then the loop can look at that to decide what action to take, and then in the same loop read your buttons and deal with menus.

The thing you need to have is called a "State Machine"

This code is really long and involved, but see if you can see how I handled the menu stuff here:

PS: Uses the same library I just linked:

That code runs a dosing pump that puts in metered amounts of chemical additives to a salt water aquarium. But it's got a HUGE menu system in it.

keyboardcowboy:
I agree that's why I am trying to find a replacement to delay to exit out of the function pointers.

The purpose of my comment in Reply #3 is that using this type of incorrect technical language obscures the problem and its solution.

There is no means to use delay() to exit from anything. If there is to be an exit it can only happen after the delay() finishes. The function will exit (or not) regardless of whether there is a call to delay() in the function. The delay() keeps you in the function rather than causing you to exit from it.

If you want an alternative way to implement a time interval then the links I gave you in Reply #3 are the standard Arduino way.

...R

Delta_G:
I've got a great menu library.

Simple Menu Class - Programming Questions - Arduino Forum

Thank you, I am going to look over it now. Gave myself a break from writing code for at least a day to evaluate the menu code I am using.

Robin, I dont believe I want a time interval.

Delta I see 3, i'm assuming move the header file where my other included files are, does the .cpp go in the same place? I'm confused on how .cpp is used if the .ino only calls to the header file but not to the .cpp file.

keyboardcowboy:
Delta I see 3, i'm assuming move the header file where my other included files are, does the .cpp go in the same place? I'm confused on how .cpp is used if the .ino only calls to the header file but not to the .cpp file.

It's not a recipe. It's not a drag and drop option. It's not a ready made solution. It's a great example if you want to study it and try to learn how it works.

It is a pure virtual class. You have to write all the code for it. All the library is is the structure to make it where you can navigate it and it calls functions. You have to implement all the virtual functions based on your hardware.

If you don't understand that, then spend some more time on that thread and some time with some C++ tutorials getting deep into classes and structs.

Here's another example implementation:

https://forum.arduino.cc/index.php?topic=358066.0

Delta_G:
It's not a recipe. It's not a drag and drop option. It's not a ready made solution. It's a great example if you want to study it and try to learn how it works.

It is a pure virtual class. You have to write all the code for it. All the library is is the structure to make it where you can navigate it and it calls functions. You have to implement all the virtual functions based on your hardware.

If you don't understand that, then spend some more time on that thread and some time with some C++ tutorials getting deep into classes and structs.

Here's another example implementation:

Rotary Encoder, Button, and LCD User interface with Menu - Exhibition / Gallery - Arduino Forum

Ahh I thought the .cpp file was somehow used since it was included in the post and wanted to make sure but it looks like you just have to move the header file appropriately and edit the code to what fits personal preference.

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

RTC_DS1307 rtc;
LiquidCrystal_I2C lcd(0x27, 20, 4);

char *pszMenuItems[] =
{
  "clock",
  "MenuItem2",
  "MenuItem3",
  "MenuItem4"
};

void action1(void);
void action2(void);
void action3(void);
void action4(void);

typedef void(*myFunc)();
myFunc Functions[4] =
{
  &action1,
  &action2,
  &action3,
  &action4
};

int upButton = 10;
int downButton = 11;
int selectButton = 12;
int menu = 1;

void setup()
{
  Serial.begin(9600);
  lcd.init();
  lcd.backlight();
  pinMode(upButton, INPUT_PULLUP);
  pinMode(downButton, INPUT_PULLUP);
  pinMode(selectButton, INPUT_PULLUP);
  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    while (1);
  }
  if (!rtc.isrunning()) {
    Serial.println("RTC lost power, lets set the time!");
  // Comment out below lines once you set the date & time.
    // Following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(__DATE__, __TIME__));
  }
  updateMenu();
}

void loop()
{
  static unsigned long
    timeButtonRead = 0;
  static byte
    lastupButton = 0xff,
    lastselButton = 0xff,
    lastdownButton = 0xff;
  byte
    currButton;
  unsigned long
    timeNow;

  //check buttons every 75mS
  timeNow = millis();
  if (timeNow - timeButtonRead < 75)
    return;
  timeButtonRead = timeNow;

  //read the button...
  currButton = digitalRead(downButton);
  //...if it's state is not the same as last...
  if (currButton != lastdownButton)
  {
    //...save the new state
    lastdownButton = currButton;
    //if it's low indicating it has been pressed...
    if (currButton == LOW)
    {
      //process action; in this case, if menu is greater than 1...
      if (menu > 1)
      {
        //...decrement it               
        menu--;
        //and update the menu
        updateMenu();

      }//if

    }//if

  }//if

  //same comments for downbutton apply for up and select
  currButton = digitalRead(upButton);
  if (currButton != lastupButton)
  {
    lastupButton = currButton;
    if (currButton == LOW)
    {
      if (menu < 4)
      {
        menu++;
        updateMenu();

      }//if

    }//if

  }//if

  currButton = digitalRead(selectButton);
  if (currButton != lastselButton)
  {
    lastselButton = currButton;
    if (currButton == LOW)
    {
      //call the function pointed to by menu in the Functions[] array
      Functions[menu - 1]();
      //when it returns, update the menu
      updateMenu();

    }//if

  }//if

}//loop

void updateMenu()
{
  lcd.clear();
  Serial.print("\n\n\n\n");
  for (int i = 0; i < 4; i++)
  {
    lcd.setCursor(0, i);
    //if menu line is selected, print a '>', otherwise a space
    if (i == (menu - 1))
    {
      Serial.print(">");
      lcd.print(">");
    }
    else
    {
      Serial.print(" ");
      lcd.print(" ");

    }//else

    //print the text of the line item
    Serial.println(pszMenuItems[i]);
    lcd.print(pszMenuItems[i]);

  }//for

}//updateMenu

void action1()
{
    DateTime now = rtc.now(); //Attaches now to action1 scope
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Current Date & Time: ");
    lcd.setCursor(0, 1);
    lcd.print(now.year(), DEC);
    lcd.print('/');
    lcd.print(now.month(), DEC);
    lcd.print('/');
    lcd .print(now.day(), DEC);
    lcd.print(" ");
    lcd.print(now.hour(), DEC);
    lcd.print(':');
    lcd.print(now.minute(), DEC);
    lcd.print(':');
    lcd.print(now.second(), DEC);
    delay(2000);
    return false;
}
//action1

void action2()
{
  lcd.clear();
  lcd.print(">Executing #2");
  Serial.println(">Executing #2");
  delay(1500);
}//action2

void action3()
{
  lcd.clear();
  lcd.print(">Executing #3");
  Serial.println(">Executing #3");
  delay(1500);
}//action3

void action4()
{
  lcd.clear();
  lcd.print(">Executing #4");
  Serial.println(">Executing #4");
  delay(1500);
}//action4

I followed how you exit your menu's with no luck, as long as delay is in the void... it is going to exit. Without the while function in the beginning of my code Void1 just flashes fast and stays at the menu. Which makes me believe itd work the same in your code but going to try a example of it to test. Also the while loop kept updating the time displayed.

Ahh I thought the .cpp file was somehow used since it was included in the post and wanted to make sure but it looks like you just have to move the header file appropriately and edit the code to what fits personal preference.

No, go read the forum post about it. You have to take the .h and the .cpp and then implement all the virtual functions in your own class.

You either have to really know your stuff with C++ or you have to be really really good at following long directions like in that post about it.

if (timeNow - timeButtonRead < 75)
    return;

Instead of that, turn that around. Make it so that if it is LONGER than 75 then { put all the rest of the stuff } in braces like that so it runs under that if. That way you can still have other stuff in loop that updates constantly or even at other rates.

void action1()
{
    DateTime now = rtc.now(); //Attaches now to action1 scope
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Current Date & Time: ");
    lcd.setCursor(0, 1);
    lcd.print(now.year(), DEC);
    lcd.print('/');
    lcd.print(now.month(), DEC);
    lcd.print('/');
    lcd .print(now.day(), DEC);
    lcd.print(" ");
    lcd.print(now.hour(), DEC);
    lcd.print(':');
    lcd.print(now.minute(), DEC);
    lcd.print(':');
    lcd.print(now.second(), DEC);
    delay(2000);
    return false;
}

No, you don't get it. I kind of feel sorry for showing it to you now. It may be a bit over our head. When I saw what you had I didn't realize how copy-hacked it was because it looked pretty good and I assumed from that you knew C++.

You can't just return true or return false. Your functions are set to return void. You promised the compiler that they would return nothing. My functions return boolean. And the code calling my functions looks at that boolean to decide whether or not to call my functions again or to go back to the menu. So all of my code can be non-blocking. No delays. If you want this to be in any way responsive or work or be able to do more than just flash a time on an LCD for 2 seconds, you're going to have to learn to do it without delay.

Instead of thinking of your loop as telling a story from beginning to end of what needs to happen, think of your loop as a super high speed checklist that runs through thousands of times per second checking lots and lots of if statements and maybe switch-cases but very few for and while loops (only if super fast) and absolutely no delay. Most of those thousands of times each second it goes through and does nothing. But occasionally it picks up on some piece of input or a time or whatever and sees that something needs to be done or changed and it makes that one small step and keeps going.

Maybe if I find some time soon I'll write a little better tutorial in that thread. Something a little more step by step. It still won't be territory for someone who doesn't have basic C++ though. You'll have to be able to understand functions and return types and non-blocking code. But I can at least get it step by step to someone who understands how to write a basic class.