A relatively simple implementation of a one button menu on an Arduino board using readily available libraries.

One button menu for Arduino

One button menu for Arduino

 

There are many methods and code examples available to implement a menu on an Arduino board. For my current project I chose to use a one button menu to reduce the footprint of this functionality. I don’t need a better ergonomy since the number of menu entries is low and this is something used only to define parameters once a while. I also wanted to implement a reliable code. That is why I chose to use available libraries that have been tested and used by many.

In the example below we will be running a simple menu to choose two parameters for bliking LEDs:

  • Frequency : 1, 2, 10 (blinking / second)
  • Intensity : low, middle, high

The possible button events will be:

  • Long click / press : enters or exits the menu.
  • Single click : moves to the next menu or sub-menu entry.
  • Double click : moves from the menu level to the sub-menu level or selects the current sub-menu value.

The objective is to show the menu functionality. We will not implement the variable intensity blinking of LEDS but simply show the parameters on a LCD screen.

There are 3 categories of functions to implement

  • Triggering menu actions. For this I found a clean library Arduino OneButton Library by Matthias Hertel based on the concept of the finite state machine. Basically a function “button.tick()” is invoked regularly in the loop to check if a button is pressed or not. Tests after tests, if the button is pressed once, twice or pressed long enough, the state of the machine is modified.
  • Managing menu entries and selecting values. The library Menu Backend by Orange-Cat provides a relatively simple way to define a set of menus. These are objects and they are related in an arborescence to each other by moves (to the left, right etc).
  • Displaying selected menu entries, simply by using a LCD screen and the standard Liquid Crystal library.

The complete sketch example can be downloaded from here.

Physical implementation

The physical implementation is straightforward. Here is the virtual TinkerCAD implementation using a UNO board:

Circuit design of the "One button menu Arduino circuit"

Circuit design of the “One button menu Arduino circuit”

The LCD screen is connected as :

  • Vss connected to ground
  • Vdd connected to 5 volts + a bypass 10 uF capacitor (optional)
  • Vo connected to ground through a 510 Ohm resistor
  • Anode to 5 Volts through a 100 Ohm resistor (optional, depends on you LCD model)
  • Cathode to ground
  • 4 datas lines D4 – PD4(4) / D5 – PD5(5) / D6 – PD6(6) / D7 – PD7(7)
  • RS to PD3(3)
  • RW to ground
  • EN to PB0(8)

Of course data lines and RS, RW, EN can be connected to other pins depending on your available pins.

The pushbutton is connected to the ground on one side and to INT0 pin on the other side.

Software

Installing the libraries
If you need to know how to install a library, see https://www.arduino.cc/en/Guide/Libraries
These libraries must be installed:

And linked in the header of the program

#include <OneButton.h>
#include <MenuBackend.h>

 

Defining the menu entries and the menu hierarchy

First we have to define the menu entries. The structure of the menu will be

Frequency
1
2
10

Intensity
Low
Middle
High

The menu and menu entries are objects defined in the MenuBackend library. They must be instantiated first:

// Menu items instantiation
MenuBackend menu = MenuBackend(menuUseEvent,menuChangeEvent);

//beneath is list of menu items needed to build the menu
MenuItem MFrequency = MenuItem(“Frequency >     “);
MenuItem MFreq1 = MenuItem(“1               “);
MenuItem MFreq2 = MenuItem(“2               “);
MenuItem MFreq10 = MenuItem(“10              “);
MenuItem MIntensity = MenuItem(“Intensity >     “);
MenuItem MIntensityLow = MenuItem(“Low              “);
MenuItem MIntensityMiddle = MenuItem(“Middle          “);
MenuItem MIntensityHigh = MenuItem(“High            “);

 

A menu object is instantiated: MenuBackend Menu = MenuBackend(menuUseEvent,menuChangeEvent);

The instance ID will be menu. The constructor has 2 parameters that are functions used to handle events raised by the menu: menuUseEvent and menuChangeEvent.

MenuItems are then instantiated. Their constructor has one name parameter that we will use to display the current menu on the LCD screen.

We complete each name with blanks to fill the 16 characters of the LCD screen.

In our implementation events will be raised by our finite state machine. Hence we will define blank menu handlers.

void menuUseEvent(MenuUseEvent used)
{
// nothing here, events will be handled by oneButton event handlers
}

void menuChangeEvent(MenuChangeEvent changed)
{
// nothing here, events will be handled by oneButton event handlers
}

 

Then we have to define the relations between menu items. This will allow to move from one item to the other.

void menuSetup()
{
menu.getRoot().add(MFrequency);

MFrequency.addRight(MFreq1);

MFreq1.addAfter(MFreq2);
MFreq1.addLeft(MFrequency);
MFreq2.addAfter(MFreq10);
MFreq2.addLeft(MFrequency);
MFreq10.addAfter(MFreq1);
MFreq10.addLeft(MFrequency);

MFrequency.addAfter(MIntensity);

MIntensity.addRight(MIntensityLow);

MIntensityLow.addAfter(MIntensityMiddle);
MIntensityLow.addLeft(MIntensity);
MIntensityMiddle.addAfter(MIntensityHigh);
MIntensityMiddle.addLeft(MIntensity);
MIntensityHigh.addAfter(MIntensityLow);
MIntensityHigh.addLeft(MIntensity);

MIntensity.addAfter(MFrequency);
}

 

menuSetup() function adds all the relations between menu and sub-menu entries. It will be invoked in the setup() of the program.

menu.getRoot().add(MFrequency); adds to the menu root the first item : “MFrequency”. This makes the first menu entry.

Then we add the Frequency sub-menu entries moves

  • We add the first sub-menu to the right of “MFrequency” with the command MFrequency.addRight(MFreq1);
  • Each sub menu allows to move to the other sub-menu entry because we define their position after each other with commands like MFreq1.addAfter(MFreq2); This is cyclical since the last entry allows to move to the first entry: MFreq10.addAfter(MFreq1);
  • We also allow to move back to the menu level for each sub-menu entry using commands like MFreq1.addLeft(MFrequency);

We then add the other menu entry MFrequency.addAfter(MIntensity); and use the same kind of commands to allow moves from sub-menu entries to sub-menu entries.

Then we allow to move from the last menu entry to the top menu entry using the command MIntensity.addAfter(MFrequency);

That is it! We have finished the structure of the menu.Happy face

Finite state machine

First we will instantiate a button object in the declarative part of the program:

const char PUSHBUTTON = 2; // PD2/INT0 menu pushbutton pin
OneButton button(PUSHBUTTON, true); // instantiation of a OneButton object identified as “button”

 

Then we have to manage the 3 possible events (click, double click, long click) raised by the button. We have to attach the 3 event handlers in the setup section of the program. It is also possible to set some parameters to fine tune the behavior of the button. setClickTicks sets the minimum delay to count a click and setPressTicks sets the minimum duration of a press. We will use a function for this:

void buttonSetup() {
button.attachClick(buttonClick);
button.attachDoubleClick(buttonDoubleclick);
button.attachLongPressStart(buttonPress);

// these are optional parameters to fine-tune the behavior. Values are in ms. Given values here are default values.
button.setClickTicks(600);
button.setPressTicks(1000);
}

 

We now have to code the 3 handlers : buttonClick, buttonDoubleclick and buttonPress.

We will define a Boolean variable that is set when the user is using the menu and unset otherwise.

bool inMenu = false;

buttonClick()

We defined that a single click moves from one menu entry to the other:

void buttonClick() {
if (inMenu) {
menu.moveDown();
printToLCD(menu.getCurrent().getName(),0);
}
}

 

When a buttonClick event is raised by the object button, if we are currently using the menu (inMenu = True) we use the moveDown() functions provided by the menu library to move to the menu or sub menu entry located under the current one.

Then we read the name of the menu entry (for example “Intensity >”), and use a function to print to LCD screen (explained after on).

buttonDoubleclick()

In the declarative part we have to declare two variables for frequency and intensity of LEDs:

int Frequency = 1;
String Intensity = “Low”;

 

We defined that a double click sets the current menu value.

void buttonDoubleclick() {

if (inMenu) {
MenuItem currentMenu = menu.getCurrent();

if (currentMenu == MFrequency) {
menu.moveRight();
}

if (currentMenu == MFreq1) {
Frequency = 1;
menu.moveLeft();
}

if (currentMenu == MFreq2) {
Frequency = 2;
menu.moveLeft();
}

if (currentMenu == MFreq10) {
Frequency = 10;
menu.moveLeft();
}

if (currentMenu == MIntensity) {
menu.moveRight();
}

if (currentMenu == MIntensityLow) {
Intensity = “Low”;
menu.moveLeft();
}

if (currentMenu == MIntensityMiddle) {
Intensity = “Mid”;
menu.moveLeft();
}

if (currentMenu == MIntensityHigh) {
Intensity = “High”;
menu.moveLeft();
}

printToLCD(menu.getCurrent().getName(),0);
}
}

 

Like the single click, the double click is taken into consideration only if we are currently using the menu ( if (inMenu)). Otherwise a accidental double click will not change any variable.

We compare the currentMenu Item with all the sub menu items that we have defined and add the relevant code.

For example if the currentMenu is Freq1, we set the Frequency variable to 1, then we move to the menu entry level and we display the “Frequency >” menu.

ButtonPress()

We defined that a buttonPress enters the menu or exits the menu (after setting the current sub-menu value)

void buttonPress() {

// buttonPress enters or exists the menu, hence the boolean inMenu value changes
inMenu = !inMenu;

if (inMenu) {
lcd.clear();

// if we just entered the menu, we have to move to the first menu entry
if (menu.getCurrent().getName() == “MenuRoot”) {
menu.moveDown();
}

printToLCD(menu.getCurrent().getName(),0);

} else { // exiting the menu

MenuItem currentMenu = menu.getCurrent();

if (currentMenu == MFreq1) {
Frequency = 1;
}

if (currentMenu == MFreq2) {
Frequency = 2;
}

if (currentMenu == MFreq10) {
Frequency = 10;
}

if (currentMenu == MIntensityLow) {
Intensity = “Low”;
}

if (currentMenu == MIntensityMiddle) {
Intensity = “Mid”;
}

if (currentMenu == MIntensityHigh) {
Intensity = “High”;
}

menu.toRoot();
lcd.clear();
}
}

 

First, buttonPress will toggle between inMenu / !inMenu, then if we are at the root of the menu we have to move to the first menu entry. If we are leaving the menu, we set the current sub menu value like we did in buttonDoubleclick. We move to the menu root and clear the LCD.

Display

We have to initialise the LCD in the declarative section:

const int LCDRS = 3; //PD3
const int LCDD4 = 4; //PD4
const int LCDD5 = 5; //PD5
const int LCDD6 = 6; //PD6
const int LCDD7 = 7; //PD7
const int LCDENABLE = 8; //PB0
LiquidCrystal lcd(LCDRS, LCDENABLE, LCDD4, LCDD5, LCDD6, LCDD7);

 

And we need a little function to display messages:

void printToLCD(String message, byte row) {
// the code here may depend on the LCD model
// here for a 16*2 LCD that I have
lcd.setCursor(0,row);
lcd.print(message);
}

 

Setup

void setup() {
lcd.begin(16, 2);
menuSetup();
buttonSetup();
}

 

Loop

void loop() {

button.tick(); // checks regularly if the button is pressed or not and raises events according to the finite state

if (!inMenu) {

// under normal operation put your code using Frequency and Intensity parameters here.
// in this example we simply display them on the LCD screen

msg = “Frequency: ” + String(Frequency);
printToLCD(msg, 0);
msg = “Intensity: ” + Intensity;
printToLCD(msg, 1);
}

}

 

In the loop we invoke button.tick(); so the finite state machine evaluates if we are in one of the possible states : no click, singleClick, doubleClick, Press and it raises events as needed.

If we are not currently using the menu we simply display selected values of frequency and intensity on the LCD.

We then add a little delay so the click and double click duration are functioning well. May need a little adjustment for your software.

We are done!

We are done!

 

Some pictures

After a long press on the button the menu displays the first menu entry “Frequency  >”

Displaying Frequency menu

Displaying Frequency menu

After a single click the menu displays the second menu entry “Intensity>”

Displaying Intensity menu

Displaying Intensity menu

After a double click the sub-menu value “Low” is displayed. If we single click we will see the other values “Middle” and the “High” displayed.

Low sub menu value

Low sub menu value

After a long click again we come back to normal operations (here we chose Frequency “1”, Intensity “Low”)

One button menu for Arduino

One button menu for Arduino

 


Download the complete sketch

Download

Download

.

Leave a Reply

Your email address will not be published. Required fields are marked *