Nested Switch/Case Issue

I like the way this setup works with only one problem that I can't seem to figure out. When you press A, the menu works great along with its submenu. When you press B everything works great along with its submenu. BUT.. if you start with A, then exit and try B, that is where everything goes down the tubes.

I am sure there is some logic that I am missing. Something is not synchronized. I have two more (C & D) with more submenu items to add but want to get this logic figured out first. Here is what I have so far:

#include <Servo.h>

Servo ServoPan;  int servoPin1 = 18; /* to hard code these: ex. int servoPin1 = 8 */
Servo ServoTilt; int servoPin2 = 19; /* to hard code these: ex. int servoPin1 = 9 */

int panLowLimit  =   0;       /* Set this variable for your Pan Servo's Low Limit */
int panHighLimit = 175;      /* Set this variable for your Pan Servo's High Limit */

int tiltLowLimit  =   0;     /* Set this variable for your Tilt Servo's Low Limit */
int tiltHighLimit = 176;    /* Set this variable for your Tilt Servo's High Limit */

char topMenu;
char subMenuA;
char subMenuB;
char subMenuC;
char subMenuD;

bool exitA = true;
bool exitB = true;
bool exitC = true;
bool exitD = true;

bool runOnceMain = true;
bool runOnceA = true;
bool runOnceB = true;
bool runOnceC;
bool runOnceD;

int setPosAngle;

int servoDelay = 25;
int posPan;
int posTilt;
int countCycles;


void setup() {
  Serial.begin (115200);
  pinMode (servoPin1, OUTPUT);  pinMode (servoPin2, OUTPUT);
  pinMode (analogPin1, INPUT);  pinMode (analogPin2, INPUT);
  /* Attach, Write and Center both Servos on Startup */
  ServoPan.attach(servoPin1); ServoPan.write((panHighLimit - panLowLimit) / 2);
  ServoTilt.attach(servoPin2); ServoTilt.write((tiltHighLimit - tiltLowLimit) / 2);
}


void mainMenu() {
  Serial.println("\n                     -- Super ServoMaster Top Menu --");
  Serial.println(" A) Calibrate      B) Auto-Sweep       C) Pot Control      D) Self Center\n");
}

void subAMenu() {
  if (runOnceA == true) {
    Serial.println("\n               -- Super ServoMaster Calibration Menu A --");
    Serial.println(" 1)  Set PanServo         2)  Set TiltServo          3) Exit to Main Menu\n");
    runOnceA = false;
  }
}

void subBMenu() {
  if (runOnceB == true) {
    Serial.println("\n               -- Super ServoMaster Auto-Sweep Menu B --");
    Serial.println(" 1)Start PanServo  2)Start TiltServo  3)Start Both)   Q)Exit to Main Menu\n");
    runOnceB = false;
  }
}

void subMenuA1() {
  runOnceA = true;
  while (subMenuA == '1') {
    if (runOnceA == true) {
      Serial.println(" Where would you like the Pan Servo? Q to Quit\n");
      runOnceA = false;
    }
    if (Serial.available() > 0) {
      setPosAngle = Serial.parseFloat();
      ServoPan.write(setPosAngle);
      Serial.print(" Pan Servo Position Angle in Degrees (0-180): ");
      Serial.print("[ ");
      Serial.print(setPosAngle);
      Serial.println(" ] Degrees");
    }
    while (Serial.available() == 0) {}
    if (Serial.peek() == 'q' || Serial.peek() == 'Q') {
      Serial.println("\n Exiting . . .");
      runOnceA = true;
      exitA = true;
      topCaseA();
      break;
    }
  }
}

void subMenuA2() {
  runOnceA = true;
  while (subMenuA == '2') {
    if (runOnceA == true) {
      Serial.println(" Where would you like the Tilt Servo? Q to Quit\n");
      runOnceA = false;
    }
    if (Serial.available() > 0) {
      setPosAngle = Serial.parseFloat();
      ServoTilt.write(setPosAngle);
      Serial.print(" Tilt Servo Position Angle in Degrees (0-180): ");
      Serial.print("[ ");
      Serial.print(setPosAngle);
      Serial.println(" ] Degrees");
    }
    while (Serial.available() == 0) {}
    if (Serial.peek() == 'q' || Serial.peek() == 'Q') {
      Serial.println("\n Exiting . . .");
      runOnceA = true;
      exitA = true;
      topCaseA();
      break;
    }
  }
}

void subMenuA3() {
  runOnceA = true;
  runOnceMain = true;
  exitA = false; // <----------------
  Serial.print(" Exiting to Main Menu and Centering Servo's . . .\n");
  ServoPan.write((panHighLimit - panLowLimit) / 2);
  ServoTilt.write((tiltHighLimit - tiltLowLimit) / 2);
  return;
}

void topCaseA() {
  exitA = false;
  subAMenu();
}

void topCaseB() {
  exitB = false;
  subBMenu();
}

void subMenuB1() {
  runOnceB = true;
  Serial.print(subMenuB);
  while (subMenuB == '1') {
    while (runOnceB == true) {
      Serial.println(" Pan Servo Activated (Q Exits)");
      runOnceB = false;
    }
    for (int posPan = panLowLimit; posPan <= panHighLimit; posPan ++) {
      ServoPan.write(posPan);
      delay(servoDelay);
    }
    for (int posPan = panHighLimit; posPan >= panLowLimit; posPan --) {
      ServoPan.write(posPan);
      delay(servoDelay);
    }
    countCycles; countCycles ++;
    Serial.print(" Pan Servo Cycle Number ");
    Serial.println(countCycles);

    if (Serial.peek() == 'Q' || Serial.peek() == 'q') {
      while (Serial.available() == 0) {}
      Serial.println("\n Exiting . . .\n");
      countCycles = 0;
      exitB = false;
      topCaseB();
      runOnceB = true;
      break;
    }
  }
}

void loop() {
  if (runOnceMain == true) {
    mainMenu();
    runOnceMain = false;
  }
  if (Serial.available() > 0 ) {
    if (exitA == true && exitB == true) {
      topMenu = Serial.read();
    }
    if (exitA == false && exitB == true) {
      subMenuA = Serial.read();

    }
    else if (exitA == true && exitB == false) {
      subMenuB = Serial.read();
    }

    switch (topMenu) {
      case 'A' : case 'a' : {
          topCaseA();
          switch (subMenuA) {
            case '1' : {
                subMenuA1();
                break;
              }
            case '2' : {
                subMenuA2();
                break;
              }
            case '3' : {
                subMenuA3(); // <-- has a return statement!
                break;
              }
            default :
              if (subMenuA >= '4' && subMenuA != 'q' && subMenuA == ' ') {
                Serial.println(" Please Choose Options 1 - 3");
              }
              break;
          } /* End subMenu Switch */
          break;
        } /* End Case A */
      case 'B' : case 'b' : {
          topCaseB();
          switch (subMenuB) {
            case '1' : {
                subMenuB1();
                break;
              }
            case '2' : {
                //   subMenuB2();
                break;
              }
            case '3' : {
                //  subMenuB3();
                break;
              }
            case '4' : {
                //  subMenuB4();
                break;
              }
            default :
              if (subMenuB >= '5' && subMenuB != 'q' && subMenuB == ' ') {
                Serial.println(" Please Choose Options 1 - 3");
              }
              break;
          } /* End subMenu Switch */
          break;
        }/* End Case B */
      default :
        if (topMenu != 'A' && topMenu != 'B' && topMenu != 'C' && topMenu != 'D' && topMenu == ' ') {
          Serial.println(" Please Choose Options A - D");
        }
    } /* End topMenu Switch */
  } /* End Serial.available */
} /* End Void Loop */

Thanks! I wouldn't be this far without this forum and the generosity of the volunteers.

I'll try.. When pressing A and doing everything in the submenu, everything works. When pressing B and doing everything in the submenu everything works. When pressing A, then exiting A and pressing B, everything is out of whack! So both functions work as advertised until the user quits out of one function, and tries the other, which is expected. The same happens when pressing B and exiting and choosing A. This works without servos on a Mega or Uno for testing. I am sure it has to do with the Serial.available functions at the top of the void loop, but I am not sure how to get around it. Thanks!

-- Super ServoMaster Top Menu --
A) Calibrate B) Auto-Sweep C) Pot Control D) Self Center

then...

-- Super ServoMaster Calibration Menu A --

  1. Set PanServo 2) Set TiltServo 3) Exit to Main Menu

then choosing 1:

Where would you like the Pan Servo? Q to Quit

User inputs the servo angle between 0 and 180..

then...

Pan Servo Position Angle in Degrees (0-180): [ 90 ] Degrees

Exiting . . .

-- Super ServoMaster Calibration Menu A --

  1. Set PanServo 2) Set TiltServo 3) Exit to Main Menu

Exiting to Main Menu and Centering Servo's . . .

-- Super ServoMaster Top Menu --
A) Calibrate B) Auto-Sweep C) Pot Control D) Self Center

HERE is where everything dies unless you choose Option A. I want to exit and then choose any of the other available choices. Does that help?

If you choose Option B, the first menu appears. I am hoping to fix that before I add more code to the problem. When choosing any other option (B,C,D) after using Option A, the program doesn't work as its supposed to. The Calibration Menu appearing when you press B is not what is supposed to happen.

It stays on the Calibration Menu (A). Even after pressing B. I am very confused by this. Yet, B works fine IF you start with B and end with B. A works fine if you start with A and end with A. No mixing the two, which is something that is rather important.

I have this entire code finished and working perfectly using if/else. I am trying to learn the difference using switch/case. I am almost there. It IS a learning experience for sure! So, any ideas on how to get the options to share?

I SEE what I am doing wrong, I just don't know how to overcome it. Looking at the top of void loop, an first saying if NO option is active, then present the user with a menu.

if (Serial.available() > 0 ) {
    if (exitA == true && exitB == true) {
      topMenu = Serial.read();
    }

I am then making the mistake of choosing between the two rather than having BOTH subMenuA and subMenuB listening into Serial.read (somehow)...

if (exitA == false && exitB == true) {
      subMenuA = Serial.read();                             //    <<-- Option A is active

    }
    else if (exitA == true && exitB == false) {        // OR Option B is active
      subMenuB = Serial.read();
    }

I get what I am doing wrong, I just do not know how you can have subMenuA, B, C and D listening to Serial.read() at the same time.

I think there are some fundamental things about how variables work that you don't understand. However, if you want to do it that way...

Deve:
I get what I am doing wrong, I just do not know how you can have subMenuA, B, C and D listening to Serial.read() at the same time.

That's easy enough.

subMenuA = Serial.read();
subMenuB = subMenuA;
subMenuC = subMenuA;
subMenuD = subMenuA;

Although you could just call it subMenuChar or something and use the same variable in all the submenu functions.

Thanks to all. I am a 60 year old who is currently auditing CS1 Java at the local College. LOTS to learn, but sometimes all I need is a nudge in the right direction. This is my solution. It works very nicely. Of course later I will go back in and revisit the structure and make changes once I learn more.

void loop() {
  if (runOnce == true) {
    mainMenu();
    runOnce = false;
  }
  if (Serial.available() > 0 ) {
    if (topMenu == 'Q') {
      topMenu = Serial.read();
    }
    if (topMenu == 'a' || topMenu == 'A') {
      subMenuA = Serial.read();
    }
    else if (topMenu == 'b' || topMenu == 'B') {
      subMenuB = Serial.read();
    }
    else if (topMenu == 'c' || topMenu == 'C') {
      subMenuC = Serial.read();
    }
    else if (topMenu == 'd' || topMenu == 'D') {
      subMenuD = Serial.read();
    }
    switch (topMenu) {
      case 'A' : case 'a' : {
          topCaseA();
          switch (subMenuA) {
            case '1' : {
                subMenuA1();
                break;
              }
            case '2' : {
                subMenuA2();
                break;
              }
            case '3' : {
                subMenuExit();
                break;
              }
              break;
          } /* End subMenu A Switch */
          break;
        } /* End Case A */
      case 'B' : case 'b' : {
          topCaseB();
          switch (subMenuB) {
            case '1' : {
                subMenuB1();
                break;
              }
            case '2' : {
                subMenuB2();
                break;
              }
            case '3' : {
                subMenuB3();
                break;
              }
            case '4' : {
                subMenuExit();
                break;
              }
            break;
          } /* End subMenu B Switch */
          break;
        } /* End Case B */
      case 'C' : case 'c' : {
          topCaseC();
          switch (subMenuC) {
            case '1' : {
                subMenuC1();
                break;
              }
            case '2' : {
                subMenuC2();
                break;
              }
            case '3' : {
                subMenuExit();
                break;
              }
            break;
          } /* End subMenu C Switch */
          break;
        } /* End Case C */
      case 'D' : case 'd' : {
          topCaseD();
          switch (subMenuD) {
            case '1' : {
                subMenuD1();
                break;
              }
            case '2' : {
                subMenuD2();
                break;
              }
            case '3' : {
                subMenuExit();
                break;
              }
            break;
          } /* End subMenu D Switch */
          break;
        } /* End Case D */
      default :
        if (topMenu != 'a' || topMenu != 'b' || topMenu != 'c' || topMenu != 'd') {
          runOnce = true;
          if (runOnce == true) {
            Serial.println(" Please Choose Options A - D");
            runOnce = false;
            topMenu = 'Q';
          }
        }
    } /* End topMenu Switch */
  } /* End Serial.available */
} /* End Void Loop */

It needs to be highly compartmentalized and each function needs to have its own menu item because the entire purpose is to show the kids how to make structured menu/submenus for whatever purpose using both switch/case and if/else if/else logic then let them improve on the structure. Thanks again!

You do if you are avoiding mixing if/else statements with switch/case. I have one program that uses if/else if logic and an identical program but uses switch/case. Its working perfectly and gives them the start they need to make whatever improvements they want. Sure I could have all the B functions together, etc, but that would defeat the purpose of the lesson. If only time would stop so I could turn into a programming genius during this school year! Wouldn't THAT be awesome? But, decent local free volunteers are hard to come by. Thanks for the tips!

I can use all the help I can get. Unfortunately, the code is over 9000 characters so it wont fit here. Feel free to look over and suggest improvements. Each kid has one of these and we are on a mission to complete the software. While it is a fun project, its also a very time consuming one! I am definitely in over my head!

Download the Servo Control Tester.ino from here:

It may look BAD from your perspective, but me? I am elated that I can actually program something that actually works! Who would have thought? (not me)