Arduino Uno Menu Template

127,985

171

61

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.

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.

Beyond the Comfort Zone Contest

Participated in the
Beyond the Comfort Zone Contest

3 People Made This Project!

Recommendations

  • For the Home Contest

    For the Home Contest
  • Make It Bridge

    Make It Bridge
  • Game Design: Student Design Challenge

    Game Design: Student Design Challenge

61 Comments

0
Hoshm
Hoshm

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

0
LemonAndLime22
LemonAndLime22

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

arduino code loop.png
0
Karel 2020
Karel 2020

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!

0
crs8
crs8

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.

0
shadowmehrdad
shadowmehrdad

Answer 3 years ago

replace this:
int maxMenuPages = round(((sizeof(menuItems) / sizeof(String)) / 2) + .5);
with this:
int maxMenuPages = sizeof(menuItems) / sizeof(String) - 2

0
shadowmehrdad
shadowmehrdad

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;

0
HugoJ22
HugoJ22

3 years ago

I have a question how would we introduce a submenu of selection.

1
tuf47540
tuf47540

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

0
Tinker007
Tinker007

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.

0
Tinker007
Tinker007

Reply 3 years ago

I wanted to suggest also that for anyone using this type of shield(that uses an analog pin for the button pushes), it's important to check what the actual analog values are for each button push. I found out that my factory built DFRobot shield yielded actual values of 0, 100, 257, 410, 639 for RIGHT, UP, DOWN, LEFT and SELECT. The sample code writer on the DFRobot site found their values to be 0, 144, 329, 504, 741. [It's worth noting that these values are what you would expect based on the resistors shown on the schematic!] You need these datapoints to ensure your code works properly. Mine initially did not. Evidently the resistors used on my board are different than shown in the schematic. Not a big deal. Once you know the actual values, you can tailor your code accordingly! Now mine works perfectly.
0
AbhijeetI3
AbhijeetI3

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.

1
Robbe Leemans
Robbe Leemans

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

0
frankje
frankje

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");

0
PaulSS
PaulSS

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.

1
MoeS10
MoeS10

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

1
frankje
frankje

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!

0
DR272727
DR272727

4 years ago

Give this man a gold medal!!

1
WagnerA12
WagnerA12

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