Serial Monitor Menu...debouncing for lack of a better word

Hi All,

Before everyone says it I know you can't debounce serial monitor input in the traditional sense but I am unsure of how else to describe this.

The Challenge:

I am using an Arduino Uno. I am trying to write a serial monitor menu to set some settings for a dc motor I am controlling. Everything actually functions ok with one exception. After you type in your selection and press the enter key the program directs you to the right submenu or submenu option but it also shows the "else" condition of "Choose Options 1 through 5" if you are in the main menu or "Choose Options 1 through 3" if you are in the sub menu.

I think it is happening because the Arduino is fast and Serial is slow so by the time the new menu pops up before I have even taken my finger off the enter button it is checking for input and thinks I have pressed enter without an entry so it displays the "else" condition.

I am not sure how to account for this. When using a button I would simply debounce the button...not sure how to do something similar here.

Any thoughts would be greatly appreciated!

The Code:

char menu;
char subMenu;
bool runOnce = true;
bool menuState = true;
int subMenuCount = 0;

void setup() {
  Serial.begin(115200);  //Initialize the serial connection
}

void loop() {
  serialMenuMain();
}


void topMenu() {

  if (runOnce == true) {
    Serial.println (F("                     Main Menu                          "));
    Serial.println (F(" 1) Main Menu Option 1"));
    Serial.println (F(" 2) Main Menu Option 2"));
    Serial.println (F(" 3) Main Menu Option 3"));
    Serial.println (F(" 4) Main Menu Option 4"));
    Serial.println (F(" 5) Main Menu Option 5"));
    runOnce = false;
    menuState = true;
  }
}

void serialMenuMain() {
  topMenu();
  while (Serial.available() == 0) {}
  if (menuState == true) {
    char menu = Serial.read();
    if (menu == '1') {
      subMenuCount = 1;
      subMenu1();
    }
    else if (menu == '2') {
      subMenuCount = 2;
      // TBD: subMenu2();
    }
    else if (menu == '3') {
      subMenuCount = 3;
      // TBD: subMenu3();
    }
    else if (menu == '4') {
      subMenuCount = 4;
      // TBD: subMenu4();
    }
    else if (menu == '5') {
      subMenuCount = 5;
      // TBD: subMenu5();
    }
    else {
      Serial.println ("Choose Options 1 through 5");
    }
  }

  if (menuState == false && subMenuCount == 1) {
    subMenu = Serial.read();
    if (subMenu == '1') {
      //set a variable
      Serial.println("SubMenu Option 1");
    }
    else if (subMenu == '2') {
      //set a variable
      Serial.println("SubMenu Option 2");
    }
    else if (subMenu == '3') {
      Serial.println("Returning to Main Menu");
      menu = 'q';
      runOnce = true;
      topMenu();
    }
    else {
      Serial.println ("Choose Options 1 through 3");
    }
  }
}

void subMenu1() {
  menuState = false;
  Serial.println (F("                      SubMenu 1       "));
  Serial.println (F(" "));
  Serial.println (F(" 1) SubMenu Option 1"));
  Serial.println (F(" 2) SubMenu Option 2"));
  Serial.println (F(" 3) SubMenu Option 3"));
}

Nope. The serial monitor is sending something like "1\n" and your program reads the 1 and reacts. Then is reads the newline and reacts.

Read Robin2's excellent tutorial Serial Basics Input to see how to do it properly.

What is the line-ending in the serial monitor set to? Right bottom in serial monitor. Changing the setting to 'none' might solve your problem.

but it also shows the "else" condition of "Choose Options 1 through 5"

i don't see that

                    Main Menu                          
 1) Main Menu Option 1
 2) Main Menu Option 2
 3) Main Menu Option 3
 4) Main Menu Option 4
 5) Main Menu Option 5
                      SubMenu 1       
 
 1) SubMenu Option 1
 2) SubMenu Option 2
 3) SubMenu Option 3
Choose Options 1 through 3
SubMenu Option 1
SubMenu Option 2
Returning to Main Menu
                     Main Menu                          
 1) Main Menu Option 1
 2) Main Menu Option 2
 3) Main Menu Option 3
 4) Main Menu Option 4
 5) Main Menu Option 5
                      SubMenu 1       
 
 1) SubMenu Option 1
 2) SubMenu Option 2
 3) SubMenu Option 3
Choose Options 1 through 3
Returning to Main Menu
                     Main Menu                          
 1) Main Menu Option 1
 2) Main Menu Option 2
 3) Main Menu Option 3
 4) Main Menu Option 4
 5) Main Menu Option 5

@gcjr & @sterretje

It depends how you have your Serial Monitor setup. If i have it setup as "Newline" or "Carriage Return" I see the behaviour described:

                    Main Menu                          
 1) Main Menu Option 1
 2) Main Menu Option 2
 3) Main Menu Option 3
 4) Main Menu Option 4
 5) Main Menu Option 5
                      SubMenu 1       
 
 1) SubMenu Option 1
 2) SubMenu Option 2
 3) SubMenu Option 3
Choose Options 1 through 3
Returning to Main Menu
                     Main Menu                          
 1) Main Menu Option 1
 2) Main Menu Option 2
 3) Main Menu Option 3
 4) Main Menu Option 4
 5) Main Menu Option 5
Choose Options 1 through 5
                      SubMenu 1       
 
 1) SubMenu Option 1
 2) SubMenu Option 2
 3) SubMenu Option 3
Choose Options 1 through 3

If I set it as "No line Ending" I see the what you are seeing:

                     Main Menu                          
 1) Main Menu Option 1
 2) Main Menu Option 2
 3) Main Menu Option 3
 4) Main Menu Option 4
 5) Main Menu Option 5
                      SubMenu 1       
 
 1) SubMenu Option 1
 2) SubMenu Option 2
 3) SubMenu Option 3
Choose Options 1 through 3

If I set it as "Both NL & CR" I see the "else" condition twice:

                   Main Menu                          
 1) Main Menu Option 1
 2) Main Menu Option 2
 3) Main Menu Option 3
 4) Main Menu Option 4
 5) Main Menu Option 5
Choose Options 1 through 5
Choose Options 1 through 5
                      SubMenu 1       
 
 1) SubMenu Option 1
 2) SubMenu Option 2
 3) SubMenu Option 3
Choose Options 1 through 3
Choose Options 1 through 3

I do not think the underlying problem can be completely solved by selecting a different setup in Serial Monitor.

@blh64 I have read through Robin2's explanation and what I am doing is extremely similar to his first example just with more happening. I haven't been able to get his second example working with more than one level of menu however.

Any thoughts on how I can get it to stop out putting the 'else' condition when it is not suppose to?

add conditions for \n or \r which are ignored so that they are not handled under the else case

@gcjr

I tired adding in exclusion conditions to the else cases but it didnt solve the problem. I also print out the menu and subMenu values and I can see that subMenu is equal to '⸮' at the time when the else condition fires incorrectly, not sure what that means though...

char menu;
char subMenu;
bool runOnce = true;
bool menuState = true;
int subMenuCount = 0;

void setup() {
  Serial.begin(115200);  //Initialize the serial connection
}

void loop() {
  serialMenuMain();
}


void topMenu() {

  if (runOnce == true) {
    Serial.println (F("                     Main Menu                          "));
    Serial.println (F(" 1) Main Menu Option 1"));
    Serial.println (F(" 2) Main Menu Option 2"));
    Serial.println (F(" 3) Main Menu Option 3"));
    Serial.println (F(" 4) Main Menu Option 4"));
    Serial.println (F(" 5) Main Menu Option 5"));
    runOnce = false;
    menuState = true;
  }
}

void serialMenuMain() {
  topMenu();
  while (Serial.available() == 0) {}
  if (menuState == true) {
    char menu = Serial.read();
    if (menu == '1') {
      subMenuCount = 1;
      subMenu1();
    }
    else if (menu == '2') {
      subMenuCount = 2;
      // TBD: subMenu2();
    }
    else if (menu == '3') {
      subMenuCount = 3;
      // TBD: subMenu3();
    }
    else if (menu == '4') {
      subMenuCount = 4;
      // TBD: subMenu4();
    }
    else if (menu == '5') {
      subMenuCount = 5;
      // TBD: subMenu5();
    }
    else if (menu != ('1' || '2' || '3' || '4' || '5' || '\0' && '\n' || '\r') || '\n' || '\r' ){
      Serial.println ("Choose Options 1 through 5");
    }
  }

  if (menuState == false && subMenuCount == 1) {
    subMenu = Serial.read();
    if (subMenu == '1') {
      //set a variable
      Serial.println("SubMenu Option 1");
    }
    else if (subMenu == '2') {
      //set a variable
      Serial.println("SubMenu Option 2");
    }
    else if (subMenu == '3') {
      Serial.println("Returning to Main Menu");
      menu = 'q';
      runOnce = true;
      topMenu();
    }
    else if (subMenu != ('1' || '2' || '3'|| '\0' && '\n' || '\r') || '\n' || '\r'){
      Serial.println ("Choose Options 1 through 3");
      Serial.println(subMenu);
      Serial.println(menu);
    }
  }
}

void subMenu1() {
  menuState = false;
  Serial.println (F("                      SubMenu 1       "));
  Serial.println (F(" "));
  Serial.println (F(" 1) SubMenu Option 1"));
  Serial.println (F(" 2) SubMenu Option 2"));
  Serial.println (F(" 3) SubMenu Option 3"));
}

You cannot compare a value to an entire array of values.
I would suggest changing to a switch/case structure like this

    char menu = Serial.read();
    switch (menu) {
      case '1':
        subMenuCount = 1;
        subMenu1();
        break;

      case '2':
        subMenuCount = 2;
        // TBD: subMenu2();
        break;

      case '3':
        subMenuCount = 3;
        // TBD: subMenu3();
        break;

      case '4':
        subMenuCount = 4;
        // TBD: subMenu4();
        break;

      case '5':
        subMenuCount = 5;
        // TBD: subMenu5();
        break;

      case '\r':
      case '\n':
        // ignore
        break;
        
      default :
        Serial.println ("Choose Options 1 through 5");
        break;
    }

So you can easily add things.

@blh64

Thanks for that thought. I was able to change my exclusion to just exclude '\n' and it works fine now...but only when I have the Serial Monitor set to "Newline". Is there a way to make it work properly regardless of how the Serial Monitor is configured?

char menu;
char subMenu;
bool runOnce = true;
bool menuState = true;
int subMenuCount = 0;

void setup() {
  Serial.begin(115200);  //Initialize the serial connection
}

void loop() {
  serialMenuMain();
}


void topMenu() {

  if (runOnce == true) {
    Serial.println (F("                     Main Menu                          "));
    Serial.println (F(" 1) Main Menu Option 1"));
    Serial.println (F(" 2) Main Menu Option 2"));
    Serial.println (F(" 3) Main Menu Option 3"));
    Serial.println (F(" 4) Main Menu Option 4"));
    Serial.println (F(" 5) Main Menu Option 5"));
    runOnce = false;
    menuState = true;
  }
}

void serialMenuMain() {
  topMenu();
  while (Serial.available() == 0) {}
  if (menuState == true) {
    char menu = Serial.read();
    if (menu == '1') {
      subMenuCount = 1;
      subMenu1();
    }
    else if (menu == '2') {
      subMenuCount = 2;
      // TBD: subMenu2();
    }
    else if (menu == '3') {
      subMenuCount = 3;
      // TBD: subMenu3();
    }
    else if (menu == '4') {
      subMenuCount = 4;
      // TBD: subMenu4();
    }
    else if (menu == '5') {
      subMenuCount = 5;
      // TBD: subMenu5();
    }
    else if (menu != ('\n')){
      Serial.println ("Choose Options 1 through 5");
    }
  }

  if (menuState == false && subMenuCount == 1) {
    subMenu = Serial.read();
    if (subMenu == '1') {
      //set a variable
      Serial.println("SubMenu Option 1");
    }
    else if (subMenu == '2') {
      //set a variable
      Serial.println("SubMenu Option 2");
    }
    else if (subMenu == '3') {
      Serial.println("Returning to Main Menu");
      menu = 'q';
      runOnce = true;
      topMenu();
    }
    else if (subMenu != ('\n')){
      Serial.println ("Choose Options 1 through 3");
      Serial.println(subMenu);
      Serial.println(menu);
    }
  }
}

void subMenu1() {
  menuState = false;
  Serial.println (F("                      SubMenu 1       "));
  Serial.println (F(" "));
  Serial.println (F(" 1) SubMenu Option 1"));
  Serial.println (F(" 2) SubMenu Option 2"));
  Serial.println (F(" 3) SubMenu Option 3"));
}

The other most common character used in line-endings is '\r', mostly in combination with '\n'; @blh64 already added it in his code in reply #7.

So your check should be

    else if (subMenu != ('\r') && subMenu != ('\n')){

There are a few other line-endings but I doubt you will ever encounter them; see Newline - Wikipedia if interested.

@sterretje

I tried that and it did not work, it ended up bringing back the problems that existed with "Newline" before and caused all the same problems as before. I also apparently posted the wrong code before. The below code is the code that is working 100% when "newline" is selected in the Serial Monitor:

char menu;
char subMenu;
bool runOnce = true;
bool menuState = true;
int subMenuCount = 0;

void setup() {
  Serial.begin(115200);  //Initialize the serial connection
}

void loop() {
  serialMenuMain();
}


void topMenu() {

  if (runOnce == true) {
    Serial.println (F("                     Main Menu                          "));
    Serial.println (F(" 1) Main Menu Option 1"));
    Serial.println (F(" 2) Main Menu Option 2"));
    Serial.println (F(" 3) Main Menu Option 3"));
    Serial.println (F(" 4) Main Menu Option 4"));
    Serial.println (F(" 5) Main Menu Option 5"));
    runOnce = false;
    menuState = true;
  }
}

void serialMenuMain() {
  topMenu();
  while (Serial.available() == 0) {}
  if (menuState == true) {
    char menu = Serial.read();
    if (menu == '1') {
      subMenuCount = 1;
      subMenu1();
    }
    else if (menu == '2') {
      subMenuCount = 2;
      // TBD: subMenu2();
    }
    else if (menu == '3') {
      subMenuCount = 3;
      // TBD: subMenu3();
    }
    else if (menu == '4') {
      subMenuCount = 4;
      // TBD: subMenu4();
    }
    else if (menu == '5') {
      subMenuCount = 5;
      // TBD: subMenu5();
    }
    else if (menu != ('\n')){
      Serial.println ("Choose Options 1 through 5");
    }
  }

  if (menuState == false || subMenuCount == 1) {
    subMenu = Serial.read();
    if (subMenu == '1') {
      //set a variable
      Serial.println("SubMenu Option 1");
    }
    else if (subMenu == '2') {
      //set a variable
      Serial.println("SubMenu Option 2");
    }
    else if (subMenu == '3') {
      Serial.println("Returning to Main Menu");
      menu = 'q';
      runOnce = true;
      topMenu();
    }
    else if (menuState == false && subMenu != ('\n')){
      Serial.println ("Choose Options 1 through 3");
      Serial.println(subMenu);
      Serial.println(menu);
    }
  }
}

void subMenu1() {
  menuState = false;
  Serial.println (F("                      SubMenu 1       "));
  Serial.println (F(" "));
  Serial.println (F(" 1) SubMenu Option 1"));
  Serial.println (F(" 2) SubMenu Option 2"));
  Serial.println (F(" 3) SubMenu Option 3"));
}

If your entire "menu" system is number driven, a much cleaner approach may be to replace your Serial.read() function calls to your own function that will only return a number

char readDigit() {
  while( Serial.available() ) {
    char c = Serial.read();
    if ( c >= '0' && c <= '9' ) return c;
  }
}

and then your code will ignore everything except '0'...'9' and, given which menu you are one, you can test for out of bounds conditions, if you need to.