Switch-Case is not working, why?

Hi,

I programmed a very simple state machine and everything is working as expected if i use IF-ELSE.
Now i just wanted to change it to SWITCH-CASE and it isn´t working, but i can´t figure out why.

Here is an example code:

void loop()
{
	runStateMachine(state);
	
	if (myDFPlayer.available()) {
		printDetail(myDFPlayer.readType(), myDFPlayer.read());
	}
	delay(5);
}

void runStateMachine(States state) {

	if (state == STATE_STARTUP) {
		if (stateEntered) {
			Serial.println(F("STATE STARTUP"));
			stateEntered = false;
		}
		int audioNr = pickRandom(startupSounds, sizeof(startupSounds) / sizeof(startupSounds[0]));
		setState(STATE_IDLE);
		myDFPlayer.play(audioNr);
	}
	else if (state == STATE_IDLE) {
		if (stateEntered) {
			Serial.println(F("STATE IDLE"));
			servoPosition(PULSE_ANGLE_NEUTRAL);
			stateEntered = false;
		}
		if (isBtnClicked()) {
			lastBtnPressed = millis();
			Serial.println("Btn clicked");
			setState(STATE_GET_ANSWER);
			myDFPlayer.readType();
			myDFPlayer.read();
		}
		....
	}
	else if (state == STATE_GET_ANSWER) {
		....
	}

This is working as expected, but this not:

void loop()
{
	runStateMachine(state);
	
	if (myDFPlayer.available()) {
		printDetail(myDFPlayer.readType(), myDFPlayer.read());
	}
	delay(5);
}

void runStateMachine(States state) {

switch(state) {
     case STATE_STARTUP: 
         if(stateEntered)  {....
         ....
         break;
     case STATE_IDLE:
         ......
         break;
     .....
     default:
         ....
}

The states are defines as an enum in a own .h file

enum States {
	STATE_STARTUP,
	STATE_IDLE,
	STATE_GET_ANSWER,
	STATE_PLAY_SOUND
};

I´ve already tried it with const short for the states, but the same problem.

Is this something arduino specific or do I have a big board in front of my head at the moment :slight_smile:

Thanks!

Please provide a detailed description of what you mean by "it isn´t working".

It would also be much more helpful if you provided a complete, verifiable demonstration of the problem, rather than the code snippet you posted.

 runStateMachine(state);

What is the value of state at this point ?
Please post a complete program that illustrates the problem

I am annoyed for hours with the problem around and therefore I forgot some details that are of course completely clear to me, but not to you...

I have a setState method as follows:

void setState(States newState) {
	stateEntered = true;
	state = newState;
}

In setup() i set the state to STATE_STARTUP and the corresponding case STATE_STARTUP is entered.
Then in StATE_STARTUP i call setState(STATE_IDLE) to change the state, but the other states are never entered.
If i only replace the Switch-Case construct with If-Else, everything is working fine and all states are entered like they should.
I set a startup state -> change state to another s2 -> s2 is entered -> change state to e.g. s3 -> s3 is entered ...and so on.

I hope this is detailed enough?

I hope this is detailed enough?

No, far from it

As requested

Please post a complete program that illustrates the problem

Did you Read this before posting a programming question ?

Ok. The complete sketch is here on pastebin

The most interesting part is:

void runStateMachine(States state) {
 
    if (state == STATE_STARTUP) {
        if (stateEntered) {
            Serial.println(F("STATE STARTUP"));
            stateEntered = false;
        }
        int audioNr = pickRandom(startupSounds, sizeof(startupSounds) / sizeof(startupSounds[0]));
        setState(STATE_IDLE);
        myDFPlayer.play(audioNr);
    }
    else if (state == STATE_IDLE) {
        if (stateEntered) {
            Serial.println(F("STATE IDLE"));
            servoPosition(PULSE_ANGLE_NEUTRAL);
            stateEntered = false;
        }
        if (isBtnClicked()) {
            lastBtnPressed = millis();
            Serial.println("Btn clicked");
            setState(STATE_GET_ANSWER);
            myDFPlayer.readType();
            myDFPlayer.read();
        }
        if (millis() - lastBtnPressed > intervall) {
            playIdleSound();
        }
    }
    else if (state == STATE_GET_ANSWER) {
        if (stateEntered) {
            Serial.println("STATE GET_ANSWER - Not in Progress");
            stateEntered = false;
        }
        answer = receiveBoolean();
        setServo(answer);
        if (answer) {
            int audioNr = pickRandom(yesSounds, sizeof(yesSounds) / sizeof(yesSounds[0]));
            myDFPlayer.play(audioNr);
        }
        else {
            int audioNr = pickRandom(noSounds, sizeof(noSounds) / sizeof(noSounds[0]));
            myDFPlayer.play(audioNr);
        }
        myDFPlayer.read();
        myDFPlayer.readType();
        setState(STATE_PLAY_SOUND);
    }
    else if (state == STATE_PLAY_SOUND) {
        if (stateEntered) {
            Serial.println("STATE PLAY_SOUND");
            stateEntered = false;
        }
        uint8_t type;
        int value;
        if (myDFPlayer.available()) {
            type = myDFPlayer.readType();
            value = myDFPlayer.read();
            if (type == DFPlayerPlayFinished) {
                Serial.println("Finished playing");
                delay(2000);
                setState(STATE_IDLE);
            }
        }
    }
    else {
        Serial.println("Default");
    }
}

You can see first i set the state to STATE_STARTUP and in this state I change state to STATE_IDLE.
In IDLE I wait to recognize a Btn click and change again the state.
And this is the serial output for the sketch

Opening port
Port open

DFRobot DFPlayer Mini Demo
Initializing DFPlayer ... (May take 3~5 seconds)
DFPlayer Mini online.
STATE STARTUP
Seed: 488
STATE IDLE
Number:9 Real Play Finished!
Number:9 Real Play Finished!

Now I tried to replace if-else with switch-case

void runStateMachine(States state) {

	switch (state)
	{
	case STATE_STARTUP:
		if (stateEntered) {
			Serial.println(F("STATE STARTUP"));
			stateEntered = false;
		}
		int audioNr = pickRandom(startupSounds, sizeof(startupSounds) / sizeof(startupSounds[0]));
		setState(STATE_IDLE);
		myDFPlayer.play(audioNr);
		break;
	case STATE_IDLE:
		if (stateEntered) {
			Serial.println(F("STATE IDLE"));
			servoPosition(PULSE_ANGLE_NEUTRAL);
			stateEntered = false;
		}
		if (isBtnClicked()) {
			lastBtnPressed = millis();
			Serial.println("Btn clicked");
			setState(STATE_GET_ANSWER);
			myDFPlayer.readType();
			myDFPlayer.read();
		}
		if (millis() - lastBtnPressed > intervall) {
			playIdleSound();
		}
		break;
	case STATE_GET_ANSWER:
		if (stateEntered) {
			Serial.println("STATE GET_ANSWER - Not in Progress");
			stateEntered = false;
		}
		answer = receiveBoolean();
		setServo(answer);
		if (answer) {
			int audioNr = pickRandom(yesSounds, sizeof(yesSounds) / sizeof(yesSounds[0]));
			myDFPlayer.play(audioNr);
		}
		else {
			int audioNr = pickRandom(noSounds, sizeof(noSounds) / sizeof(noSounds[0]));
			myDFPlayer.play(audioNr);
		}
		myDFPlayer.read();
		myDFPlayer.readType();
		setState(STATE_PLAY_SOUND);
		break;
	case  STATE_PLAY_SOUND:
		if (stateEntered) {
			Serial.println("STATE PLAY_SOUND");
			stateEntered = false;
		}
		uint8_t type;
		int value;
		if (myDFPlayer.available()) {
			type = myDFPlayer.readType();
			value = myDFPlayer.read();
			if (type == DFPlayerPlayFinished) {
				Serial.println("Finished playing");
				delay(2000);
				setState(STATE_IDLE);
			}
		}
		break;
	default:
		Serial.println("Default");
	}
}

everything else is unchanged.
And the corresponding serial output is:

Opening port
Port open

DFRobot DFPlayer Mini Demo
Initializing DFPlayer ... (May take 3~5 seconds)
DFPlayer Mini online.
STATE STARTUP
Seed: 473
Number:9 Real Play Finished!
Number:9 Real Play Finished!

As you can see state IDLE is not entered
But the value of state changed to 1 which is correct (see enum declaration)

I think i found the problem.
Case statements are only labels and are interpreted as jumps directly to the label.
If there are variable declarations in case blocks there is a scope problem and although there is no error during compile it causes this problem.
I put curly brackets around the problematic blocks and now it is working as expected and 100% similar to the if-else code.
More information about that can be found here and here.
Therefore I will continue with the if-else version.

If there are variable declarations in case blocks there is a scope problem and although there is no error during compile it causes this problem.

Something has changed then because declaring variables inside the code for a case used to cause an explicit error

Therefore I will continue with the if-else version.

Does that work ?

Personally I favour switch/case where the switch variable can be resolved to an integer as I find it easier to understand and maintain and it is perfect for state machines, but, of course, YMMV

UKHeliBob:
Something has changed then because declaring variables inside the code for a case used to cause an explicit error
Does that work ?

Both, the if-else version and the switch-case (without {}) compile without errors and run. But the switch-case version only enters the first state and does not recognize the other states if the state variable changes.
If I had seen any error with the switch-case version, error finding would have been a lot easier :wink:
I use Arduini IDE 1.8.10 with VisualStudio and vMicro Plugin.

UKHeliBob:
Personally I favour switch/case where the switch variable can be resolved to an integer as I find it easier to understand and maintain and it is perfect for state machines, but, of course, YMMV

I also prefer switch case, but since just with such a mistake no explicit error is output and the program runs with the unexpected behavior, I see here a hard-to-find source of error.
And as a precaution to grab braces around each case I do not like it that way

If I had seen any error with the switch-case version, error finding would have been a lot easier

No explicit error but you do get a warning

R02D02:
I also prefer switch case, but since just with such a mistake no explicit error is output and the program runs with the unexpected behavior, I see here a hard-to-find source of error.

Try IDE -> file/preferences/compiler warnings/all.