Menu and sub menu scrolling with keypad

Can someone help me how can I make a sub menu?

#include <Keypad.h>

const byte ROWS = 4; //four rows
const byte COLS = 4; //four columns
char keys[ROWS][COLS] = {
  { '1','2','3', 'A' },
  { '4','5','6', 'B' },
  { '7','8','9', 'C' },
  { '*','0','#', 'D' }
};
byte rowPins[ROWS] = { 53, 51, 49, 47 }; //connect to the row pinouts of the keypad {1, 2, 3, 4}  
byte colPins[COLS] = { 52, 50, 48, 46 }; //connect to the column pinouts of the keypad {5, 6, 7, 8}

Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);

//Keys definition
/////////////////////////////////////////////////////////////////////////////////////////////////////
const int upKey = '2';
const int downKey = '8';
const int selectKey = '5';
const int leftKey = '4';
const int rightKey = '6';
/////////////////////////////////////////////////////////////////////////////////////////////////////

//Menu definition
/////////////////////////////////////////////////////////////////////////////////////////////////////
int currentMenuItem = 0;
int lastState = 0;
const int maxMenu = 3;
const int minMenu = 1;
////////////////////////////////////////////////////////////////////////////////////////////////////

void setup()
{

  Serial.begin(9600);

}

void loop()
{
  mainMenu();
}

void mainMenu() {
  int state = 0;
  int x = char(keys);

  char key = keypad.getKey();
  if (key != NO_KEY) {
    if (key == upKey) {
      state = 1;
    }
    else if (key == downKey) {
      state = 2;
    }
    else if (key == selectKey) {
      state = 3;
    }
    else if (key == leftKey) {
    }
    else if (key == rightKey) {
    }
  }

  if (state != lastState) {
    if (state == 1) {
      //If Up
      currentMenuItem ++;
    if (currentMenuItem > maxMenu) currentMenuItem = minMenu;
      displayMenu(currentMenuItem);
      //Serial.println(currentMenuItem);
    }
    else if (state == 2) {
      //If Down
    currentMenuItem--;
    if(currentMenuItem < minMenu) currentMenuItem = maxMenu;
      displayMenu(currentMenuItem);
      //Serial.println(currentMenuItem);
    }
    else if (state == 3 && currentMenuItem == 2) {
      //If Selected
      selectMenuControl();
      //Serial.println(currentMenuItem);
    }
    else if (state == 3 && currentMenuItem == 3) {
      //If Selected
      selectMenuSettings();
      //Serial.println(currentMenuItem);
    }
    lastState = state;
  }
  delay(5);
}

void displayMenu(int x) {
  switch (x) {
  case 1:
    Serial.println("-> Basic");
    break;
  case 2:
    Serial.println("-> Control");
    break;
  case 3:
    Serial.println("-> Settings");
    break;
  }
}
void selectMenuControl() {

    Serial.println("Automatically Manually Back");
  
}
void selectMenuSettings() {

    Serial.println("Settings options");
  
}

Do you have a specific problem or error?

I think it's actually a bit complicated. The below is an attempt to explain (with working code). Note that I can modify and fix this to my needs and you may not be able to do so. Do a search for an Arduino menu library if you're not comfortable to use the below, I think that they do exist.

This was written based on the last question in your other thread; it's just a demo and does not have your options.

The menu looks like

Main menu
  |
  +--> Serial port menu
  |      +--> Baudrate menu
  |      |      +--> 9600
  |      |      +--> 19200
  |      |      +--> Back
  |      +--> Baudrate menu
  |      |      +--> 9600
  |      |      +--> 19200
  |      |      +--> Back
  |      +--> Parity menu
  |      |      +--> None
  |      |      +--> Odd
  |      |      +--> Even
  |      |      +--> Back
  |      +--> Stopbits menu (not implemented)
  |      +--> Back
  +--> Led 1 menu
  |      +--> Toggle
  |      +--> Blink
  |      +--> Back
  +--> Led 2 menu (not implemented)

First we define a struct that can hold a menu item

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
};

title is self-explanatory. action is the pointer to a function that needs to be executed when the user clicks 'OK'; the action functions take one parameter; they will also (have to) return true or false to indicate if they were completed or not. param is a pointer to the parameter(s) for the function that will be executed. subMenu is a pointer to an optional submenu.

Several MENUITEMS form a menu; we again place them in a struct

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

title should again be self-explanatory; you possibly don't need it. items is a pointer to menu items for this menu; because we use a pointer, we can not determine the size of the number of items at run-time so numItems holds this information. selected indicates which item in the menu is currently selected and parentMenu is a pointer to the parent of this menu (for the situation where you want to go back from a sub menu to it's parent.

I've also created a macro that saves some typing when determining the number of items in a menu.

// macro to calculate number of elements in an array
#define NUMELEMENTS(X) (sizeof(X) / sizeof(X[0]))

Next you need to write all functions that can be called from the menu items; for now they can be empty functions. Those functions need to be known to the compiler before you define the MENUITEMS; I've used so-called function prototypes so you can place the actual function implementations wherever you want.

The below demonstrates the above and how to use the functions and parameters in loop().

// 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
};

// prototype; without this, the compiler does not know in time that toggleLed1 exists
bool toggleLed1(void *);

// menu items for led 1; has a single item (for now)
MENUITEM led1MenuItems[] =
{
  {"Toggle led", toggleLed1, NULL, NULL}, // title, function, no parameters, no submenu
};

void setup()
{
}

void loop()
{
  led1MenuItems[0].action(led1MenuItems[0].param);
  delay(1000);

}

/*
  toggle led implementation
*/
bool toggleLed1(void *)
{
  digitalWrite(13, !digitalRead(13));
  return true;
}

The first line in loop() shows how you can call the function and how to pass the parameter; toggleLed1 does not need a parameter and in the definition of the menu item we use NULL which will be passed.

To be continued.

So now we have a little bit of the basics, we can implement the menu

// 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
};

/****************************
  prototypes for all functions that can be called from the menus
****************************/
bool setBaudrate(void *baudrate);
bool setParity(void *parity);
bool toggleLed1(void *);
bool blinkLed1(void *);

/****************************
  menu prototypes so compiler does not complain that it does not know the menu yet
  only menus that can be a parent menu need to be here; others are allowed
****************************/
extern MENU mainMenu;
extern MENU serialportMenu;

/****************************
  baudrate and parity parameters
****************************/
// selectable baudrates
const long baudrate9600 = 9600;
const long baudrate19200 = 19200;

// selectable parities
const char parityN = 'N';
const char parityO = 'O';
const char parityE = 'E';

/****************************
  led1 related
****************************/
// variable to rememember what LED must do
// 0 = on/off, 1 = blink
int ledMode = 0;


THE MENU STRUCTURE COMES HERE
...
...

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



void setup()
{
}

void loop()
{
}

/****************************
  all possible functions that can be called from the menus
****************************/

bool setBaudrate(void *baudrate)
{
  return true;
}

bool setParity(void *parity)
{
  return true;
}

bool toggleLed1(void *)
{
  return true;
}

bool blinkLed1(void *)
{
  return true;
}

The above basically puts the framework in place. What is missing is the menu structure and a means to display the menu (I'm using the serial port for that later on).

So let's implement the menus

MENUITEM baudrateMenuItems[] =
{
  // title, function, parameter, submenu
  {"9600", setBaudrate, (void*)&baudrate9600, NULL},
  {"19200", setBaudrate, (void*)&baudrate19200, NULL},
  {"Back", NULL, NULL, NULL},
};

MENU baudrateMenu =
{
  // title, menu items, number of items, currently selected item, parent menu
  "Baudrate", baudrateMenuItems, NUMELEMENTS(baudrateMenuItems), 0, &serialportMenu
};

The baudrate menu contains 3 menu items (baudrateMenuItems array) and none of them have a submenu (NULL); all of them (except Back) have a related action function and a related paramter. The parameter refers to the earlier defined const variables; as the parameter is a pointer to a void, we need to take the address of the const variable (&) and cast it to a void pointer (void *). The use of the void pointer will allow you to pass anything to an action function; the action function will need to 'translate' it back to something that it can use.

The MENU shows the use of the earlier defined macro to calculate the number of items in the menu and the use of the paremt menu; the currently selected item is 0 (the first item).

We can implement the other items for the serial port submenus

MENUITEM parityMenuItems[] =
{
  {"None", setParity, (void*)&parityN, NULL},
  {"Odd", setParity, (void*)&parityO, NULL},
  {"Even", setParity, (void*)&parityE, NULL},
  {"Back", NULL, NULL, NULL},
};

MENU parityMenu =
{
  "Parity", parityMenuItems, NUMELEMENTS(parityMenuItems), 0, &serialportMenu
};

And next create the serial port menu by combining the above

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
};

Note the the MENUITEMS now don't have a function and a parameter but a submenu. he MENU should now be self-explanatory.

We can also implement the menu for led1

MENUITEM led1MenuItems[] =
{
  {"Toggle led", toggleLed1, NULL, NULL},
  {"Blink led", blinkLed1, NULL, NULL},
  {"Back", NULL, NULL, NULL},
};

MENU led1Menu =
{
  "Led 1", led1MenuItems, NUMELEMENTS(led1MenuItems), 0, &mainMenu
};

And finally combine everything in the main menu

MENUITEM mainMenuItems[] =
{
  {"Serial setup", NULL, NULL, &serialportMenu},
  {"Led1", NULL, NULL, &led1Menu},
  {"Led2", NULL, NULL, NULL},
};

MENU mainMenu =
{
  "Main menu", mainMenuItems, NUMELEMENTS(mainMenuItems), 0, NULL
};

To be continued; we're having a power failure so not sure how far I can get.

Now we still need a fuction to display the (selected) menu; as indicated earlier, I’m using the serial port.

void displayMenu()
{
  Serial.println(currentMenu->title);
  Serial.println("=========================");
  for (int itemCnt = 0; itemCnt < currentMenu->numItems; itemCnt++)
  {
    if (currentMenu->selected == itemCnt)
    {
      Serial.print("* ");
    }
    else
    {
      Serial.print("  ");
    }
    Serial.println(currentMenu->items[itemCnt].title);
  }
}

Because the currentMenu is a pointer to a menu, we need to use ‘->’ instead of the ‘.’ that you’re more familiar with.
The code first displays the menu title followed by a for-loop that displays the items in that menu. It also print is ‘*’ in front of the currently selected item.

And next we can implement setup(); setup() will do some initialisation and next display the currently selected menu.

void setup()
{
  // put your setup code here, to run once:
  Serial.begin(250000);
  while (!Serial); // for 32U4 based boards

  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);

  // display the current menu
  displayMenu();
}

The first part of loop() handles the user input and takes action based on that. I have used the letters ‘d’, ‘u’ and ‘x’ (and their uppercase versions) and the user input is read from the serial port. The code does not scroll from the last item to the first item and vice versa; if the last item is selected and the user presses ‘d’, nothing will happen.

void loop()
{
  if (Serial.available() > 0)
  {
    char ch = Serial.read();

    switch (ch)
    {
      // scroll down
      case 'D':
      case 'd':
        currentMenu->selected++;
        if (currentMenu->selected >= currentMenu->numItems)
        {
          currentMenu->selected = currentMenu->numItems - 1;
        }
        break;
      // scroll up
      case 'U':
      case 'u':
        if (currentMenu->selected > 0)
        {
          currentMenu->selected--;
        }
        break;
      // 'execute' selected item
      case 'X':
      case 'x':
        if (currentMenu->items[currentMenu->selected].subMenu != NULL)
        {
          // if there is a submenu, change to submenu
          currentMenu = currentMenu->items[currentMenu->selected].subMenu;
        }
        else if (currentMenu->items[currentMenu->selected].action != NULL)
        {
          // if there is an action, setup current action and current parameter
          //currentMenu->items[currentMenu->selected].action(currentMenu->items[currentMenu->selected].param);
          currentAction = currentMenu->items[currentMenu->selected].action;
          currentParam = currentMenu->items[currentMenu->selected].param;
        }
        else if (currentMenu->parentMenu != NULL)
        {
          // if there is a paremnt menu, change to the parent menu
          currentMenu = currentMenu->parentMenu;
        }
        else
        {
          // if everything else fails, switch to main menu
          currentMenu = &mainMenu;
        }
        break;
    }
    displayMenu();
  }

And the last part of loop() willl execute the selected action function (if any) and if ledMode is set to blink it will blink the led.

  if (currentAction != NULL)
  {
    if (currentAction(currentParam) == true)
    {
      currentAction = NULL;
      currentParam = NULL;
    }
  }

  if (ledMode == 1)
  {
    blinkWithoutDelay();
  }
}

The code checks the return value of the action function; if it’s true, currentAction and currentParam are set to NULL so a new iteration through loop will not execute the fubction again.

To be continued; still power failure.

And lastly the implementations of the action functions and the blinkWithoutDelay()

All action functions return true or false depending on if they are finished or not. This is a leftover from and earlier approach; with the code given above, this is a requirement. The below implementations always return true.

bool setBaudrate(void *baudrate)
{
  long *br = (long*)baudrate;
  Serial.print(">>");
  Serial.println(*br);

  return true;
}

As said earlier, the action functions take a pointer to a void as a parameter. setBaudrate casts the parameter to a pointer to a long and next prints it.

setParity casts the paramter to a pointer to a char; as this is a single character ('N','O','E'), it uses write() to display it

bool setParity(void *parity)
{
  char *par = (char*)parity;
  Serial.print(">>");
  Serial.write(*par);
  Serial.println();

  return true;
}

And the last two action functions

bool toggleLed1(void *)
{
  ledMode = 0;
  digitalWrite(13, !digitalRead(13));
  return true;
}

bool blinkLed1(void *)
{
  ledMode = 1;
  return true;
};

toggleLed1 will set ledMode to 0 so it will not blink and next switch the led from on to off or vice versa. blinkLed1 will set the ledMode to 1 so it will blink (using blinkWithoutDelay in loop()).

And lastly the blinbkWithout

void blinkWithoutDelay()
{
  static unsigned long lastTime = 0;
  if (millis() - lastTime >= 500)
  {
    digitalWrite(13, !digitalRead(13));
    lastTime = millis();
  }
}

To prevent any problems combining the pieces presented above, below the full code

// 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
};

/****************************
  prototypes for all functions that can be called from the menus
****************************/
bool setBaudrate(void *baudrate);
bool setParity(void *parity);
bool toggleLed1(void *);
bool blinkLed1(void *);

/****************************
  menu prototypes so compiler does not complain that it does not know the menu yet
  only menus that can be a parent menu need to be here; others are allowed
****************************/
extern MENU mainMenu;
extern MENU serialportMenu;

/****************************
  baudrate and parity parameters
****************************/
// selectable baudrates
const long baudrate9600 = 9600;
const long baudrate19200 = 19200;
// selectable parities
const char parityN = 'N';
const char parityO = 'O';
const char parityE = 'E';

/****************************
  led1 related
****************************/
// variable to rememember what LED must do
// 0 = on/off, 1 = blink
int ledMode = 0;

MENUITEM baudrateMenuItems[] =
{
  {"9600", setBaudrate, (void*)&baudrate9600, NULL},
  {"19200", setBaudrate, (void*)&baudrate19200, NULL},
  {"Back", NULL, NULL, NULL},
};

MENU baudrateMenu =
{
  "Baudrate", baudrateMenuItems, NUMELEMENTS(baudrateMenuItems), 0, &serialportMenu
};

MENUITEM parityMenuItems[] =
{
  {"None", setParity, (void*)&parityN, NULL},
  {"Odd", setParity, (void*)&parityO, NULL},
  {"Even", setParity, (void*)&parityE, NULL},
  {"Back", NULL, NULL, NULL},
};

MENU parityMenu =
{
  "Parity", parityMenuItems, NUMELEMENTS(parityMenuItems), 0, &serialportMenu
};

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
};

MENUITEM led1MenuItems[] =
{
  {"Toggle led", toggleLed1, NULL, NULL},
  {"Blink led", blinkLed1, NULL, NULL},
  {"Back", NULL, NULL, NULL},
};

MENU led1Menu =
{
  "Led 1", led1MenuItems, NUMELEMENTS(led1MenuItems), 0, &mainMenu
};


MENUITEM mainMenuItems[] =
{
  {"Serial setup", NULL, NULL, &serialportMenu},
  {"Led1", NULL, NULL, &led1Menu},
  {"Led2", NULL, NULL, NULL},
};

MENU mainMenu =
{
  "Main menu", mainMenuItems, NUMELEMENTS(mainMenuItems), 0, NULL
};


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


void displayMenu()
{
  Serial.println(currentMenu->title);
  Serial.println("=========================");
  for (int itemCnt = 0; itemCnt < currentMenu->numItems; itemCnt++)
  {
    if (currentMenu->selected == itemCnt)
    {
      Serial.print("* ");
    }
    else
    {
      Serial.print("  ");
    }
    Serial.println(currentMenu->items[itemCnt].title);
  }
}


void setup()
{
  // put your setup code here, to run once:
  Serial.begin(250000);
  while (!Serial); // for 32U4 based boards

  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);

  // display the current menu
  displayMenu();
}

void loop()
{
  if (Serial.available() > 0)
  {
    char ch = Serial.read();

    switch (ch)
    {
      case 'D':
      case 'd':
        currentMenu->selected++;
        if (currentMenu->selected >= currentMenu->numItems)
        {
          currentMenu->selected = currentMenu->numItems - 1;
        }
        break;
      case 'U':
      case 'u':
        if (currentMenu->selected > 0)
        {
          currentMenu->selected--;
        }
        break;
      case 'X':
      case 'x':
        if (currentMenu->items[currentMenu->selected].subMenu != NULL)
        {
          // if there is a submenu, change to submenu
          currentMenu = currentMenu->items[currentMenu->selected].subMenu;
        }
        else if (currentMenu->items[currentMenu->selected].action != NULL)
        {
          // if there is an action, setup current action and current parameter
          //currentMenu->items[currentMenu->selected].action(currentMenu->items[currentMenu->selected].param);
          currentAction = currentMenu->items[currentMenu->selected].action;
          currentParam = currentMenu->items[currentMenu->selected].param;
        }
        else if (currentMenu->parentMenu != NULL)
        {
          // if there is a paremnt menu, change to the parent menu
          currentMenu = currentMenu->parentMenu;
        }
        else
        {
          // if everything else fails, switch to main menu
          currentMenu = &mainMenu;
        }
        break;
    }
    displayMenu();
  }

  if (currentAction != NULL)
  {
    if (currentAction(currentParam) == true)
    {
      currentAction = NULL;
      currentParam = NULL;
    }
  }

  if (ledMode == 1)
  {
    blinkWithoutDelay();
  }
}

/****************************
  all possible functions that can be called from the menu
****************************/

bool setBaudrate(void *baudrate)
{
  long *br = (long*)baudrate;
  Serial.print(">>");
  Serial.println(*br);

  return true;
}

bool setParity(void *parity)
{
  char *par = (char*)parity;
  Serial.print(">>");
  Serial.write(*par);
  Serial.println();

  return true;
}

bool toggleLed1(void *)
{
  ledMode = 0;
  digitalWrite(13, !digitalRead(13));
  return true;
}

bool blinkLed1(void *)
{
  ledMode = 1;
  return true;
};

void blinkWithoutDelay()
{
  static unsigned long lastTime = 0;
  if (millis() - lastTime >= 500)
  {
    digitalWrite(13, !digitalRead(13));
    lastTime = millis();
  }
}

I want to try this script, has anyone ever tried?
http://www.semesin.com/project/2018/04/13/dinamik-menu-dan-submenu-dengan-keypad-dan-lcd-16x2-menggunakan-arduino/

An alternative you can try is also this one:

I wrote it for my projects specifically to be used for menus on 1 and 2 line displays.