From b9ac65d567bb151323d3fc1c9b65933422d5b084 Mon Sep 17 00:00:00 2001 From: levi Balling Date: Tue, 7 Mar 2017 21:44:48 -0700 Subject: [PATCH 1/4] Added keywords.txt to support Arduino IDE highlighting. --- keywords.txt | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 keywords.txt diff --git a/keywords.txt b/keywords.txt new file mode 100644 index 0000000..7115f2d --- /dev/null +++ b/keywords.txt @@ -0,0 +1,39 @@ +MenuLCD KEYWORD1 +MenuManager KEYWORD1 +MenuEntry KEYWORD1 +MenuIntHelper KEYWORD1 +addChild KEYWORD2 +addSibling KEYWORD2 +setPrevSibling KEYWORD2 +addActionCallback KEYWORD2 +getMenuText KEYWORD2 +setParent KEYWORD2 +getNextSibling KEYWORD2 +getPrevSibling KEYWORD2 +getChild KEYWORD2 +getParent KEYWORD2 +ExecuteCallback KEYWORD2 +isBackEntry KEYWORD2 +numIncrease KEYWORD2 +numDecrease KEYWORD2 +getInt KEYWORD2 +MenuLCDSetup KEYWORD2 +PrintMenu KEYWORD2 +PrintLineRight KEYWORD2 +PrintLine KEYWORD2 +getLines KEYWORD2 +getCharacters KEYWORD2 +ClearLCD KEYWORD2 +getLCD KEYWORD2 +WipeMenu KEYWORD2 +addMenuRoot KEYWORD2 +getMenuRoot KEYWORD2 +DrawMenu KEYWORD2 +DoMenuAction KEYWORD2 +MenuUp KEYWORD2 +MenuDown KEYWORD2 +MenuSelect KEYWORD2 +SelectRoot KEYWORD2 +MenuBack KEYWORD2 +DoIntInput KEYWORD2 +DrawInputRow KEYWORD2 \ No newline at end of file From 8526b5f241cbafc0c4b67e66003ad22b52c84ade Mon Sep 17 00:00:00 2001 From: levi Balling Date: Sat, 11 Mar 2017 22:27:11 -0700 Subject: [PATCH 2/4] Added the much more in depth version of the README.md. I'm also working on adding a new example file. --- README.md | 135 ++++++++++- examples/Arduino_LCD_Menu/Example1.ino | 317 +++++++++++++++++++++++++ 2 files changed, 448 insertions(+), 4 deletions(-) create mode 100644 examples/Arduino_LCD_Menu/Example1.ino diff --git a/README.md b/README.md index d1902c2..a901d7c 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,145 @@ -# Adruino_LCD_MENU README +# Arduino_LCD_MENU + +This library is to combine the functionality of the LCD(LiquidCrystal) and the use of a menu tree. +Very common functionality that. This is combined to speed up the startup time of creating menus on LCDs. + +### Getting Started + +Include the neccessary libraries (See Installing for more details). + + #include + #include + #include + #include + +Identify which pins you will be using for your LCD screen( see https://learn.adafruit.com/character-lcds/wiring-a-character-lcd for more details) + + #define LCD_D7 = 4; + #define LCD_D6 = 6; + #define LCD_D5 = 7; + #define LCD_D4 = 8; + #define LCD_E = 9; + #define LCD_RS = 10; + +Declare your global variables MenuLCD and MenuManager + + MenuLCD(RS, E, D4, D5, D6, D7, NumOfColumns, NumOfRows) + MenuManager(MenuLCD* pMenuLCD); + +Here is an example with the values defined. The use of the '&' symbol gives the menuController access to the lcdController. +This getting started and example expects the use of a 16x2 LCD display. + + MenuLCD lcdController(LCD_RS, LCD_E, LCD_D4, LCD_D5, LCD_D6, LCD_D7, 16, 2); + MenuManager menuController( &lcdController); + +Now it is time to setup your menu. This is where it is helpful to write out your tree before you begin. +For this example we will be using the following Tree. It is a simple tree that will print "Hello" if you select the Node, and similarly for "World" + + RootNode + | + |----HelloNode (prints "Hello") + | + |----WorldNode (prints "World") + +In your 'void setup()' Code section add the following to initialize the LCD. + + lcdController.MenuLCDSetup(); + +In order for the Nodes to perform some task. It will need instructions on what to do. +We do this by setting up a callback function. It is the function that we want to call when the node is selected. + +void helloCallback( char* menuText, void *userData) +{ + lcdController.printMenu("Hello", 0);// "Hello" is the string to print, 0 is the Row +} +void worldCallback( char* menuText, void *userData) +{ + lcdController.printMenu("World", 1);// print on the bottom row +} + +Now that we have our callbacks, lets start creating the Menu options starting with the root node. Order matters with the library it uses a depth-first traversal order. + + // Define the objects + MenuEntry * rootMenuEntry = new MenuEntry("RootNode", NULL, NULL); + MenuEntry * helloMenuEntry = new MenuEntry("RootNode Hello", NULL, helloCallback); + MenuEntry * worldMenuEntry = new MenuEntry("RootNode World", NULL, worldCallback); + + //Add the root node + menuController.addMenuRoot(rootMenuEntry); + menuController.addChild(helloMenuEntry); + menuController.addChild(worldMenuEntry); + +It is important to make sure that the menu is in the root starting location when you finish setting up. + + menuController.SelectRoot(); + +To draw the Menu + + menuController.DrawMenu(); + +After the 'setup' and once you are in the 'loop' folder. You will need to manage whether to go 'UP', 'DOWN', 'SELECT', or 'BACK'. +Here is an example of all 4 options. + + menuController.DoMenuAction( MENU_ACTION_UP); + menuController.DoMenuAction( MENU_ACTION_DOWN); + menuController.DoMenuAction( MENU_ACTION_SELECT); + menuController.DoMenuAction( MENU_ACTION_BACK); + +Whether these actions are tied to a button, or serial input, or something else, It is up to you. +In order to run the 'helloCallback' run the following. + + menuController.DoMenuAction( MENU_ACTION_SELECT); + menuController.DoMenuAction( MENU_ACTION_SELECT);//prints 'Hello' + +For full implementation of Hello Menu World see Example1.ino(todo link to example). + +In order to create Mutliple root level options here is how. + +//TODO Display the Multiple Root level tree +//TODO code example of multiple Root level tree + +### Installing + +Install the library like any other arduino zip library. + + Download the arduino library as a zip file (Arduino_LCD_Menu.zip) + +In the Arduino IDE goto + + Sketch -> Include Library -> Add .ZIP Library + +Find the Arduino_LCD_Menu.zip file and upload that. + +Then add the library by selecting in the list of libraries + + Sketch -> Include Library -> Arduino_LCD_Menu + +That's it the library has be added. Goto the Getting started section for more details on how to use the library. + +### Contributing + +Feel free to post contributions to this library. + +### Authors Original library: By David Andrews - May 6, 2012 -Modified by Tweep, February 2017 +Modified by @Tweep, February 2017 +Modified by @Ashitakalax, March 2017 -License: Modified BSD (See complete license at end) +*License*: Modified BSD (See complete license at end) -Purpose: To shorten the time to get an Adriuno project running using a menuing system. +*Purpose*: To shorten the time to get an Adriuno project running using a menuing system. Changes : + - Added additional example + - Added Syntax Highlighting - new constructor added which allows to enable / disable the execution of callback functions when the root menu has been selected. + + const bool doRootAction = false; MenuLCD g_menuLCD( LCDRS, LCDE, LCDD4, LCDD5, LCDD6, LCDD7, 16, 2); MenuManager g_menuManager( &g_menuLCD, doRootAction); diff --git a/examples/Arduino_LCD_Menu/Example1.ino b/examples/Arduino_LCD_Menu/Example1.ino new file mode 100644 index 0000000..0b65731 --- /dev/null +++ b/examples/Arduino_LCD_Menu/Example1.ino @@ -0,0 +1,317 @@ +/* + Example1 +Copyright Dustin Andrews, David Andrews 2012 +Licensed under the follwing license: + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the distribution. +The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include +#include "MenuEntry.h" +#include "MenuLCD.h" +#include "MenuManager.h" + +//This example is a Stopwatch and Timer. Although it is mostly functional, it might not be the best +// user interface. The layout was created more to provide examples of a stopwatch/timer. + + +//Edit your particular harware setup here - See LiquidCrystal documentation for details +const int LCDD7 = 4; +const int LCDD6 = 6; +const int LCDD5 = 7; +const int LCDD4 = 8; +const int LCDE = 9; +const int LCDRS = 10; + +//Now create the MenuLCD and MenuManager classes. +MenuLCD g_menuLCD( LCDRS, LCDE, LCDD4, LCDD5, LCDD6, LCDD7, 16, 2); +MenuManager g_menuManager( &g_menuLCD);//pass the g_menuLCD object to the g_menuManager with the & operator. + +//Global variables used by the sample application +//when the display is showing user results (e.g. time elapsed), the next "select" should send it back into the menu. +unsigned int g_isDisplaying = false; +int g_timerTime = 23; +long g_timerRunning = false; +long g_timerTarget = 0; +long g_autoReset = false; +long g_stopMillis = 0; +long g_startMillis = 0; + +byte g_smiley[8] = { + B00000, + B10001, + B00000, + B00000, + B10001, + B01110, + B00000, +}; + +byte g_frown[8] = { + B00000, + B10001, + B00000, + B00000, + B00000, + B01110, + B10001, +}; + + +//end Global variables section + +//setupMenus +//This function is called during setup to populate the menu with the tree of nodes +//This can be a bit brain-bending to write. If you draw a tree you want for your menu first +// this code can be a little easier to write. Add the nodes in a depth-first order for +// the easiest code and least amount of temporary variables. +// http://en.wikipedia.org/wiki/Depth-first +// The MenuManager code is the same for building the menu as for selecting it via inputs. +// You create the menu entries and then move about the menu structure using Up, Down, Select as if you +// were selecting them via the inputs, and then use either AddChild or Add sibling from the selected node +// to create your menu. +// +// This sample code is a simple stopwatch. Our menu will look like this: +// Stopwatch +// |-Start +// |-Stop +// |-Reset +// Timer +// |-Set Time +// |-AutoReset +// | |-On +// | |-Off +// |-Start +// |-Stop +// Credits + + +void setupMenus() +{ + g_menuLCD.MenuLCDSetup(); + //Add nodes via a depth-first traversal order + MenuEntry * p_menuEntryRoot; + //Add root node + //MenuEntry( char * menuText, void * userData/*=0*/, MENU_ACTION_CALLBACK_FUNC func); + p_menuEntryRoot = new MenuEntry("Stopwatch", NULL, NULL); + g_menuManager.addMenuRoot( p_menuEntryRoot ); + g_menuManager.addChild( new MenuEntry("Stopwatch Start", NULL, WatchStartCallback) ); + g_menuManager.addChild( new MenuEntry("Stopwatch Stop", NULL, WatchStopCallback ) ); + g_menuManager.addChild( new MenuEntry("Reset", NULL, WatchResetCallback) ); + g_menuManager.addChild( new MenuEntry("Back", (void *) &g_menuManager, MenuEntry_BackCallbackFunc) ); + + g_menuManager.addSibling( new MenuEntry("Timer", NULL, NULL ) ); + //Now we want to select the "Timer" entry so we can add children under that node + //"Timer" is one down from "Stopwatch", so we issue the down command + g_menuManager.MenuDown(); + g_menuManager.addChild( new MenuEntry("Set Time", NULL, SetTimeCallback ) ); + //now move down to the "Time" node to add children by selecting the "Timer" node + g_menuManager.MenuSelect(); + //Add "time"'s sibling "AutoReset" and select it + g_menuManager.addSibling( new MenuEntry( "AutoReset", NULL, NULL) ); + g_menuManager.MenuDown(); + + //Add "AutoReset"'s children + //Use the built-in BOOL setting callbacks from MenuEntry.h: MenuEntry_Bool*CallbackFunc + g_menuManager.addChild( new MenuEntry( "Turn Reset On", (void *) (&g_autoReset), MenuEntry_BoolTrueCallbackFunc ) ); + g_menuManager.addChild( new MenuEntry( "Turn Reset Off", (void *) (&g_autoReset), MenuEntry_BoolFalseCallbackFunc ) ); + g_menuManager.addChild( new MenuEntry("Back", (void *) &g_menuManager, MenuEntry_BackCallbackFunc) ); + + //Add timer start and stop + g_menuManager.addSibling( new MenuEntry( "Countdown Start", NULL, TimerStartCallback) ); + g_menuManager.addSibling( new MenuEntry( "Countdown Stop", NULL, TimerStopCallback) ); + g_menuManager.addSibling( new MenuEntry("Back", (void *) &g_menuManager, MenuEntry_BackCallbackFunc) ); + + //Get the selection state back to the root for startup and to add the last entry + g_menuManager.SelectRoot(); + g_menuManager.addSibling( new MenuEntry( "Credits", NULL, CreditsCallback) ); + //Make sure the menu is drawn correctly after all changes are done + g_menuManager.DrawMenu(); + + g_menuManager.addSibling( new MenuEntry( "Draw Smiley", NULL, SmileyCallback) ); + + g_menuLCD.getLCD()->createChar( 0, g_smiley ); + g_menuLCD.getLCD()->createChar( 1, g_frown ); +} + + + +void setup() +{ + Serial.begin(115200); + Serial.print("Ready."); + setupMenus(); +} + + + +void loop() +{ + //The example shows using bytes on the serial port to move the menu. You can hook up your buttons or other controls. + char incomingByte = 0; + if (Serial.available() > 0) + { + incomingByte = Serial.read(); + } + switch( incomingByte ) + { + case 'u': + g_menuManager.DoMenuAction( MENU_ACTION_UP ); + break; + case 'd': + g_menuManager.DoMenuAction( MENU_ACTION_DOWN ); + break; + case 's': + if( g_isDisplaying ) + { + g_isDisplaying = false; + g_menuManager.DrawMenu(); + } + else + { + g_menuManager.DoMenuAction( MENU_ACTION_SELECT ); + } + break; + case 'b': + g_menuManager.DoMenuAction( MENU_ACTION_BACK ); + break; + default: + break; + } + if( g_timerRunning && g_timerTarget < millis()) + { + long time = millis(); + + Serial.print( "time = " ); + Serial.println( time ); + + Serial.println("Timer Goes Off HERE!"); + if( g_autoReset) + { + Serial.print( "timer target was "); + Serial.println( g_timerTarget ); + g_timerTarget = time + ( g_timerTime *1000 ); + Serial.print( "timer target is now "); + Serial.println( g_timerTarget ); + Serial.print( "timerTime is " ); + Serial.println( g_timerTime ); + Serial.println( "--------" ); + } + else + { + g_timerRunning = false; + } + } +} + +//This is a sample callback funtion for when a menu item with no children (aka command) is selected +void WatchStartCallback( char* pMenuText, void *pUserData ) +{ + g_startMillis = millis(); + char *pTextLines[2] = {"Clock Started", "" }; + g_menuLCD.PrintMenu( pTextLines, 2, 3 ); + g_isDisplaying = true; +} + + +//This is a sample callback funtion for when a menu item with no children (aka command) is selected +void WatchStopCallback( char* pMenuText, void *pUserData ) +{ + g_stopMillis = millis(); + + char strSeconds[50]; + dtostrf( ((float)(g_stopMillis-g_startMillis))/1000, 1, 2, strSeconds ); + char *pTextLines[2] = {"Elapsed Time", strSeconds }; + g_menuLCD.PrintMenu( pTextLines, 2, 3 ); + g_isDisplaying = true; +} + +//This is a sample callback funtion for when a menu item with no children (aka command) is selected +void WatchResetCallback( char* pMenuText, void *pUserData ) +{ + g_startMillis = 0; + g_stopMillis = 0; + char *pTextLines[2] = {"Clock reset", "" }; + g_menuLCD.PrintMenu( pTextLines, 2, 3 ); +} + +//This callback uses the built-in Int Input routine in MenuManager.h to request input of a integer number from the +//user. Control will pass to the DoIntInput function until the user finishes. the g_timerTime will be set to the +//value the user selects. +void SetTimeCallback( char* pMenuText, void *pUserData ) +{ + char *pLabel = "Timer seconds"; + int iNumLabelLines = 1; + int iMin = 1; + int iMax = 1000; + int iStart = 60; + //Each user input action (such as a turn of rotary enocoder or push of button + //will step this amount + int iStep = 5; + + g_menuManager.DoIntInput( iMin, iMax, iStart, iStep, &pLabel, iNumLabelLines, &g_timerTime ); + Serial.print("Timer time" ); + Serial.println( g_timerTime ); +} +//This is a sample callback funtion for when a menu item with no children (aka command) is selected +void TimerStartCallback( char* pMenuText, void *pUserData ) +{ + g_timerTarget = millis() + (g_timerTime * 1000);//This is buggy- doesn't handle wrap-around of the millis output. Too bad :( + Serial.print( "timer target = "); + Serial.println( g_timerTarget ); + Serial.print( "time = " ); + Serial.println( millis()); + g_timerRunning = true; + char strSeconds[50]; + itoa( g_timerTime, strSeconds, 10 ); + char *pTextLines[2] = {"Go!", strSeconds }; + g_menuLCD.PrintMenu( pTextLines, 2, 3 ); + g_isDisplaying = true; +} + + +//This is a sample callback funtion for when a menu item with no children (aka command) is selected +void TimerStopCallback( char* pMenuText, void *pUserData ) +{ + g_timerRunning = false; +} + +void CreditsCallback( char* pMenuText, void *pUserData ) +{ + char *pTextLines[2] = {"David Andrews ", "Dustin Andrews" }; + g_menuLCD.PrintMenu( pTextLines, 2, 1 ); + delay(5000); + char *pTextLines2[2] = {"http://authenti","cinvention.com"}; + g_menuLCD.PrintMenu( pTextLines2, 2, 5 ); + g_isDisplaying = true; +} + +void SmileyCallback( char* pMenuText, void *pUserData ) +{ + for( int i = 0; i < 10 ; ++i ) + { + g_menuLCD.ClearLCD(); + g_menuLCD.getLCD()->setCursor( 8,0 ); + g_menuLCD.getLCD()->print( (char)0 ); + delay(500); + g_menuLCD.ClearLCD(); + g_menuLCD.getLCD()->setCursor( 8,0 ); + g_menuLCD.getLCD()->print( (char)1 ); + delay(500); + } +} + From 3ec453dfaf2176c30cc73dac9bbfa3d1575696a0 Mon Sep 17 00:00:00 2001 From: levi Balling Date: Sat, 11 Mar 2017 22:30:22 -0700 Subject: [PATCH 3/4] fixed the callback section --- README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a901d7c..f5f0296 100644 --- a/README.md +++ b/README.md @@ -48,14 +48,15 @@ In your 'void setup()' Code section add the following to initialize the LCD. In order for the Nodes to perform some task. It will need instructions on what to do. We do this by setting up a callback function. It is the function that we want to call when the node is selected. -void helloCallback( char* menuText, void *userData) -{ - lcdController.printMenu("Hello", 0);// "Hello" is the string to print, 0 is the Row -} -void worldCallback( char* menuText, void *userData) -{ - lcdController.printMenu("World", 1);// print on the bottom row -} + void helloCallback( char* menuText, void *userData) + { + lcdController.printMenu("Hello", 0);// "Hello" is the string to print, 0 is the Row + } + + void worldCallback( char* menuText, void *userData) + { + lcdController.printMenu("World", 1);// print on the bottom row + } Now that we have our callbacks, lets start creating the Menu options starting with the root node. Order matters with the library it uses a depth-first traversal order. From 0e9ef2976ca359b28a645a432819823942cfee3a Mon Sep 17 00:00:00 2001 From: levi Balling Date: Mon, 13 Mar 2017 22:19:23 -0600 Subject: [PATCH 4/4] Added a simplified version of the implementation. I was able to test it out. The pins would have to change, but at least It is something easier for new users to figure out. --- README.md | 57 +++-- examples/Arduino_LCD_Menu/Example1.ino | 317 ------------------------- examples/Arduino_LCD_Menu/Example2.ino | 164 +++++++++++++ 3 files changed, 201 insertions(+), 337 deletions(-) delete mode 100644 examples/Arduino_LCD_Menu/Example1.ino create mode 100644 examples/Arduino_LCD_Menu/Example2.ino diff --git a/README.md b/README.md index f5f0296..f07c8ca 100644 --- a/README.md +++ b/README.md @@ -33,13 +33,15 @@ This getting started and example expects the use of a 16x2 LCD display. MenuManager menuController( &lcdController); Now it is time to setup your menu. This is where it is helpful to write out your tree before you begin. -For this example we will be using the following Tree. It is a simple tree that will print "Hello" if you select the Node, and similarly for "World" +For this example we will be using the following Tree. It is a simple tree that will print "M#-S#" if you select the Node where M# refers to the Main Node, and S# is for the subnode. - RootNode + M1Node | - |----HelloNode (prints "Hello") + |----M1-S1Node (prints "M1-S1 Callback", delay 5s) | - |----WorldNode (prints "World") + |----M1-S2Node (prints "M1-S2 Callback", delay 5s) + | + M2Node (prints "M2 Callback", delay 5s) In your 'void setup()' Code section add the following to initialize the LCD. @@ -48,27 +50,42 @@ In your 'void setup()' Code section add the following to initialize the LCD. In order for the Nodes to perform some task. It will need instructions on what to do. We do this by setting up a callback function. It is the function that we want to call when the node is selected. - void helloCallback( char* menuText, void *userData) + void M1S1Callback( char* menuText, void *userData) { - lcdController.printMenu("Hello", 0);// "Hello" is the string to print, 0 is the Row + char *menuLines[2] = {"M1-S1 Callback", "" }; + lcdController.PrintMenu(menuLines, 2, 3);// PrintMenu( char ** MenuString, int number of Lines, SelectedLine) + delay(5000); } - void worldCallback( char* menuText, void *userData) - { - lcdController.printMenu("World", 1);// print on the bottom row - } + void M1S2Callback( char* menuText, void *userData) + { + char *menuLines[2] = {"M1-S2 Callback", "" }; + lcdController.PrintMenu(menuLines, 2, 3);// "Hello" is the string to print, 0 is the Row + delay(5000); + } + + void M2Callback( char* menuText, void *userData) + { + char *menuLines[2] = {"M2 Callback", "" }; + lcdController.PrintMenu(menuLines, 2, 3); + delay(5000); + } Now that we have our callbacks, lets start creating the Menu options starting with the root node. Order matters with the library it uses a depth-first traversal order. // Define the objects - MenuEntry * rootMenuEntry = new MenuEntry("RootNode", NULL, NULL); - MenuEntry * helloMenuEntry = new MenuEntry("RootNode Hello", NULL, helloCallback); - MenuEntry * worldMenuEntry = new MenuEntry("RootNode World", NULL, worldCallback); + MenuEntry * rootMenuEntry = new MenuEntry("M1", NULL, NULL); + MenuEntry * M1S1MenuEntry = new MenuEntry("M1-S1", NULL, M1S1Callback); + MenuEntry * M1S2MenuEntry = new MenuEntry("M1-S2", NULL, M1S2Callback); + MenuEntry * M2MenuEntry = new MenuEntry("M2", NULL, M2Callback); + //Add the root node - menuController.addMenuRoot(rootMenuEntry); - menuController.addChild(helloMenuEntry); - menuController.addChild(worldMenuEntry); + menuController.addMenuRoot(rootMenuEntry); + menuController.addChild(M1S1MenuEntry); + menuController.addChild(M1S2MenuEntry); + menuController.addSibling(M2MenuEntry); + It is important to make sure that the menu is in the root starting location when you finish setting up. @@ -79,7 +96,7 @@ To draw the Menu menuController.DrawMenu(); After the 'setup' and once you are in the 'loop' folder. You will need to manage whether to go 'UP', 'DOWN', 'SELECT', or 'BACK'. -Here is an example of all 4 options. +Here is an example of all 4 options. Note: in the example2.ino the actions are triggered by button presses. menuController.DoMenuAction( MENU_ACTION_UP); menuController.DoMenuAction( MENU_ACTION_DOWN); @@ -87,12 +104,12 @@ Here is an example of all 4 options. menuController.DoMenuAction( MENU_ACTION_BACK); Whether these actions are tied to a button, or serial input, or something else, It is up to you. -In order to run the 'helloCallback' run the following. +In order to run the 'M1-S1Callback' run the following. menuController.DoMenuAction( MENU_ACTION_SELECT); - menuController.DoMenuAction( MENU_ACTION_SELECT);//prints 'Hello' + menuController.DoMenuAction( MENU_ACTION_SELECT); -For full implementation of Hello Menu World see Example1.ino(todo link to example). +For full implementation of Hello Menu World see Example2.ino(todo link to example). In order to create Mutliple root level options here is how. diff --git a/examples/Arduino_LCD_Menu/Example1.ino b/examples/Arduino_LCD_Menu/Example1.ino deleted file mode 100644 index 0b65731..0000000 --- a/examples/Arduino_LCD_Menu/Example1.ino +++ /dev/null @@ -1,317 +0,0 @@ -/* - Example1 -Copyright Dustin Andrews, David Andrews 2012 -Licensed under the follwing license: - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the distribution. -The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - - -#include -#include "MenuEntry.h" -#include "MenuLCD.h" -#include "MenuManager.h" - -//This example is a Stopwatch and Timer. Although it is mostly functional, it might not be the best -// user interface. The layout was created more to provide examples of a stopwatch/timer. - - -//Edit your particular harware setup here - See LiquidCrystal documentation for details -const int LCDD7 = 4; -const int LCDD6 = 6; -const int LCDD5 = 7; -const int LCDD4 = 8; -const int LCDE = 9; -const int LCDRS = 10; - -//Now create the MenuLCD and MenuManager classes. -MenuLCD g_menuLCD( LCDRS, LCDE, LCDD4, LCDD5, LCDD6, LCDD7, 16, 2); -MenuManager g_menuManager( &g_menuLCD);//pass the g_menuLCD object to the g_menuManager with the & operator. - -//Global variables used by the sample application -//when the display is showing user results (e.g. time elapsed), the next "select" should send it back into the menu. -unsigned int g_isDisplaying = false; -int g_timerTime = 23; -long g_timerRunning = false; -long g_timerTarget = 0; -long g_autoReset = false; -long g_stopMillis = 0; -long g_startMillis = 0; - -byte g_smiley[8] = { - B00000, - B10001, - B00000, - B00000, - B10001, - B01110, - B00000, -}; - -byte g_frown[8] = { - B00000, - B10001, - B00000, - B00000, - B00000, - B01110, - B10001, -}; - - -//end Global variables section - -//setupMenus -//This function is called during setup to populate the menu with the tree of nodes -//This can be a bit brain-bending to write. If you draw a tree you want for your menu first -// this code can be a little easier to write. Add the nodes in a depth-first order for -// the easiest code and least amount of temporary variables. -// http://en.wikipedia.org/wiki/Depth-first -// The MenuManager code is the same for building the menu as for selecting it via inputs. -// You create the menu entries and then move about the menu structure using Up, Down, Select as if you -// were selecting them via the inputs, and then use either AddChild or Add sibling from the selected node -// to create your menu. -// -// This sample code is a simple stopwatch. Our menu will look like this: -// Stopwatch -// |-Start -// |-Stop -// |-Reset -// Timer -// |-Set Time -// |-AutoReset -// | |-On -// | |-Off -// |-Start -// |-Stop -// Credits - - -void setupMenus() -{ - g_menuLCD.MenuLCDSetup(); - //Add nodes via a depth-first traversal order - MenuEntry * p_menuEntryRoot; - //Add root node - //MenuEntry( char * menuText, void * userData/*=0*/, MENU_ACTION_CALLBACK_FUNC func); - p_menuEntryRoot = new MenuEntry("Stopwatch", NULL, NULL); - g_menuManager.addMenuRoot( p_menuEntryRoot ); - g_menuManager.addChild( new MenuEntry("Stopwatch Start", NULL, WatchStartCallback) ); - g_menuManager.addChild( new MenuEntry("Stopwatch Stop", NULL, WatchStopCallback ) ); - g_menuManager.addChild( new MenuEntry("Reset", NULL, WatchResetCallback) ); - g_menuManager.addChild( new MenuEntry("Back", (void *) &g_menuManager, MenuEntry_BackCallbackFunc) ); - - g_menuManager.addSibling( new MenuEntry("Timer", NULL, NULL ) ); - //Now we want to select the "Timer" entry so we can add children under that node - //"Timer" is one down from "Stopwatch", so we issue the down command - g_menuManager.MenuDown(); - g_menuManager.addChild( new MenuEntry("Set Time", NULL, SetTimeCallback ) ); - //now move down to the "Time" node to add children by selecting the "Timer" node - g_menuManager.MenuSelect(); - //Add "time"'s sibling "AutoReset" and select it - g_menuManager.addSibling( new MenuEntry( "AutoReset", NULL, NULL) ); - g_menuManager.MenuDown(); - - //Add "AutoReset"'s children - //Use the built-in BOOL setting callbacks from MenuEntry.h: MenuEntry_Bool*CallbackFunc - g_menuManager.addChild( new MenuEntry( "Turn Reset On", (void *) (&g_autoReset), MenuEntry_BoolTrueCallbackFunc ) ); - g_menuManager.addChild( new MenuEntry( "Turn Reset Off", (void *) (&g_autoReset), MenuEntry_BoolFalseCallbackFunc ) ); - g_menuManager.addChild( new MenuEntry("Back", (void *) &g_menuManager, MenuEntry_BackCallbackFunc) ); - - //Add timer start and stop - g_menuManager.addSibling( new MenuEntry( "Countdown Start", NULL, TimerStartCallback) ); - g_menuManager.addSibling( new MenuEntry( "Countdown Stop", NULL, TimerStopCallback) ); - g_menuManager.addSibling( new MenuEntry("Back", (void *) &g_menuManager, MenuEntry_BackCallbackFunc) ); - - //Get the selection state back to the root for startup and to add the last entry - g_menuManager.SelectRoot(); - g_menuManager.addSibling( new MenuEntry( "Credits", NULL, CreditsCallback) ); - //Make sure the menu is drawn correctly after all changes are done - g_menuManager.DrawMenu(); - - g_menuManager.addSibling( new MenuEntry( "Draw Smiley", NULL, SmileyCallback) ); - - g_menuLCD.getLCD()->createChar( 0, g_smiley ); - g_menuLCD.getLCD()->createChar( 1, g_frown ); -} - - - -void setup() -{ - Serial.begin(115200); - Serial.print("Ready."); - setupMenus(); -} - - - -void loop() -{ - //The example shows using bytes on the serial port to move the menu. You can hook up your buttons or other controls. - char incomingByte = 0; - if (Serial.available() > 0) - { - incomingByte = Serial.read(); - } - switch( incomingByte ) - { - case 'u': - g_menuManager.DoMenuAction( MENU_ACTION_UP ); - break; - case 'd': - g_menuManager.DoMenuAction( MENU_ACTION_DOWN ); - break; - case 's': - if( g_isDisplaying ) - { - g_isDisplaying = false; - g_menuManager.DrawMenu(); - } - else - { - g_menuManager.DoMenuAction( MENU_ACTION_SELECT ); - } - break; - case 'b': - g_menuManager.DoMenuAction( MENU_ACTION_BACK ); - break; - default: - break; - } - if( g_timerRunning && g_timerTarget < millis()) - { - long time = millis(); - - Serial.print( "time = " ); - Serial.println( time ); - - Serial.println("Timer Goes Off HERE!"); - if( g_autoReset) - { - Serial.print( "timer target was "); - Serial.println( g_timerTarget ); - g_timerTarget = time + ( g_timerTime *1000 ); - Serial.print( "timer target is now "); - Serial.println( g_timerTarget ); - Serial.print( "timerTime is " ); - Serial.println( g_timerTime ); - Serial.println( "--------" ); - } - else - { - g_timerRunning = false; - } - } -} - -//This is a sample callback funtion for when a menu item with no children (aka command) is selected -void WatchStartCallback( char* pMenuText, void *pUserData ) -{ - g_startMillis = millis(); - char *pTextLines[2] = {"Clock Started", "" }; - g_menuLCD.PrintMenu( pTextLines, 2, 3 ); - g_isDisplaying = true; -} - - -//This is a sample callback funtion for when a menu item with no children (aka command) is selected -void WatchStopCallback( char* pMenuText, void *pUserData ) -{ - g_stopMillis = millis(); - - char strSeconds[50]; - dtostrf( ((float)(g_stopMillis-g_startMillis))/1000, 1, 2, strSeconds ); - char *pTextLines[2] = {"Elapsed Time", strSeconds }; - g_menuLCD.PrintMenu( pTextLines, 2, 3 ); - g_isDisplaying = true; -} - -//This is a sample callback funtion for when a menu item with no children (aka command) is selected -void WatchResetCallback( char* pMenuText, void *pUserData ) -{ - g_startMillis = 0; - g_stopMillis = 0; - char *pTextLines[2] = {"Clock reset", "" }; - g_menuLCD.PrintMenu( pTextLines, 2, 3 ); -} - -//This callback uses the built-in Int Input routine in MenuManager.h to request input of a integer number from the -//user. Control will pass to the DoIntInput function until the user finishes. the g_timerTime will be set to the -//value the user selects. -void SetTimeCallback( char* pMenuText, void *pUserData ) -{ - char *pLabel = "Timer seconds"; - int iNumLabelLines = 1; - int iMin = 1; - int iMax = 1000; - int iStart = 60; - //Each user input action (such as a turn of rotary enocoder or push of button - //will step this amount - int iStep = 5; - - g_menuManager.DoIntInput( iMin, iMax, iStart, iStep, &pLabel, iNumLabelLines, &g_timerTime ); - Serial.print("Timer time" ); - Serial.println( g_timerTime ); -} -//This is a sample callback funtion for when a menu item with no children (aka command) is selected -void TimerStartCallback( char* pMenuText, void *pUserData ) -{ - g_timerTarget = millis() + (g_timerTime * 1000);//This is buggy- doesn't handle wrap-around of the millis output. Too bad :( - Serial.print( "timer target = "); - Serial.println( g_timerTarget ); - Serial.print( "time = " ); - Serial.println( millis()); - g_timerRunning = true; - char strSeconds[50]; - itoa( g_timerTime, strSeconds, 10 ); - char *pTextLines[2] = {"Go!", strSeconds }; - g_menuLCD.PrintMenu( pTextLines, 2, 3 ); - g_isDisplaying = true; -} - - -//This is a sample callback funtion for when a menu item with no children (aka command) is selected -void TimerStopCallback( char* pMenuText, void *pUserData ) -{ - g_timerRunning = false; -} - -void CreditsCallback( char* pMenuText, void *pUserData ) -{ - char *pTextLines[2] = {"David Andrews ", "Dustin Andrews" }; - g_menuLCD.PrintMenu( pTextLines, 2, 1 ); - delay(5000); - char *pTextLines2[2] = {"http://authenti","cinvention.com"}; - g_menuLCD.PrintMenu( pTextLines2, 2, 5 ); - g_isDisplaying = true; -} - -void SmileyCallback( char* pMenuText, void *pUserData ) -{ - for( int i = 0; i < 10 ; ++i ) - { - g_menuLCD.ClearLCD(); - g_menuLCD.getLCD()->setCursor( 8,0 ); - g_menuLCD.getLCD()->print( (char)0 ); - delay(500); - g_menuLCD.ClearLCD(); - g_menuLCD.getLCD()->setCursor( 8,0 ); - g_menuLCD.getLCD()->print( (char)1 ); - delay(500); - } -} - diff --git a/examples/Arduino_LCD_Menu/Example2.ino b/examples/Arduino_LCD_Menu/Example2.ino new file mode 100644 index 0000000..064431f --- /dev/null +++ b/examples/Arduino_LCD_Menu/Example2.ino @@ -0,0 +1,164 @@ +/* + Arduino_LCD_Menu Example2 +Copyright Levi Balling(aka Ashitakalax) +Licensed under the follwing license: +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +following conditions are met: +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the distribution. +The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include +// Using Bounce2 library for button control +#include + +// LCD PINS and Objects +#define LC_RS_PIN 23 +#define LC_EN_PIN 21 +#define LC_D7_PIN 41 +#define LC_D6_PIN 40 +#define LC_D5_PIN 39 +#define LC_D4_PIN 38 + +MenuLCD lcdController(LC_RS_PIN, LC_EN_PIN, LC_D4_PIN, LC_D5_PIN, LC_D6_PIN, LC_D7_PIN, 16, 2); +MenuManager menuController( &lcdController); + +// Example uses 4 buttons +// User Input Switches +#define BUTTON_UP_PIN 45 +#define BUTTON_DOWN_PIN 44 +#define BUTTON_BACK_PIN 43 +#define BUTTON_SELECT_PIN 42 +Bounce debounceUp = Bounce(); +Bounce debounceDown = Bounce(); +Bounce debounceBack = Bounce(); +Bounce debounceSelect = Bounce(); + +/** + * Sets up the LCD Controller, the Menu, and calls the Setup Buttons. + */ +void setup() { + // put your setup code here, to run once: + + lcdController.MenuLCDSetup(); + + // Define the Menu objects + MenuEntry * rootMenuEntry = new MenuEntry("M1", NULL, NULL); + MenuEntry * M1S1MenuEntry = new MenuEntry("M1-S1", NULL, M1S1Callback); + MenuEntry * M1S2MenuEntry = new MenuEntry("M1-S2", NULL, M1S2Callback); + MenuEntry * M2MenuEntry = new MenuEntry("M2", NULL, M2Callback); + + //Add the root node, then it's children + menuController.addMenuRoot(rootMenuEntry); + menuController.addChild(M1S1MenuEntry); + menuController.addChild(M1S2MenuEntry); + menuController.addSibling(M2MenuEntry); + menuController.SelectRoot(); + menuController.DrawMenu(); + + //Setup buttons + setupButtons(); +} + +/** + * Sets up 4 debounced buttons to handle the Menu Options + */ +void setupButtons() +{ + // Setup the Inputs and debouncers + pinMode(BUTTON_UP_PIN, INPUT_PULLUP); + debounceUp.attach(BUTTON_UP_PIN); + debounceUp.interval(5); + + // Setup Down Button + pinMode(BUTTON_DOWN_PIN, INPUT_PULLUP); + debounceDown.attach(BUTTON_DOWN_PIN); + debounceDown.interval(5); + + // Setup Back Button + pinMode(BUTTON_BACK_PIN, INPUT_PULLUP); + debounceBack.attach(BUTTON_BACK_PIN); + debounceBack.interval(5); + + // Setup Select Button + pinMode(BUTTON_SELECT_PIN, INPUT_PULLUP); + debounceSelect.attach(BUTTON_SELECT_PIN); + debounceSelect.interval(5); +} + +/** + * Main Loop function that just checks the Buttons repeatedly + */ +void loop() +{ + updateButtons(); +} + +/** + * The Callback for the Sub Node M1-S1 + */ +void M1S1Callback( char* menuText, void *userData) +{ + char *menuLines[2] = {"M1-S1 Callback", "" }; + lcdController.PrintMenu(menuLines, 2, 3);// PrintMenu( char ** MenuString, int number of Lines, SelectedLine) + delay(5000); +} + +/** + * The Callback for the Sub Node M1-S2 + */ +void M1S2Callback( char* menuText, void *userData) +{ + char *menuLines[2] = {"M1-S2 Callback", "" }; + lcdController.PrintMenu(menuLines, 2, 3);// "Hello" is the string to print, 0 is the Row + delay(5000); +} + +/** + * The callback for the sibiling node M2 + */ +void M2Callback( char* menuText, void *userData) +{ + char *menuLines[2] = {"M2 Callback", "" }; + lcdController.PrintMenu(menuLines, 2, 3); + delay(5000); +} + +/** + * Simple function to listen to the button presses to trigger the menu actions + */ +void updateButtons() +{ + debounceUp.update(); + debounceDown.update(); + debounceBack.update(); + debounceSelect.update(); + + if(debounceUp.fell()) + { + menuController.DoMenuAction(MENU_ACTION_UP); + } + else if(debounceDown.fell()) + { + menuController.DoMenuAction(MENU_ACTION_DOWN); + } + else if(debounceBack.fell()) + { + menuController.DoMenuAction(MENU_ACTION_BACK); + } + else if(debounceSelect.fell()) + { + menuController.DoMenuAction(MENU_ACTION_SELECT); + } +}