How to make a menu with submenus?

How can I make a menu with submenus with more submenus on 20x4 i2c LCD?

I'm trying to make an alarm clock that has a 4x4 keypad, ds3231, and a 20x4 i2c LCD

Also, I'm a rookie at coding so please help me out!

Heres the code:


#include <LiquidCrystal_I2C.h>      // for LCD
#include <RTClib.h>                 // for RTC
#include <EEPROM.h>
#include <Keypad.h>
#include <Wire.h>




LiquidCrystal_I2C lcd(0x27, 20, 4); // create LCD with I2C address 0x27, 20 characters per line, 4 lines
RTC_DS3231 rtc;





// Character to hold key input
char customKey;

// Constants for row and column sizes
const byte ROWS = 4;
const byte COLS = 4;

// Array to represent keys on keypad
char hexaKeys[ROWS][COLS] = {
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};

// Connections to Arduino
byte rowPins[ROWS] = {9, 8, 7, 6};
byte colPins[COLS] = {5, 4, 3, 2};

// Create keypad object
Keypad customKeypad = Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);

//clockScreen
bool clockScreenOn = true;
int mainScreenMode = 0;
boolean backlightON = true;

//Rtc
int DD, MM, YY, H, TH, M, S, AH, AM, setH, setM;
int i = 0;
String sDD;
String sMM;
String sYY;
String sH;
String sTH;
String sM;
String sS;
String flag;

//alarm
String aH = "12";
String aM = "00";
String alarm = "     ";
boolean setAlarmTime = false;
boolean alarmON = false;
int alarmSwitchPin = 13;
bool alarmSwitch;
boolean turnItOn = false;

//buzzer and sounders
int buzzPin = 10;
int sounderPin = 11;
bool isBeepOn = true;

//appearance
bool isAppearance;
bool backlightOn = true;

//set
bool isSelected;
bool settingTime = false;
bool settingDate = false;

//settings
bool settingMenuOn = false;
/*
  // macro to calculate number of elements in an array
  #define NUMELEMENTS(X) (sizeof(X) / sizeof(X[0]))

  struct MENUITEM
  {
  char *title;                // item title
  bool (*action)(void *ptr);  // function to execute
  void *param;                // parameter for function to execute
  struct MENU *subMenu;       // submenu for this menu item
  };

  struct MENU
  {
  char *title;              // menu title
  MENUITEM *items;          // menu items
  int numItems;             // number of menu items
  int selected;             // item that is selected
  struct MENU *parentMenu;  // parent menu of this menu
  };

  extern MENU mainMenu;
  extern MENU serialportMenu;

  MENU *currentMenu = &mainMenu;            // currently selected menu
  bool (*currentAction)(void *ptr) = NULL;  // function to execute
  void *currentParam = NULL;                // parameter for function to execute

  const long baudrate9600 = 9600;
  const long baudrate19200 = 19200;

  MENUITEM serialportMenuItems[] =
  {
  {"Baudrate", NULL, NULL, &baudrateMenu},
  {"Parity", NULL, NULL, &parityMenu},
  {"Stopbits", NULL, NULL, NULL},
  {"Back", NULL, NULL, NULL},
  };

  MENU serialportMenu =
  {
  "Serial port", serialportMenuItems, NUMELEMENTS(serialportMenuItems), 0, &mainMenu
  };
*/
//set
int state = 0;
char c1, c2, c3, c4;
int i1, i2, i3, i4;


void setup() {
  lcd.init();       // initialize lcd
  lcd.backlight();  // switch-on lcd backlight

  rtc.begin();       // initialize rtc
  rtc.disable32K();

  Serial.begin(9600);


  pinMode(alarmSwitchPin, INPUT);

  AH = EEPROM.read(0);
  AM = EEPROM.read(1);

  bigNumbers_setup();

}

void(* resetFunc) (void) = 0;


void loop() {
  getTimeDate();
  appearance();
  set();
  setings();
  if (isBeepOn) {
    beeper();
  }
  if (clockScreenOn) {
    clockScreen();
  }
  customKey = customKeypad.getKey();

  alarmSwitch = !digitalRead(alarmSwitchPin);
  alarmON = alarmSwitch;

  if (customKey == '*') {
    lcd.clear();
    clockScreenOn = true;
    isSelected = false;
    settingTime = false;
    settingDate = false;
    settingMenuOn = false;
    setAlarmTime = false;
    isAppearance = false;
  }

}


void getTimeDate() {
  DateTime now = rtc.now();
  DD = now.day();
  MM = now.month();
  YY = now.year();
  H = now.hour();
  TH = now.twelveHour();
  M = now.minute();
  S = now.second();
  if (now.isPM()) flag = "PM";
  else flag = "AM";

  if (DD < 10) {
    sDD = '0' + String(DD);
  } else {
    sDD = DD;
  }
  if (MM < 10) {
    sMM = '0' + String(MM);
  } else {
    sMM = MM;
  }
  sYY = YY - 2000;
  if (H < 10) {
    sH = '0' + String(H);
  } else {
    sH = H;
  }
  if (TH < 10) {
    sTH = '0' + String(TH);
  } else {
    sTH = TH;
  }
  if (M < 10) {
    sM = '0' + String(M);
  } else {
    sM = M;
  }
  if (S < 10) {
    sS = '0' + String(S);
  } else {
    sS = S;
  }
  if (AH < 10) {
    aH = '0' + String(AH);
  } else {
    aH = AH;
  }
  if (AM < 10) {
    aM = '0' + String(AM);
  }  else {
    aM = AM;
  }
}

void clockScreen() {
  lcd.setCursor(2, 0); //First row
  if (rtc.begin()) {
    if (mainScreenMode == 0) {
      printNumber(TH, 2);
      lcd.setCursor(9, 0);
      lcd.print("|");
      lcd.setCursor(9, 1);
      lcd.print("|");
      printNumber(M, 11);
      lcd.setCursor(17, 0);
      lcd.print(flag);
      lcd.setCursor(17, 1);
      if (S < 10) {
        lcd.print("0");
        lcd.print(S);
      } else {
        lcd.print(S);
      }

    } if (mainScreenMode == 1) {
      printNumber(H, 2);
      lcd.setCursor(9, 0);
      lcd.print("|");
      lcd.setCursor(9, 1);
      lcd.print("|");
      printNumber(M, 11);
      lcd.setCursor(17, 1);
      if (S < 10) {
        lcd.print("0");
        lcd.print(S);
      } else {
        lcd.print(S);
      }
    }
  } if (mainScreenMode == 2) {
    printNumber(TH, 2);
    lcd.setCursor(9, 0);
    lcd.print("|");
    lcd.setCursor(9, 1);
    lcd.print("|");
    printNumber(M, 11);
    lcd.setCursor(17, 0);
    lcd.print(flag);
    lcd.setCursor(17, 1);
    if (S < 10) {
      lcd.print("0");
      lcd.print(S);
    } else {
      lcd.print(S);
    }
  }

  if (mainScreenMode != 2) {

    String line4 = sMM + "/" + sDD + "/" + sYY + "|";
    lcd.setCursor(1, 2); //Third row
    if (rtc.begin()) {
      lcd.print(line4);
    }
    if (clockScreenOn) {
      lcd.setCursor(11, 2);
      lcd.setCursor(11, 2);
      if (! rtc.begin()) {
        lcd.setCursor(11, 3);
        lcd.print("No RTC");
      } else if (rtc.begin()) {
        String line3 = aH + ":" + aM;
        lcd.setCursor(11, 2);
        lcd.print(line3);
        if (alarmON) {
          lcd.setCursor(0, 3);
          lcd.print("Alarm On");
          lcd.print(" |");
        } else {
          lcd.setCursor(0, 3);
          lcd.print("Alarm Off");
          lcd.print("|");
        }


      }
    }
  }
}

void beeper() {
  if (customKey && customKey != '#') {
    tone(buzzPin, 1750, 30);
  }
  if (customKey && customKey == '#') {
    tone(buzzPin, 1650, 30);
  }
  if (customKey == '*') {
    tone(buzzPin, 1650, 30);
    delay(70);
    tone(buzzPin, 1650, 20);
  }
  if (customKey == 'A' || customKey == 'B' || customKey == 'C' || customKey == 'D') {
    tone(buzzPin, 1350, 30);
  }


}
void appearance() {
  //backlight
  if (backlightOn) {
    lcd.backlight();
  } else {
    lcd.noBacklight();
  }
  //aRGB

  //apperance options
  if (customKey == 'A' && !settingTime && !settingDate && !isSelected) {
    isAppearance = true;
    lcd.clear();
    lcd.print("Appearance");
    lcd.setCursor(0, 1);
    lcd.print("Press 1-2");
  }
  if (isAppearance) {
    clockScreenOn = false;
    if (customKey == '1') {
      lcd.clear();
      if (backlightOn) {
        backlightOn = false;
        lcd.print("Backlight Off");
        lcd.setCursor(0, 1);
        lcd.print("Press * to back");
      } else {
        backlightOn = true;
        lcd.print("Backlight On");
        lcd.setCursor(0, 1);
        lcd.print("Press * to back");
      }
    }
    if (customKey == '2') {
      mainScreenMode++;
      if (mainScreenMode > 2) {
        mainScreenMode = 0;
      }
      lcd.clear();
      lcd.print("Mscreen Mode: ");
      lcd.print(mainScreenMode);
      lcd.setCursor(0, 1);
      if (mainScreenMode == 0) {
        lcd.print("12 Hour(Default)");
      }
      if (mainScreenMode == 1) {
        lcd.print("24 hour");
      }
      if (mainScreenMode == 2) {
        lcd.print("Just Numbers(12 h)");
      }
    }
    if (customKey == '*') {
      lcd.clear();
      clockScreenOn = true;
      isAppearance = false;
    }
  }
}

void set() {
  if (customKey == 'C' && !settingTime && !settingDate && !isAppearance) {
    isSelected = true;
    clockScreenOn = false;
    lcd.clear();
    lcd.print("A to set time");
    lcd.setCursor(0, 1);
    lcd.print("B to set date");
  }
  if (customKey == '*') {
    lcd.clear();
    clockScreenOn = true;
    isSelected = false;
  }
  if (isSelected && !settingTime && !settingDate) {
    if (customKey == 'A') {
      settingTime = true;
      isSelected = false;
      state = 0;
      c1 = NO_KEY;
      c2 = NO_KEY;
      c3 = NO_KEY;
      c4 = NO_KEY;
      lcd.clear();
    }
    if (customKey == 'B') {
      settingDate = true;
      isSelected = false;
      state = 4;
      c1 = NO_KEY;
      c2 = NO_KEY;
      c3 = NO_KEY;
      c4 = NO_KEY;
      lcd.clear();
    }
  }
  if (customKey == 'B' && !settingTime && !settingDate && !isAppearance) {
    setAlarmTime = true;
    clockScreenOn = false;
    lcd.clear();
    c1 = NO_KEY;
    c2 = NO_KEY;
    c3 = NO_KEY;
    c4 = NO_KEY;
    state = 12;
  }
  if (settingTime == true) {
    isAppearance = false;
    if (state == 0) {
      lcd.setCursor(0, 0);
      lcd.print("Set hours(1)");
      if (customKey && customKey >= '0' && customKey <= '2') {
        c1 = customKey;
        lcd.setCursor(0, 1);
        lcd.print(c1);
      }
      if (customKey == '#' && c1 != NULL) {
        state++;
      }
    }


    if (state == 1) {
      lcd.setCursor(0, 0);
      lcd.print("Set hours(2)");
      if (customKey && customKey >= '0' && customKey <= '9') {
        c2 = customKey;
        lcd.setCursor(1, 1);
        lcd.print(c2);
      }
      if (customKey == '#' && c2 != NULL) {
        state++;
        lcd.clear();
      }
    }

    if (state == 2) {
      lcd.setCursor(0, 0);
      lcd.print("Set minutes(1)");
      if (customKey && customKey >= '0' && customKey <= '5') {
        c3 = customKey;
        lcd.setCursor(0, 1);
        lcd.print(c3);
      }
      if (customKey == '#' && c3 != NULL) {
        state++;
      }
    }

    if (state == 3) {
      lcd.setCursor(0, 0);
      lcd.print("Set minutes(2)");
      if (customKey && customKey >= '0' && customKey <= '9') {
        c4 = customKey;
        lcd.setCursor(1, 1);
        lcd.print(c4);
      }
      if (customKey == '#' && c4 != NULL) {
        settingTime = false;
        setTime();
      }
    }
    if (customKey == '*') {
      lcd.clear();
      settingTime = false;
      clockScreenOn = true;
    }
  }
  if (settingDate == true) {
    if (state == 4) {
      isAppearance = false;
      lcd.setCursor(0, 0);
      lcd.print("Set years(1)");
      if (customKey && customKey >= '0' && customKey <= '3') {
        c1 = customKey;
        lcd.setCursor(0, 1);
        lcd.print(c1);
      }
      if (customKey == '#' && c1 != NULL) {
        state++;
      }
    }
    if (state == 5) {
      isAppearance = false;
      lcd.setCursor(0, 0);
      lcd.print("Set years(2)");
      if (customKey && customKey >= '0' && customKey <= '9') {
        c2 = customKey;
        lcd.setCursor(1, 1);
        lcd.print(c2);
      }
      if (customKey == '#' && c2 != NULL) {
        state++;
      }
    }
    if (state == 6) {
      isAppearance = false;
      lcd.setCursor(0, 0);
      lcd.print("Set years(3)");
      if (customKey && customKey >= '0' && customKey <= '9') {
        c3 = customKey;
        lcd.setCursor(2, 1);
        lcd.print(c3);
      }
      if (customKey == '#' && c3 != NULL) {
        state++;
      }
    }
    if (state == 7) {
      isAppearance = false;
      lcd.setCursor(0, 0);
      lcd.print("Set years(4)");
      if (customKey && customKey >= '0' && customKey <= '9') {
        c4 = customKey;
        lcd.setCursor(3, 1);
        lcd.print(c4);
      }
      if (customKey == '#' && c4 != NULL) {
        i1 = (c1 - 48) * 1000;
        i2 = (c2 - 48) * 100;
        i3 = (c3 - 48) * 10;
        i4 = c4 - 48;
        YY = i1 + i2 + i3 + i4;
        lcd.clear();
        lcd.print("Saving year...");
        delay(500);
        rtc.adjust(DateTime(YY, MM, DD, H, M, S));
        lcd.clear();
        c1 = NO_KEY;
        c2 = NO_KEY;
        c3 = NO_KEY;
        c4 = NO_KEY;
        state++;
      }
    }
    if (state == 8) {
      isAppearance = false;
      lcd.setCursor(0, 0);
      lcd.print("Set month(1)");
      if (customKey && customKey >= '0' && customKey <= '3') {
        c1 = customKey;
        lcd.setCursor(0, 1);
        lcd.print(c1);
      }
      if (customKey == '#' && c1 != NULL) {
        state++;
      }
    }
    if (state == 9) {
      isAppearance = false;
      lcd.setCursor(0, 0);
      lcd.print("Set month(2)");
      if (customKey && customKey >= '0' && customKey <= '3') {
        c2 = customKey;
        lcd.setCursor(1, 1);
        lcd.print(c2);
      }
      if (customKey == '#' && c2 != NULL) {
        state++;
        lcd.clear();
      }
    }
    if (state == 10) {
      isAppearance = false;
      lcd.setCursor(0, 0);
      lcd.print("Set day(1)");
      if (customKey && customKey >= '0' && customKey <= '9') {
        c3 = customKey;
        lcd.setCursor(0, 1);
        lcd.print(c3);
      }
      if (customKey == '#' && c3 != NULL) {
        state++;
      }
    }
    if (state == 11) {
      isAppearance = false;
      lcd.setCursor(0, 0);
      lcd.print("Set day(2)");
      if (customKey && customKey >= '0' && customKey <= '9') {
        c4 = customKey;
        lcd.setCursor(1, 1);
        lcd.print(c4);
      }
      if (customKey == '#' && c4 != NULL) {
        i1 = (c1 - 48) * 10;
        i2 = (c2 - 48);
        i3 = (c3 - 48) * 10;
        i4 = c4 - 48;
        MM = i1 + i2;
        DD = i3 + i4;
        lcd.clear();
        lcd.print("Saving...");
        delay(500);
        rtc.adjust(DateTime(YY, MM, DD, H, M, S));
        lcd.clear();
        c1 = NO_KEY;
        c2 = NO_KEY;
        c3 = NO_KEY;
        c4 = NO_KEY;
        settingDate = false;
        clockScreenOn = true;
      }
    }
  }
  if (setAlarmTime == true) {
    isAppearance = false;
    if (state == 12) {
      lcd.setCursor(0, 0);
      lcd.print("Set alm hours(1)");
      if (customKey && customKey >= '0' && customKey <= '2') {
        c1 = customKey;
        lcd.setCursor(0, 1);
        lcd.print(c1);
      }
      if (customKey == '#' && c1 != NULL) {
        state++;
      }
    }
    if (state == 13) {
      lcd.setCursor(0, 0);
      lcd.print("Set alm hours(2)");
      if (customKey && customKey >= '0' && customKey <= '9') {
        c2 = customKey;
        lcd.setCursor(1, 1);
        lcd.print(c2);
      }
      if (customKey == '#' && c2 != NULL) {
        state++;
        lcd.clear();
      }
    }
    if (state == 14) {
      lcd.setCursor(0, 0);
      lcd.print("Set a minutes(1)");
      if (customKey && customKey >= '0' && customKey <= '5') {
        c3 = customKey;
        lcd.setCursor(0, 1);
        lcd.print(c3);
      }
      if (customKey == '#' && c3 != NULL) {
        state++;
      }
    }
    if (state == 15) {
      lcd.setCursor(0, 0);
      lcd.print("Set a minutes(2)");
      if (customKey && customKey >= '0' && customKey <= '9') {
        c4 = customKey;
        lcd.setCursor(1, 1);
        lcd.print(c4);
      }
      if (customKey == '#' && c4 != NULL) {
        i1 = (c1 - 48) * 10;
        i2 = (c2 - 48);
        i3 = (c3 - 48) * 10;
        i4 = c4 - 48;
        AH = i1 + i2;
        AM = i3 + i4;
        lcd.clear();
        lcd.print("Saving...");
        delay(500);
        EEPROM.write(0, AH);  //Save the alarm hours to EEPROM 0
        EEPROM.write(1, AM); //Save the alarm minutes to EEPROM 1
        lcd.clear();
        c1 = NO_KEY;
        c2 = NO_KEY;
        c3 = NO_KEY;
        c4 = NO_KEY;
        setAlarmTime = false;
        clockScreenOn = true;
      }
    }
  }
  if (customKey == '*') {
    lcd.clear();
    clockScreenOn = true;
    settingTime = false;
    settingDate = false;
    setAlarmTime = false;
  }
}



void setTime() {
  settingTime = false;
  lcd.clear();
  lcd.print("Saving...");
  //saving part
  i1 = (c1 - 48) * 10;
  i2 = c2 - 48;
  i3 = (c3 - 48) * 10;
  i4 = c4 - 48;
  H = i1 + i2;
  M = i3 + i4;
  rtc.adjust(DateTime(YY, MM, DD, H, M, 0));
  delay(500);
  lcd.clear();
  // clear part
  c1 = NO_KEY;
  c2 = NO_KEY;
  c3 = NO_KEY;
  c4 = NO_KEY;
  clockScreenOn = true;
}

void setings() {
  if (customKey == 'D') {
    settingMenuOn = true;
    clockScreenOn = false;
    lcd.clear();
  }


  if (settingMenuOn) {
    isSelected = false;
    settingTime = false;
    settingDate = false;
    setAlarmTime = false;
    isAppearance = false;



  }
}

At the bottom of the 764 lines of code is where the menus are going to be displayed and operated(interacted)

I think you will find several libraries that does the work for yo if you do a search

Hello
Or you brew your own solution by using structured arrays for the menues.

The easier you make it to read and copy your code the more likely it is that you will get help

Please follow the advice given in the link below when posting code , use code tags and post the code here

If you get errors when compiling please copy them from the IDE using the "Copy error messages" button and paste the clipboard here in code tags

How would I do that?

Which one do you recommend to me?

No, I have not tried any of them.

Hello
Take a C++ lesson and study the usage of ENUM, ARRAY, STRUCT and Range-based for loop instructions.

This seems like the kind of job for structures and linked lists.

However, since you said

maybe you should try with a flat menu (only one level) first. Try to make a circular linked list first; then a doubly linked circular list (with next and previous pointers). At this point, you can add pointers to parent and child list to your structure, and you have the frame for a menu as deeply nested as you like (within reason). Draw the menu tree you want on paper first, that's a huge helper. If you have null pointers in your struct (e.g. the pointer to submenu when there are none), be careful when you dereference.

While we are at it, I suggest that you forgo the String class and go for c-strings instead.

On where?

Show me a example code

world wide web

This should get you started with single-linked lists. Then, you can build up and add complexity, but start simple and make it work before going further.

Try tcMenu (TcMenu - IoT ready menu designer and library for Arduino and mbed · The Coders Corner)
It's a great framework for menus, easy to use

I think I should do something like this:

struct MENU
{
  char *title;              // menu title
  MENUITEM *items;          // menu items
  int numItems;             // number of menu items
  int selected;             // item that is selected
  struct MENU *parentMenu;  // parent menu of this menu
};
struct MENUITEM
{
  char *title;                // item title
  bool (*action)(void *ptr);  // function to execute
  void *param;                // parameter for function to execute
  struct MENU *subMenu;       // submenu for this menu item
};

Now I need menu items and a way to display and interact with it

Yes, but you don't need 2 similar structures that do nearly the same thing. When you have a leaf item (no children, i.e submenus) you set the subMenu pointer to null. Also remember to add a next pointer (and a prev one if you want do go in both directions), so you can navigate between menus on the same level. Also,

on itself will not work, unless you do a typedef first.

This, however:

is OK.

As I said in post #9, start very simple, test, and add complexity when you have a working skeleton: that's how I'd work, anyway. Try to start with a flat menu and just display a dummy string for every menu item. See if you can navigate back and forth.

1 Like

Such a nice task this menu stuff. I wanted to add that you can do a simplier way also...but u need two things perfectly figured out
Updating your lcd (includes clearing and selecting the right text according to where u are)
Detecting button push and act accordingly
(U can simplify by only going in one direction only )(press button as many times) get one of the many functions presented here for the button
Then represent states in paper with button behavior. If menus are few u dont need the elegant and clever linked list or pointing c++ (perhaps) althought in the end ull find imitating that behavior..

And how would I do that?
Because I tried my best not to use clear() in the main screen.