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"));
}
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
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?
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"));
}
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"));
}
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.