Learn electronics, coding, and projects — step by step.

Arduino Simple LCD Menu

George Bantique July 18, 2020 4 Comments

Table of Contents

In this article, I will show you a simple LCD Menu implementation I am working on.

In here, we use an Arduino Uno board, a 16x2 LCD for the display, and some tactile buttons for controlling the menu.

The menu is implemented using state machines for the menu navigation. A button debouncing is implemented to prevent unknown button actions.

So here it, feel free to modify it to your liking.

CIRCUIT DIAGRAM

VIDEO DEMONSTRATION

SOURCE CODE

Simple 16x2 LCD Menu Implementation

/*
 *  Simple 16x2 LCD Menu Implementation 
 *    - this uses a finite state machine
 *    - a button debouncing is also implemented to prevent unknown button action
 *  Please feel free to modify this to your liking, but please leave this comment as credit  
 *  
 *  Author: George Bantique
 *          @TechToTinker
 *          July 18, 2020 *   
 */

#include "LiquidCrystal.h"

LiquidCrystal lcd(7, 6, 5, 4, 3, 2);

#define button_B    A0
#define button_D    A1
#define button_U    A2
#define button_E    A3

#define ONBOARD_LED 13

#define DEFAULT_DELAY 300


char buttonPressed = '0';

byte menuLevel = 0;     // Level 0: no menu display, display anything you like
                        // Level 1: display main menu
                        // Level 2: display sub menu

byte menu = 1;
byte sub = 1;

unsigned long relay_val_1 = 0;
unsigned long relay_val_2 = 0;
unsigned long relay_val_3 = 0;

bool LED_STATE = false;

bool currState_B = HIGH;
bool currState_D = HIGH;
bool currState_U = HIGH;
bool currState_E = HIGH;
          
bool prevState_B = HIGH; 
bool prevState_D = HIGH; 
bool prevState_U = HIGH; 
bool prevState_E = HIGH; 

unsigned long prevTime_B = 0;
unsigned long prevTime_D = 0;
unsigned long prevTime_U = 0;
unsigned long prevTime_E = 0;

unsigned long waitTime_B = 50;
unsigned long waitTime_D = 50;
unsigned long waitTime_U = 50;
unsigned long waitTime_E = 50;


void setup() {
  lcd.begin(16,2);

  Serial.begin(9600);

  pinMode(button_B, INPUT_PULLUP);
  pinMode(button_D, INPUT_PULLUP);
  pinMode(button_U, INPUT_PULLUP);
  pinMode(button_E, INPUT_PULLUP);

  pinMode(A4,INPUT);

  pinMode(ONBOARD_LED, OUTPUT);
  digitalWrite(ONBOARD_LED, LED_STATE);
  
  showHomeScreen();
}

void loop() {

  checkButton();

  // You can do other things here below
}

void checkButton() {

  // Button Debouncing
  bool currRead_B = digitalRead(button_B);
  bool currRead_D = digitalRead(button_D);
  bool currRead_U = digitalRead(button_U);
  bool currRead_E = digitalRead(button_E);

  if (currRead_B != prevState_B) {
    prevTime_B = millis();
  }
  if (currRead_D != prevState_D) {
    prevTime_D = millis();
  }
  if (currRead_U != prevState_U) {
    prevTime_U = millis();
  }
  if (currRead_E != prevState_E) {
    prevTime_E = millis();
  }

  if ((millis() - prevTime_B) > waitTime_B) {
    if (currRead_B != currState_B) {
      currState_B = currRead_B;
      if (currState_B == LOW) {
        buttonPressed = 'B';
      } 
    }
  } else buttonPressed = '0';
  if ((millis() - prevTime_D) > waitTime_D) {
    if (currRead_D != currState_D) {
      currState_D = currRead_D;
      if (currState_D == LOW) {
        buttonPressed = 'D';
      } 
    }
  } else buttonPressed = '0';
  if ((millis() - prevTime_U) > waitTime_U) {
    if (currRead_U != currState_U) {
      currState_U = currRead_U;
      if (currState_U == LOW) {
        buttonPressed = 'U';
      } else {
        //buttonPressed = '0';
      }
    }
  } else buttonPressed = '0';
  if ((millis() - prevTime_E) > waitTime_E) {
    if (currRead_E != currState_E) {
      currState_E = currRead_E;
      if (currState_E == LOW) {
        buttonPressed = 'E';
      } 
    }
  } else buttonPressed = '0';

  prevState_B = currRead_B;
  prevState_D = currRead_D;
  prevState_U = currRead_U;
  prevState_E = currRead_E;

  processButton(buttonPressed);

}

void processButton(char buttonPressed) {

  switch(menuLevel) {
    case 0: // Level 0
      switch ( buttonPressed ) {
        case 'E': // Enter
          menuLevel = 1;
          menu = 1;
          updateMenu();
          delay(DEFAULT_DELAY);
          break;
        case 'U': // Up
          break;
        case 'D': // Down
          break;
        case 'B': // Back
          break;
        default:
          break;
      }
      break;
    case 1: // Level 1, main menu
      switch ( buttonPressed ) {
        case 'E': // Enter
          updateSub();
          menuLevel = 2;  // go to sub menu
          updateSub();
          delay(DEFAULT_DELAY);
          break;
        case 'U': // Up
          menu++;
          updateMenu();
          delay(DEFAULT_DELAY);
          break;
        case 'D': // Down
          menu--;
          updateMenu();
          delay(DEFAULT_DELAY);
          break;
        case 'B': // Back
          menuLevel = 0;  // hide menu, go back to level 0
          showHomeScreen();
          delay(DEFAULT_DELAY);
          break;
        default:
          break;
      } 
      break;
    case 2: // Level 2, sub menu
      switch ( buttonPressed ) {
        case 'E': 
          menuLevel = 1;
          updateMenu();
          delay(DEFAULT_DELAY);
          break;
        case 'U': // U
          if (menu == 1) {
            if (relay_val_1 < 3600000) {  // 1 hour max
              relay_val_1 = relay_val_1 + 1;
            } else {
              relay_val_1 = 3600000;
            }
          } else if (menu == 2) {       
            if (relay_val_2 < 3600000) {  // 1 hour max
              relay_val_2 = relay_val_2 + 1;
            } else {
              relay_val_2 = 3600000;
            }
          } else if (menu == 3) {
            if (relay_val_3 < 3600000) {  // 1 hour max  
              relay_val_3 = relay_val_3 + 1;
            } else {
              relay_val_3 = 3600000;
            }
          }
          updateSub();
          delay(DEFAULT_DELAY);
          break;
        case 'D': // D
          if (menu == 1) {
            if (relay_val_1 == 0) {
              relay_val_1 = 0;
            } else {
              relay_val_1 = relay_val_1 - 1;
            }
          } else if (menu == 2) {
            if (relay_val_2 == 0) {
              relay_val_2 = 0;
            } else {
              relay_val_2 = relay_val_2 - 1;
            }
          } else if (menu == 3) {
            if (relay_val_3 == 0) {
              relay_val_3 = 0;
            } else {
              relay_val_3 = relay_val_3 - 1;
            }
          }
          updateSub();
          delay(DEFAULT_DELAY);
          break;
        case 'B': // L
          menuLevel = 1;  // go back to main menu
          updateMenu();
          delay(DEFAULT_DELAY);
          break;
        default:  
          break;
      } 
      break;
    case 3: // Level 3, sub menu of sub menu
    
      break;
    default:
      break;
  }
  
}

void updateMenu() {
  
  switch (menu) {
    case 0:
      menu = 1;
      break;
    case 1:
      lcd.clear();
      lcd.print(">Relay 1: ");
      lcd.print(relay_val_1);
      lcd.setCursor(0, 1);
      lcd.print(" Relay 2 ");
      break;
    case 2:
      lcd.clear();
      lcd.print(" Relay 1 ");
      lcd.setCursor(0, 1);
      lcd.print(">Relay 2: ");
      lcd.print(relay_val_2);
      break;
    case 3:
      lcd.clear();
      lcd.print(">Relay 3: ");
      lcd.print(relay_val_3);
      lcd.setCursor(0, 1);
      lcd.print("             ");
      break;
    case 4:
      menu = 3;
      break;
  }
  
}

void updateSub() {
  switch (menu) {
    case 0:

      break;
    case 1:
      lcd.clear();
      lcd.print(" Relay 1:");
      lcd.setCursor(0, 1);
      lcd.print("  Val 1 = ");
      lcd.print(relay_val_1);
      break;
    case 2:
      lcd.clear();
      lcd.print(" Relay 2:");
      lcd.setCursor(0, 1);
      lcd.print("  Val 2 = ");
      lcd.print(relay_val_2);
      break;
    case 3:
      lcd.clear();
      lcd.print(" Relay 3:");
      lcd.setCursor(0, 1);
      lcd.print("  Val 3 = ");
      lcd.print(relay_val_3);
      break;
    case 4:
      menu = 3;
      break;
  }
}

void showHomeScreen() {
  lcd.clear();
  lcd.println("  TechToTinker  ");
  lcd.setCursor(0,1);
  lcd.println("  - R for menu  ");
}

Multiple level 16x2 LCD Menu Implementation

/*
/*
 *  Multi-level 16x2 LCD Menu Implementation 
 *    - this is the same as the Simple 16x2 LCD Menu Implementation but with additional levels
 *    - this uses a finite state machine
 *    - a button debouncing is also implemented to prevent unknown button action
 *  Please feel free to modify this to your liking, but please leave this comment as credit  
 *  
 *  Author: George Bantique
 *          @TechToTinker
 *          July 18, 2020 *   
 */

#include "LiquidCrystal.h"

LiquidCrystal lcd(7, 6, 5, 4, 3, 2);

#define button_B    A0
#define button_D    A1
#define button_U    A2
#define button_E    A3

#define ONBOARD_LED 13

#define DEFAULT_DELAY 300


char buttonPressed = '0';

byte menuLevel = 0;     // Level 0: no menu display, display anything you like
                        // Level 1: display main menu
                        // Level 2: display sub menu
                        // Level 3: display sub menu of sub menu

byte menu = 1;
byte sub = 1;

unsigned long relay_val_1 = 0;
unsigned long relay_val_2 = 0;
unsigned long relay_val_3 = 0;

bool LED_STATE = false;

bool currState_B = HIGH;
bool currState_D = HIGH;
bool currState_U = HIGH;
bool currState_E = HIGH;
          
bool prevState_B = HIGH; 
bool prevState_D = HIGH; 
bool prevState_U = HIGH; 
bool prevState_E = HIGH; 

unsigned long prevTime_B = 0;
unsigned long prevTime_D = 0;
unsigned long prevTime_U = 0;
unsigned long prevTime_E = 0;

unsigned long waitTime_B = 50;
unsigned long waitTime_D = 50;
unsigned long waitTime_U = 50;
unsigned long waitTime_E = 50;

byte charUp[8] = {
        B00100,
        B01110,
        B11111,
        B00000,
        B00000,
        B00000,
        B00000,
        B00000
};
byte charDown[8] = {
        B00000,
        B00000,
        B00000,
        B00000,
        B00000,
        B11111,
        B01110,
        B00100
};
byte charUpDown[8] = {
        B00100,
        B01110,
        B11111,
        B00000,
        B00000,
        B11111,
        B01110,
        B00100
};


void setup() {
  lcd.begin(16,2);
  lcd.createChar(0, charUp);
  lcd.createChar(1, charDown);
  lcd.createChar(2, charUpDown);
  
  Serial.begin(9600);

  pinMode(button_B, INPUT_PULLUP);
  pinMode(button_D, INPUT_PULLUP);
  pinMode(button_U, INPUT_PULLUP);
  pinMode(button_E, INPUT_PULLUP);

  pinMode(A4,INPUT);

  pinMode(ONBOARD_LED, OUTPUT);
  digitalWrite(ONBOARD_LED, LED_STATE);
  
  updateLevel_0();
}

void loop() {

  checkButton();

  // You can do other things here below
}

void checkButton() {

  // Button Debouncing
  bool currRead_B = digitalRead(button_B);
  bool currRead_D = digitalRead(button_D);
  bool currRead_U = digitalRead(button_U);
  bool currRead_E = digitalRead(button_E);

  if (currRead_B != prevState_B) {
    prevTime_B = millis();
  }
  if (currRead_D != prevState_D) {
    prevTime_D = millis();
  }
  if (currRead_U != prevState_U) {
    prevTime_U = millis();
  }
  if (currRead_E != prevState_E) {
    prevTime_E = millis();
  }

  if ((millis() - prevTime_B) > waitTime_B) {
    if (currRead_B != currState_B) {
      currState_B = currRead_B;
      if (currState_B == LOW) {
        buttonPressed = 'B';
      } 
    }
  } else buttonPressed = '0';
  if ((millis() - prevTime_D) > waitTime_D) {
    if (currRead_D != currState_D) {
      currState_D = currRead_D;
      if (currState_D == LOW) {
        buttonPressed = 'D';
      } 
    }
  } else buttonPressed = '0';
  if ((millis() - prevTime_U) > waitTime_U) {
    if (currRead_U != currState_U) {
      currState_U = currRead_U;
      if (currState_U == LOW) {
        buttonPressed = 'U';
      } else {
        //buttonPressed = '0';
      }
    }
  } else buttonPressed = '0';
  if ((millis() - prevTime_E) > waitTime_E) {
    if (currRead_E != currState_E) {
      currState_E = currRead_E;
      if (currState_E == LOW) {
        buttonPressed = 'E';
      } 
    }
  } else buttonPressed = '0';

  prevState_B = currRead_B;
  prevState_D = currRead_D;
  prevState_U = currRead_U;
  prevState_E = currRead_E;

  processButton(buttonPressed);

}

void processButton(char buttonPressed) {

  switch(menuLevel) {
    case 0:                     // Level 0, home screen
      switch ( buttonPressed ) {
        case 'E':               // Enter
          menu = 1;
          menuLevel = 1;        // go to main menu
          updateLevel_1();      // show main menu
          delay(DEFAULT_DELAY);
          break;
        case 'U': // Up
          break;
        case 'D': // Down
          break;
        case 'B': // Back
          menuLevel = 0;        // go to home screen
          updateLevel_0();      // show home screen
          delay(DEFAULT_DELAY);
          break;
        default:
          break;
      }
      break;
    case 1:                     // Level 1, main menu
      switch ( buttonPressed ) {
        case 'E':               // Enter
          sub = 1;
          menuLevel = 2;        // go to sub menu
          updateLevel_2();      // show sub menu
          delay(DEFAULT_DELAY);
          break;
        case 'U':               // Up
          menu++;
          updateLevel_1();      // show main menu
          delay(DEFAULT_DELAY);
          break;
        case 'D':               // Down
          menu--;
          updateLevel_1();      // show main menu
          delay(DEFAULT_DELAY);
          break;
        case 'B': // Back
          menuLevel = 0;        // hide menu, go back to level 0
          updateLevel_0();      // show home screen
          delay(DEFAULT_DELAY);
          break;
        default:
          break;
      } 
      break;
    case 2:                     // Level 2, sub menu
      switch ( buttonPressed ) {
        case 'E':               // Enter
          if (sub == 2) {       // Jump to sub menu of sub menu only when edit is necessary
            menuLevel = 3;        // go to sub menu of sub menu
            updateLevel_3();      // show sub menu of sub menu
          } else if (sub == 3) {
            executeAction();
            delay(1000);
            menuLevel = 2;        // go to sub menu
            updateLevel_2();      // show sub menu
          }
          delay(DEFAULT_DELAY);
          break;
        case 'U':               // Up
          sub++;
          updateLevel_2();
          delay(DEFAULT_DELAY);
          break;
        case 'D':               // Down
          sub--;
          updateLevel_2();      // show main menu
          delay(DEFAULT_DELAY);
          break;
        case 'B':               // Back
          menuLevel = 1;        // go back to level 1
          updateLevel_1();      // show main menu
          delay(DEFAULT_DELAY);
          break;
        default:
          break;
      } 
      break;
    case 3:                     // Level 3, sub menu of sub menu
      switch ( buttonPressed ) {
        case 'E':               // Enter
          menuLevel = 2;        // go back to level 2
          updateLevel_2();      // show sub menu
          delay(DEFAULT_DELAY);
          break;
        case 'U':               // Up
          if (sub == 2) {       // edit value
            if        (menu == 1) {
              if (relay_val_1 < 3600000) {  // 1 hour max
                relay_val_1 = relay_val_1 + 1;
              } else {
                relay_val_1 = 3600000;
              }
            } else if (menu == 2) {       
              if (relay_val_2 < 3600000) {  // 1 hour max
                relay_val_2 = relay_val_2 + 1;
              } else {
                relay_val_2 = 3600000;
              }
            } else if (menu == 3) {
              if (relay_val_3 < 3600000) {  // 1 hour max  
                relay_val_3 = relay_val_3 + 1;
              } else {
                relay_val_3 = 3600000;
              }
            }
          }
          updateLevel_3();      // show sub menu
          delay(DEFAULT_DELAY);
          break;
        case 'D':               // Down
          if (sub == 2) {       // edit value
            if        (menu == 1) {
              if (relay_val_1 == 0) {
                relay_val_1 = 0;
              } else {
                relay_val_1 = relay_val_1 - 1;
              }
            } else if (menu == 2) {
              if (relay_val_2 == 0) {
                relay_val_2 = 0;
              } else {
                relay_val_2 = relay_val_2 - 1;
              }
            } else if (menu == 3) {
              if (relay_val_3 == 0) {
                relay_val_3 = 0;
              } else {
                relay_val_3 = relay_val_3 - 1;
              }
            }
          }
          updateLevel_3();      // show sub menu
          delay(DEFAULT_DELAY);
          break;
        case 'B':               // Back
          menuLevel = 2;        // go back to main menu
          updateLevel_2();      // show main menu
          delay(DEFAULT_DELAY);
          break;
        default:  
          break;
      } 
      break;
    default:
      break;
  }
  
}

void updateLevel_0() {
  lcd.clear();
  lcd.println("  TechToTinker  ");
  lcd.setCursor(0,1);
  lcd.println("  - R for menu  ");
}

void updateLevel_1 () {
  
  switch (menu) {
    case 0:
      menu = 1;
      break;
    case 1:
      lcd.clear();
      lcd.print(">Relay 1: ");
      lcd.print(relay_val_1);
      lcd.setCursor(0, 1);
      lcd.print(" Relay 2 ");
      lcd.setCursor(15,1);
      lcd.write((byte)1);     // down arrow
      break;
    case 2:
      lcd.clear();
      lcd.print(" Relay 1 ");
      lcd.setCursor(0, 1);
      lcd.print(">Relay 2: ");
      lcd.print(relay_val_2);
      lcd.setCursor(15,1);
      lcd.write((byte)2);     // up and down arrow
      break;
    case 3:
      lcd.clear();
      lcd.print(">Relay 3: ");
      lcd.print(relay_val_3);
      lcd.setCursor(0, 1);
      lcd.print("             ");
      lcd.setCursor(15,1);
      lcd.write((byte)0);     // up arrow
      break;
    case 4:
      menu = 3;
      break;
  }
  
}

void updateLevel_2 () {
  switch (menu) {
    case 0:
      break;
    case 1:                                 // Relay 1
      switch (sub) {
        case 0:
          break;
        case 1:
          lcd.clear();
          lcd.print(" Relay 1:");
          lcd.setCursor(0, 1);
          lcd.print("  Val 1 = ");
          lcd.print(relay_val_1);
          lcd.setCursor(15,1);
          lcd.write((byte)1);     // down arrow
          break;
        case 2:
          lcd.clear();
          lcd.print(" Relay 1:");
          lcd.setCursor(0, 1);
          lcd.print("  Edit Val 1   ");
          lcd.print(relay_val_1);
          lcd.setCursor(15,1);
          lcd.write((byte)2);     // up and down arrow
          break;
        case 3:
          lcd.clear();
          lcd.print(" Relay 1:");
          lcd.setCursor(0, 1);
          lcd.print("  Execute Val 1");
          lcd.setCursor(15,1);
          lcd.write((byte)0);     // up arrow
          break;
        default:
          break;
      }
      break;
    case 2:                                 // Relay 2
      switch (sub) {
        case 0:
          break;
        case 1:
          lcd.clear();
          lcd.print(" Relay 2:");
          lcd.setCursor(0, 1);
          lcd.print("  Val 2 = ");
          lcd.print(relay_val_2);
          lcd.setCursor(15,1);
          lcd.write((byte)1);     // down arrow
          break;
        case 2:
          lcd.clear();
          lcd.print(" Relay 2:");
          lcd.setCursor(0, 1);
          lcd.print("  Edit Val 2   ");
          lcd.setCursor(15,1);
          lcd.write((byte)2);     // up and down arrow
          break;
        case 3:
          lcd.clear();
          lcd.print(" Relay 2:");
          lcd.setCursor(0, 1);
          lcd.print("  Execute Val 2");
          lcd.setCursor(15,1);
          lcd.write((byte)0);     // up arrow
          break;
        default:
          break;
      }
      break;
    case 3:                               // Relay 3
      switch (sub) {
        case 0:
          break;
        case 1:
          lcd.clear();
          lcd.print(" Relay 3:");
          lcd.setCursor(0, 1);
          lcd.print("  Val 3 = ");
          lcd.print(relay_val_3);
          lcd.setCursor(15,1);
          lcd.write((byte)1);     // down arrow
          break;
        case 2:
          lcd.clear();
          lcd.print(" Relay 3:");
          lcd.setCursor(0, 1);
          lcd.print("  Edit Val 3   ");
          lcd.setCursor(15,1);
          lcd.write((byte)2);     // up and down arrow
          break;
        case 3:
          lcd.clear();
          lcd.print(" Relay 3:");
          lcd.setCursor(0, 1);
          lcd.print("  Execute Val 3");
          lcd.setCursor(15,1);
          lcd.write((byte)0);     // up arrow
          break;
        default:
          break;
      }
      break;
    case 4:
      sub = 3;
      break;
  }
}

void updateLevel_3 () {
  switch (menu) {
    case 0:

      break;
    case 1:
      lcd.clear();
      lcd.print(" Relay 1:");
      lcd.setCursor(0, 1);
      lcd.print("  Val 1 = ");
      lcd.print(relay_val_1);
      break;
    case 2:
      lcd.clear();
      lcd.print(" Relay 2:");
      lcd.setCursor(0, 1);
      lcd.print("  Val 2 = ");
      lcd.print(relay_val_2);
      break;
    case 3:
      lcd.clear();
      lcd.print(" Relay 3:");
      lcd.setCursor(0, 1);
      lcd.print("  Val 3 = ");
      lcd.print(relay_val_3);
      break;
    case 4:
      sub = 3;
      break;
  }
}

void executeAction () {
  switch (menu) {
    case 0:

      break;
    case 1:
      lcd.clear();
      lcd.print(" Executing # 1");
      break;
    case 2:
      lcd.clear();
      lcd.print(" Executing # 2");
      break;
    case 3:
      lcd.clear();
      lcd.print(" Executing # 3");
      break;
    case 4:
      sub = 3;
      break;
  }
}

CALL TO ACTION

If you found this tutorial as helpful, please Like and Share this to your friends. Don't forget to Subscribe. Click this to Subscribe to TechToTinker Youtube channel.

Thank you and have a good day.

×

4 Comments

  1. sir the code is nice i hope i can contact you

  2. Hello there, you may contact me through tech.to.tinker@gmail.com.

    Regards,
    George Bantique | tech.to.tinker@gmail.com

  3. Hello Sir, I Need your help with changing some parameters in the Submenu. I have dropped you a mail too. Please go through it. I have been trying by myself for days but due to my limited knowledge in this domain, I'm clueless as to how do i proceed.

  4. Thanks for sharing such an amazing article, really informative

    Visit here : Digital signage Singapore

Leave a Reply

Required fields are marked *






Related Articles: (by Tags)