Simple universal LCD menu code

First post here. I was struggling to find an easy to implement menu system that works on a variety of LCDs. I am brand new to the arduino, and so I didn’t really understand how to use the class-based menuBackend that has been floating around here. So instead, I wrote my own. This should work with 1x16, 2x16, 4x20 or any other dimension LCD. This function creates a “Do” loop and keeps looping while waiting for user input. There is a comment near the top that indicates where you can put code that will be called in the background while looping.

If anyone has any tips or tweaks, I would like to hear it.

A few implementation notes:

  • set totalRows & totalCols to the dimensions of your LCD.

  • set menuTimeout to however long you want to wait with no user feedback before bailing out of menu (in millis). If you don’t want menu to timeout, just comment out the “if (timeoutTime<millis())” statement.

  • This was set up to use a rotary encoder, but the code should be commented enough so you can figure out what code should go where using buttons. (see post a few down for a suggested implementation using buttons).

/*
REQUIRED GLOBAL VARIABLES & DEFINITIONS
*/
#define MOVECURSOR 1  // constants for indicating whether cursor should be redrawn
#define MOVELIST 2  // constants for indicating whether cursor should be redrawn
byte totalRows = 4;  // total rows of LCD
byte totalCols = 20;  // total columns of LCD
unsigned long timeoutTime = 0;  // this is set and compared to millis to see when the user last did something.
const int menuTimeout = 10000; // time to timeout in a menu when user doesn't do anything.

Here is the rest of the code.
Updated to 1.1 on 2/26/11.

/*
MENU ROUTINE
 */
void basicMenu(){

  byte topItemDisplayed = 0;  // stores menu item displayed at top of LCD screen
  byte cursorPosition = 0;  // where cursor is on screen, from 0 --> totalRows. 

  // redraw = 0  - don't redraw
  // redraw = 1 - redraw cursor
  // redraw = 2 - redraw list
  byte redraw = MOVELIST;  // triggers whether menu is redrawn after cursor move.
  byte i=0; // temp variable for loops.
  byte totalMenuItems = 0;  //a while loop below will set this to the # of menu items.

// Put the menu items here. Remember, the first item will have a 'position' of 0.
  char* menuItems[]={
    "Set Timer 1", 
    "Set Timer 2", 
    "Set Probe 1 Temp",
    "Set Probe 2 Temp", 
    "Probe 1 Controller",
    "Probe 2 Controller",
    "Advanced Settings",
    "",
  };

  while (menuItems[totalMenuItems] != ""){
    totalMenuItems++;  // count how many items are in list.
  }
  totalMenuItems--;  //subtract 1 so we know total items in array.

  lcd.clear();  // clear the screen so we can paint the menu.

  boolean stillSelecting = true;  // set because user is still selecting.

  timeoutTime = millis() + menuTimeout; // set initial timeout limit. 

  do   // loop while waiting for user to select.
  {

    /*
    IF YOU WANT OTHER CODE GOING ON IN THE BACKGROUND
    WHILE WAITING FOR THE USER TO DO SOMETHING, PUT IT HERE
    */
    
    /*
    my code uses a rotary encoder for input. 
    You should obviously change the code to meet your needs.
    For a button, you could do something like this, but note that
    it does not have ANY debouncing and will scroll for as long as
    the button is being pushed. This is not a button tutorial, 
    so you should look elsewhere on how to implement that. Just 
    remember that ALL of the code between the corresponding 'case'
    and 'break' should be moved to each button push routine.
    
    
    buttonState = digitalRead(buttonPin);
    if (buttonState == HIGH) {     
      AND THEN PUT THE CORRESPONDING CODE
      FROM BELOW HERE
  } 
  */
    switch(read_encoder()) 
    {  // analyze encoder response. Default is 0.


    case 1:  // ENCODER ROTATED UP. EQUIVALENT OF 'UP' BUTTON PUSHED

      timeoutTime = millis()+menuTimeout;  // reset timeout timer
      //  if cursor is at top and menu is NOT at top
      //  move menu up one.
      if(cursorPosition == 0 && topItemDisplayed > 0)  //  Cursor is at top of LCD, and there are higher menu items still to be displayed.
      {
        topItemDisplayed--;  // move top menu item displayed up one. 
        redraw = MOVELIST;  // redraw the entire menu
      }

      // if cursor not at top, move it up one.
      if(cursorPosition>0)
      {
        cursorPosition--;  // move cursor up one.
        redraw = MOVECURSOR;  // redraw just cursor.
      }
      break;

    case 2:    // ENCODER ROTATED UP. EQUIVALENT OF 'DOWN' BUTTON PUSHED

      timeoutTime = millis()+menuTimeout;  // reset timeout timer
      // this sees if there are menu items below the bottom of the LCD screen & sees if cursor is at bottom of LCD 
      if((topItemDisplayed + (totalRows-1)) < totalMenuItems && cursorPosition == (totalRows-1))
      {
        topItemDisplayed++;  // move menu down one
        redraw = MOVELIST;  // redraw entire menu
      }
      if(cursorPosition<(totalRows-1))  // cursor is not at bottom of LCD, so move it down one.
      {
        cursorPosition++;  // move cursor down one
        redraw = MOVECURSOR;  // redraw just cursor.
      }
      break;

    case 4:  // ENCODER BUTTON PUSHED FOR SHORT PERIOD & RELEASED.
             // EQUIVALENT TO 'SELECT' OR 'OKAY' BEING PUSHED 

      timeoutTime = millis()+menuTimeout;  // reset timeout timer
      switch(topItemDisplayed + cursorPosition) // adding these values together = where on menuItems cursor is.
      {
      //  put code to be run when specific item is selected in place of the Serial.print filler.
      // the Serial.print code can be removed, but DO NOT change the case & break structure. 
      // (Obviously, you should have as many case instances as you do menu items.)
      case 0:  // menu item 1 selected
        Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        // there is no menuSubMenu1() function. BUT, to have nested menus,
        // copy this function(i.e. all of basicMenu) into a new function named 
        // whatever you want for your sub menu items and repeat.
//        menuSubMenu1();
        break;

      case 1:  // menu item 2 selected
        Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        break;

      case 2:  // menu item 3 selected
        Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        break;

      case 3:  // menu item 4 selected
        Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        break;

      case 4:  // menu item 5 selected
        Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        break;

      case 5:  // menu item 6 selected
        Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        break;


      case 6:  // menu item 7 selected
        Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        break;
        
        // add as many "case #:" as items you have. You could put 
        //  line separators in menuList and leave out the 
        //  corresponding case, which would mean that nothing
        // would be triggered when user selected the line separator.  
      }
      break;
      
    case 8:  // encoder button was pushed for long time. This corresponds to "Back" or "Cancel" being pushed.
      stillSelecting = false;
      Serial.println("Button held for a long time");
      break;

    }

    switch(redraw){  //  checks if menu should be redrawn at all.
    case MOVECURSOR:  // Only the cursor needs to be moved.
      redraw = false;  // reset flag.
      if (cursorPosition > totalMenuItems) // keeps cursor from moving beyond menu items.
        cursorPosition = totalMenuItems;
      for(i = 0; i < (totalRows); i++){  // loop through all of the lines on the LCD
        lcd.setCursor(0,i);
        lcd.print(" ");                      // and erase the previously displayed cursor
        lcd.setCursor((totalCols-1), i);
        lcd.print(" ");
      }
      lcd.setCursor(0,cursorPosition);      // go to LCD line where new cursor should be & display it.
      lcd.print(">");
      lcd.setCursor((totalCols-1), cursorPosition);
      lcd.print("<");
      break;  // MOVECURSOR break.

    case MOVELIST:  // the entire menu needs to be redrawn
      redraw=MOVECURSOR;  // redraw cursor after clearing LCD and printing menu.
      lcd.clear(); // clear screen so it can be repainted.
      if(totalMenuItems>((totalRows-1))){  // if there are more menu items than LCD rows, then cycle through menu items.
        for (i = 0; i < (totalRows); i++){
          lcd.setCursor(1,i);
          lcd.print(menuItems[topItemDisplayed + i]);
        }
      }
      else{  // if menu has less items than LCD rows, display all available menu items.
        for (i = 0; i < totalMenuItems+1; i++){
          lcd.setCursor(1,i);
          lcd.print(menuItems[topItemDisplayed + i]);
        }
      }
      break;  // MOVELIST break
    }

    if (timeoutTime<millis()){  // user hasn't done anything in awhile
      stillSelecting = false;  // tell loop to bail out.
      /*
      in my main code, I had a function that
       displayed a default screen on the LCD, so
       I would put that function here, and it would
       bail out to the default screen.
       defaultScreen();
       */
    }
  } 


  while (stillSelecting == true);  //
}

Good work. How many buttons do you need to operate your menu? I have something I could share but will need to smooth my code before I can post. You got me started thinking about doing it.

Thanks.

With my rotary encoder, I technically only have 1 button. If you were to break out the encoder functions into buttons, it would need a minimum of 3 - up, down, button 3 pressed a short time (<500ms) to select the menu item and button 3 held a longer time (>500ms) to cancel/go back. You could make the “hold button 3” into it’s own 4th button - “cancel.”

In the code, the cases after switch(read_encoder) correspond to the following button presses:
case 1 - up button
case 2 - down button
case 4 - select button
case 8 - cancel button

(Side note: I use powers of 2 (instead of 1,2,3,4) for the case switch because with the rotary encoder code I use, rotary states can be added up. For instance, if the button is being held down (case 16) while the rotary is being rotated (case 1), you get: 1 + 16 = 17).

Hi!

is that how you implent buttons in to your code:

case 1:  
    buttonState = digitalRead(buttonUp);
    if (buttonState == HIGH) { 
 break;

Thanks

This should work with 1x16, 2x16, 4x20

I doubt that it will work properly for the right hand half of most 16x1 displays or for lines 3 and 4 of a 16x4 display. This is due to limitations of the current LiquidCrystal library. It will probably work better with LiquidCrystal440.

If anyone has any tips or tweaks, I would like to hear it. ... ... set totalRows & totalCols to the dimensions of your LCD, minus 1 (e.g. for 2x16, totalRows = 1, totalCols = 15).

I would let the user specify the actual dimensions and do the subtracting within the program.

Don

floresta:

This should work with 1x16, 2x16, 4x20

I doubt that it will work properly for the right hand half of most 16x1 displays or for lines 3 and 4 of a 16x4 display. This is due to limitations of the current LiquidCrystal library. It will probably work better with LiquidCrystal440.

Interesting. I didn't know about that limit. While I only have a 4x20 LCD, I declared lcd.begin(16,1), and (16,4) and it seemed to work fine.

I would let the user specify the actual dimensions and do the subtracting within the program.

Thanks for the suggestion. I made the changes in the original code above.

Nitro_boy:
Hi!

is that how you implent buttons in to your code:

case 1:  

buttonState = digitalRead(buttonUp);
    if (buttonState == HIGH) {
break;





Thanks

This is a rough sketch for you, but this might work. Instead of the line

switch(read_encoder())

You’ll have:

switch(read_buttons())

Then, create a new function, such as the one below (I have not compiled this, so it may not work at first).

unsigned long lastButtonPressed; // this is when the last button was pressed. It's used to debounce.
const int debounceTime = 50; //this is the debounce and hold delay. Otherwise, you will FLY 
                                          //through the menu by touching the button. 

void read_buttons(){  // you may need to swap "void" with "int" or "byte"
   byte returndata = 0;
   int buttonState; // this might not be the correct declaration.
   // remember to declare what buttonUp, buttonDown, buttonSelect, buttonCancel are
if ((lastButtonPressed + debounceTime) < millis(){  // see if it's time to check the buttons again
   buttonState = digitalRead(buttonUp);
   if (buttonState == HIGH){
     returndata = returndata + 1;
     lastButtonPressed = millis();
}

   buttonState = digitalRead(buttonDown);
   if (buttonState == HIGH){
     returndata = returndata + 2;
     lastButtonPressed = millis();
}

   buttonState = digitalRead(buttonSelect);
   if (buttonState == HIGH){
     returndata = returndata + 4;
     lastButtonPressed = millis();
}

   buttonState = digitalRead(buttonCancel);
   if (buttonState == HIGH){
     returndata = returndata + 8;
     lastButtonPressed = millis();
}
}
  return returndata; // this spits back to the function that calls it the variable returndata.
}

This was all off the cuff, but it should give you a start. Good luck.

Interesting. I didn't know about that limit. While I only have a 4x20 LCD, I declared lcd.begin(16,1), and (16,4) and it seemed to work fine.

As it stands right now the LiquidCrystal library does not do much with the information it gets from the lcd.begin function. It does not use the column information at all and it only checks to see if the row information is '1' or something other than '1'. The default (if lcd.begin is not present) is, unfortunately, '1'.

The problem with the 16x1 devices is that most of them are actually 8x2 internally. So if your 16x1 doesn't display anything on the right side (with any LCD library) then try treating it as an 8x2. The problem with the 16x4 devices is that the initial addresses for lines 3 and 4 are different than the initial addresses for the same lines on 20x4 devices. Check out the [u]LCD Addressing[/u] link at http://web.alfredstate.edu/weimandn for more information.

I doubt that your 20x4 device worked at all on lines 2, 3 or 4 when using lcd.begin(16,1). You should also have noticed a difference in contrast on line 1.

Don

Thank you for the reply but this is my first LCD project and im not quite sure about where to insert this.

Here is my threat about my problem: http://arduino.cc/forum/index.php/topic,53260.0.html

I would be very grateful if you could help me.

Thanks

Nitro_boy:
Thank you for the reply but this is my first LCD project and im not quite sure about where to insert this.

In the Arduino sketch app, search for “switch(read_encoder())” and when you find it, replace it with, “switch(read_buttons())” (omitting the “” of course).

Then, go to the top of your sketch, before “void setup()” and paste this in:

unsigned long lastButtonPressed; // this is when the last button was pressed. It's used to debounce.
const int debounceTime = 50; //this is the debounce and hold delay. Otherwise, you will FLY 
                                          //through the menu by touching the button.

Then, scroll to the bottom of your sketch, after all your other functions, and paste this code in:

void read_buttons(){  // you may need to swap "void" with "int" or "byte"
  byte returndata = 0;
  int buttonState; 
  // *** REMEMBER to declare buttonUp, buttonDown, buttonSelect, & buttonCancel pins

  if ((lastButtonPressed + debounceTime) < millis()){  // see if it's time to check the buttons again
    
    // read Up button
    buttonState = digitalRead(buttonUp);
    if (buttonState == HIGH){
      returndata = returndata + 1;
      lastButtonPressed = millis();
    }

    // read Down button
    buttonState = digitalRead(buttonDown);
    if (buttonState == HIGH){
      returndata = returndata + 2;
      lastButtonPressed = millis();
    }

    // read Select button
    buttonState = digitalRead(buttonSelect);
    if (buttonState == HIGH){
      returndata = returndata + 4;
      lastButtonPressed = millis();
    }

    // read Cancel button
    buttonState = digitalRead(buttonCancel);
    if (buttonState == HIGH){
      returndata = returndata + 8;
      lastButtonPressed = millis();
    }
  }
  return returndata; // this spits back to the function that calls it the variable returndata.
}

Thanks but now it says for switch(read_buttons()):

switch quantity not an integer

and somthing else do I have to include LiquidCrystal.h library ?

Nitro_boy: Thanks but now it says for switch(read_buttons()):

switch quantity not an integer

I think this should work... In "void read_buttons()" replace byte returndata = 0; with int returndata = 0;

and somthing else do I have to include LiquidCrystal.h library ?

Yep.

This is the code:

/*REQUIRED GLOBAL VARIABLES & DEFINITIONS
*/
#include <LiquidCrystal.h>
#define MOVECURSOR 1  /
#define MOVELIST 2  
byte totalRows = 4;  
byte totalCols = 20;  
unsigned long timeoutTime = 0;  
const int menuTimeout = 10000; 

unsigned long lastButtonPressed; 
const int debounceTime = 50; 
                                          
                                          
LiquidCrystal lcd(10, 11, 12, 13, 14, 15, 16);


    buttonState = digitalRead(buttonPin);
    if (buttonState == HIGH) {     
      AND THEN PUT THE CORRESPONDING CODE
      FROM BELOW HERE
  } 
  */
   switch(read_buttons()) 
    {  // analyze encoder response. Default is 0.


    case 1:  

      timeoutTime = millis()+menuTimeout;  // reset timeout timer
      //  if cursor is at top and menu is NOT at top
      //  move menu up one.
      if(cursorPosition == 0 && topItemDisplayed > 0)  //  Cursor is at top of LCD, and there are higher menu items still to be displayed.
      {
        topItemDisplayed--;  // move top menu item displayed up one. 
        redraw = MOVELIST;  // redraw the entire menu
      }

      // if cursor not at top, move it up one.
      if(cursorPosition>0)
      {
        cursorPosition--;  // move cursor up one.
        redraw = MOVECURSOR;  // redraw just cursor.
      }
      break;

    case 2:    

      timeoutTime = millis()+menuTimeout;  // reset timeout timer
      // this sees if there are menu items below the bottom of the LCD screen & sees if cursor is at bottom of LCD 
      if((topItemDisplayed + (totalRows-1)) < totalMenuItems && cursorPosition == (totalRows-1))
      {
        topItemDisplayed++;  // move menu down one
        redraw = MOVELIST;  // redraw entire menu
      }
      if(cursorPosition<(totalRows-1))  // cursor is not at bottom of LCD, so move it down one.
      {
        cursorPosition++;  // move cursor down one
        redraw = MOVECURSOR;  // redraw just cursor.
      }
      break;

    case 4:  

      timeoutTime = millis()+menuTimeout;  // reset timeout timer
      switch(topItemDisplayed + cursorPosition) // adding these values together = where on menuItems cursor is.
      {
      //  put code to be run when specific item is selected in place of the Serial.print filler.
      // the Serial.print code can be removed, but DO NOT change the case & break structure. 
      // (Obviously, you should have as many case instances as you do menu items.)
      case 0:  // menu item 1 selected
        Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        // there is no menuSubMenu1() function. BUT, to have nested menus,
        // copy this function(i.e. all of basicMenu) into a new function named 
        // whatever you want for your sub menu items and repeat.
//        menuSubMenu1();
        break;

      case 1:  // menu item 2 selected
        Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        break;

      case 2:  // menu item 3 selected
        Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        break;

      case 3:  // menu item 4 selected
        Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        break;

      case 4:  // menu item 5 selected
        Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        break;

      case 5:  // menu item 6 selected
        Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        break;


      case 6:  // menu item 7 selected
        Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        break;
        
        // add as many "case #:" as items you have. You could put 
        //  line separators in menuList and leave out the 
        //  corresponding case, which would mean that nothing
        // would be triggered when user selected the line separator.  
      }
      break;
      
    case 8:  // encoder button was pushed for long time. This corresponds to "Back" or "Cancel" being pushed.
      stillSelecting = false;
      Serial.println("Button held for a long time");
      break;

    }

    switch(redraw){  //  checks if menu should be redrawn at all.
    case MOVECURSOR:  // Only the cursor needs to be moved.
      redraw = false;  // reset flag.
      if (cursorPosition > totalMenuItems) // keeps cursor from moving beyond menu items.
        cursorPosition = totalMenuItems;
      for(i = 0; i < (totalRows); i++){  // loop through all of the lines on the LCD
        lcd.setCursor(0,i);
        lcd.print(" ");                      // and erase the previously displayed cursor
        lcd.setCursor((totalCols-1), i);
        lcd.print(" ");
      }
      lcd.setCursor(0,cursorPosition);      // go to LCD line where new cursor should be & display it.
      lcd.print(">");
      lcd.setCursor((totalCols-1), cursorPosition);
      lcd.print("<");
      break;  // MOVECURSOR break.

    case MOVELIST:  // the entire menu needs to be redrawn
      redraw=MOVECURSOR;  // redraw cursor after clearing LCD and printing menu.
      lcd.clear(); // clear screen so it can be repainted.
      if(totalMenuItems>((totalRows-1))){  // if there are more menu items than LCD rows, then cycle through menu items.
        for (i = 0; i < (totalRows); i++){
          lcd.setCursor(1,i);
          lcd.print(menuItems[topItemDisplayed + i]);
        }
      }
      else{  // if menu has less items than LCD rows, display all available menu items.
        for (i = 0; i < totalMenuItems+1; i++){
          lcd.setCursor(1,i);
          lcd.print(menuItems[topItemDisplayed + i]);
        }
      }
      break;  // MOVELIST break
    }

    if (timeoutTime<millis()){  // user hasn't done anything in awhile
      stillSelecting = false;  // tell loop to bail out.
      /*
      in my main code, I had a function that
       displayed a default screen on the LCD, so
       I would put that function here, and it would
       bail out to the default screen.
       defaultScreen();
       */
    }
  } 


  while (stillSelecting == true);  //
}

void read_buttons(){  // you may need to swap "void" with "int" or "byte"
   int returndata = 0;
   int buttonState; // this might not be the correct declaration.
   // remember to declare what buttonUp, buttonDown, buttonSelect, buttonCancel are
if ((lastButtonPressed + debounceTime) < millis(){  // see if it's time to check the buttons again
   buttonState = digitalRead(buttonUp);
   if (buttonState == HIGH){
     returndata = returndata + 1;
     lastButtonPressed = millis();
}

   buttonState = digitalRead(buttonDown);
   if (buttonState == HIGH){
     returndata = returndata + 2;
     lastButtonPressed = millis();
}

   buttonState = digitalRead(buttonSelect);
   if (buttonState == HIGH){
     returndata = returndata + 4;
     lastButtonPressed = millis();
}

   buttonState = digitalRead(buttonCancel);
   if (buttonState == HIGH){
     returndata = returndata + 8;
     lastButtonPressed = millis();
}
}
  return returndata; // this spits back to the function that calls it the variable returndata.
}

And it stil says that I have to switch quantity not an integer. I have cut out some code to fit it in the 9500 limit.

And it stil says that I have to switch quantity not an integer. I have cut out some code to fit it in the 9500 limit.

Maybe try changing "void read_buttons()" to "int read_buttons()"

Learning how to debug errors like this is important. I would recommend breaking down your sketch into pieces. Put the "read_buttons" function into a new sketch w/ basic code that serial.prints the output of read_buttons and see if you are getting the responses you want, and then go from there.

sample sketch for testing read_buttons()

int returnedData = 0;

void setup(){
}

void loop(){
returnedData = 0;
returnedData = read_buttons();
if (returnedData != 0)
  Serial.println(returnedData);
}

thanks for the help but I figured that i'm not up to the task so I will learn some basics frst.

Thanks

Is there any chance that you can share your encoder routine? Thanks in advance.

First post but thought I would try to give a little back to the forums

I was looking for a simple menu system for a 16x2 LCD, I wanted it to have a setup mode and a running mode. What i took was the core code developed by polepole the OP of the this thread and adapted it. Once done I realised given some of the follow on posts it may be of use to others if I trimmed out my code and re posted it.

Compiled and uploaded it uses 3 buttons up, down and select - to enter the menu system press the up and down together as the arduino is rebooted. Once completed in the setup function it then drops out to the main void loop.

Ive tried to comment it as much as is sensible in order to help another plagarist like me to hack it into what they need. Also given some rough guide on how to switch it to running as a menu based app i.e. the Menu is the core code and not a setup function.

I am no professional coder and generally look for snippets of code other have produced learning from them to achieve what I need.

Hope someone finds it useful

Code posted on following post

Ric

Part 1:

/// Adaption of the Simple LCD menu system writen by PolePole - Arduino forums



//I use the 3 wire LCD driver using an 74HC595 chip - Very good at saving pins http://www.stephenhobley.com/blog/2011/02/07/controlling-an-lcd-with-just-3-pins/
// If you use the conventional LiquidCrystal.h comment out the lines with 595 in them and uncomment the conventional ones, of course setting the pin numbers as required


// include the library code for the LCD
// #include <LiquidCrystal.h>
#include <LiquidCrystal595.h>  

// initialize the interface pins for the LCD
// LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
LiquidCrystal595 lcd(2,3,4);     // datapin, latchpin, clockpin






//Define Various Variables used throughout

int timer = 0;
byte totalRows = 2;                // total rows of LCD
byte totalCols = 16;               // total columns of LCD
int returndata = 0;                // Used for return of button presses
unsigned long timeoutTime = 0;     // this is set and compared to millis to see when the user last did something.
const int menuTimeout = 20000;     // time to timeout in a menu when user doesn't do anything.
unsigned long lastButtonPressed;   // this is when the last button was pressed. It's used to debounce.
const int debounceTime = 150;      // this is the debounce and hold delay. Otherwise, you will FLY through the menu by touching the button. 
const int buttonUp = 8;            // Set pin for UP Button
const int buttonDown = 9;          // Set pin for DOWN Button
const int buttonSelect = 11;             // Set pin for SLELECT Button
const int buttonCancel = 12;             // Set pun for CANCEL Button (No currently used)

int buttonStateUp = 0;             // Initalise ButtonStates
int buttonStateDown = 0;
int buttonState;

int count = 0;                     // Temp variable for void demo


// constants for indicating whether cursor should be redrawn
#define MOVECURSOR 1 
// constants for indicating whether cursor should be redrawn
#define MOVELIST 2  










// Main setup routine

void setup()

{

// Setup stuff that needs to be done regardless of whether entering setup mode or not
  
  
    // set up the LCD's number of columns and rows: 
    lcd.begin(totalCols, totalRows);  
  
    // Turn on the LCD Backlight
    lcd.setLED1Pin(1);      
    
    // initialize the serial communications port:
    Serial.begin(9600);
        
    // Set Buttons as input for testing whether to enter setup mode
    pinMode(buttonUp, INPUT);
    pinMode(buttonDown, INPUT);
  

    // Read the Button States
    
    buttonStateUp = digitalRead(buttonUp);
    buttonStateDown = digitalRead(buttonDown);
  
  
// Start Setup Mode if both up and down buttons pressed together
 
  if (buttonStateUp == HIGH && buttonStateDown == HIGH) { 
  
  
  byte topItemDisplayed = 0;  // stores menu item displayed at top of LCD screen
  byte cursorPosition = 0;  // where cursor is on screen, from 0 --> totalRows. 

  // redraw = 0  - don't redraw
  // redraw = 1 - redraw cursor
  // redraw = 2 - redraw list
  byte redraw = MOVELIST;  // triggers whether menu is redrawn after cursor move.
  byte i=0; // temp variable for loops.
  byte totalMenuItems = 0;  //a while loop below will set this to the # of menu items.

// Create list of Menu Items
  char* menuItems[]={
    "Menu Item 1", 
    "Menu Item 2", 
    "Menu Item 3",
    "Menu Item 4 ", 
    "Menu Item 5", 
    "Menu Item 6",
    "",
  };

// count how many items are in list.

  while (menuItems[totalMenuItems] != ""){
    totalMenuItems++;  
  }
  
  //subtract 1 so we know total items in array.
  totalMenuItems--;  
  

  lcd.clear();  // clear the screen so we can paint the menu.

  boolean stillSelecting = true;  // set because user is still selecting.

  timeoutTime = millis() + menuTimeout; // set initial timeout limit. 

  do   // Run a loop while waiting for user to select menu option.
  {
 
// Call any other setup actions required and/or process anything else required whilst in setup mode as opposed to things setup regardless of setup mode


    
// Call read buttons routine which analyzes buttons and gets a response. Default response is 0.  
switch(read_buttons())
    {  

   
      // Case responses depending on what is returned from read buttons routine
      
      case 1:  // 'UP' BUTTON PUSHED

      timeoutTime = millis()+menuTimeout;  // reset timeout timer
      //  if cursor is at top and menu is NOT at top
      //  move menu up one.
      if(cursorPosition == 0 && topItemDisplayed > 0)  //  Cursor is at top of LCD, and there are higher menu items still to be displayed.
      {
        topItemDisplayed--;  // move top menu item displayed up one. 
        redraw = MOVELIST;  // redraw the entire menu
      }

      // if cursor not at top, move it up one.
      if(cursorPosition>0)
      {
        cursorPosition--;  // move cursor up one.
        redraw = MOVECURSOR;  // redraw just cursor.
      }
      break;

   
   
   
      case 2:    // 'DOWN' BUTTON PUSHED

      timeoutTime = millis()+menuTimeout;  // reset timeout timer
      // this sees if there are menu items below the bottom of the LCD screen & sees if cursor is at bottom of LCD 
      if((topItemDisplayed + (totalRows-1)) < totalMenuItems && cursorPosition == (totalRows-1))
      {
        topItemDisplayed++;  // move menu down one
        redraw = MOVELIST;  // redraw entire menu
      }
      if(cursorPosition<(totalRows-1))  // cursor is not at bottom of LCD, so move it down one.
      {
        cursorPosition++;  // move cursor down one
        redraw = MOVECURSOR;  // redraw just cursor.
      }
      break;

      
      
      
      
      
      case 4:  // SELECT BUTTON PUSHED

      timeoutTime = millis()+menuTimeout;  // reset timeout timer
      switch(topItemDisplayed + cursorPosition) // adding these values together = where on menuItems cursor is.
      {
      case 0:  // menu item 1 selected          
        lcd.clear();
        lcd.print("Run Item1 code");
        lcd.setCursor(0,1);
        lcd.print("from here");
        Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        delay(2000);
        stillSelecting = false;
        
        
       break;

      case 1:  // menu item 2 selected
        lcd.clear();
        lcd.print("Run Item2 code");
        lcd.setCursor(0,1);
        lcd.print("from here");
        Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        break;

      case 2:  // menu item 3 selected
        lcd.clear();
        lcd.print("Run Item3 code");
        lcd.setCursor(0,1);
        lcd.print("from here");Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        break;

      case 3:  // menu item 4 selected
        lcd.clear();
        lcd.print("Run Item4 code");
        lcd.setCursor(0,1);
        lcd.print("from here");Serial.print("Menu item ");       Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        break;

      case 4:  // menu item 5 selected
        lcd.clear();
        lcd.print("Run Item5 code");
        lcd.setCursor(0,1);
        lcd.print("from here");Serial.print("Menu item ");Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        break;

      case 5:  // menu item 6 selected
        lcd.clear();
        lcd.print("Run Item6 code");
        lcd.setCursor(0,1);
        lcd.print("from here");Serial.print("Menu item ");        Serial.print("Menu item ");
        Serial.print(topItemDisplayed + cursorPosition);
        Serial.print(" selected - ");
        Serial.println(menuItems[topItemDisplayed + cursorPosition]);
        break;
       
      }
      break;
      
    
    
    
    
    
    
    //case 8:  //  CANCEL BUTTON PUSHED - Not currently used
    //  stillSelecting = false;
    //  Serial.println("Button held for a long time");
    //  break;

Part 2:

 }

    switch(redraw){  //  checks if menu should be redrawn at all.
    case MOVECURSOR:  // Only the cursor needs to be moved.
      redraw = false;  // reset flag.
      if (cursorPosition > totalMenuItems) // keeps cursor from moving beyond menu items.
        cursorPosition = totalMenuItems;
      for(i = 0; i < (totalRows); i++){  // loop through all of the lines on the LCD
        lcd.setCursor(0,i);
        lcd.print(" ");                      // and erase the previously displayed cursor
        lcd.setCursor((totalCols-1), i);
        lcd.print(" ");
      }
      lcd.setCursor(0,cursorPosition);      // go to LCD line where new cursor should be & display it.
      lcd.print(">");
      lcd.setCursor((totalCols-1), cursorPosition);
      lcd.print("<");
      break;  // MOVECURSOR break.

    case MOVELIST:  // the entire menu needs to be redrawn
      redraw=MOVECURSOR;  // redraw cursor after clearing LCD and printing menu.
      lcd.clear(); // clear screen so it can be repainted.
      if(totalMenuItems>((totalRows-1))){  // if there are more menu items than LCD rows, then cycle through menu items.
        for (i = 0; i < (totalRows); i++){
          lcd.setCursor(1,i);
          lcd.print(menuItems[topItemDisplayed + i]);
        }
      }
      else{  // if menu has less items than LCD rows, display all available menu items.
        for (i = 0; i < totalMenuItems+1; i++){
          lcd.setCursor(1,i);
          lcd.print(menuItems[topItemDisplayed + i]);
        }
      }
   
      break;  // MOVELIST break
      
    }

    if (timeoutTime<millis()){  // user hasn't done anything in awhile
      stillSelecting = false;  // tell loop to bail out.
     
    }
  } 


  while (stillSelecting == true);  //
  
//End of Start Setup mode if
}
  
  
//End of Void Setup() 


// Clear LCD on exit from setup routine
lcd.clear();
}  


void loop()
{
  
// Put your Main code in here


// If you want your main code to be the menu system then copy the contents of void setup into here
// If you do copy void setup copy (then delete) it from the section '// Start Setup Mode if both up and down buttons pressed together' onwards
// Delete the line      'if (buttonStateUp == HIGH && buttonStateDown == HIGH) { '
// Also make sure you delete the last '}' from the end of the void setup, Labeled 'End of Start Setup mode if'
// When your routine runs it will now go straight into the main menu function



lcd.clear();
lcd.print("now void loop");
lcd.setCursor(0,1);
lcd.print(count);
count = count + 1;
delay(500);

}




// This routine reads the buttons, applys the debounce and returns the result to the calling routine

int read_buttons(){  // you may need to swap "void" with "int" or "byte"
  
  int returndata = 0;
   

  if ((lastButtonPressed + debounceTime) < millis()){  // see if it's time to check the buttons again
    
    // read Up button
    buttonState = digitalRead(buttonUp);
   
    if (buttonState == HIGH){
      returndata = returndata + 1;
      lastButtonPressed = millis();
    }

    // read Down button
    buttonState = digitalRead(buttonDown);
    
    if (buttonState == HIGH){
      returndata = returndata + 2;
      lastButtonPressed = millis();
    }

    // read Select button
    buttonState = digitalRead(buttonSelect);
    if (buttonState == HIGH){
      returndata = returndata + 4; 
      lastButtonPressed = millis();
    }

    // read Cancel button - Not used at present
    //buttonState = digitalRead(buttonCancel);
    //if (buttonState == HIGH){
    //  returndata = returndata + 8;
    //  lastButtonPressed = millis();
    //}
  }
  
  return returndata; // this spits back to the function that calls it the variable returndata.
}