Arduino Uno Menu Template

48,220

123

49

Intro: Arduino Uno Menu Template

While working on a new Instructable (coming soon) I had decided to use an Arduino Uno with an LCD/button shield I purchased off of AliExpress. It's a knockoff of the DFRobot Shield. I knew that I needed a menu for my project but was becoming so disillusioned with the terrible menu template programs available for the Arduino Uno. Many of which were not designed to work with this shield. I decided to make my own.

Since I put a significant amount of work into making this menu template so easily modifiable I figured I would share it.

Step 1: Parts

Parts are relatively simple. All you'll need is an Arduino Uno and a LCD Keypad Shield. I purchased mine from AliExpress for a total of $6.75.

Step 2: Modifying the Program

The program attached is written with the basic Arduino libraries of Wire.h and LiquidCrystal.h so there should be nothing else for you to download besides this program.

One of the things you'll have to modify is the array of menu item names on line 27 and the programming content for each sub menu. I've started with 10 possible menu items in the program. If you want 10 or fewer just modify line 27 to create your main menu structure.

String menuItems[] = {"ITEM 1", "ITEM 2", "ITEM 3", "ITEM 4", "ITEM 5", "ITEM 6"};

For example, you could do fewer items:

String menuItems[] = {"DISTANCE", "TIME", "REPEAT", "DIRECTION", "START", "ABOUT"};

Or you can add more (up to 10 of course):

String menuItems[] = {"DISTANCE", "DELAY", "RANDOMIZE", "TIME", "REPEAT", "DIRECTION", "START", "ABOUT"};

Beyond 10 items you will have to add additional "cases" to the "switch/case" section starting on line 167. You will also have to add additional menuItemX[] (where X is 11, 12, 13, etc.) void functions at the bottom of the program.

After you have created the menu structure you want it's time to create the content for each one of those sub menus. The first menuItem void function starts on line 275. I put in some default code to print "Sub Menu X" for each menu item as well as a code to wait for a back button to be pressed. Below as an example of what you could do in the sub menu. This is the sub menu I'm using to modify and store the "savedDistance" variable. The up and down buttons are used to select the distance and when back is pressed it dumps back to the main menu.

void menuItem1() { // Function executes when you select the 1st item from main menu
  int activeButton = 0;
  lcd.clear();
  lcd.setCursor(0, 1);
  drawInstructions();
  lcd.setCursor(0, 0);
  lcd.print("DISTANCE: ");
  lcd.print(savedDistance);
  lcd.print(" in");
  while (activeButton == 0) {
    int button;
    readKey = analogRead(0);
    if (readKey < 790) {
      delay(100);
      readKey = analogRead(0);
    }
    button = evaluateButton(readKey);
    switch (button) {
      case 2:
        button = 0;
        savedDistance = savedDistance + 1;
        savedDistance = constrain(savedDistance,0,36);
        lcd.setCursor(10,0);
        lcd.print("     ");
        lcd.setCursor(10,0);        
        lcd.print(savedDistance);
        lcd.print(" in");
        break;
      case 3:
        button = 0;
        savedDistance = savedDistance - 1;
        savedDistance = constrain(savedDistance,0,36);
        lcd.setCursor(10,0);
        lcd.print("     ");
        lcd.setCursor(10,0);
        lcd.print(savedDistance);
        lcd.print(" in");
        break;
      case 4:  // This case will execute if the "back" button is pressed
        button = 0;
        lcd.clear();
        lcd.setCursor(2,0);
        lcd.print("-- VALUES --");
        lcd.setCursor(2,1);
        lcd.print("-- STORED --");
        delay(1500);
        activeButton = 1;
        break;
    }
  }
}

This function generates the distance menu shown in the picture above.

Step 3: Programming Buttons to Tasks

This LCD shield has 6 buttons, only 4 of which I'm using in my program. There is a select, up, down, left, right, and reset button. The reset is hard wired into the Arduino Uno reset so there is nothing we can do with it. The select button is left off of my program and I just used the right button instead. For some reason having the select button be so far to the left of up and down felt unnatural for menu navigation.

The function that determines which button you just pressed is called evaluateButton(). Right before you call that function you need to read the analog signal from the A0 input on the Uno and save it to the readKey variable. When you call the evaluateButton() function make it evaluateButton(readKey). This will send that function the signal from A0 and the function will determine which button that is associated with and return the button number in integer form.

Here's an example of calling for a button check:

int button;                         // Declares a fresh button variable
readKey = analogRead(0); // Reads the analog signal from A0 and saves it to readKey if (readKey < 790) { // If the signal drops below 790 that means a button was // pressed. The if statement delays the program for 100 microseconds // to debounce the button press and let voltage stabilize delay(100); readKey = analogRead(0); // Once the voltage has stabilized read the signal again } button = evaluateButton(readKey); // Call the evaluateButton function and send it the readKey data // Whatever integer comes back save it to the button variable

The evaluateButton() function looks like this:

int evaluateButton(int x) {
int result = 0; if (x < 50) { result = 1; // right } else if (x < 195) { result = 2; // up } else if (x < 380) { result = 3; // down } else if (x < 790) { result = 4; // left } return result; }

The easiest and cleanest way to use that button variable is with a switch/case. A switch/case function essentially looks at that button variable and matches it to "case". The programming inside that case is executed if it matches the button pressed. For example:

switch (button) {
case 0: // When button returns as 0 break; case 1: // When button returns as 1 lcd.clear(); lcd.print("Forward Button"); break; case 2: // When button returns as 2 lcd.clear(); lcd.print("Up Button"); break; case 3: // When button returns as 3 lcd.clear(); lcd.print("Down Button"); break; }

Ideally you will nest the button evaluation call and the switch/case inside of a do/while or while loop that will continue to execute until a button is pressed.

Share

Recommendations

  • Audio Contest 2018

    Audio Contest 2018
  • Furniture Contest 2018

    Furniture Contest 2018
  • Metalworking Contest

    Metalworking Contest

49 Discussions

0
None
AbhijeetI3

Question 3 months ago

thanks for the menu, i have been using this as an interface to perform various functions of different components such as servo, led, buzzer etc . So i want my servo to move to 90 degree when i select the first item (i.e ITEM 1) in the menu. i tried my way replacing Sub Menu 1 with servo.write(90) but its not working.i m actually new to this area, a little help will be thankful.

0
None
Robbe Leemans

1 year ago

Paul,

If i upload the program on the arduino + lcd shield,

Then there will be: > ITEM 1

ITEM 2

With the > moving from top to bottom. And then comes: Sub Menu 2. Do you know what am i doing wrong?

Greetings, Robbe

2 replies
0
None
frankjeRobbe Leemans

Reply 3 months ago

I think you're doing nothing wrong - there's a typo or copy/paste error in the code. In the menuItem1() function, it says lcd.print("Sub Menu 2");

0
None
PaulSSRobbe Leemans

Reply 1 year ago

One thing I neglected is that the Adafruit library reads buttons with lcd.readButtons() while the shield I used needs to measure analog voltage drop across the buttons. You'll have to go through the code and replace my button calls with the Adafruit commands. It's just too time consuming for me to experiment without actually having the shield here.

0
None
MoeS10

Question 3 months ago on Step 3

Hi, kindly explain to me that meaning of this program .Thanks
int maxmenupages = round(((sizeof(menuitems) / sizeof(string)) / 2) + .5);

1 more answer
0
None
frankjeMoeS10

Answer 3 months ago

I think this line works only in specific cases (i.e. for certain sizes of the elements of the menuItems array). When using the PaulSS's code, I decided to implement the following changes:

(1.) The last item in the menuItems array must be an empty string, i.e. an "". This works as end "end of array" marker for point (3.) below.

(2.) In the section captioned as "// Menu control variables", remove the line MoeS10 asked about, and replace it with the following initialization:

int maxMenuPages = 0;
int maxCursorPosition = 0;

(3.) In the setup() function, introduce the following lines:

while(menuItems[maxCursorPosition]!=""){maxCursorPosition++;}
maxCursorPosition--;
maxMenuPages= maxCursorPosition;
if(maxMenuPages>=1)maxMenuPages -= 1;

The first line finds the index of the "end of array" marker (see (1.) above.)

The second line sets maxCursorPosition to the last actual element of the menuItems array.

Lines 3 & 4 set the number of menu pages. This is generally 1 less than the number of cursor positions (because we see 2 menu items at the same time), unless the number of cursor positions is 1 or less. (Note that we start counting at 0.)

(4.) The operateMainMenu() function makes use of similar calculations of the number of menu pages or cursor positions (all 7 lines containing "constrain"). These terms should be replaced by the variables we computed in (3.) above. The corrected code snippet comes here:

case 2:
button = 0;
if (menuPage == 0) {
cursorPosition = cursorPosition - 1;
cursorPosition = constrain(cursorPosition, 0, maxCursorPosition);
}
if (menuPage % 2 == 0 and cursorPosition % 2 == 0) {
menuPage = menuPage - 1;
menuPage = constrain(menuPage, 0, maxMenuPages);
}

if (menuPage % 2 != 0 and cursorPosition % 2 != 0) {
menuPage = menuPage - 1;
menuPage = constrain(menuPage, 0, maxMenuPages);
}

cursorPosition = cursorPosition - 1;
cursorPosition = constrain(cursorPosition, 0, maxCursorPosition);

mainMenuDraw();
drawCursor();
activeButton = 1;
break;
case 3:
button = 0;
if (menuPage % 2 == 0 and cursorPosition % 2 != 0) {
menuPage = menuPage + 1;
menuPage = constrain(menuPage, 0, maxMenuPages);
}

if (menuPage % 2 != 0 and cursorPosition % 2 == 0) {
menuPage = menuPage + 1;
menuPage = constrain(menuPage, 0, maxMenuPages);
}

cursorPosition = cursorPosition + 1;
cursorPosition = constrain(cursorPosition, 0, maxCursorPosition);

Hope this helps!

0
None
DR272727

5 months ago

Give this man a gold medal!!

0
None
WagnerA12

5 months ago

//anemómetro con LCD

#include <LiquidCrystal.h>

LiquidCrystal lcd(7, 8, 9, 10, 11, 12);

float veloc1= 0;// entrada A1

int tiempo=0;

int cnt=0;

float v1=0;

float v2=0;

void setup() {

lcd.begin(16, 2); // Fijamos el numero de caracteres y filas

lcd.begin(16, 2); // Fijamos el numero de caracteres y filas

lcd.print(" Anemometro EC"); // Aqi va el mensaje

analogReference(INTERNAL);// pone como referencia interna 1.1V

}

void loop() {

v1 =(analogRead(1)); // lectura de sensor A1

veloc1= (v1*0.190); // 0,190 corresponde a la pendiente de la curva aca deben poner el numero que calcularon

lcd.setCursor(0, 1); // Ponte en la line 1, posicion 0

lcd.print(veloc1); //muestra la velocidad del viento en el LCD

lcd.setCursor(4, 1); // ponte en linea 1, posicion 5

lcd.print("Km/h");

lcd.setCursor(9, 1);

lcd.print("Max");

if (veloc1>v2)v2=veloc1,lcd.print (v2,1); // muestra la velocidad maxima que alcanzo

delay(1000);

}Detalhes

0
None
WagnerA12

5 months ago

Good evening! I was looking at this code very interesting, I am new in this area of arduino and I need an orientation, I want to add in this code another than an anemometer, I will post here, thank you for any help, my idea is to make a weather station with various information over time, I can understand where I put it, it will be easier to replicate the others.

0
None
JiriM30

6 months ago

Hello, I'm not too experienced in arduino ...

Please help . need the structure:

LCD keypad Shield 1602

lcd.begin (16, 2);

Main menu

10 temperature sensors DS18S20 defined as:

lcd.print (sensor001); LCD line 0

lcd.print (sensor002); LCD line 1

clear lcd

lcd.print (sensor003); LCD line0

lcd.prin t (sensor004); LCD line1

clearlcd

lcd.print (sensor005); LCD line0

lcd.print (sensor006); LCD line1

clear lcd

lcd.print (sensor007); LCD line0

lcd.print (sensor008); LCD line1

clearlcd

lcd.print (sensor009); LCD line0

lcd.print (sensor010); LCD line1

in format: 20:50 C - (9 bit resolution)

if not pressed, select the loop sensor temperature rotation

after pressing the button select the entry in the menu 1 to 10

Enter the right button

in Menu 1 (Pump Controller)

submenu 1 setting of the variable +/- (temperature on)

submenu 2 setting of the variable +/- (temperature off)

Save button left

if there is no action for 5 seconds, the last item is saved and return to temperature display

Thank you

Jiri Mitura

0
None
TaranA3

7 months ago

I need some help, when I go to upload the code to my Arduino, I get

Arduino: 1.8.5 (Windows Store 1.8.10.0) (Windows 10), Board: "Arduino/Genuino Uno"

catan_game_menu:7: error: expected '}' before 'int'

int readKey;

^

catan_game_menu:7: error: expected ',' or ';' before 'int'

C:\Users\taran\Downloads\catan_game_menu\catan_game_menu.ino: In function 'void loop()':

catan_game_menu:71: error: 'mainMenuDraw' was not declared in this scope

mainMenuDraw();

^

catan_game_menu:72: error: 'drawCursor' was not declared in this scope

drawCursor();

^

catan_game_menu:73: error: 'operateMainMenu' was not declared in this scope

operateMainMenu();

^

C:\Users\taran\Downloads\catan_game_menu\catan_game_menu.ino: In function 'void operateMainMenu()':

catan_game_menu:126: error: 'readKey' was not declared in this scope

readKey = analogRead(0);

^

catan_game_menu:131: error: 'evaluateButton' was not declared in this scope

button = evaluateButton(readKey);

^

catan_game_menu:139: error: 'menuItem1' was not declared in this scope

menuItem1();

^

catan_game_menu:142: error: 'menuItem2' was not declared in this scope

menuItem2();

^

catan_game_menu:195: error: a function-definition is not allowed here before '{' token

int evaluateButton(int x) {

^

catan_game_menu:297: error: expected '}' at end of input

}

^

exit status 1
expected '}' before 'int'

and my code is

String menuItems[] = {"catan", "catan explores and pirates"

// Navigation button variables
int readKey;

// Menu control variables
int menuPage = 0;
int maxMenuPages = round(((sizeof(menuItems) / sizeof(String)) / 2) + .5);
int cursorPosition = 0;

// Creates 3 custom characters for the menu display
byte downArrow[8] = {
0b00100, // *
0b00100, // *
0b00100, // *
0b00100, // *
0b00100, // *
0b10101, // * * *
0b01110, // ***
0b00100 // *
};

byte upArrow[8] = {
0b00100, // *
0b01110, // ***
0b10101, // * * *
0b00100, // *
0b00100, // *
0b00100, // *
0b00100, // *
0b00100 // *
};

byte menuCursor[8] = {
B01000, // *
B00100, // *
B00010, // *
B00001, // *
B00010, // *
B00100, // *
B01000, // *
B00000 //
};

#include <Wire.h>
#include <LiquidCrystal.h>

// Setting the LCD shields pins
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

void setup() {

// Initializes serial communication
Serial.begin(9600);

// Initializes and clears the LCD screen
lcd.begin(16, 2);
lcd.clear();

// Creates the byte for the 3 custom characters
lcd.createChar(0, menuCursor);
lcd.createChar(1, upArrow);
lcd.createChar(2, downArrow);
}

void loop() {
mainMenuDraw();
drawCursor();
operateMainMenu();
}

// This function will generate the 2 menu items that can fit on the screen. They will change as you scroll through your menu. Up and down arrows will indicate your current menu position.
void mainMenuDraw() {
Serial.print(menuPage);
lcd.clear();
lcd.setCursor(1, 0);
lcd.print("catan");
lcd.setCursor(1, 1);
lcd.print("catan explorers and pirates");
if (menuPage == 0) {
lcd.setCursor(15, 1);
lcd.write(byte(2));
}
}

// When called, this function will erase the current cursor and redraw it based on the cursorPosition and menuPage variables.
void drawCursor() {
for (int x = 0; x < 2; x++) { // Erases current cursor
lcd.setCursor(0, x);
lcd.print(" ");
}

// The menu is set up to be progressive (menuPage 0 = Item 1 & Item 2, menuPage 1 = Item 2 & Item 3, menuPage 2 = Item 3 & Item 4), so
// in order to determine where the cursor should be you need to see if you are at an odd or even menu page and an odd or even cursor position.
if (menuPage % 2 == 0) {
if (cursorPosition % 2 == 0) { // If the menu page is even and the cursor position is even that means the cursor should be on line 1
lcd.setCursor(0, 0);
lcd.write(byte(0));
}
if (cursorPosition % 2 != 0) { // If the menu page is even and the cursor position is odd that means the cursor should be on line 2
lcd.setCursor(0, 1);
lcd.write(byte(0));
}
}
if (menuPage % 2 != 0) {
if (cursorPosition % 2 == 0) { // If the menu page is odd and the cursor position is even that means the cursor should be on line 2
lcd.setCursor(0, 1);
lcd.write(byte(0));
}
if (cursorPosition % 2 != 0) { // If the menu page is odd and the cursor position is odd that means the cursor should be on line 1
lcd.setCursor(0, 0);
lcd.write(byte(0));
}
}
}


void operateMainMenu() {
int activeButton = 0;
while (activeButton == 0) {
int button;
readKey = analogRead(0);
if (readKey < 790) {
delay(100);
readKey = analogRead(0);
}
button = evaluateButton(readKey);
switch (button) {
case 0: // When button returns as 0 there is no action taken
break;
case 1: // This case will execute if the "forward" button is pressed
button = 0;
switch (cursorPosition) { // The case that is selected here is dependent on which menu page you are on and where the cursor is.
case 0:
menuItem1();
break;
case 1:
menuItem2();
break;

activeButton = 1;
mainMenuDraw();
drawCursor();
break;
case 2:
button = 0;
if (menuPage == 0) {
cursorPosition = cursorPosition - 1;
cursorPosition = constrain(cursorPosition, 0, ((sizeof(menuItems) / sizeof(String)) - 1));
}
if (menuPage % 2 == 0 and cursorPosition % 2 == 0) {
menuPage = menuPage - 1;
menuPage = constrain(menuPage, 0, maxMenuPages);
}

if (menuPage % 2 != 0 and cursorPosition % 2 != 0) {
menuPage = menuPage - 1;
menuPage = constrain(menuPage, 0, maxMenuPages);
}

cursorPosition = cursorPosition - 1;
cursorPosition = constrain(cursorPosition, 0, ((sizeof(menuItems) / sizeof(String)) - 1));

mainMenuDraw();
drawCursor();
activeButton = 1;
break;
case 3:
button = 0;
if (menuPage % 2 == 0 and cursorPosition % 2 != 0) {
menuPage = menuPage + 1;
menuPage = constrain(menuPage, 0, maxMenuPages);
}

if (menuPage % 2 != 0 and cursorPosition % 2 == 0) {
menuPage = menuPage + 1;
menuPage = constrain(menuPage, 0, maxMenuPages);
}

cursorPosition = cursorPosition + 1;
cursorPosition = constrain(cursorPosition, 0, ((sizeof(menuItems) / sizeof(String)) - 1));
mainMenuDraw();
drawCursor();
activeButton = 1;
break;
}
}
}

// This function is called whenever a button press is evaluated. The LCD shield works by observing a voltage drop across the buttons all hooked up to A0.
int evaluateButton(int x) {
int result = 0;
if (x < 50) {
result = 1; // right
} else if (x < 195) {
result = 2; // up
} else if (x < 380) {
result = 3; // down
} else if (x < 790) {
result = 4; // left
}
return result;
}

// If there are common usage instructions on more than 1 of your menu items you can call this function from the sub
// menus to make things a little more simplified. If you don't have common instructions or verbage on multiple menus
// I would just delete this void. You must also delete the drawInstructions()function calls from your sub menu functions.
void drawInstructions() {
lcd.setCursor(0, 1); // Set cursor to the bottom line
lcd.print("Use ");
lcd.write(byte(1)); // Up arrow
lcd.print("/");
lcd.write(byte(2)); // Down arrow
lcd.print(" buttons");
}

void catan () { // Function executes when you select the 2nd item from main menu
int activeButton = 0;

lcd.clear();
lcd.setCursor(3, 0);
lcd.print(" catan ";

buildUp();
lcd.clear();
lcd.setCursor(7, 0);
lcd.print(diceOne);
lcd.setCursor(7, 1);
lcd.print(diceTwo);

if (diceOne + diceTwo == 7) {
lcd.clear();
lcd.setCursor(7, 0);
lcd.print("7");
lcd.setCursor(3, 1);
lcd.print("The robber");
}

if (diceOne + diceTwo == 5,4,3,2,6 ){
lcd.clear();
lcd.setCursor(7, 0);
lcd.print("5,4,3,2,6");
}

}
}

void catan explores and pirates () { // Function executes when you select the 2nd item from main menu
int activeButton = 0;

lcd.clear();
lcd.setCursor(3, 0);
lcd.print("catan explores and pirates ");


buildUp();
lcd.clear();
lcd.setCursor(7, 0);
lcd.print(diceOne);
lcd.setCursor(7, 1);
lcd.print(diceTwo);

if (diceOne + diceTwo == 7) {
lcd.clear();
lcd.setCursor(7, 0);
lcd.print("7");
lcd.setCursor(3, 1);
lcd.print("The pirate");
}

if (diceOne + diceTwo == 5,4,3,2,6 ){
lcd.clear();
lcd.setCursor(7, 0);
lcd.print("5,4,3,2,6");
}

if (BUTTON_UP == HIGH) {
buildUp();
lcd.clear();
lcd.setCursor(7, 0);
lcd.print(diceOne);

}

if (diceOne == 4, 6, 3, 2) {
lcd.clear();
lcd.setCursor(7, 0);
lcd.print("4,6,3,2");
lcd.setCursor(3, 1);
lcd.print ("fish");
}
}
}

0
None
amaro88

7 months ago

Hey, Paul!

This is a great tutorial. However, I am using a rotary encoder to control my menu. would you be able to help me to adjust the control part of this menu?

thanks!

0
None
MattiV

8 months ago

how i make menu to display main display show my code air temp,water temp some other sensor value, has ok,

but no have lcd area show next, need menu and button 1pcs or switch change new display,

relay 1,2,3,4. r1 on or off, 2 on or off,etc , show relay have on or off.

pot 1,2,3,4. pot1 value xx, pot2 val xx, pot 3 val xx . show selected value at potentiometer what relay working, sensor val > pot val do relay on or off.

and button or switch can select display 1 or display 2. back to main menu.need help, i no understand now how make. i need help make menu and switch code. sensors and main display code have ready, but how add second display switch can change display new and show relays and potentiometers ? i have 20x4 i2c display and switch and many sensor at nano board, i mean switch position 1 main display, position2 relay display.

0
None
edgarricci

1 year ago

The code is awesome! Thank you very much Paul. I modified the code for singles switches (digitalRead) and a rotary encoder instead to use the shield that you have.

I have a question: If I use less than 6 items, there is something wrong in the last item of the menu. There is something i am missing. Any clue?

Again, thanks a lot!

1 reply
0
None
ShannonY6edgarricci

Reply 8 months ago

Hey man!! Do you mind sending me your code? I really need it for my school project!! here is my email shannonyapmusic@gmail.com

thank you so much!!

0
None
RamezR1

9 months ago

very nice code I modified it to work with a 4x4 matrix keypad and 16x4 lcd, here is the code if someone needs it:

#include <Wire.h>

#include <LiquidCrystal.h>

#include <Keypad.h> // load keypad library

// Menu control variables

int itemNo = 0;

int cursorPosition = 0;

String menuItems[] = {"ITEM 1", "ITEM 2", "ITEM 3", "ITEM 4", "ITEM 5", "ITEM 6", "ITEM 7", "ITEM 8", "ITEM 9", "ITEM 10", "ITEM 11", "ITEM 12"};

// submenu 1 control variables

String SubmenuItem1 [] = {"Pump On", "Pump Off"};

int subitemNo = 0;

int subcursorPosition = 1;

// Creates 3 custom characters for the menu display

byte downArrow[8] = {

0b00100, // *

0b00100, // *

0b00100, // *

0b00100, // *

0b00100, // *

0b10101, // * * *

0b01110, // ***

0b00100 // *

};

byte upArrow[8] = {

0b00100, // *

0b01110, // ***

0b10101, // * * *

0b00100, // *

0b00100, // *

0b00100, // *

0b00100, // *

0b00100 // *

};

byte menuCursor[8] = {

B01000, // *

B00100, // *

B00010, // *

B00001, // *

B00010, // *

B00100, // *

B01000, // *

B00000 //

};

LiquidCrystal lcd(27, 26, 25, 24, 23, 22);

// Defining keypad

const byte ROWS = 4; //four rows

const byte COLS = 4; //three columns

char keys[ROWS][COLS] = {

{'1','2','3','A'},

{'4','5','6','B'},

{'7','8','9','C'},

{'*','0','#','D'}

};

// Defining keypad pins on arduino

byte rowPins[ROWS] = {43, 41, 39, 37}; //connect to the row pinouts of the keypad

byte colPins[COLS] = {35, 33, 31, 29}; //connect to the column pinouts of the keypad

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

void setup() {

// Initializes serial communication

Serial.begin(9600);

// Initializes and clears the LCD screen

lcd.begin(16, 4);

lcd.clear();

// Creates the byte for the 3 custom characters

lcd.createChar(0, menuCursor);

lcd.createChar(1, upArrow);

lcd.createChar(2, downArrow);

}

void loop() {

Serial.println (subcursorPosition);

mainMenuDraw();

drawCursor();

operateMainMenu();

}

// This function will generate the 4 menu items that can fit on the screen. They will change as you scroll through your menu.

//Up and down arrows will indicate your current menu position.

void mainMenuDraw() {

lcd.clear();

lcd.setCursor(1, 0);

lcd.print(menuItems[itemNo]);

lcd.setCursor(1, 1);

lcd.print(menuItems[itemNo + 1 ]);

lcd.setCursor(1, 2);

lcd.print(menuItems[itemNo +2 ]);

lcd.setCursor(1, 3);

lcd.print(menuItems[itemNo + 3]);

if (cursorPosition >= 0 and cursorPosition <= 3) {

lcd.setCursor(15, 3);

lcd.write(byte(2));

}

else if (cursorPosition > 3 and cursorPosition < 8) {

lcd.setCursor(15, 3);

lcd.write(byte(2));

lcd.setCursor(15, 0);

lcd.write(byte(1));

}

else if (cursorPosition >= 8) {

lcd.setCursor(15, 0);

lcd.write(byte(1));

}

}

// When called, this function will erase the current cursor and redraw it based on the cursorPosition and itemNo variables.

void drawCursor() {

for (int x = 0; x < 4; x++) { // Erases current cursor

lcd.setCursor(0, x);

lcd.print(" ");

}

// When called, this function will determine where the cursor should be

if (cursorPosition == 0 || cursorPosition == 4 || cursorPosition == 8 ){

lcd.setCursor (0,0);

lcd.write(byte(0));

}

if (cursorPosition == 1 || cursorPosition == 5 || cursorPosition == 9 ){

lcd.setCursor (0,1);

lcd.write(byte(0));

}

if (cursorPosition == 2 || cursorPosition == 6 || cursorPosition == 10 ){

lcd.setCursor (0,2);

lcd.write(byte(0));

}

if (cursorPosition == 3 || cursorPosition == 7 || cursorPosition == 11){

lcd.setCursor (0,3);

lcd.write(byte(0));

}

}

void operateMainMenu() {

int activeButton = 0;

while (activeButton == 0) {

int button;

button = evaluateButton();

switch (button) {

case 0: // When button returns as 0 there is no action taken i.e no button pressed.

break;

case 1: // This case will execute if the "right" button is pressed

button = 0;

switch (cursorPosition) { // The case that is selected here is dependent on which menu page you are on and where the cursor is.

case 0:

menuItem1();

break;

case 1:

menuItem2();

break;

case 2:

menuItem3();

break;

case 3:

menuItem4();

break;

case 4:

menuItem5();

break;

case 5:

menuItem6();

break;

case 6:

menuItem7();

break;

case 7:

menuItem8();

break;

case 8:

menuItem9();

break;

case 9:

menuItem10();

break;

case 10:

menuItem11();

break;

case 11:

menuItem12();

break;

}

activeButton = 1;

mainMenuDraw();

drawCursor();

break;

case 2: // This case will execute if the "up" button is pressed

button = 0;

if (cursorPosition == 4 || cursorPosition == 8 ){

itemNo = itemNo - 4;

}

if (cursorPosition != 0){

cursorPosition = cursorPosition - 1 ;

}

mainMenuDraw();

drawCursor();

activeButton = 1;

break;

case 3: // This case will execute if the "down" button is pressed

button = 0;

if (cursorPosition != 11){

}

if (cursorPosition == 3 || cursorPosition == 7 ){

itemNo = itemNo + 4 ;

}

cursorPosition = cursorPosition + 1 ;

if (cursorPosition >= 11){

cursorPosition = 11 ;

}

mainMenuDraw();

drawCursor();

activeButton = 1;

break;

}

}

}

int evaluateButton() {

int result = 0;

char key = keypad.getKey();

if (key == 'B') {

result = 2; // up

}

if (key == 'C') {

result = 3; // down

}

if (key == '*') {

result = 4; // left

}

if (key == '#') {

result = 1; // right

}

if (key == 'A') {

result = 5; // select

}

return result;

}

// If there are common usage instructions on more than 1 of your menu items you can call this function from the sub

// menus to make things a little more simplified. If you don't have common instructions or verbage on multiple menus

// I would just delete this void. You must also delete the drawInstructions()function calls from your sub menu functions.

void drawInstructions() {

lcd.setCursor(0, 1); // Set cursor to the bottom line

lcd.print("Use ");

lcd.write(byte(1)); // Up arrow

lcd.print("/");

lcd.write(byte(2)); // Down arrow

lcd.print(" buttons");

}

void menuItem1() { // Function executes when you select the 2nd item from main menu

int activeButton = 0;

lcd.clear();

lcd.setCursor(3, 0);

lcd.print("Sub Menu 1");

while (activeButton == 0) {

int button;

button = evaluateButton();

switch (button) {

case 4: // This case will execute if the "back" button is pressed

button = 0;

activeButton = 1;

break;

}

}

}

void menuItem2() { // Function executes when you select the 2nd item from main menu

int activeButton = 0;

lcd.clear();

lcd.setCursor(3, 0);

lcd.print("Sub Menu 2");

while (activeButton == 0) {

int button;

button = evaluateButton();

switch (button) {

case 4: // This case will execute if the "back" button is pressed

button = 0;

activeButton = 1;

break;

}

}

}

void menuItem3() { // Function executes when you select the 3rd item from main menu

int activeButton = 0;

lcd.clear();

lcd.setCursor(3, 0);

lcd.print("Sub Menu 3");

while (activeButton == 0) {

int button;

button = evaluateButton();

switch (button) {

case 4: // This case will execute if the "back" button is pressed

button = 0;

activeButton = 1;

break;

}

}

}

void menuItem4() { // Function executes when you select the 4th item from main menu

int activeButton = 0;

lcd.clear();

lcd.setCursor(3, 0);

lcd.print("Sub Menu 4");

while (activeButton == 0) {

int button;

button = evaluateButton();

switch (button) {

case 4: // This case will execute if the "back" button is pressed

button = 0;

activeButton = 1;

break;

}

}

}

void menuItem5() { // Function executes when you select the 5th item from main menu

int activeButton = 0;

lcd.clear();

lcd.setCursor(3, 0);

lcd.print("Sub Menu 5");

while (activeButton == 0) {

int button;

button = evaluateButton();

switch (button) {

case 4: // This case will execute if the "back" button is pressed

button = 0;

activeButton = 1;

break;

}

}

}

void menuItem6() { // Function executes when you select the 6th item from main menu

int activeButton = 0;

lcd.clear();

lcd.setCursor(3, 0);

lcd.print("Sub Menu 6");

while (activeButton == 0) {

int button;

button = evaluateButton();

switch (button) {

case 4: // This case will execute if the "back" button is pressed

button = 0;

activeButton = 1;

break;

}

}

}

void menuItem7() { // Function executes when you select the 7th item from main menu

int activeButton = 0;

lcd.clear();

lcd.setCursor(3, 0);

lcd.print("Sub Menu 7");

while (activeButton == 0) {

int button;

button = evaluateButton();

switch (button) {

case 4: // This case will execute if the "back" button is pressed

button = 0;

activeButton = 1;

break;

}

}

}

void menuItem8() { // Function executes when you select the 8th item from main menu

int activeButton = 0;

lcd.clear();

lcd.setCursor(3, 0);

lcd.print("Sub Menu 8");

while (activeButton == 0) {

int button;

button = evaluateButton();

switch (button) {

case 4: // This case will execute if the "back" button is pressed

button = 0;

activeButton = 1;

break;

}

}

}

void menuItem9() { // Function executes when you select the 9th item from main menu

int activeButton = 0;

lcd.clear();

lcd.setCursor(3, 0);

lcd.print("Sub Menu 9");

while (activeButton == 0) {

int button;

button = evaluateButton();

switch (button) {

case 4: // This case will execute if the "back" button is pressed

button = 0;

activeButton = 1;

break;

}

}

}

void menuItem10() { // Function executes when you select the 10th item from main menu

int activeButton = 0;

lcd.clear();

lcd.setCursor(3, 0);

lcd.print("Sub Menu 10");

while (activeButton == 0) {

int button;

button = evaluateButton();

switch (button) {

case 4: // This case will execute if the "back" button is pressed

button = 0;

activeButton = 1;

break;

}

}

}

void menuItem11() { // Function executes when you select the 10th item from main menu

int activeButton = 0;

lcd.clear();

lcd.setCursor(3, 0);

lcd.print("Sub Menu 11");

while (activeButton == 0) {

int button;

button = evaluateButton();

switch (button) {

case 4: // This case will execute if the "back or left" button is pressed

button = 0;

activeButton = 1;

break;

}

}

}

void menuItem12() { // Function executes when you select the 10th item from main menu

int activeButton = 0;

lcd.clear();

lcd.setCursor(3, 0);

lcd.print("Sub Menu 12");

while (activeButton == 0) {

int button;

button = evaluateButton();

switch (button) {

case 4: // This case will execute if the "back or left" button is pressed

button = 0;

activeButton = 1;

break;

}

}

}

0
None
sojoao

1 year ago

Hey Paul,

Thank you thank you thank you! This was so helpful.

One little thing though: when I add one more Menu, this menu does NOT appear in the LCD screen. And when I press "down" and the cursor is already in the last menu, it will go up, and then when I press "right" the submenu of the last menu will appear (even though the cursor is not on the last menu).

Do you know what is happening?

Thank you again,

Joao

3 replies
0
None
PatrickS302cjrsignsandgraphics

Reply 9 months ago

Hi! you need to change the follow codeline, in the "// Menu control variables"

int maxMenuPages = round(((sizeof(menuItems) / sizeof(String)) / 2) + .5

to

int maxMenuPages = round((sizeof(menuItems) / sizeof(String)) - 2);

You can read my uploadet Code above, he works with 11 MenuItems.

Cheers, Patrick

0
None
cjrsignsandgraphicsPatrickS302

Reply 9 months ago

Thanks patrick.
I managed to figure it out just after i posted.

As im new to the arduino and the coding side of it. It took me a while to get my head around it.

Just got to figure out now how to add sub menus to the sub menu and also how to make it so i can input action like
Turn on yes or no etc.

Simple things to most i suspect. But to me its still all a learning experience with a fun little project