Trying to make a menu application with I2C

Hello everyone. I'm trying to make a menu application with Arduino Uno. However, I couldn't get it right. I keep starting over, but I still can't reach a result. If you have any suggestions or corrections, I would appreciate it.
I will use I2C 16x2 Led Display and 5 buttons.

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


const int BUTTON1_PIN = 2;
const int BUTTON2_PIN = 3;
const int BUTTON3_PIN = 4;
const int BUTTON4_PIN = 5;
const int BUTTON5_PIN = 6;


LiquidCrystal_I2C lcd(0x3F, 16, 2);


enum MainMenu {
  MENU_1,
  MENU_2,
  MENU_3,
};

enum SubMenu {
  SUBMENU_1,
  SUBMENU_2,
};


int variable1 = 0;
bool variable2 = false;

void setup() {
  pinMode(BUTTON1_PIN, INPUT_PULLUP);
  pinMode(BUTTON2_PIN, INPUT_PULLUP);
  pinMode(BUTTON3_PIN, INPUT_PULLUP);
  pinMode(BUTTON4_PIN, INPUT_PULLUP);
  pinMode(BUTTON5_PIN, INPUT_PULLUP);
  lcd.init();
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("Main Menu");
  delay(1000);
}


void loop() {
  static MainMenu mainMenu = MENU_1;
  static SubMenu subMenu = SUBMENU_1;


  if (readButton()) {
    if (subMenu == SUBMENU_1) {
      handleMainMenu(mainMenu); 
      if (mainMenu == MENU_2) {
        subMenu = SUBMENU_2; 
      }
    }

   
    if (subMenu == SUBMENU_2) {
      handleSubMenu(subMenu); 
      if (mainMenu != MENU_2) {
        subMenu = SUBMENU_1; 
      }
    }
  }
}


int printMainMenu(MainMenu menu) {
  lcd.clear();
  switch (menu) {
    case MENU_1:
      lcd.print("1. Sub Menu");
      lcd.setCursor(0, 1);
      lcd.print("2. Sub menu");
      break;
    case MENU_2:
      lcd.print("1. Option 1");
      lcd.setCursor(0, 1);
      lcd.print("2. Option 2");
      lcd.setCursor(10, 1);
      lcd.print("3. Option 3");
      lcd.setCursor(0, 0);
      lcd.print(variable1);
      break;
    case MENU_3:
      lcd.print("1. Option 1");
      lcd.setCursor(0, 1);
      lcd.print("2. Option 2");
      lcd.setCursor(10, 1);
      lcd.print("3. Option 3");
      lcd.setCursor(0, 0);
      lcd.print(variable2);
      break;
  }
}


int handleMainMenu(MainMenu& menu) {
  printMainMenu(menu);
  switch (menu) {
    case MENU_1:
      if (digitalRead(BUTTON1_PIN) == HIGH) {
        function1();
        break;
      }
      if (digitalRead(BUTTON2_PIN) == HIGH) {
        function2();
        break;
      }
      if (digitalRead(BUTTON5_PIN) == HIGH) {
        menu = MENU_2;
        break;
      }
      break;
    case MENU_2:
      if (digitalRead(BUTTON1_PIN) == HIGH) {
        variable1++;
        break;
      }
      if (digitalRead(BUTTON2_PIN) == HIGH) {
        variable1--;
        break;
      }
      if (digitalRead(BUTTON3_PIN) == HIGH) {
        variable2 = !variable2;
        break;
      }
      if (digitalRead(BUTTON5_PIN) == HIGH) {
        menu = MENU_3;
        break;
      }
      break;
    case MENU_3:
      if (digitalRead(BUTTON1_PIN) == HIGH) {
        variable2 = true;
        break;
      }
      if (digitalRead(BUTTON2_PIN) == HIGH) {
        variable2 = false;
        break;
      }
      if (digitalRead(BUTTON5_PIN) == HIGH) {
        menu = MENU_1;
        break;
      }
      break;
  }
}


int handleSubMenu(SubMenu& submenu) {
  lcd.clear();
  switch (submenu) {
    case SUBMENU_1:
      lcd.print("Sub Menu 1");
      // Submenu 1 operations
      break;
    case SUBMENU_2:
      lcd.print("Sub Menu 2");
      // Submenu 2 operations
      break;
  }
}
int readButton() {
  if (digitalRead(BUTTON1_PIN) == LOW || digitalRead(BUTTON2_PIN) == LOW || digitalRead(BUTTON3_PIN) == LOW) {
    return 1;
  } else {
    return 0;
  }
}

Please describe what you expect to happen and what happens instead.

1 Like

My expectation is to have 3 main menus that I can navigate, to have submenus within these main menus and to run functions in the options in these submenus. Instead, when I upload the code to the arduino, the screen resets itself constantly or freezes.

Sounds like a wiring problem, unstable connections, etc. Please post a photo of a hand drawn wiring diagram, with pins and parts clearly labeled and a photo of your setup.

Test the setup with this program stub at the bottom, which just prints one line on the screen. Once that is working, add one tiny bit at t time.

Some of that code makes no sense, and I can't imagine why it compiles and loads.
Where is function1() defined, for example?

      if (digitalRead(BUTTON1_PIN) == HIGH) {
        function1();
        break;
      }

What does this do? Where does the pointer come from?

int handleMainMenu(MainMenu& menu) {
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

const int BUTTON1_PIN = 2;
const int BUTTON2_PIN = 3;
const int BUTTON3_PIN = 4;
const int BUTTON4_PIN = 5;
const int BUTTON5_PIN = 6;


LiquidCrystal_I2C lcd(0x3F, 16, 2);


enum MainMenu {
  MENU_1,
  MENU_2,
  MENU_3,
};

enum SubMenu {
  SUBMENU_1,
  SUBMENU_2,
};


int variable1 = 0;
bool variable2 = false;

void setup() {
  pinMode(BUTTON1_PIN, INPUT_PULLUP);
  pinMode(BUTTON2_PIN, INPUT_PULLUP);
  pinMode(BUTTON3_PIN, INPUT_PULLUP);
  pinMode(BUTTON4_PIN, INPUT_PULLUP);
  pinMode(BUTTON5_PIN, INPUT_PULLUP);
  lcd.init();
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("Main Menu");
  delay(1000);
}

void loop() {}

First, function1() function2() etc. functions will be added later.
second question : the handleButton function does not have a pointer. The function takes two arguments: MainMenu& mainMenu and SubMenu& subMenu. Here the & symbol indicates that the variables passed to the function are reference parameters. That is, the variables of type MainMenu and SubMenu passed to the function can be changed inside the function and these changes will also take effect outside. This means that they are not pointers to the function, but reference parameters.

and the wiring diagram

Hello arrowthe22

Provide functional names for the buttons, like Up, Down, Left, Right and Select.

The sketch won´t compile.

'function1' was not declared in this scope

Please explain how you can successfully compile and load a program that is missing many parts.

Please post the code you are actually loading onto the Arduino.

Let me be a bit more clear. The "&" makes no sense here and is guaranteed to crash an Arduino.

int handleMainMenu(MainMenu& menu) {
  printMainMenu(menu);
  switch (menu) {

Actually, code compiled in IDE but it can't compile in Tincercad

Which code ?

The code you posted did not and will not successfully compile. Several fatal error messages were issued.

Good luck with your project!

The first code i posted

I know. I'm new to coding. I was able to figure it out by looking at sources on the Internet.

exit status 1
'function1' was not declared in this scope

And what your expection now?

You have a partial understanding how call by reference works. Do a bit more reading and study.

I was aiming to improve myself by doing a project like this. But I really screwed up.

I'm actually studying mechatronics engineering. But I need to make a lot of progress on this subject.

To design, code and test a sketch for a menue and submenue function is a complex task.
Useful to do this are structured arrays and some other powerfull C++ instruction out of the OOP toolbox.

Thank you

Hi!

Take a look in this example:

#include <Bounce2.h>
#include <LiquidCrystal_I2C.h>

#define UPDATE_INTERVAL 1 // Time is seconds

const byte upButtonPin = 2;
const byte downButtonPin = 3;
const byte leftButtonPin = 4;
const byte rightButtonPin = 5;
const byte selectButtonPin = 6;

int mainMenuIndex = 0;
int subMenuIndex = 99;
int cursorPosition = 0;
unsigned long prevMillis = 0;

bool inMainMenu = true;

LiquidCrystal_I2C lcd(0x27, 16, 2);

Bounce2::Button up = Bounce2::Button();
Bounce2::Button down = Bounce2::Button();
Bounce2::Button left = Bounce2::Button();
Bounce2::Button right = Bounce2::Button();
Bounce2::Button select = Bounce2::Button();

void setup() {

  Serial.begin(115200);

  lcd.init();
  lcd.backlight();

  up.attach(upButtonPin, INPUT_PULLUP);
  up.interval(10);
  up.setPressedState(LOW);

  down.attach(downButtonPin, INPUT_PULLUP);
  down.interval(10);
  down.setPressedState(LOW);

  left.attach(leftButtonPin, INPUT_PULLUP);
  left.interval(10);
  left.setPressedState(LOW);

  right.attach(rightButtonPin, INPUT_PULLUP);
  right.interval(10);
  right.setPressedState(LOW);

  select.attach(selectButtonPin, INPUT_PULLUP);
  select.interval(10);
  select.setPressedState(LOW);
}


void loop() {

  left.update();
  up.update();
  down.update();
  right.update();
  select.update();

  if (inMainMenu == true) //In mainMenu
  {
    handleMainMenu();

    if (select.pressed())
    {
      Serial.print("mainMenu ");
      Serial.print(mainMenuIndex);
      Serial.println(" selected!");

      inMainMenu = false;
      lcd.clear();
      prevMillis = millis() - (UPDATE_INTERVAL * 1000UL);
    }
    else if (up.pressed())
    {
      mainMenuIndex++;
      if (mainMenuIndex > 3)
      {
        mainMenuIndex = 3;
      }

      Serial.print("mainMenu = ");
      Serial.println(mainMenuIndex);

      prevMillis = millis() - (UPDATE_INTERVAL * 1000UL);
    }
    else if (down.pressed())
    {
      mainMenuIndex--;
      if (mainMenuIndex < 0)
      {
        mainMenuIndex = 0;
      }

      Serial.print("mainMenu = ");
      Serial.println(mainMenuIndex);

      prevMillis = millis() - (UPDATE_INTERVAL * 1000UL);
    }
  }
  else // In subMenu
  {
    handleSubMenu();

    if (select.pressed())
    {
      subMenuIndex = cursorPosition;

      Serial.print("subMenu ");
      Serial.print(subMenuIndex);
      Serial.println(" selected!");

      lcd.clear();
      prevMillis = millis() - (UPDATE_INTERVAL * 1000UL);
    }
    else if (up.pressed())
    {
      cursorPosition = 0;

      Serial.print("cursorPosition = ");
      Serial.println(cursorPosition);

      prevMillis = millis() - (UPDATE_INTERVAL * 1000UL);
    }
    else if (down.pressed())
    {
      cursorPosition = 1;

      Serial.print("cursorPosition = ");
      Serial.println(cursorPosition);

      prevMillis = millis() - (UPDATE_INTERVAL * 1000UL);
    }
  }
}

void handleSubMenu()
{
  switch (subMenuIndex)
  {
    case 0:
      {
        if ((millis() - prevMillis) > (UPDATE_INTERVAL * 1000UL))
        {
          lcd.noCursor();
          lcd.noBlink();
          lcd.setCursor(1, 0);
          lcd.print("0 SubMenu selec");
          prevMillis = millis();
        }
        break;
      }
    case 1:
      {
        if ((millis() - prevMillis) > (UPDATE_INTERVAL * 1000UL))
        {
          lcd.noCursor();
          lcd.noBlink();
          lcd.setCursor(1, 0);
          lcd.print("1 SubMenu selec");
          prevMillis = millis();
        }
        break;
      }
    case 99:
      {
        if ((millis() - prevMillis) > (UPDATE_INTERVAL * 1000UL))
        {
          lcd.noCursor();
          lcd.noBlink();
          lcd.setCursor(1, 0);
          lcd.print("0 SubMenu");
          lcd.setCursor(1, 1);
          lcd.print("1 SubMenu");

          lcd.blink();
          lcd.setCursor(0, cursorPosition);

          prevMillis = millis();
          Serial.println("*");
        }
        break;
      }
    default:
      {
        break;
      }
  }
}

void handleMainMenu()
{
  switch (mainMenuIndex)
  {
    case 0:
      {
        if ((millis() - prevMillis) > (UPDATE_INTERVAL * 1000UL))
        {
          lcd.noCursor();
          lcd.noBlink();
          lcd.setCursor(2, 0);
          lcd.print("Menu 0");
          prevMillis = millis();
        }
        break;
      }
    case 1:
      {
        if ((millis() - prevMillis) > (UPDATE_INTERVAL * 1000UL))
        {
          lcd.noCursor();
          lcd.noBlink();
          lcd.setCursor(2, 0);
          lcd.print("Menu 1");
          prevMillis = millis();
        }
        break;
      }
    case 2:
      {
        if ((millis() - prevMillis) > (UPDATE_INTERVAL * 1000UL))
        {
          lcd.noCursor();
          lcd.noBlink();
          lcd.setCursor(2, 0);
          lcd.print("Menu 2");
          prevMillis = millis();
        }
        break;
      }
    case 3:
      {
        if ((millis() - prevMillis) > (UPDATE_INTERVAL * 1000UL))
        {
          lcd.noCursor();
          lcd.noBlink();
          lcd.setCursor(2, 0);
          lcd.print("Menu 3");
          prevMillis = millis();
        }
        break;
      }
    default:
      {
        break;
      }
  }
}

Try learn how it work and adapt to your project.

Let me know if there's any doubt.

Best regards.