Tutorial: manage menu and LCD display with Arduino
Working on my MIDI ribbon controller I needed to implement a menu to easily configure several parameters, but I found information on how to handle a menu, how to manage an LCD display, but very poor info on how to combine the two things. However, thanks to Alexander Brevig Menubackend library I managed to create the menu I wanted.I downloaded the Alexander Brevig Library version 1.4 and I slightly modified it adding, at line 195 of the MenuBackend.h file , immediately before the line "private:", this method:
void toRoot() {
setCurrent( &getRoot() );
}
I needed this method to be able to return easily to the root of the menu. In my sketch I then imported and the Menubackend library and the LiquidCrystal library which is included in the Arduino IDE. The MenuBackend library allows to manage a very flexible menu. You can create the menu structure using such methods as addRight, addLeft, Add and AddBefore to add the entries to the right, left, below or above other menu items. To browse an item other intuitive methods are used such as moveLeft, MoveRight, moveUp and moveDown. In my example I created a structure like this.
Main
|
Item1-------------------------------Item2----------------------------------------------------Item3
| |
Item1SubItem1-----Item1SubItem2 Item2SubItem1------Item2SubItem2------Item2SubItem3
MenuBackend triggers an event whenever the current menu item changes and so I used to "print"on the LCD display the current menu item. In addition another event is triggered when an item is selected by the method "use". This could be used to change the status of your application, but in this case I simply print to LCD the phrase "You used" followed by the name of the used menu item.
To navigate the menus I used 4 buttons (Left, Right, Esc and Enter).
For this project you need the following components:
- Arduino compatible (should well be fine the old Arduino Diecimila)
- an Hitachi HD44780 compatible LCD display
- 4 buttons
- 4 resistors 10k
Optionally, to facilitate connections you can use a card for prototypes (protoshield). I have used one from Nuelectronics.
The schematic is very simple. There are 12 of the 16-pin LCD display connected to the Arduino board: 6 are input pins, 2 power pins, 2are used to power the interior light ,1 to enable writing ( it is connected to ground )and 1 to adjust the contrast ( even is connected to the this ground to have always the maximum contrast). The 4 buttons are connected to four input pins of the Arduino, which also are connected to the ground through 10K resistors. The other end of the buttons is connected to the Arduino 5V.
images developed using Fritzing
To connect the LCD display to the Arduino I first soldered two 6-pin strips male on the proto pcb and then, thanks to the an idea given to me by Emanuel in the comments of another post, I used an old parallel cable for IDE hard disks.
Update: As noted by Liudr for safety reasons is better to use a very old 40 wires calble instead of the 80 wires cable used by me in these pictures.
I think this type of structure although very simple may be useful in many situations to manage menu and LCD display with Arduino, and ican be used as a basis for other modifications.
You can download the source code here.
/*
Copyright Giuseppe Di Cillo (www.coagula.org)
Contact: dicillo@coagula.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
IMPORTANT: to use the menubackend library by Alexander Brevig download it at http://www.arduino.cc/playground/uploads/Profiles/MenuBackend_1-4.zip and add the next code at line 195
void toRoot() {
setCurrent( &getRoot() );
}
*/
#include <MenuBackend.h> //MenuBackend library - copyright by Alexander Brevig
#include <LiquidCrystal.h> //this library is included in the Arduino IDE
const int buttonPinLeft = 8; // pin for the Up button
const int buttonPinRight = 9; // pin for the Down button
const int buttonPinEsc = 10; // pin for the Esc button
const int buttonPinEnter = 11; // pin for the Enter button
int lastButtonPushed = 0;
int lastButtonEnterState = LOW; // the previous reading from the Enter input pin
int lastButtonEscState = LOW; // the previous reading from the Esc input pin
int lastButtonLeftState = LOW; // the previous reading from the Left input pin
int lastButtonRightState = LOW; // the previous reading from the Right input pin
long lastEnterDebounceTime = 0; // the last time the output pin was toggled
long lastEscDebounceTime = 0; // the last time the output pin was toggled
long lastLeftDebounceTime = 0; // the last time the output pin was toggled
long lastRightDebounceTime = 0; // the last time the output pin was toggled
long debounceDelay = 500; // the debounce time
// LiquidCrystal display with:
// rs on pin 2
// rw on 3
// enable on pin 6
// d4, d5, d6, d7 on pins 4, 5, 6, 7
LiquidCrystal lcd(2, 3, 4, 5, 6, 7);
//Menu variables
MenuBackend menu = MenuBackend(menuUsed,menuChanged);
//initialize menuitems
MenuItem menu1Item1 = MenuItem("Item1");
MenuItem menuItem1SubItem1 = MenuItem("Item1SubItem1");
MenuItem menuItem1SubItem2 = MenuItem("Item1SubItem2");
MenuItem menu1Item2 = MenuItem("Item2");
MenuItem menuItem2SubItem1 = MenuItem("Item2SubItem1");
MenuItem menuItem2SubItem2 = MenuItem("Item2SubItem2");
MenuItem menuItem3SubItem3 = MenuItem("Item2SubItem3");
MenuItem menu1Item3 = MenuItem("Item3");
void setup()
{
pinMode(buttonPinLeft, INPUT);
pinMode(buttonPinRight, INPUT);
pinMode(buttonPinEnter, INPUT);
pinMode(buttonPinEsc, INPUT);
lcd.begin(16, 2);
//configure menu
menu.getRoot().add(menu1Item1);
menu1Item1.addRight(menu1Item2).addRight(menu1Item3);
menu1Item1.add(menuItem1SubItem1).addRight(menuItem1SubItem2);
menu1Item2.add(menuItem2SubItem1).addRight(menuItem2SubItem2).addRight(menuItem3SubItem3);
menu.toRoot();
lcd.setCursor(0,0);
lcd.print("www.coagula.org");
} // setup()...
void loop()
{
readButtons(); //I splitted button reading and navigation in two procedures because
navigateMenus(); //in some situations I want to use the button for other purpose (eg. to change some settings)
} //loop()...
void menuChanged(MenuChangeEvent changed){
MenuItem newMenuItem=changed.to; //get the destination menu
lcd.setCursor(0,1); //set the start position for lcd printing to the second row
if(newMenuItem.getName()==menu.getRoot()){
lcd.print("Main Menu ");
}else if(newMenuItem.getName()=="Item1"){
lcd.print("Item1 ");
}else if(newMenuItem.getName()=="Item1SubItem1"){
lcd.print("Item1SubItem1");
}else if(newMenuItem.getName()=="Item1SubItem2"){
lcd.print("Item1SubItem2 ");
}else if(newMenuItem.getName()=="Item2"){
lcd.print("Item2 ");
}else if(newMenuItem.getName()=="Item2SubItem1"){
lcd.print("Item2SubItem1 ");
}else if(newMenuItem.getName()=="Item2SubItem2"){
lcd.print("Item2SubItem2 ");
}else if(newMenuItem.getName()=="Item2SubItem3"){
lcd.print("Item2SubItem3 ");
}else if(newMenuItem.getName()=="Item3"){
lcd.print("Item3 ");
}
}
void menuUsed(MenuUseEvent used){
lcd.setCursor(0,0);
lcd.print("You used ");
lcd.setCursor(0,1);
lcd.print(used.item.getName());
delay(3000); //delay to allow message reading
lcd.setCursor(0,0);
lcd.print("www.coagula.org");
menu.toRoot(); //back to Main
}
void readButtons(){ //read buttons status
int reading;
int buttonEnterState=LOW; // the current reading from the Enter input pin
int buttonEscState=LOW; // the current reading from the input pin
int buttonLeftState=LOW; // the current reading from the input pin
int buttonRightState=LOW; // the current reading from the input pin
//Enter button
// read the state of the switch into a local variable:
reading = digitalRead(buttonPinEnter);
// check to see if you just pressed the enter button
// (i.e. the input went from LOW to HIGH), and you've waited
// long enough since the last press to ignore any noise:
// If the switch changed, due to noise or pressing:
if (reading != lastButtonEnterState) {
// reset the debouncing timer
lastEnterDebounceTime = millis();
}
if ((millis() - lastEnterDebounceTime) > debounceDelay) {
// whatever the reading is at, it's been there for longer
// than the debounce delay, so take it as the actual current state:
buttonEnterState=reading;
lastEnterDebounceTime=millis();
}
// save the reading. Next time through the loop,
// it'll be the lastButtonState:
lastButtonEnterState = reading;
//Esc button
// read the state of the switch into a local variable:
reading = digitalRead(buttonPinEsc);
// check to see if you just pressed the Down button
// (i.e. the input went from LOW to HIGH), and you've waited
// long enough since the last press to ignore any noise:
// If the switch changed, due to noise or pressing:
if (reading != lastButtonEscState) {
// reset the debouncing timer
lastEscDebounceTime = millis();
}
if ((millis() - lastEscDebounceTime) > debounceDelay) {
// whatever the reading is at, it's been there for longer
// than the debounce delay, so take it as the actual current state:
buttonEscState = reading;
lastEscDebounceTime=millis();
}
// save the reading. Next time through the loop,
// it'll be the lastButtonState:
lastButtonEscState = reading;
//Down button
// read the state of the switch into a local variable:
reading = digitalRead(buttonPinRight);
// check to see if you just pressed the Down button
// (i.e. the input went from LOW to HIGH), and you've waited
// long enough since the last press to ignore any noise:
// If the switch changed, due to noise or pressing:
if (reading != lastButtonRightState) {
// reset the debouncing timer
lastRightDebounceTime = millis();
}
if ((millis() - lastRightDebounceTime) > debounceDelay) {
// whatever the reading is at, it's been there for longer
// than the debounce delay, so take it as the actual current state:
buttonRightState = reading;
lastRightDebounceTime =millis();
}
// save the reading. Next time through the loop,
// it'll be the lastButtonState:
lastButtonRightState = reading;
//Up button
// read the state of the switch into a local variable:
reading = digitalRead(buttonPinLeft);
// check to see if you just pressed the Down button
// (i.e. the input went from LOW to HIGH), and you've waited
// long enough since the last press to ignore any noise:
// If the switch changed, due to noise or pressing:
if (reading != lastButtonLeftState) {
// reset the debouncing timer
lastLeftDebounceTime = millis();
}
if ((millis() - lastLeftDebounceTime) > debounceDelay) {
// whatever the reading is at, it's been there for longer
// than the debounce delay, so take it as the actual current state:
buttonLeftState = reading;
lastLeftDebounceTime=millis();;
}
// save the reading. Next time through the loop,
// it'll be the lastButtonState:
lastButtonLeftState = reading;
//records which button has been pressed
if (buttonEnterState==HIGH){
lastButtonPushed=buttonPinEnter;
}else if(buttonEscState==HIGH){
lastButtonPushed=buttonPinEsc;
}else if(buttonRightState==HIGH){
lastButtonPushed=buttonPinRight;
}else if(buttonLeftState==HIGH){
lastButtonPushed=buttonPinLeft;
}else{
lastButtonPushed=0;
}
}
void navigateMenus() {
MenuItem currentMenu=menu.getCurrent();
switch (lastButtonPushed){
case buttonPinEnter:
if(!(currentMenu.moveDown())){ //if the current menu has a child and has been pressed enter then menu navigate to item below
menu.use();
}else{ //otherwise, if menu has no child and has been pressed enter the current menu is used
menu.moveDown();
}
break;
case buttonPinEsc:
menu.toRoot(); //back to main
break;
case buttonPinRight:
menu.moveRight();
break;
case buttonPinLeft:
menu.moveLeft();
break;
}
lastButtonPushed=0; //reset the lastButtonPushed variable
}

Tutorial: come gestire menu e display LCD con Arduino by Giuseppe Di Cillo - Coagula.org is licensed under a Creative Commons Attribuzione - Non commerciale - Condividi allo stesso modo 3.0 Unported License.
Comments(40)
Very good tutorial!! although , I don´t know where to input the routines selected. IE: menuitem3 selected, turns pin 10 high for 3 seconds. Very nice tut. Kudos for you.
Thanks!
You need to modify the code in the menuUsed routine. I.E:
void menuUsed(MenuUseEvent used){
if( used.item == menuItem3 ){
digitalWrite(10, HIGH);
delay(3000);
digitalWrite(10, LOW);
}
if( used.item == menuItem4 ){
//your code here
}
//etc.
}
Thank you so much for this tutorial!
I want to change the structure so that after I select an subitem of Item1, it automatically puts me into Item2, to select a subitem of Item2. Any suggestions on how to accomplish this?
Unfortunately there is no direct command to accomplish this, but probably if you put the code below in every subitem1 routine, it should work:
menu.toRoot();
menu.use();
menu.moveRight();
I seem to be having difficulties adding more menu items. I cant seem to get more than 3 menuitems. I have no difficulties adding more subitems however. Is this something that needs to be done in the library? I want to have 5 menuitems (each with their subitems) all up.
Any assistance would be greatly appreciated!
I don't think there is a limit to the menuitems, but i need to check it out.
How much big is the code? If you run out of memory you could have strange code beahviour.
The code I have is only 9174 bytes out of 30720. It allows me to compile the code, but wont when I run it, the new menu options I have added don't appear. If you could check out if there is a limit, I would be very appreciative!
Thank you
I think there is small missprint at line 63:
MenuItem menuItem3SubItem3 = MenuItem("Item2SubItem3"); should be
MenuItem menuItem2SubItem3 = MenuItem("Item2SubItem3");
and
menu1Item2.add(menuItem2SubItem1).addRight(menuItem2SubItem2).addRight(menuItem3SubItem3); should be
menu1Item2.add(menuItem2SubItem1).addRight(menuItem2SubItem2).addRight(menuItem2SubItem3);
Thank you Giuseppe for this example!!!
Thank you very much! I'll fix it soon. Fortunately, the code works even with those errors but is less readable.
Thank you for such nice work.
Can somebody help me. I already made my menu using this article. But i need to incorporate my system with stepper motor. in my LCD menu i have an instruction that if i press right button the number will increment or using left button vice versa. But after i put the instruction "Stepper myStepper(200, 10,11,12,13)" The increment function did not work properly. Before i declare my stepper. The value will increase from 1 to 2 to 3 and so on. But after i declare my stepper the value increase from 1 to 3 to 5 to 7 and so on. Can you guys help me. :(
my code is in this forum
http://www.electronicslab.ph/forum/index.php?topic=29033.0
Hi
I used your code and it is very good. I can add more subitems ans subitems in the subitems! :D
My problem is when i push the esc button it goes to the main root. I want to rewrite this part to put just to the previous menu item.
An other issue:
I want to control PWM output with this menu, If i make a Menuitem like PWM and a Subitem like PWM value, and If i add 256 PWM subitem into the PWM value menu to control every single state of the output and make 256 " if " statement to write out all the subitems, do you think is would be work?? Or can you give me some other solution? Because it is a lot of code.
Thanks :D
All the Best
You can try the menu.moveUp statement to go to the previous menu item. I suggest for the "PWM problem" to use the PWM item to enter in a state where the Up and Down buttons works to change a variable value. I used this type of code in my "MIDI Ribbon Controller" to change some settings via LCD menu.
i was just wondering if it is possible to get rid of the first menu so it is more like this.
Item1------------------------------------------Item2----------------------------------------------------Item3
| |
Item1SubItem1-----Item1SubItem2 Item2SubItem1------Item2SubItem2------Item2SubItem3
or add more to the first menu so it is like this
menu---------------------------------------------------------------------menu1
| |
Item1------------------------------------------Item2 Item1------------------------------------------Item2
| | |
Item1SubItem1-----Item1SubItem2 Item2SubItem1 Item1SubItem1-----Item1SubItem2
I think that is poossible modifying the code.
Hi, I am setting this up again (as my last hard drive died)
I have added the library, and made the change at line 195.
When I try to compile the program, I get the error : expected unqualified-id before string constant
and it highlights the /* right before the "IMPORTANT: to use the menubackend library by Alexander Brevig download it at..."
Any ideas what I am doing wrong?
Maybe something has gone wrong with the modification of the code.
I think the MenuBackend.h from the link is changed. After line 195 is private: not protected:
You can post your lib?
You're right: it's "private:" instead of "protected:". Fixed. Thanks.
Ok. Is necessary to restart arduino after save modification in MenuBackend.h
Hi!
Is it possible to show many menu items in 20x4 lcd at once? Something like:
------------------------------------
| > ITEM 1 |
| ITEM 2 |
| ITEM 3 |
| ITEM 4 |
------------------------------------
After selecting one MainItem one or all subitems will show etc.
Sure but you need to modify the code.
¡Great example!
Someone knows how to attach the same submenu to multiples menus. The thing is that I have to add this to more than 64 options...
Here the code we change to use multiples menus at the same time (without repeating each)
MenuItem *moveDown() {
if (after) { after->back = this; }
if (after->remember_parent) {
after->setBefore(this);
MenuItem *node = after->getRight();
while(node && (node != after)){
node->setBefore(this);
node = node->getRight();
}
}
return after;
}
more info on
https://github.com/CrearAyT/Guitarra-Vas-a-Llorar/tree/experimental/Menu...
https://github.com/CrearAyT/Guitarra-Vas-a-Llorar/blob/experimental/guit...
Great work! Thank you very much for sharing.
Hello, I have a problem with your example, I copied and pasted into arduino program, I modified the file with notepad (open unicode file) I then saved the data compiled and I get this error:
C: \ Users \ Jose Ramon \ Desktop \ arduino-1.0 \ libraries \ MenuBackend / MenuBackend.h: In function 'void setup ()':
C: \ Users \ Jose Ramon \ Desktop \ arduino-1.0 \ libraries \ MenuBackend / MenuBackend.h: 197: error: 'void MenuBackend :: toRoot ()' is private
sketch_mar19a: 80: error: within this context
C: \ Users \ Jose Ramon \ Desktop \ arduino-1.0 \ libraries \ MenuBackend / MenuBackend.h: In function 'void menuUsed (MenuUseEvent)':
C: \ Users \ Jose Ramon \ Desktop \ arduino-1.0 \ libraries \ MenuBackend / MenuBackend.h: 197: error: 'void MenuBackend :: toRoot ()' is private
sketch_mar19a: 131: error: within this context
C: \ Users \ Jose Ramon \ Desktop \ arduino-1.0 \ libraries \ MenuBackend / MenuBackend.h: In function 'void navigateMenus ()':
C: \ Users \ Jose Ramon \ Desktop \ arduino-1.0 \ libraries \ MenuBackend / MenuBackend.h: 197: error: 'void MenuBackend :: toRoot ()' is private
sketch_mar19a: 275: error: within this context
The modification is as follows:
...........................
MenuUseEvent furniture * current = {};
cb_menuUse (furniture);
}
}
toRoot void () {
setCurrent (& getRoot ());
}
private:
void setCurrent (MenuItem * next) {
if (next) {..........................
Problem is the modification of the *. H?
And of course thank you very much for the code sample.
I suppose the error is "toRoot void ()" instead of "void toRoot()".
Hello!
You said, in a comment in your sketch, this: "I splitted button reading and navigation in two procedures because
in some situations I want to use the button for other purpose (eg. to change some settings)"
How can I use the same buttons to change (define) a parameter that is situated somewhere in the menu?
Thank you!
You can see the code for my Midi Ribbon Controller.
Regarding the ribbon cable, I have a hunch your ribbon cable was not old enough. It looks like a 80-wire 40-connection IDE cable. Lots of the connections are internally connected to ground. It's dangerous to use such a cable for your purpose. Best is to find real old cables, 40-wire 40-connections. I have a blog post about these cables. Hope it helps:
http://liudr.wordpress.com/2011/06/25/ide-harddrive-cables/
This is very interesting. Thanks. I'll fix it in the post.
hello...i have problem when i compile this code...i copy and paste that code into arduino..after compile that code, i got the error...error is about the menu.toRoot();...can anyone help me??
Have you modified the library as noted in the post?
First and formost, Thank You!!! your menu structure is fantastic!!!
I have it working great with my Oxygen Analyzer project!!!! But I do hove one question I was hoping you could help me with.
I have 3 analog sensors that I have inserted into your menu structure under item3 and it works great the only problem is these 3 sensors only update when I refresh menu3, I assume this is because they are not part of the Loop. I have tried inserting them under the loop function but they remain on the screen no matter what menu I've selected. Is there a way or place I could insert my sensor code to allow them to loop only when menu3 is selected and clear when any other menu is selected?
Again great job!!!!!!
I thank you in advance for any help you can provide.
Dave
You're welcome :-)
You can try to put the sensor reading code in the main loop inside an if that verify using the menu.getCurrent() method that the current item is the item3.
Thank you for this tutorial Giuseppe.
I was having the same problem with an analog sensor but this corrected the issue.
Now i'm trying to figure out how to replace menu.getCurrent() in the 'if' with something like used.item.getName()
so the sensor output will only show if the menu item representing it is selected.
Any ideas?
MenuItem currentMenu=menu.getCurrent();
if(currentMenu.getName() == 'xxxx');
should do the trick!
I got it to work the way I want by tethering the 'if' statment you suggested to a dummy menu item below the selection
menu item. So as your scrolling left to right you see the options but you only see the specific data when you select an option by pressing enter.
Thanks again!
Dear Giuseppe Di Cillo,
How can I change the MenuBackend.h file ?
Must I do this in the arduino software?
I have try it on the microsoft visual express but it is notworking.
You say that you must change this on row 195.
This is the first time that I must change something in to the .h file.
You can use a simple text editor such as notepad for windows or TextEdit for Mac.
Post new comment