Introduction: 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.
Attachments
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.
Attachments
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.

Participated in the
Beyond the Comfort Zone Contest
3 People Made This Project!
- photospherix made it!
- Manuel AgustínA made it!
- theolee made it!
61 Comments
1 year ago
Hi,
Thanks for the great info here. I am new to coding so please be kind haha. Now either I’m being dense or I’m missing something here, I’m trying to Add the select button to mirror the the same as “right” I’ve added “else if (x
Question 2 years ago on Step 3
Hi!
Your code is very useful PaulSS.
I want to use this menu without interrupt the loop function. I read the "blink without delay" post (https://www.arduino.cc/en/Tutorial/BuiltInExamples/BlinkWithoutDelay) but that logic is useless for this problem.
I can't think of the solution.
Some clue?
Thanks!
Question 2 years ago on Step 2
Hi,
Is there a way I can loop a section of code once one of the menu options is pressed.
At the moment, once a menu option is pressed that code only runs once until the back button is pressed.
I would like the section of code to loop until the back button is pressed
Tip 2 years ago
Thanks Paul for making this. It`s truly nice!
As a tip to what to do with the select button I use it now to turn the keypadshield on and off.
It`s quite simple:
Add a int called screenstate on the top of the menu and define the pin for the LCD keypadhsield backlight (nr. 10)
const int pin_BL = 10;
int ScreenState=1;(allows to know whether the screeen is currently on or off.
In void setup() add the pin as an output
pinMode(pin_BL, OUTPUT);
digitalWrite(pin_BL,HIGH);
Rewrite the evaluate button function as follows to include the select button (result will be 5):
int evaluateButton(int x) {
int result = 0;
if (x < 60) {
result = 1; // right
} else if (x < 200) {
result = 2; // up
} else if (x < 400) {
result = 3; // down
} else if (x < 600) {
result = 4; // left
}else if (x < 800) {
result = 5; // select
}
Add in the operate menu a case 5 involving screenstate:
case 5:
button = 0;
if (ScreenState==1) {
ScreenState=0;
digitalWrite(pin_BL,LOW);
}
else if (ScreenState==0){
ScreenState=1;
digitalWrite(pin_BL,HIGH);
}
activeButton = 1;
break;
Cheers!
Question 3 years ago
Hello, has anyone else been having problems adding items into the menu? I added two items into the "menueItems" variable as directed and one of them shows up but the other does not.
Extra Info: I can still access the 8th items sub menu by pressing down one more time and pressing right but the menu does not show I am on that item. Then when I return to the main menu it shows me I am on one item but takes me to a different sub menu when the right button is pressed.
Answer 3 years ago
replace this:
int maxMenuPages = round(((sizeof(menuItems) / sizeof(String)) / 2) + .5);
with this:
int maxMenuPages = sizeof(menuItems) / sizeof(String) - 2
3 years ago
Thanks, PaulSS
I think the evaluation of maxMenuPages should be corrected this way (in my exprience with your ***king awesome code):
int maxMenuPages = sizeof(menuItems) / sizeof(String) - 2;
3 years ago
I have a question how would we introduce a submenu of selection.
Reply 3 years ago
I have made a aquarium controller with many sub menu. Here it is.
Question 3 years ago
This is exactly what I've been looking for! Although, it seems my buttons are coming back unresponsive, and I can't seem to find why. Any thought on how to fix this?
Am using a adafruit 16x2 lcd shield kit
Answer 3 years ago
The shield you are using is not the same as the one used here. The one you are using utilizes I2C serial communication to interface to both the LCD and the buttons. The design of the Adafruit shield frees up several I/O pins of the Arduino.
The DFRobot shield used here connects to the Arduino I/O pins directly. It does not use I2C. The LCD uses digital pins 4 thru 10, while the buttons (excluding Reset) use just one analog input pin. Look at the schematic on the DFRobot site and you can see how this is accomplished. In short, when no buttons are pressed, the analog pin is pulled high to VCC. Via a resistive voltage divider, pushing a button produces a predictable voltage at the analog input pin and the software determines what button is pressed based on the input voltage.
Reply 3 years ago
Question 4 years 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.
5 years 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
Reply 4 years 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");
Reply 5 years 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.
Question 4 years ago on Step 3
Hi, kindly explain to me that meaning of this program .Thanks
int maxmenupages = round(((sizeof(menuitems) / sizeof(string)) / 2) + .5);
Answer 4 years 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!
4 years ago
Give this man a gold medal!!
4 years 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