menu systems for use with one button

I've been dabbling with frankensteins creation for a while now and Im about to get rid of the bodged menu it has and move to something a bit more extensive. There seem to be a bewildering array of option for menu libraries out there, but i wonder which is generally seen as the current best option, in as much as its easy to use, doesnt chew up resources (memory) and is adaptable.

Currently I navigate with a single button tied to three events (click, double click and long click) for navigate, select and confirm. I activate the menu system by watching button activity, outside of those times the system is either active or asleep.

Over to you guys! :slight_smile:

This is perfectly suited for concurrent state machines. These can easily be implemented using my State machine library. One machine checks the button clicks and the other handles the menu.

#include <SM.h>
const byte btn = 4;
const int clickMin = 50;
const int clickMax = 350;

enum Click {noClick, sClick, dClick, lClick};
Click cmd;

SM clickCmd(cIdle);//click detection state machine
SM menu(m0H, m0B);//menu state machine

void setup(){
    Serial.begin(115200);
    pinMode(btn, INPUT);
}//setup()

void loop(){
  EXEC(clickCmd);
  EXEC(menu);
}//loop()

State cIdle(){
  if(digitalRead(btn)) clickCmd.Set(cPress);
}//cIdle()

State cPress(){
  if(clickCmd.Timeout(clickMin)) clickCmd.Set(cPclk);
}//cPress()

State cLong(){
  if(!digitalRead(btn)) clickCmd.Set(cIdle);
}//cLong()

State cRelease(){
  if(clickCmd.Timeout(clickMin)) clickCmd.Set(cPdbl);
}//cRelease()

State cDbl(){
  if(!digitalRead(btn)) clickCmd.Set(cIdle);
}//cDbl()

State cPclk(){
  if(!digitalRead(btn)) clickCmd.Set(cRelease);
  if(clickCmd.Timeout(clickMax)){
    clickCmd.Set(cLong);
    cmd = lClick;
  }//if(Timeout)
}//cPclk()

State cPdbl(){
  if(digitalRead(btn)){
    clickCmd.Set(cDbl);
    cmd = dClick;
  }//if(press)
  if(clickCmd.Timeout(clickMax)){
    clickCmd.Set(cIdle);
    cmd = sClick;
  }//if(Timeout)
}//cPdbl()

void instruct(char* c, char* s, char* d, char* l){
  Serial.println(c);//menu heading
  if(*s){//check item for single click
    Serial.print("single click = ");
    Serial.println(s);//menu item for click
  }//if(s)
  if(*d){//check item for double click
    Serial.print("double click = ");
    Serial.println(d);//menu item for double click
  }//if(d)
  if(*l){//check item for long click
    Serial.print("long click = ");
    Serial.println(l);
  }//if(l)
  Serial.println();
}//instruct()

State m0H(){
  cmd = noClick;//reset command
  instruct("m0", "m1", "m2", "");
}//m0H()

State m0B(){
  switch(cmd){
    case sClick: menu.Set(m1H, m1B); break;
    case dClick: menu.Set(m2H, m2B); break;
  }//switch(cmd)
}//m0B()

State m1H(){
  cmd = noClick;//reset command
  instruct("m1", "m11", "m12", "m0");
}//m1H()

State m1B(){
  switch(cmd){
    case sClick: menu.Set(m11H, m11B); break;
    case dClick: menu.Set(m12H, m12B); break;
    case lClick: menu.Set(m0H, m0B); break;
  }//switch(cmd)
}//m1B()

State m2H(){
  cmd = noClick;//reset command
  instruct("m2", "", "", "m0");
}//m2H()

State m2B(){
  switch(cmd){
    case lClick: menu.Set(m0H, m0B); break;
  }//switch(cmd)
}//m2B()

State m11H(){
  cmd = noClick;//reset command
  instruct("m11", "", "", "m0");
}//m11H()

State m11B(){
  switch(cmd){
    case lClick: menu.Set(m0H, m0B); break;
  }//switch(cmd)
}//m11B()

State m12H(){
  cmd = noClick;//reset command
  instruct("m12", "", "", "m0");
}//m12H()

State m12B(){
  switch(cmd){
    case lClick: menu.Set(m0H, m0B); break;
  }//switch(cmd)
}//m12B()

State diagrams are attached

click detection state diagram.png

menu state diagram.png

Thanks, I'll take a look.

scrumfled:
I've been dabbling with frankensteins creation for a while now and Im about to get rid of the bodged menu it has and move to something a bit more extensive. There seem to be a bewildering array of option for menu libraries out there, but i wonder which is generally seen as the current best option, in as much as its easy to use, doesnt chew up resources (memory) and is adaptable.

Currently I navigate with a single button tied to three events (click, double click and long click) for navigate, select and confirm. I activate the menu system by watching button activity, outside of those times the system is either active or asleep.

Over to you guys! :slight_smile:

To use a single button, you need to detect something. The only things you can detect are:

  • Is the button pressed?
  • Was the button pressed before?
  • How long is the button pressed?

Depending on the PREVIOUS button press, the next one does something else.
The simplest example is a toggle. If "something" is ON, the next press turns it OFF. If it was OFF, the next press turns it ON.

You can also use time. For example, short button presses can mean "select next" while a long press means "enter or accept".

Double clicks are tricky for the user to get right. As you know, the double click timeout for a PC is user settable because different people "double click" at different speeds. Stay away from double click detection if you can.

You could have a hypothetical menu that monitors a motor. You want to select any ONE of several readings:

  • RPM
  • VOLTS
  • AMPS
  • TEMPERATURE
  • CANCEL

So, a LONG button press would mean "Bring up the menu"

Once the menu is up (showing the 4 options), a short press cycles through them 1..2..3..4,5...1 (rolls around from the end back to the beginning).

Let's say you wanted to monitor "Amps". You would press and hold the button until the menu popped up, At this point, "RPM" would be highlighted (bold font, reverse colors, depending on your display).

Then, one short press selects "Volts". Next press selects "Amps". This is the one you want, so now you press AND HOLD the button. After the "long" time passes, the display reverts to "run mode" and displays AMPS.

Of course, you always need a "Cancel" option in the menu in case you got into it by mistake and don't want to change anything.

The idea is that you detect a button press and grab the current millis() value, then wait until the button is released.

The elapsed time is "millis() - start". If the elapsed time is, say, less than 1000 (1 second), it's considered a "short" press". If it's longer than 1000, then it's a "long" press and your code acts accordingly.

Note that you also need to debounce the button. A mechanical button, when pressed, "bounces" and goes open-closed-open-closed several tens of times in the course of a millisecond or so (and the microcontroller can detect each one!)

Therefore your code has to work like this:

uint8_t check_button (uint8_t port)
{
    pinMode (port, INPUT_PULLUP);
    if (digitalRead (port) == HIGH) {
        return 0;
    }
    uint16_t timeout = 1000; // change this if necessary
    while (timeout--) {
        if (digitalRead (port) == HIGH) {
            timeout = 1000; // button bounced, reset countdown
        }
    }
    return 1; // we got 1000 successive contacts, therefore button is no longer bouncing
}

The above code simply gets you a clean button detection. Then you have to time how long it remained pressed.

You put in your pin number connected to the button, then read a "0" if it's not pressed or a "1" if it is.

Hopefully this is enough to get you started. Good luck!

Are buttons expensive? I see a lot of devices where they're obviously making millions of them and a few microcents spent on a button will add up. For a homemade Arduino device, unless you don't have space to put in buttons, I would always try to put in a full set of up/down/back/enter.

Guys,

I already have working code, so i know about button debouncing etc and a rudimentary menu system. What i was asking is from all the available (more capable/flexible) menu libraries, which are recommended or currently well thought of.

MorganS:
Are buttons expensive? I see a lot of devices where they're obviously making millions of them and a few microcents spent on a button will add up. For a homemade Arduino device, unless you don't have space to put in buttons, I would always try to put in a full set of up/down/back/enter.

Depends on what you mean by "expensive".

Those lousy little SMT pushbuttons (that only work for 100 presses or so) probably cost less than 1 cent in industrial bulk quantities.

Now, a GOOD button such as a Cherry or Microswitch with snap-action contacts probably costs over 1 or 2 dollars in bulk quantity.

If you are building your own one-off project, why not buy good buttons? Buy high quality ones that have a nice tactile feel, maybe an LED internal light if you like that kind of thing and any other features you may want.

Note that ANY type of button will need debouncing (except maybe those magnetic hall effect switches or optical switches).

For user menu controls, I usually use 4 buttons. One is "Enter" or "Accept", the other is "Back" or "Cancel", the last two are "Up" and "Down" (or "Next" and "Previous").

scrumfled:
Guys,

I already have working code, so i know about button debouncing etc and a rudimentary menu system. What i was asking is from all the available (more capable/flexible) menu libraries, which are recommended or currently well thought of.

My favorite menu program is the one I write for myself. Then:

  • I understand it
  • I learn something
  • It works exactly like I want
  • It doesn't bloat with features I don't want

Write your own. You'll be glad you did.

The thing is, state machines solve most (i would guess 90% or so) arduino programs. Including:

  • Menu systems, like this
  • Input acceptors. like this. I have even written morse translators and calculators
  • Timing
  • Concurrency
  • Debouncing
  • and so on...

In addition to that state diagram are very powerful planning and abstraction tools. And state machines are stable and easy to debug (which is hardly ever needed)