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) {
....
}
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.
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
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
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.