Programming menus on 16x2 lcd

Okay, So I know there are umpteen different ways to do menus on lcds for the arduino, but frankly I'm having difficulty getting anything to work for my use case (which may be more to do with me than anything else)

Background: I'm trying to use an Adafruit LCD that uses i2c The project I'm working on is a clothes dryer that uses an Arduino brain. There are two main variables that I'm trying to control: heat and time.

I'd like to set up a menu like

  • Heat

  • Auto

  • Hi

  • Med

  • Lo

  • Off

  • Time

  • Auto

  • 90

  • 60

  • 45

  • 30

  • 15

What is the best way to do this? I've taken a look at several menu libs, particularly "MenuBackend", but I just cannot get them to give me a simple menu setup.

Can some of you smart peeps help a dude out?

Thanks, JR

PS, I'm not attaching code because I'd prefer to start from scratch.

I normally write something like that in a switch/case then allow selections to be made in further switch/cases. it would require at least 3 buttons

edit----up-----down

another button may be required if you want to add something like start program.

gpop1: I normally write something like that in a switch/case then allow selections to be made in further switch/cases. it would require at least 3 buttons

edit----up-----down

another button may be required if you want to add something like start program.

So I can switch case within a case? I feel like Xzibit would have something to say about that :)

So I can switch case within a case?

you can do anything you like in a case. a bit like using "else if" except its faster for the processor as it doesn't have to check each "if" it just skips down the switch, runs that piece then breaks to the end of the switch.

So I can switch case within a case?

Did it work when you tried? .

all of this junk is write with in a case and its been running non stop for over 3 months. Yes the button code shouldn’t really be there as its lazy cut and paste coding but its easy to read and the mega had plenty of memory to spare. another switch with the same name holds the code to make stuff happen as this is just to display and set the option.

  case 4:
  
  prev_turner_options = turner_options;
   if ((up_button ==1)&&(prev_up_button == 0)) {
turner_options++;
prev_up_button=1;}
if (up_button ==0) {prev_up_button=0;}//debounce
  
   if ((down_button ==1)&&(prev_down_button == 0)) {
    
turner_options--;
prev_down_button=1;}
if (down_button ==0){prev_down_button=0;}//debounce 
  
  if (clear_oneshot ==0){lcd.clear();
 clear_oneshot =1;}
 lcd.setCursor(0,0);
 lcd.print ("  turner options");
 lcd.setCursor(0,1);
 lcd.print ("  press up/down ");
 lcd.setCursor(0,2);
 if (turner_options<1) {turner_options =5;}
 if (turner_options>5) {turner_options =1;}
 
 switch (turner_options){
   case 1:
   lcd.print ("             off            ");
   break;
   case 2:
   lcd.print ("       auto 2hrs        ");
   break;
 case 3:
   lcd.print ("         level             ");
   break;
   case 4:
   lcd.print (" shipping hold 24hs ");
   break;
   case 5:
   lcd.print (" shipping hold 48hs ");
   break;
 }

 lcd.setCursor(0,3);
 lcd.print ("release edit to set");
 if ( prev_turner_options != turner_options){memory_update=7;}
 
 break;

thanks @gpop, I think I'm starting to get it. Just so I'm clear though, switch(turner_options) is actually within case 4?

yep that’s with in case 4:

this isn’t the way you should write it as it wastes memory but its easy to read and understand the idea. plus i had less then 3 weeks of experience when i wrote the code.

heres some sections of the code i used

  if (edit_button ==0)// 0=not in edit mode
  {
 if ((up_button ==1)&&(prev_up_button == 0)) {
  screen ++;//do this when not in edit, do not do this in edit mode
prev_up_button=1;}
if (up_button ==0) {prev_up_button=0;}//debounce
  
   if ((down_button ==1)&&(prev_down_button == 0)) {
  screen --;
prev_down_button=1;}
if (down_button ==0){prev_down_button=0;}//debounce
  }

this part is used to either change screens with in the screen switch or is disabled while in edit

the code below is the main switch called screen. screen is used twice depending on edit mode. so if its not being edited then the first switch is used. If its being edited (edit=1) then the second switch is used

 //not in edit mode 
  if (edit_button==0){
switch (screen){
   case 4:
 if (clear_oneshot ==0){lcd.clear();
 clear_oneshot =1;}
 lcd.setCursor(0,0);
  lcd.print ("turner control");
 lcd.setCursor(0,1);
 lcd.print (" current set-up");
 lcd.setCursor(0,2);
switch (turner_options){
   case 1:
   lcd.print ("       off        ");
   break;
   case 2:
   lcd.print ("    auto 2hrs     ");
   break;
 case 3:
   lcd.print ("      level       ");
   break; 
   case 4:
   lcd.print ("shipping hold 24hs");
   break;
   case 5:
   lcd.print ("shipping hold 48hs");
   break;
 }
 lcd.setCursor(0,3);
  lcd.print ("hold edit to change");
 
 break;
}}
 
 
    //edit code..................................................................................
  if (edit_button ==1)//up down buttons are now disabled and used to change set points
  {
   switch (screen) {
       case 4:
  
  prev_turner_options = turner_options;
   if ((up_button ==1)&&(prev_up_button == 0)) {
turner_options++;
prev_up_button=1;}
if (up_button ==0) {prev_up_button=0;}//debounce
  
   if ((down_button ==1)&&(prev_down_button == 0)) {
    
turner_options--;
prev_down_button=1;}
if (down_button ==0){prev_down_button=0;}//debounce 
  
  if (clear_oneshot ==0){lcd.clear();
 clear_oneshot =1;}
 lcd.setCursor(0,0);
 lcd.print ("  turner options");
 lcd.setCursor(0,1);
 lcd.print ("  press up/down ");
 lcd.setCursor(0,2);
 if (turner_options<1) {turner_options =5;}
 if (turner_options>5) {turner_options =1;}
 
 switch (turner_options){
   case 1:
   lcd.print ("       off          ");
   break;
   case 2:
   lcd.print ("      auto 2hrs     ");
   break;
 case 3:
   lcd.print ("      level         ");
   break;
   case 4:
   lcd.print (" shipping hold 24hs ");
   break;
   case 5:
   lcd.print (" shipping hold 48hs ");
   break;
 }}

my program used 28 screens of which 18 had parts that were changeable by the operator. The reason being i didn’t want to have to connect a computer to it again to make changes

Thanks for the help again, @gpop1,
Here’s what I came up with. It may be a little unconventional for nested menus, but it seems to work :slight_smile:

One thing I noticed was that the screen flashes an awful lot - to the point it’s tough to read, especially at an odd angle.

#include <Wire.h>
#include <Adafruit_MCP23017.h>
#include <Adafruit_RGBLCDShield.h>

Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield();

#define ON 0x7
#define OFF 0x0
byte icons[3][7] = {{ 0x0, 0x4, 0x6, 0x1f, 0x6, 0x4, 0x0},
  {0x0, 0xe, 0x15, 0x17, 0x11, 0xe, 0x0},
  {0x4, 0xe, 0xe, 0x1e, 0x1b, 0xb, 0x6}
};

//! Index into the bitmap array for the  icons.
const int ARROW_ICON_IDX = 0;
const int CLOCK_ICON_IDX = 1;
const int FLAME_ICON_IDX = 2;

uint8_t clicked_buttons;
int menu = 0;
int menu2 = 0;
void setup() {
  lcd.begin(16, 2);
  lcd.createChar(ARROW_ICON_IDX, icons[ARROW_ICON_IDX]);
  lcd.createChar(CLOCK_ICON_IDX, icons[CLOCK_ICON_IDX]);
  lcd.createChar(FLAME_ICON_IDX, icons[FLAME_ICON_IDX]);
}

void loop() {
  read_button_clicks();
  menu_fun();
  delay(50);
}

//! Return a bitmask of clicked buttons.(stolen from hunt_the_wumpus from adafruit)
/*!
  Examine the bitmask of buttons which are currently pressed and compare against
  the bitmask of which buttons were pressed last time the function was called.
  If a button transitions from pressed to released, return it in the bitmask.

  \return the bitmask of clicked buttons
*/
void read_button_clicks() {
  static uint8_t last_buttons = 0;

  uint8_t buttons = lcd.readButtons();
  clicked_buttons = (last_buttons ^ buttons) & (~buttons);
  last_buttons = buttons;
}

void menu_fun() {
  if (clicked_buttons & BUTTON_RIGHT) {
    menu++;
  }
  if (clicked_buttons & BUTTON_LEFT) {
    menu--;
  }
  if (menu > 2 && menu < 100) {
    menu = 0;
  }
  if (menu < 0) {
    menu = 2;
  }
  if (menu > 106 && menu < 200) {
    menu = 101;
  }
  if (menu < 101 && menu > 99) {
    menu = 106;
  }
  if (menu > 205 && menu < 300) {
    menu = 201;
  }
  if (menu < 201 && menu > 199) {
    menu = 205;
  }
  switch (menu) {
    case 0:
      lcd.clear();
      lcd.write(ARROW_ICON_IDX);
      lcd.print("time ");
      lcd.print("heat");
      if (clicked_buttons & BUTTON_SELECT) {
        menu = 101;
      }
      break;
    case 1:
      lcd.clear();
      lcd.print(" time");
      lcd.write(ARROW_ICON_IDX);
      lcd.print("heat");
      if (clicked_buttons & BUTTON_SELECT) {
        menu = 201;
      }
      break;
    case 101:
      lcd.clear();
      lcd.write(CLOCK_ICON_IDX);
      lcd.print("time");
      lcd.setCursor(0, 1);
      lcd.write(ARROW_ICON_IDX);
      lcd.print("Auto 90 60 45 30 15");
      if (clicked_buttons & BUTTON_UP) {
        menu = 0;
      }
      break;
    case 102:
      lcd.clear();
      lcd.write(CLOCK_ICON_IDX);
      lcd.print("time");
      lcd.setCursor(0, 1);
      lcd.print(" Auto");
      lcd.write(ARROW_ICON_IDX);
      lcd.print("90 60 45 30 15");
      break;
    case 103:
      lcd.clear();
      lcd.write(CLOCK_ICON_IDX);
      lcd.print("time");
      lcd.setCursor(0, 1);
      lcd.print(" Auto 90");
      lcd.write(ARROW_ICON_IDX);
      lcd.print("60 45 30 15");
      break;
    case 104:
      lcd.clear();
      lcd.write(CLOCK_ICON_IDX);
      lcd.print("time");
      lcd.setCursor(0, 1);
      lcd.print(" Auto 90 60");
      lcd.write(ARROW_ICON_IDX);
      lcd.print("45 30 15");
      break;
    case 105:
      lcd.clear();
      lcd.write(CLOCK_ICON_IDX);
      lcd.print("time");
      lcd.setCursor(0, 1);
      lcd.print("90 60 45");
      lcd.write(ARROW_ICON_IDX);
      lcd.print("30 15");
      break;
    case 106:
      lcd.clear();
      lcd.write(CLOCK_ICON_IDX);
      lcd.print("time");
      lcd.setCursor(0, 1);
      lcd.print("90 60 45 30");
      lcd.write(ARROW_ICON_IDX);
      lcd.print("15");
      break;
    case 201:
      lcd.clear();
      lcd.write(FLAME_ICON_IDX);
      lcd.print("heat");
      lcd.setCursor(0, 1);
      lcd.write(ARROW_ICON_IDX);
      lcd.print("Auto Hi Med Low Off");
      if (clicked_buttons & BUTTON_UP) {
        menu = 0;
      }
      break;
    case 202:
      lcd.clear();
      lcd.write(FLAME_ICON_IDX);
      lcd.print("heat");
      lcd.setCursor(0, 1);
      lcd.print(" Auto");
      lcd.write(ARROW_ICON_IDX);
      lcd.print("Hi Med Low Off");
      if (clicked_buttons & BUTTON_UP) {
        menu = 0;
      }
      break;
    case 203:
      lcd.clear();
      lcd.write(FLAME_ICON_IDX);
      lcd.print("heat");
      lcd.setCursor(0, 1);
      lcd.print(" Auto Hi");
      lcd.write(ARROW_ICON_IDX);
      lcd.print("Med Low Off");
      if (clicked_buttons & BUTTON_UP) {
        menu = 0;
      }
      break;
    case 204:
      lcd.clear();
      lcd.write(FLAME_ICON_IDX);
      lcd.print("heat");
      lcd.setCursor(0, 1);
      lcd.print("Hi Med");
      lcd.write(ARROW_ICON_IDX);
      lcd.print("Low Off");
      if (clicked_buttons & BUTTON_UP) {
        menu = 0;
      }
      break;
    case 205:
      lcd.clear();
      lcd.write(FLAME_ICON_IDX);
      lcd.print("heat");
      lcd.setCursor(0, 1);
      lcd.print("Hi Med Low");
      lcd.write(ARROW_ICON_IDX);
      lcd.print("Off");
      if (clicked_buttons & BUTTON_UP) {
        menu = 0;
      }
      break;
  }
}

lcd.clear(); Will cause flickering if it is used too often.

Also there is no need to update your screen faster than once ever 200-500mS as a LCD display lags badly.

just before switch (menu) you could compare prev_menu to menu and then if then clear_oneshot =0;

if (clear_oneshot ==0){lcd.clear(); clear_oneshot =1;}

that way it only clears the lcd when the data being displayed has changed.

end of switch set prev_menu = menu;

gpop1:
just before switch (menu) you could compare prev_menu to menu and then if then clear_oneshot =0;

if (clear_oneshot ==0){lcd.clear();
clear_oneshot =1;}

that way it only clears the lcd when the data being displayed has changed.

end of switch set prev_menu = menu;

Interesting. I may switch to that approach.
Here’s how I solved it:
I moved menu_fun() into read_button_clicks() so that the menu only updates if a button has been clicked.

#include <Wire.h>
#include <Adafruit_MCP23017.h>
#include <Adafruit_RGBLCDShield.h>

Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield();

#define ON 0x7
#define OFF 0x0
byte icons[3][7] = {{ 0x0, 0x4, 0x6, 0x1f, 0x6, 0x4, 0x0},
  {0x0, 0xe, 0x15, 0x17, 0x11, 0xe, 0x0},
  {0x4, 0xe, 0xe, 0x1e, 0x1b, 0xb, 0x6}
};

//! Index into the bitmap array for the  icons.
const int ARROW_ICON_IDX = 0;
const int CLOCK_ICON_IDX = 1;
const int FLAME_ICON_IDX = 2;

uint8_t clicked_buttons;
int menu = 0;
void setup() {
  lcd.begin(16, 2);
  lcd.createChar(ARROW_ICON_IDX, icons[ARROW_ICON_IDX]);
  lcd.createChar(CLOCK_ICON_IDX, icons[CLOCK_ICON_IDX]);
  lcd.createChar(FLAME_ICON_IDX, icons[FLAME_ICON_IDX]);
  menu_fun();
}

void loop() {
  read_button_clicks();
  //menu_fun();
  delay(50);
}

//! Return a bitmask of clicked buttons.(stolen from hunt_the_wumpus from adafruit)
/*!
  Examine the bitmask of buttons which are currently pressed and compare against
  the bitmask of which buttons were pressed last time the function was called.
  If a button transitions from pressed to released, return it in the bitmask.

  \return the bitmask of clicked buttons
*/
void read_button_clicks() {
  static uint8_t last_buttons = 0;

  uint8_t buttons = lcd.readButtons();
  clicked_buttons = (last_buttons ^ buttons) & (~buttons);
  last_buttons = buttons;
  if(clicked_buttons){menu_fun();}
}

void menu_fun() {
  if (clicked_buttons & BUTTON_RIGHT) {
    menu++;
  }
  if (clicked_buttons & BUTTON_LEFT) {
    menu--;
  }
  if (menu > 2 && menu < 100) {
    menu = 0;
  }
  if (menu < 0) {
    menu = 2;
  }
  if (menu > 106 && menu < 200) {
    menu = 101;
  }
  if (menu < 101 && menu > 99) {
    menu = 106;
  }
  if (menu > 205 && menu < 300) {
    menu = 201;
  }
  if (menu < 201 && menu > 199) {
    menu = 205;
  }
  switch (menu) {
    case 0:
      lcd.clear();
      lcd.write(ARROW_ICON_IDX);
      lcd.print("time ");
      lcd.print("heat");
      if (clicked_buttons & BUTTON_SELECT) {
        menu = 101;
      }
      break;
    case 1:
      lcd.clear();
      lcd.print(" time");
      lcd.write(ARROW_ICON_IDX);
      lcd.print("heat");
      if (clicked_buttons & BUTTON_SELECT) {
        menu = 201;
      }
      break;
    case 101:
      lcd.clear();
      lcd.write(CLOCK_ICON_IDX);
      lcd.print("time");
      lcd.setCursor(0, 1);
      lcd.write(ARROW_ICON_IDX);
      lcd.print("Auto 90 60 45 30 15");
      if (clicked_buttons & BUTTON_UP) {
        menu = 0;
      }
      break;
    case 102:
      lcd.clear();
      lcd.write(CLOCK_ICON_IDX);
      lcd.print("time");
      lcd.setCursor(0, 1);
      lcd.print(" Auto");
      lcd.write(ARROW_ICON_IDX);
      lcd.print("90 60 45 30 15");
            if (clicked_buttons & BUTTON_UP) {
        menu = 0;
      }
      break;
    case 103:
      lcd.clear();
      lcd.write(CLOCK_ICON_IDX);
      lcd.print("time");
      lcd.setCursor(0, 1);
      lcd.print(" Auto 90");
      lcd.write(ARROW_ICON_IDX);
      lcd.print("60 45 30 15");
            if (clicked_buttons & BUTTON_UP) {
        menu = 0;
      }
      break;
    case 104:
      lcd.clear();
      lcd.write(CLOCK_ICON_IDX);
      lcd.print("time");
      lcd.setCursor(0, 1);
      lcd.print(" Auto 90 60");
      lcd.write(ARROW_ICON_IDX);
      lcd.print("45 30 15");
            if (clicked_buttons & BUTTON_UP) {
        menu = 0;
      }
      break;
    case 105:
      lcd.clear();
      lcd.write(CLOCK_ICON_IDX);
      lcd.print("time");
      lcd.setCursor(0, 1);
      lcd.print("90 60 45");
      lcd.write(ARROW_ICON_IDX);
      lcd.print("30 15");
            if (clicked_buttons & BUTTON_UP) {
        menu = 0;
      }
      break;
    case 106:
      lcd.clear();
      lcd.write(CLOCK_ICON_IDX);
      lcd.print("time");
      lcd.setCursor(0, 1);
      lcd.print("90 60 45 30");
      lcd.write(ARROW_ICON_IDX);
      lcd.print("15");
            if (clicked_buttons & BUTTON_UP) {
        menu = 0;
      }
      break;
    case 201:
      lcd.clear();
      lcd.write(FLAME_ICON_IDX);
      lcd.print("heat");
      lcd.setCursor(0, 1);
      lcd.write(ARROW_ICON_IDX);
      lcd.print("Auto Hi Med Low Off");
      if (clicked_buttons & BUTTON_UP) {
        menu = 0;
      }
      break;
    case 202:
      lcd.clear();
      lcd.write(FLAME_ICON_IDX);
      lcd.print("heat");
      lcd.setCursor(0, 1);
      lcd.print(" Auto");
      lcd.write(ARROW_ICON_IDX);
      lcd.print("Hi Med Low Off");
      if (clicked_buttons & BUTTON_UP) {
        menu = 0;
      }
      break;
    case 203:
      lcd.clear();
      lcd.write(FLAME_ICON_IDX);
      lcd.print("heat");
      lcd.setCursor(0, 1);
      lcd.print(" Auto Hi");
      lcd.write(ARROW_ICON_IDX);
      lcd.print("Med Low Off");
      if (clicked_buttons & BUTTON_UP) {
        menu = 0;
      }
      break;
    case 204:
      lcd.clear();
      lcd.write(FLAME_ICON_IDX);
      lcd.print("heat");
      lcd.setCursor(0, 1);
      lcd.print("Hi Med");
      lcd.write(ARROW_ICON_IDX);
      lcd.print("Low Off");
      if (clicked_buttons & BUTTON_UP) {
        menu = 0;
      }
      break;
    case 205:
      lcd.clear();
      lcd.write(FLAME_ICON_IDX);
      lcd.print("heat");
      lcd.setCursor(0, 1);
      lcd.print("Hi Med Low");
      lcd.write(ARROW_ICON_IDX);
      lcd.print("Off");
      if (clicked_buttons & BUTTON_UP) {
        menu = 0;
      }
      break;
  }
}

I don’t know if that’s the “right” way, but it seems to work :smiley:

I’m now working on integrating this into my main program code. Wish me luck :slight_smile: