-
Notifications
You must be signed in to change notification settings - Fork 36
How to
The following is a small reference on how to do common tasks when creating a menu:
- Create menu object
- Init and draw menu to the screen
- Enable Cyrillic
- Create menu page
- Create
byte
menu item - Create
int
menu item - Create
float
menu item - Create
double
menu item - Create
boolean
menu item - Create
char[17]
(a.k.a.char[GEM_STR_LEN]
) menu item - Create
byte
option select - Create
int
option select - Create
float
option select - Create
double
option select - Create
char[n]
option select - Add menu item callback
- Make menu item readonly
- Hide or show menu item
- Create button (basic)
- Create button (advanced, with custom context)
- Create submenu
- Detect key presses with
KeyDetector
- Detect key presses with
U8g2
- Control menu from your sketch
- Disable floating-point support
With default settings, where glcd
is an object of class GLCD
that uses SoftwareSerial to communicate with LCD equipped with SparkFun Graphic LCD Serial Backpack:
// Constants for the pins SparkFun Graphic LCD Serial Backpack is connected to and SoftwareSerial object
const byte rxPin = 8;
const byte txPin = 9;
SoftwareSerial serialLCD(rxPin, txPin);
// Instance of GLCD class
GLCD glcd(serialLCD);
// Menu object
GEM menu(glcd);
Related examples: Example 01: Basic, Example 02: Blink, Example 03: Party Hard!
First, choose proper constructor of U8g2 library, based on LCD controller being used and desired configuration (U8G2_KS0108_128X64_1
in the example). Then proceed as follows:
// Create an instance of the U8g2 library.
// Use constructor that matches your setup (see https://github.com/olikraus/u8g2/wiki/u8g2setupcpp for details).
// This instance is used to call all the subsequent U8g2 functions (internally from GEM library,
// or manually in your sketch if it is required).
// Please update the pin numbers according to your setup. Use U8X8_PIN_NONE if the reset pin is not connected
U8G2_KS0108_128X64_1 u8g2(U8G2_R0, 8, 9, 10, 11, 12, 13, 18, 19, /*enable=*/ A0, /*dc=*/ A1, /*cs0=*/ A3, /*cs1=*/ A2, /*cs2=*/ U8X8_PIN_NONE, /* reset=*/ U8X8_PIN_NONE); // Set R/W to low!
// Menu object
GEM_u8g2 menu(u8g2);`
Related examples: Example 01: Basic, Example 02: Blink, Example 03: Party Hard!
Generally in setup()
function of the sketch:
// Serial communications setup
serialLCD.begin(115200);
// LCD reset
delay(500);
glcd.reset();
delay(1000);
// Uncomment the following lines in dire situations
// (e.g. when screen becomes unresponsive after shutdown)
glcd.reset();
delay(1000);
// Menu init
menu.init();
// Menu setup: add menu items to menu pages to menu here (see below for examples)
menuPageMain.addMenuItem(menuItemByte);
menu.setMenuPageCurrent(menuPageMain);
// Menu draw
menu.drawMenu();
Related examples: Example 01: Basic, Example 02: Blink, Example 03: Party Hard!
Generally in setup()
function of the sketch:
// U8g2 library init.
u8g2.begin();
// Menu init
menu.init();
// Menu setup: add menu items to menu pages to menu here (see below for examples)
menuPageMain.addMenuItem(menuItemByte);
menu.setMenuPageCurrent(menuPageMain);
// Menu draw
menu.drawMenu();
Optionally, to utilize built-in key press event detection features of U8g2 library, supply u8g2.begin()
method with pin numbers the push-buttons are attached to (read more for details):
// U8g2 library init. Pass pin numbers the buttons are connected to.
// The push-buttons should be wired with pullup resistors (so the LOW means that the button is pressed)
u8g2.begin(/*Select/OK=*/ 7, /*Right/Next=*/ 4, /*Left/Prev=*/ 3, /*Up=*/ 5, /*Down=*/ 2, /*Home/Cancel=*/ 6);
Related examples: Example 01: Basic, Example 02: Blink, Example 03: Party Hard!
For U8g2 version of GEM only. You may use Cyrillic in menu title, menu item labels (including buttons and menu page links), and select options. Editable strings with Cyrillic characters are not supported (edit mode of such strings may lead to unpredictable results due to incompatibility with 2-byte characters). Increases required program storage space, use cautiously. By default Cyrillic typeset is off. To enable Cyrillic support, call enableCyrillic()
method before init()
:
menu.enableCyrillic();
Related examples: Example 01: Basic
Create menu page labeled "Main Menu":
GEMPage menuPageMain("Main Menu");
Add menu page to menu and set it as current (generally in setup()
function of the sketch, before menu.drawMenu()
is called):
menu.setMenuPageCurrent(menuPageMain);
Related examples: Example 01: Basic, Example 02: Blink, Example 03: Party Hard!
Define byte
variable:
byte byteNumber = 42;
Create menu item linked to byteNumber
variable and labeled "Byte":
GEMItem menuItemByteNumber("Byte:", byteNumber);
Add menu item to existing menu page (generally in setup()
function of the sketch, before menu.drawMenu()
is called):
menuPageMain.addMenuItem(menuItemByteNumber);
Define int
variable:
int intNumber = -512;
Create menu item linked to intNumber
variable and labeled "Int":
GEMItem menuItemIntNumber("Int:", intNumber);
Add menu item to existing menu page (generally in setup()
function of the sketch, before menu.drawMenu()
is called):
menuPageMain.addMenuItem(menuItemIntNumber);
Related examples: Example 01: Basic, Example 02: Blink, Example 03: Party Hard!
Define float
variable:
float floatNumber = 3.1416;
Create menu item linked to floatNumber
variable and labeled "Float":
GEMItem menuItemFloatNumber("Float:", floatNumber);
Add menu item to existing menu page (generally in setup()
function of the sketch, before menu.drawMenu()
is called):
menuPageMain.addMenuItem(menuItemFloatNumber);
By default, float
variable is displayed with 6 digits after decimal point (and rounded accordingly if necessary), i.e. 3.141600
. To change that, call GEMItem::setPrecision()
method:
menuItemFloatNumber.setPrecision(3);
That will result in 3.142
being displayed as a value of the menu item (and that will overwrite original value of 3.1416
upon saving).
See Floating-point variables section of Readme for more details on
float
anddouble
variables support.
Define double
variable:
double doubleNumber = -2.7183;
Create menu item linked to doubleNumber
variable and labeled "Double":
GEMItem menuItemDoubleNumber("Double:", doubleNumber);
Add menu item to existing menu page (generally in setup()
function of the sketch, before menu.drawMenu()
is called):
menuPageMain.addMenuItem(menuItemDoubleNumber);
By default, double
variable is displayed with 6 digits after decimal point (and rounded accordingly if necessary), i.e. -2.718300
. To change that, call GEMItem::setPrecision()
method:
menuItemDoubleNumber.setPrecision(3);
That will result in -2.718
being displayed as a value of the menu item (and that will overwrite original value of -2.718
upon saving).
See Floating-point variables section of Readme for more details on
float
anddouble
variables support.
Define boolean
variable:
boolean flag = false;
Create menu item linked to flag
variable and labeled "Bored?":
GEMItem menuItemFlag("Bored?", flag);
Add menu item to existing menu page (generally in setup()
function of the sketch, before menu.drawMenu()
is called):
menuPageMain.addMenuItem(menuItemFlag);
Related examples: Example 01: Basic, Example 03: Party Hard!
GEM_STR_LEN
(equals 17) is the only length of the editable char
array currently supported by GEM (keep in mind that the last element is reserved for terminating character, so the actual length available for user data is GEM_STR_LEN - 1
or 16). Using length different from GEM_STR_LEN
may cause overflows and unpredicted behaviour. Define char[17]
or char[GEM_STR_LEN]
variable:
char name[GEM_STR_LEN] = "Jack Daniels";
Create menu item linked to name
variable and labeled "Name":
GEMItem menuItemName("Name", name);
Add menu item to existing menu page (generally in setup()
function of the sketch, before menu.drawMenu()
is called):
menuPageMain.addMenuItem(menuItemName);
Related examples: Example 02: Blink
Define byte
variable:
byte byteNumber = 42;
Create array of options:
SelectOptionByte optionsArray[] = {{"Opt 1", 10}, {"Opt 2", 42}, {"Opt 3", 115}};
Supply array of options to GEMSelect
constructor (note that length of the array should be explicitly supplied/calculated because array is passed by reference):
GEMSelect myByteSelect(3, optionsArray);
// or
GEMSelect myByteSelect(sizeof(optionsArray)/sizeof(SelectOptionByte), optionsArray);
// or (no need to explicitly define optionsArray[] in this case)
GEMSelect myByteSelect(3, (SelectOptionByte[]){{"Opt 1", 10}, {"Opt 2", 42}, {"Opt 3", 115}});
Create menu item linked to byteNumber
variable and to myByteSelect
, and labeled "Number":
GEMItem menuItemByteNumber("Number:", byteNumber, myByteSelect);
Add menu item to existing menu page (generally in setup()
function of the sketch, before menu.drawMenu()
is called):
menuPageMain.addMenuItem(menuItemByteNumber);
Related examples: Example 03: Party Hard!
Define int
variable:
int intNumber = -512;
Create array of options:
SelectOptionInt optionsArray[] = {{"Opt 1", -512}, {"Opt 2", 0}, {"Opt 3", 512}};
Supply array of options to GEMSelect
constructor (note that length of the array should be explicitly supplied/calculated because array is passed by reference):
GEMSelect myIntSelect(3, optionsArray);
// or
GEMSelect myIntSelect(sizeof(optionsArray)/sizeof(SelectOptionInt), optionsArray);
// or (no need to explicitly define optionsArray[] in this case)
GEMSelect myIntSelect(3, (SelectOptionInt[]){{"Opt 1", -512}, {"Opt 2", 0}, {"Opt 3", 512}});
Create menu item linked to intNumber
variable and to myIntSelect
, and labeled "Number":
GEMItem menuItemIntNumber("Number:", intNumber, myIntSelect);
Add menu item to existing menu page (generally in setup()
function of the sketch, before menu.drawMenu()
is called):
menuPageMain.addMenuItem(menuItemIntNumber);
Define float
variable:
float floatNumber = -3.14;
Create array of options:
SelectOptionFloat optionsArray[] = {{"Opt 1", -3.14}, {"Opt 2", 0}, {"Opt 3", 3.14}};
Supply array of options to GEMSelect
constructor (note that length of the array should be explicitly supplied/calculated because array is passed by reference):
GEMSelect myFloatSelect(3, optionsArray);
// or
GEMSelect myFloatSelect(sizeof(optionsArray)/sizeof(SelectOptionFloat), optionsArray);
// or (no need to explicitly define optionsArray[] in this case)
GEMSelect myFloatSelect(3, (SelectOptionFloat[]){{"Opt 1", -3.14}, {"Opt 2", 0}, {"Opt 3", 3.14}});
Create menu item linked to floatNumber
variable and to myFloatSelect
, and labeled "Number":
GEMItem menuItemFloatNumber("Number:", floatNumber, myFloatSelect);
Add menu item to existing menu page (generally in setup()
function of the sketch, before menu.drawMenu()
is called):
menuPageMain.addMenuItem(menuItemFloatNumber);
Define double
variable:
double doubleNumber = -2.7183;
Create array of options:
SelectOptionDouble optionsArray[] = {{"Opt 1", -2.7183}, {"Opt 2", 0}, {"Opt 3", 2.7183}};
Supply array of options to GEMSelect
constructor (note that length of the array should be explicitly supplied/calculated because array is passed by reference):
GEMSelect myDoubleSelect(3, optionsArray);
// or
GEMSelect myDoubleSelect(sizeof(optionsArray)/sizeof(SelectOptionDouble), optionsArray);
// or (no need to explicitly define optionsArray[] in this case)
GEMSelect myDoubleSelect(3, (SelectOptionDouble[]){{"Opt 1", -2.7183}, {"Opt 2", 0}, {"Opt 3", 2.7183}});
Create menu item linked to doubleNumber
variable and to myDoubleSelect
, and labeled "Number":
GEMItem menuItemDoubleNumber("Number:", doubleNumber, myDoubleSelect);
Add menu item to existing menu page (generally in setup()
function of the sketch, before menu.drawMenu()
is called):
menuPageMain.addMenuItem(menuItemDoubleNumber);
There is no restriction on the length of character array that can be used with option select. However, make sure that character array of associated with menu item variable (of type char[n]
) is big enough to hold select option with the longest value to avoid overflows. Define char[n]
variable:
char[5] name = "Alex";
Create array of options:
SelectOptionChar optionsArray[] = {{"Alexander", "Alex"}, {"Michael", "Mike"}, {"Peter", "Pete"}};
Supply array of options to GEMSelect
constructor (note that length of the array should be explicitly supplied/calculated because array is passed by reference):
GEMSelect myNameSelect(3, optionsArray);
// or
GEMSelect myNameSelect(sizeof(optionsArray)/sizeof(SelectOptionChar), optionsArray);
// or (no need to explicitly define optionsArray[] in this case)
GEMSelect myNameSelect(3, (SelectOptionChar[]){{"Alexander", "Alex"}, {"Michael", "Mike"}, {"Peter", "Pete"}});
Create menu item linked to name
variable and to myNameSelect
, and labeled "Name":
GEMItem menuItemName("Name:", name, myNameSelect);
Add menu item to existing menu page (generally in setup()
function of the sketch, before menu.drawMenu()
is called):
menuPageMain.addMenuItem(menuItemName);
You can define function that will be called when associated with menu item variable is successfully saved. You should forward-declare it before supplying it to menu item GEMItem
constructor:
void validateInterval();
GEMItem menuItemInterval("Interval:", interval, validateInterval);
void loadPreset();
GEMItem menuItemName("Name:", name, myNameSelect, loadPreset);
Later in the sketch define this function:
void validateInterval() {
// Do something, e.g. validate value of the variable and change it accordingly:
if (interval < 0) {
interval = 0;
}
}
void loadPreset() {
// Do something, e.g. print selected value to Serial Monitor:
Serial.print("Name option set: ");
Serial.println(name);
// ...or apply predefined values to other variables
// based on the selected option, thus creating sort of preset selector
switch (name) {
case "Alex":
interval = 120;
break;
case "Mike":
interval = 500;
break;
case "Pete":
interval = 1000;
break;
}
}
Related examples: Example 02: Blink, Example 03: Party Hard!
Readonly menu items are marked with symbol ^
and won't allow user to interact with the associated variable (as well as activate readonly menu links or buttons). Of course, it still can be changed programmatically.
You can mark menu item as readonly during its creation, e.g.:
GEMItem menuItemByteNumber("Byte:", byteNumber, GEM_READONLY);
GEMItem menuItemName("Name:", name, myNameSelect, GEM_READONLY);
GEMItem menuItemButton("Print", printData, GEM_READONLY);
GEMItem menuItemSettings("Settings", menuPageSettings, GEM_READONLY);
Note that you cannot specify both readonly mode and callback (see above) in the same constructor. However, you can set readonly mode for menu item with (or without) callback explicitly later using GEMItem::setReadonly()
method:
menuItemByteNumber.setReadonly(); // Enable readonly mode
menuItemName.setReadonly(false); // Disable readonly mode
To get current state of the menu item use GEMItem.getReadonly()
method:
boolean isReadonly = menuItemByteNumber.getReadonly();
You can use it to toggle readonly mode of the menu item by supplying its negated output directly to GEMItem::setReadonly()
method:
menuItemByteNumber.setReadonly(!menuItemByteNumber.getReadonly());
Related examples: Example 03: Party Hard!
Hidden menu items won't be printed to the screen the next time menu is drawn (and user won't be able to activate them via graphical interface of the menu). To hide or show menu item use the following methods:
menuItemByteNumber.hide(); // Hide
menuItemPrintButton.hide(false); // Show
menuItemName.show(); // Show
To get current state of the menu item use GEMItem.getHidden()
method:
boolean isHidden = menuItemByteNumber.getHidden();
You can use it to toggle menu item visibility by supplying its negated output directly to GEMItem::hide()
method:
menuItemByteNumber.hide(!menuItemByteNumber.getHidden());
To create menu button that will call certain user-defined function, you should first forward-declare this function before supplying it to menu item GEMItem
constructor:
void printData();
GEMItem menuItemPrintButton("Print", printData);
Later in the sketch define this function:
void printData() {
Serial.print("Number is: ");
Serial.println(number);
}
Related examples: Example 01: Basic
To create menu button that will create new context (which can have its own enter (setup) and exit callbacks as well as loop function), you should first forward-declare initial function before supplying it to menu item GEMItem
constructor:
void buttonAction();
GEMItem menuItemButton("Blink!", buttonAction);
Later in the sketch define this function:
void buttonAction() {
// Declaration of context functions
menu.context.loop = buttonContextLoop;
menu.context.enter = buttonContextEnter; // Optional
menu.context.exit = buttonContextExit; // Optional
// Call of context functions
menu.context.enter();
//menu.context.allowExit = false; // Set to false if required to manually exit
// the context loop from within the loop itself
// (default is true)
}
Then define all context-related functions (optional enter and exit callbacks, and loop function):
void buttonContextEnter() {
// Running some user-defined routines that usually belongs to setup()
// Clear LCD screen
glcd.clearScreen();
// Initialize digital pin LED_BUILTIN as an output.
pinMode(LED_BUILTIN, OUTPUT);
}
void buttonContextLoop() {
// Do something in the loop() function you would normally do in the sketch,
// e.g. blink built in LED
digitalWrite(LED_BUILTIN, HIGH);
delay(500);
digitalWrite(LED_BUILTIN, LOW);
delay(500);
}
void buttonContextExit() {
// Set GEM specific settings to their values
menu.reInit();
// Draw menu back to screen
menu.drawMenu();
// Clear context (assigns `nullptr` values to function pointers
// of the `context` property of the `GEM` object
// and resets `allowExit` flag to its default state)
menu.clearContext();
}
To exit currently running context and return to menu, press button associated with GEM_KEY_CANCEL
key (only if context.allowExit
flag is set to its default value of true
, otherwise you should handle exit from the loop manually and call context.exit()
explicitly) - context.exit()
callback will be called.
Related examples: Example 02: Blink, Example 03: Party Hard!
Create additional menu page and menu item linked to this page:
GEMPage menuPageSettings("Settings");
GEMItem menuItemMainSettings("Settings", menuPageSettings);
Add menu item to existing menu page, where you'd like the link to the submenu to appear, and specify parent menu page, so that menu would know what page to return to when exiting submenu (generally in setup()
function of the sketch, before menu.drawMenu()
is called):
menuPageMain.addMenuItem(menuItemMainSettings);
menuPageSettings.setParentMenuPage(menuPageMain);
Related examples: Example 02: Blink
KeyDetector library provides an easy way to detect single digital push-button presses. To make use of it, first install and include it into your sketch:
#include <KeyDetector.h>
Then define pins you want to detect signal on:
const byte downPin = 2;
const byte leftPin = 3;
const byte rightPin = 4;
const byte upPin = 5;
const byte cancelPin = 6;
const byte okPin = 7;
Create array of Key
objects that will link GEM key identifiers with corresponding pin:
Key keys[] = {{GEM_KEY_UP, upPin}, {GEM_KEY_RIGHT, rightPin}, {GEM_KEY_DOWN, downPin}, {GEM_KEY_LEFT, leftPin}, {GEM_KEY_CANCEL, cancelPin}, {GEM_KEY_OK, okPin}};
Create KeyDetector
object:
KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key));
To account for switch bounce effect of the buttons (if occur) you may want to specify debounceDelay
(in ms) as the second argument to KeyDetector
constructor:
KeyDetector myKeyDetector(keys, sizeof(keys)/sizeof(Key), 10);
Specify push-buttons pin modes in setup()
routine:
pinMode(downPin, INPUT);
pinMode(leftPin, INPUT);
pinMode(rightPin, INPUT);
pinMode(upPin, INPUT);
pinMode(cancelPin, INPUT);
pinMode(okPin, INPUT);
Modify the loop()
function to include key detection routines as follows:
void loop() {
// If menu is ready to accept button press...
if (menu.readyForKey()) {
// ...detect key press using KeyDetector library
myKeyDetector.detect();
// Pass pressed button to menu
// (pressed button ID is stored in trigger property of KeyDetector object)
menu.registerKeyPress(myKeyDetector.trigger);
}
}
Related examples: Example 01: Basic, Example 02: Blink, Example 03: Party Hard!
If you are using U8g2 version of GEM, you can utilize U8g2 library built-in key press event detection (although it is still possible to use other key detection methods as well). To use it, first supply u8g2.begin()
method with pin numbers the push-buttons are attached to:
u8g2.begin(/*Select/OK=*/ 7, /*Right/Next=*/ 4, /*Left/Prev=*/ 3, /*Up=*/ 5, /*Down=*/ 2, /*Home/Cancel=*/ 6);
Modify the loop()
function to include press event detection through u8g2.getMenuEvent()
method as follows:
void loop() {
// If menu is ready to accept button press...
if (menu.readyForKey()) {
// ...detect key press using U8g2 library
// and pass pressed button to menu
menu.registerKeyPress(u8g2.getMenuEvent());
}
}
To make menu perform requested operation (navigate, enter or exit edit mode, save variable, etc.) call menu.registerKeyPress()
method with one of the GEM key identifiers as an argument:
menu.registerKeyPress(GEM_KEY_NONE); // Do nothing
menu.registerKeyPress(GEM_KEY_UP); // Navigate up through the menu items list,
// select next value of the digit/char
// of editable variable, or previous option in select
menu.registerKeyPress(GEM_KEY_RIGHT); // Navigate through the link to another (child) menu page,
// select next digit/char of editable variable,
// execute code associated with button
menu.registerKeyPress(GEM_KEY_DOWN); // Navigate down through the menu items list,
// select previous value of the digit/char
// of editable variable, or next option in select
menu.registerKeyPress(GEM_KEY_LEFT); // Navigate through the Back button to the previous menu page,
// select previous digit/char of editable variable
menu.registerKeyPress(GEM_KEY_CANCEL); // Navigate to the previous (parent) menu page,
// exit edit mode without saving the variable,
// exit context loop if allowed within context's settings
menu.registerKeyPress(GEM_KEY_OK); // Toggle boolean menu item, enter edit mode
// of the associated non-boolean variable,
// exit edit mode with saving the variable,
// execute code associated with button
It is possible to exclude support for editable float
and double
variables to save some space on your chip (up to 10% of program storage space on UNO). For that, locate file config.h that comes with the library, open it and comment out corresponding inclusion, i.e. change this line:
#include "config/support-float-edit.h"
to
// #include "config/support-float-edit.h"
Note that option selects support float
and double
variables regardless of this setting.
- Test bench (push-buttons):
- Example 01: Basic
- Example 02: Blink
- Example 03: Party Hard!
- Test bench (rotary encoder):
- Example 05: Encoder
- Example 06: Todo List