Menu with LCD, Rotary Encoder, and Buttons

So I started to write code for a menu system and found myself using almost 200 switch cases. I was wondering if someone had a better way or if I was way off on my method.

The way it works is the encoder keeps track of turns, in a certain menu, keeping track of the location in the menu. For example, the main menu has 4 items, main menu being location zero. The 4 items would be given numbers 1-4. When you select one of those menu items the menu location becomes that items number. In that menu there are three items(0-2) when item 0 is selected your menu location becomes 5. This logic is applied to any given location in the menu. I end up with 192 different menu locations. I use the menu location number as the case variable too keep track of where I am in the menu, as well as do the correct task when the final menu item is selected.

I am not sure the 328 can handle this many cases. Most cases will be used to keep track of the menu location. What is the best way to handle this?

Flow chart

Prototype

After I found out how many menu locations I had I decided to hold off on coding and see if there was a better way other than switch. So, this code is not complete by any means, but it should show what I am trying to accomplish. Here is what I have so far.

------------CODE-----------------------------------------------------------------------------------------------

#include <LiquidCrystal.h>

LiquidCrystal lcd(8, 7, 4, 11, 12, 13); //LCD pins set

const int encoder_A = 2; //encoder pin A set to digital pin two as interrupt
const int encoder_B = 3; //encoder pin B set to digital pin three
const int menu = 14; //using A0 as a digital pin for a menu button
const int blue_only = 15; //using A1 as a digital pin for fluorescence viewing button
const int select = 10; //select button(encoder click) set to digital pin ten

const int menu_max[6] = {3, 2, 2, 5, 5, 1};
const int turn [4] = {0, 0, -1, 1};

byte encoder_state = 0; //tells us witch way we are going 2(10) for backwards or 3(11) for forwards
byte menu_location = 0; //tells us where we are in the menu

int encoder_position = 0; //the line of the menu that is to be selected

//menu strings
const char* main_menu = {“Select Schedule”, “Set Schedules”, “Set Time&Date”, “Set Fluorescence”};
const char* schedules_m = {“Optimal”, “Breeding”, “Custom”};
const char* seasons_m = {“With Seasons”, “Without Seasons”};
//First item in menus for printing when selected
const char* main_menu_select = {“Optimal”,“Optimal”};

void setup()
{
//Setting user input pins accordingly
pinMode(encoder_A,INPUT);
pinMode(encoder_B,INPUT);
pinMode(menu,INPUT);
pinMode(blue_only,INPUT);
pinMode(select,INPUT);

//Enabling internal pull up resistors
digitalWrite(encoder_A,HIGH);
digitalWrite(encoder_B,HIGH);
digitalWrite(menu,HIGH);
digitalWrite(blue_only,HIGH);
digitalWrite(select,HIGH);

//lcd is 16X2
lcd.begin(16, 2);
lcd.clear();
lcd.print(“Let the Testing Commence”);

//Setting interrupt 0(aka ecoder_A) to look for low to high and calls the encoding function
attachInterrupt(digitalPinToInterrupt(encoder_A),encoding,RISING);
}

//What happens when we use the encoder
void encoding()
{

encoder_state =0; // reset the state
encoder_state = encoder_state + digitalRead(encoder_A); // add the state of encoder_A
encoder_state <<= 1; // makes room for encoder_B
encoder_state = encoder_state + digitalRead(encoder_B); // add the state of encoder_B

encoder_position = encoder_position + turn[encoder_state];
if(encoder_position < 0)
{
encoder_position = menu_max[menu_location];
}
if(encoder_position > menu_max[menu_location])
{
encoder_position = 0;
}

switch(menu_location)
{
case 0:
lcd.clear();
lcd.print(“Main Menu -4-”);
lcd.setCursor(0,2);
lcd.print(main_menu[encoder_position]);
lcd.print(encoder_position);
break;

case 1:
case 2:
lcd.clear();
lcd.print(main_menu[menu_location - 1]);
lcd.setCursor(0,2);
lcd.print(schedules_m[encoder_position]);
lcd.print(encoder_position);
break;

case 3:

case 4:

case 5:
lcd.clear();
lcd.print(schedules_m[0]);
lcd.setCursor(0,2);
lcd.print(seasons_m[encoder_position]);
break;
}

return;
}

void loop()
{
if((digitalRead(select)) == LOW)
{
delay(200);

//We use a switch to advance to the correct menu depending on our current menu location
switch(menu_location)
{
case 0:
menu_location = encoder_position + 1;
lcd.clear();
lcd.print(main_menu[encoder_position]);
lcd.setCursor(0,2);
lcd.print(main_menu_select[encoder_position]);
encoder_position = 0;
break;
case 1:
break;
case 2:
menu_location += encoder_position + 3;
lcd.clear();
lcd.print(“Optimal -2-”);
lcd.print(menu_location);
lcd.setCursor(0,2);
lcd.print(“With Seasons”);
encoder_position = 0;
break;
}
}
if(((digitalRead(menu)) == LOW)&&(menu_location != 0))
{

delay(200);

//double click menu quickly or hold down to go back to main menu
for(int i = 0; i < 200; i++)
{
if((digitalRead(menu)) == LOW)
{
menu_location = 0;
}
}

//like with select a switch is used to go to the correct previous menu
switch(menu_location)
{
case 0:
case 1:
case 2:
case 3:
case 4:
menu_location = 0;
lcd.clear();
lcd.print(“Main Menu -4-”);
lcd.setCursor(0,2);
lcd.print(main_menu[encoder_position]);
break;

}
}

}

The best way to do this is with Object Oriented programming (C++). Create Menu classes and transition to a different menu object.

There are existing libraries that do some of this for you.

For example:

Thanks for the quick reply. All I know about programming is from c so I'm going to need to watch some tuts on objects and classes. Hopefully I can still use most of my code.

iesusko:
Thanks for the quick reply. All I know about programming is from c so I'm going to need to watch some tuts on objects and classes. Hopefully I can still use most of my code.

You may not want to re-use your code once you do this. But the upside is that your code will be MUCH more readable.

Thread here

User Interface for Rotary Encoder, Button, and LCD.