OpenMoCo AVR Libraries
Motion Control and MoCoBus Libraries for the AVR Platform
The OpenMoCo Menu Manager provides a nearly complete automation of a menuing system. This class is designed to allow for rapid development of menuing systems on Arduino devices, using five input controls (buttons), and character displays of at least two rows. More...
|OMMenuMgr (OMMenuItem *c_first, uint8_t c_type=MENU_ANALOG)|
|void||setAnalogButtonPin (uint8_t p_pin, const int p_values, int p_thresh)|
|void||setDigitalButtonPins (const int p_pins)|
|void||enable (bool p_en)|
|void||setDrawHandler (void(*p_func)(char *, int, int, int))|
|void||setRoot (OMMenuItem *p_root)|
|unsigned int||holdModifier ()|
The OpenMoCo Menu Manager provides a nearly complete automation of a menuing system. This class is designed to allow for rapid development of menuing systems on Arduino devices, using five input controls (buttons), and character displays of at least two rows.
OpenMoCo Menu Manager Class
Designed to make it easy to implement menu systems, the Menu Manager requires the developer to do little more than specify the structure of the menu, and how to draw on the display.
See http://www.dynamicperception.com for more information
(c) 2008-2012 C.A. Church / Dynamic Perception LLC
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/.
The Menu Manager displays a character-based menu when triggered by the user. Five user inputs are used (although only four are required): Enter, Up, Down, Back, and Forward. Forward is optional. These inputs may either be buttons tied to digital pins, or up to five buttons tied to a single analog pin using a series of resistors to present different analog values when a button is pressed.
The menu is triggered by hitting the Enter key by the user when the menu is enabled. When it is displayed, the user is presented with all available options at the current menu level, one per row, with a cursor indicating the currently selected item. The up and down keys scroll the list through all available items at the current menu level. If the user presses enter, or forward on the current item, the item will be presented as either a value edit, a new menu, or an action will be executed as defined by the user based on the type of the menu entry. The back button will cancel out of the current input, go back one menu level, or exit the menu entirely if the user is at the root menu level.
When a value is being edited, the user may adjust the value within the range specified by the developer non-destructively using the up and down buttons. The user may cancel the edit of the value by using the back button, or may save the value using either of enter or forward. When the user saves the value, it is written directly to the variable specified by the developer. All numeric types are supported; individual bit flags, and special select lists are supported as well. For select lists, the user is presented with strings defined by the developer for the numeric values stored in the target variable, providing the ability to give the user a more complete and obvious solution.
Menu items may also be sub-menus. You can even re-use the same sub-menus under different menus with minimal impact to flash size and SRAM usage, letting you use the same menus under different contexts.
Menu items may be actions - you may wish to draw special screens and allow for use-case driven inputs. In this manner, you can trigger callbacks when menu items are triggered and disable the menu easily while interacting with the user, and then seamlessly place the user back into the menu where they left off when they are done.
Finally, the Menu Manager can be used to easily poll the user inputs without drawing the menu as needed by the developer.
To create menu items, you'll need to specify their contents and create them using special datatypes which will keep them in program memory until needed. The basic item is defined via the MENU_ITEM data type, and specifies the label to give to the item, the type of item it is, the length (for certain types), and the target of the item. For example:
There are three cores types of items:
To create a value item, we must also create a special variables which tells the menu system how to process the item. This special variable will be constructed again from a new type, with several parameters:
Here, we see that we can specify the type of data the item points to, a maximum value, a minimum value, and the target variable (by address) to be edited. Minimum and Maximum values are limited to the range of a signed long, no matter the data type, and are not used for select lists.
The following Data Type values are allowed:
Of course, items on their own are of little use - we wouldn't typically go through all of this work to just have a single input! To create a menu, we need to create a list of items in that menu, and then create the item which represents the menu. For this, again, we have a special data type that allows us to easily create them and store in program space:
We may, of course, create multiple lists, use the same list in multiple items, and use the same items in multiple lists.
Creating a select list is a little more complex than a normal item value. Since we're displaying the user a list of strings, and mapping them back to numeric values, we need to create this mapping.
To do so, we need to create the items that will appear in lists, and the lists which use them:
Note here, for each item we specify the numeric value associated with the string, and the string to display to the user. We can use these select items in any select list, but it is important to note that you should not duplicate a numeric value in one list.
The numeric values are specified as a byte type, and are limited to 0-255.
Finally, to create the target for a menu item to use this select list, we must create a special variable that specifies how to handle the select, where to store the target value, the length of the list, and the list to use:
A bit flag input allows you to change only one bit in a target variable, for example, in the case where you want to store up to 8 on/off flags in a single byte of data to preserve RAM. The Menu Manager supports doing this by creating a special target for the MENU_VALUE you're creating:
We'll note that the new MENU_FLAG type has two parts: the bit position, and the address of the byte containing the bit. The bit position is from the right and is of the range 0-7, so this item would modify the bit marked with 'x' in the following pattern: B1111x111
In this example, when the user interacts with the "Flag Edit" menu item, the user will be displayed the OM_MENU_FLAG_ON and OM_MENU_FLAG_OFF strings based on the value of bit 3 in the 'theseFlags' variable. When saved by the user, the correct bit value will be written back to bit 3 in 'theseFlags'.
If you want to change the strings used for On and Off, see the Setting Control Parameters section below.
An action is a function that will be called when a user presses enter or forward on that item. This can be used to display special screens, or call some other behavior more complex than just setting a parameter.
All functions being called by actions must take no parameters, and return void. When an action is called, your callback handler is called. If the menu is still enabled when your callback handler exits, the menu will be re-drawn where it was when the action was initiated, otherwise the menu will not be displayed again until the menu is re-enabled and the checkInput() method is called again. (For more info on disabling the menu, and polling inputs, see the Polling the Menu section below.)
For example, we can create a special screen that is drawn when an action is triggered, which waits until the user presses a button before returning to the menu:
After we have created all of our menu items, lists, selects, and so forth, we need to initialize the OMMenuMgr object with a root item. This root item will be the starting point when the user triggers the menu, and should be an ITEM_MENU in most cases.
Additionally, we need to specify whether our input is analog (MENU_ANALOG, default), or digital (MENU_DIGITAL).
Note that we always pass the root item by address, and not by value! You can always change the root item later, using the setRoot() method.
After we have created our menu structure, we must specify how to handle user input. To do this, we must tell the Menu Manager how to read our buttons.
In both analog and digital cases, we must create a two-dimensional list of how to read buttons, and their meanings. This list will always have 5 lists of 2 elements. The format is:
The button definition is always one of the following:
BUTTON_FORWARD, BUTTON_BACK, BUTTON_INCREASE, BUTTON_DECREASE, BUTTON_SELECT
The how to read button instruction will differ based on whether you are doing analog or digital input. For digital inputs, the first element will be the Arduino digital pin # of the button, and for analog inputs, the first element will be analogRead() value associated with the button.
For digital input, we simply need to pass this array to the setDigitalButtonPins() method. Also note, that for digital inputs the input pins will be pulled HIGH internally and you are expected to swing them to LOW when pressed.
For analog input, we must also specify the pin to read the value from, and the threshold for reading the values. The threshold is very important, as analog inputs are rarely exact; this threshold is the variance that will be applied to each value during comparison. These values and the button definition array are sent to the setAnalogButtonPin() method.
The following is a complete example of initializing an analog input:
If you do not wish to use the forward button, it is not required. However, you must still specify five buttons in the array. To ignore the forward button (if you want to use only four, instead of five, buttons), simply make sure that BUTTON_FORWARD is the last element in the array, and re-use one of the earlier read values.
The menu must be polled regularly, such as during your loop() function, to check for input, display the menu, or interact with it. A single method is all that needs to be called for this activity.
This method returns the button pressed or BUTTON_NONE if none were, so you may also use it to handle user input if you need, and disable the menu if required, see:
While the Menu Manager creates the content for display, and manages what data should be displayed and when, it does not have any built-in display methodology. This allows you to use nearly any display you like, since you will be controlling how the data is sent to the display.
To achieve this, a callback model is used - you specify a function for each required display activity, and pass them as arguments to the set...Handler() methods.
Three functions are required: Draw, and Exit.
Draw handles displaying strings to the screen. It is passed four arguments: a character pointer, a row, a column, and the length of the characters stored at the address in the character pointer.
The character pointer will not always be null-terminated, and cannot be relied upon to be null-terminated. Always use the length argument, which can be relied upon.
The following shows a model handler for drawing using a standard LiquidCrystal object named 'lcd':
Note that in the above function, we handle characters outside of the normal ascii range, as the MenuMgr cannot gaurantee that every character is displayable.
Exit is called when the user exits the menu, and it is cleared. It should bring you to whatever state you need to be when the menu isn't displayed.
The following is a model example of a clear callback handler:
Of course, not every display is the same. Display parameters are set at compile time, and controlled via define's.
Unfortunately, the Arduino IDE does not allow you to specify defines to the compiler, so you cannot effectively change these from the Arduino IDE. For other IDE users, you may re-define them via your build process. For Arduino IDE users, you will need to edit the library directly.
Each menu value has a final parameter, with the EEPROM byte address to store the data. If you do not want to store the data in EEPROM, use 0 as the address, or simply not pass that parameter at all. For example:
Now, when these values are exercised by the user, foo will be stored to EEPROM, starting at byte 10, and bar will not be stored in EEPROM.
The following is a complete example of a sketch with a menu:
Constructs an OMMenuMgr instance, with a specified root item, and a specified input type.
|c_first||A pointer to an OMMenuItem representing the root of the menu|
|c_type||The input type for the menu, either MENU_ANALOG or MENU_DIGITAL|
Check for User Input and Handle
Checks to see if any button has been pressed by the user, and reports back the button pressed, or BUTTON_NONE if none are pressed.
If the menu is enabled and drawn, and any button is pressed, normal handling of the menu is executed. If the menu is enabled, but not drawn, the menu will be drawn only if BUTTON_SELECT is pressed. Otherwise, no activity will occur.
Enable or Disable Handling of Menu
You may disable handling of menu to allow the class instance to just poll and report back the currently pressed button. This is particularly useful when executing actions from menu items, where the user input is needed.
|p_en||Enable (true), or disable (false) menu display|
|unsigned int holdModifier||(||)|
Get Hold Modifier
If a button is held by the user, the hold modifier is increased every OM_MENU_DEBOUNCE period of time. In between these time periods, checkInput() returns BUTTON_NONE, to prevent duplication of input (e.g. bouncing).
By checking this value, you can determine the difference between a button being held (hold modifier > 1) and debounced, and no button being pressed. E.g.:
Setup Analog Button Input
For UI's with multiple buttons assigned to a single analog pin (e.g. using a resistor network to report different values), this method allows you to specify how this pin shall be read.
This method is NOOP if the menu type is not MENU_ANALOG
The digital pin # is specified, along with an array of values and their meanings, and a threshold for those values. The array of values and meanings should be two-dimensional with two values in the second dimension, and five rows in the first dimension. The two values shall represent the integer value of analogRead() assigned to that button, and the button type. e.g.:
|p_pin||The Arduino Digital Pin to use|
|p_values||The list of button values and function|
|p_thresh||The threshold deviation from the specified value to match the button read. You must provide some threshold, as the analogRead() values will vary. The range of value to be matched for each button is between (value - thresh) and (value + thresh). This threshold should be large enough to account for the variation in your circuit, but by no means should two button values overlap.|
|void setDigitalButtonPins||(||const int||p_values||)|
Setup Digital Button Input
For UI's with digital button input (e.g. one digital input per button), this method allows you to specify the pins for digital input.
An array is passed that defines, for each button, it's digital pin and function. There must be two dimensions, with five rows and two data elements. The first data element indicates the Arduino digital pin, and the second element indicates the function.
|p_values||The list of button values and functions|
|void setDrawHandler||(||void(*)(char *, int, int, int)||p_func||)|
|void setRoot||(||OMMenuItem *||p_root||)|