Strange behavior in switch / case section

Hi,

I'm fairly new to Arduino programming. Currently I'm building a project to trigger an 'alarm' when a certain button is pushed.

I decided to use a 'state machine' using a switch/case loop. However, on debugging I came into a weird problem.

The code below works fine: the loop starts in the S_IDLE state and on pushing the button it switches to the S_ALARMTRIG state which then enters the S_LEDON state to flash the LED.

#define S_IDLE 8
#define S_LEDON 3
#define S_WAITON 4
#define S_LEDOFF 5
#define S_WAITOFF 6
#define S_TURNOFF 7
#define S_ALARMTRIG 9

const int buttonPin = 4;
const int ledPin = 13;

int ledState = LOW;
int buttonState;
int lastButtonState = LOW;

static int state = S_IDLE;

unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50;

void setup()
{
  Serial.begin(9600);
  Serial.println("Setup-loop");
  pinMode(buttonPin, INPUT);
  pinMode(ledPin, OUTPUT);
  Serial.println("Setup-loop ran - starting main looop");

}

void loop()
{

  static unsigned long ts; // om de huidige tijd voor pauzes in te bewaren

  switch (state)
  {

    case S_LEDON:
      Serial.println(state);
      digitalWrite(ledPin, HIGH); // schakel LED in
      ts = millis(); // noteer de huidige tijd
      state = S_WAITON; // ga naar de volgende state
      break;

    case S_WAITON:
      Serial.println(state);
      if (millis() > ts + 1000)
      {
        state = S_LEDOFF;
      }
      break;

    case S_LEDOFF:
      digitalWrite(ledPin, LOW); // schakel LED uit

      ts = millis(); // noteer de huidige tijd
      state = S_WAITOFF;
      break;

    case S_WAITOFF:
      Serial.println(state);

      // zodra er 1 seconde verstreken is , ga naar terug naar fase S_LEDON
      if (millis() > ts + 1000)
      {
        state = S_LEDON;
      }
      break;

    case S_TURNOFF:
      ledState = LOW;
      state = S_IDLE;
      break;

    case S_ALARMTRIG:
      state = S_LEDON;
      break;
    case S_IDLE:
      Serial.println(state);

      int reading = digitalRead(buttonPin);

      if (reading != lastButtonState) {
        lastDebounceTime = millis();
      }

      if ((millis() - lastDebounceTime) > debounceDelay) {
        if (reading != buttonState) {
          buttonState = reading;

          if (buttonState == HIGH) {
            ledState = !ledState;
            state = S_ALARMTRIG;
          }
        }
      }
      digitalWrite(ledPin, ledState);
      lastButtonState = reading;
      break;

    default:
      state = S_IDLE;
      break;
  }
}

However, the code below doesn't work because it hangs on pressing the button, although it is EXACTLY the same, apart from the position of the case S_IDLE (which is now in the beginning of the switch() statement rather than the end).

#define S_IDLE 8
#define S_LEDON 3
#define S_WAITON 4
#define S_LEDOFF 5
#define S_WAITOFF 6
#define S_TURNOFF 7
#define S_ALARMTRIG 9

const int buttonPin = 4;
const int ledPin = 13;

int ledState = LOW;
int buttonState;
int lastButtonState = LOW;

static int state = S_IDLE;

unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50;

void setup()
{
  Serial.begin(9600);
  Serial.println("Setup-loop");
  pinMode(buttonPin, INPUT);
  pinMode(ledPin, OUTPUT);
  Serial.println("Setup-loop ran - starting main looop");

}

void loop()
{

  static unsigned long ts; // om de huidige tijd voor pauzes in te bewaren

  switch (state)
  {
    case S_IDLE:
      Serial.println(state);

      int reading = digitalRead(buttonPin);

      if (reading != lastButtonState) {
        lastDebounceTime = millis();
      }

      if ((millis() - lastDebounceTime) > debounceDelay) {
        if (reading != buttonState) {
          buttonState = reading;

          if (buttonState == HIGH) {
            ledState = !ledState;
            state = S_ALARMTRIG;
          }
        }
      }
      digitalWrite(ledPin, ledState);
      lastButtonState = reading;
      break;
    case S_LEDON:
      Serial.println(state);
      digitalWrite(ledPin, HIGH); // schakel LED in
      ts = millis(); // noteer de huidige tijd
      state = S_WAITON; // ga naar de volgende state
      break;

    case S_WAITON:
      Serial.println(state);
      if (millis() > ts + 1000)
      {
        state = S_LEDOFF;
      }
      break;

    case S_LEDOFF:
      digitalWrite(ledPin, LOW); // schakel LED uit

      ts = millis(); // noteer de huidige tijd
      state = S_WAITOFF;
      break;

    case S_WAITOFF:
      Serial.println(state);

      // zodra er 1 seconde verstreken is , ga naar terug naar fase S_LEDON
      if (millis() > ts + 1000)
      {
        state = S_LEDON;
      }
      break;

    case S_TURNOFF:
      ledState = LOW;
      state = S_IDLE;
      break;

    case S_ALARMTRIG:
      state = S_LEDON;
      break;


    default:
      state = S_IDLE;
      break;
  }
}

Can anyone clear this up for me? I really don't understand how only the position of the case S_IDLE statement can make a difference in this case...

You really should not declare variables inside your case statement. If you turn on all warnings during compiling inside the IDE, it will warn about not liking that. I'm not sure this is the cause of your problem. How do you have your button wired up? Does it contain a pull-up or pull-down resistor? Consider using the built in pull-up resistors of the arduino, then wire one side of the button to a pin and the other side to ground. When the button is pressed, it reads LOW and when it is not pressed, it reads HIGH.

Also, your different states can more easily be declared using a enumeration:

enum States { S_IDLE, S_LEDON, S_WAITON, S_LEDOFF, S_WAITOFF, S_TURNOFF, S_ALARMTRIG };

You don't really case what exact value each state has, just that each one is different.

void loop()
{

  static unsigned long ts; // om de huidige tijd voor pauzes in te bewaren
  int reading;
  
  switch (state)
    case S_IDLE:
      Serial.println(state);

      reading = digitalRead(buttonPin);
      ....

blh64:
You really should not declare variables inside your case statement. If you turn on all warnings during compiling inside the IDE, it will warn about not liking that.

Thanks for the tip - I'll declare the variable where you suggested.

blh64:
How do you have your button wired up? Does it contain a pull-up or pull-down resistor? Consider using the built in pull-up resistors of the arduino, then wire one side of the button to a pin and the other side to ground. When the button is pressed, it reads LOW and when it is not pressed, it reads HIGH.

I'm currently using a 10 kOhm pull-down resistor. To use the built-in pull-up resistor, I just have to set the pinMode to INPUT_PULLUP, right?

blh64:
Also, your different states can more easily be declared using a enumeration:

Thanks for the tip! Do I have to change the switch variable to 'States' (instead of the current 'state') or doesn't that matter?

You really should not declare variables inside your case statement.

The code won't compile for me precisely because of the declaration inside the case statement. This is with IDE version 1.8.5 if it makes a difference

There is a solution for this which just requires the code for the case to be enclosed in { and }

avh:
Thanks for the tip - I'll declare the variable where you suggested.
I'm currently using a 10 kOhm pull-down resistor. To use the built-in pull-up resistor, I just have to set the pinMode to INPUT_PULLUP, right?
Thanks for the tip! Do I have to change the switch variable to 'States' (instead of the current 'state') or doesn't that matter?

You can't have both pull-up and pull-down resistors. Just pick one.
You don't have to change the 'state' variable. enumerations are the same as ints.

Thanks for all your help!

I have implemented your suggestions with good results and have added some code for sending / receiving text messages.

When an alarm occurs, a text is sent to a certain number. If the Arduino receives a text message, the alarm is stopped. (state resets to S_IDLE)

I assumed it would be best practice to use as little global variables as possible and to try and declare variables within the function where they're used - is this correct?

#include <GSM.h>

// states definiëren voor de case loop
enum States { S_IDLE, S_LEDON, S_WAITON, S_LEDOFF, S_WAITOFF, S_TURNOFF, S_ALARMTRIG };

#define PINNUMBER "" // voer hier de PIN-code van de SIM-kaart in

// initializeer GSM-library
GSM gsmAccess; // include a 'true' parameter to enable debug
GSM_SMS sms;

const int buttonPin = 4; // digitale pin waar drukknop aanhangt (+ 10 kOhm pulldown weerstand)
const int ledPin = 13;  // digitale pin voor de LED (ingebouwde weerstand in Arduino)

static int state = S_IDLE;  // beginstate voor de case-loop

void setup()
{
  Serial.begin(9600); // start seriële communicatie met computer
  //Serial.println("Setup-loop");
  pinMode(buttonPin, INPUT_PULLUP);
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, HIGH);

  // Maak verbinding met het GSM-netwerk
  boolean nietVerbonden = true;  // check of er verbinding is met het GSM-netwerk

  while (nietVerbonden)
  {
    if (gsmAccess.begin(PINNUMBER) == GSM_READY) // zodra er verbinding is wordt GSM_READY verzonden
      nietVerbonden = false;
    else
    {
      Serial.println("geen verbinding");
      delay(1000);
    }
  }
  Serial.println("Verbinding met het netwerk - klaar voor gebruik");
  digitalWrite(ledPin, LOW);
  // Serial.println("Setup-loop done");

}

void loop()
{
  int ledState = LOW;         // huidige status van de LED pin
  int buttonState;             // huidige uitlezing van de buttonpin
  int lastButtonState = LOW;   // vorige uitlezing van de buttonpin
  
  unsigned long lastDebounceTime = 0;  // het laatste moment dat de output-pin getoggled was
  unsigned long debounceDelay = 50;    // debounce tijd
  
  static unsigned long ts; // om de huidige tijd voor pauzes in te bewaren
  
  int reading;

  switch (state)
  {

    case S_IDLE: // tijdens deze status wordt er geluisterd of er op de knop wordt gedrukt
      //      Serial.println(state);

      reading = digitalRead(buttonPin);
      if (reading != lastButtonState) {
        lastDebounceTime = millis();
      }

      if ((millis() - lastDebounceTime) > debounceDelay) {
        if (reading != buttonState) {
          buttonState = reading;

          if (buttonState == LOW) {
            ledState = !ledState;
            state = S_ALARMTRIG;  // als er voldoende lang op de knop wordt gedrukt gaan we in alarm
          }
        }
      }
      digitalWrite(ledPin, ledState);
      lastButtonState = reading;
      break;

    case S_LEDON:
      digitalWrite(ledPin, HIGH); // schakel LED in
      ts = millis(); // noteer de huidige tijd
      state = S_WAITON; // ga naar de volgende state
      break;

    case S_WAITON:
      // Serial.println(state);
      if (sms.available()) {  // controleer of er een SMS ontvangen werd
        recvSMS();            // zo ja, draai de subroutine om sms uit te lezen
      }
      // zodra er 1 seconde verstreken is , ga naar de volgende fase
      if (millis() > ts + 1000)
      {
        state = S_LEDOFF;
      }
      break;

    case S_LEDOFF:
      digitalWrite(ledPin, LOW); // schakel LED uit
      ts = millis(); // noteer de huidige tijd
      state = S_WAITOFF;
      break;

    case S_WAITOFF:
      // Serial.println(state);

      // zodra er 1 seconde verstreken is , ga naar terug naar fase S_LEDON
      if (millis() > ts + 1000)
      {
        state = S_LEDON;
      }
      break;

    case S_TURNOFF: // om het alarm uit te schakelen
      ledState = LOW;
      state = S_IDLE;  // ga terug naar de 'IDLE' state
      break;

    case S_ALARMTRIG: // het alarm wordt getriggerd
      sendSMS();  // draai de subroutine om een SMS te sturen
      state = S_LEDON;  // ga naar de LEDON state
      break;

    default:
      state = S_IDLE;
      break;
  }
}

void sendSMS() {
  char remoteNum[13] = "+00000000"; // telefoonnummer om berichten naar te verzenden
  char txtMsg[100] = "ALARM! Please send message back to disable alarm"; // inhoud van de te verzenden SMS
  
  Serial.print("Bericht naar nummer: ");
  Serial.println(remoteNum);

  // geef het bericht weer
  Serial.println("Verzonden bericht:");
  Serial.println(txtMsg);

  // stuur het bericht
  sms.beginSMS(remoteNum);
  sms.print(txtMsg);
  sms.endSMS();
}

void recvSMS() {
  char c; // bevat de text van een SMS
  char senderNumber[13];  // telefoonnummer van de verzender
  
  Serial.print("Bericht ontvangen van: ");
  sms.remoteNumber(senderNumber, 20);
  Serial.println(senderNumber);

  while (c = sms.read()) {
    Serial.print(c);

  }
  //Serial.println("\nEINDE BERICHT");
  // verwijder het bericht van de SIM kaart
  state = S_TURNOFF;
  sms.flush();
  // Serial.println("SMS verwijderd");
}

I assumed it would be best practice to use as little global variables as possible and to try and declare variables within the function where they're used - is this correct?

That is certainly regarded as best practice when working on large projects and particularly when multiple programmers are involved. It stops confusion and outright conflict between variables declared as global, possibly in different places in the code.

As to whether it matters in a single programmer environment in a modest project is, however, open to debate.

Thanks.

Some final questions: what does the 'States' mean in this enum statement?

enum States { S_IDLE, S_LEDON, S_WAITON, S_LEDOFF, S_WAITOFF, S_TURNOFF, S_ALARMTRIG };

Could I just leave it out?

enum { S_IDLE, S_LEDON, S_WAITON, S_LEDOFF, S_WAITOFF, S_TURNOFF, S_ALARMTRIG };

And: most sketches I come across on the internet store small numbers such as pin numbers in integers:

const int buttonPin = 4;

Would it not suffice to store small numbers like that in bytes?

const byte buttonPin = 4;

I realize that this may not matter in most cases, but in situations where memory is scarce it might make a difference?

avh:
Thanks.

Some final questions: what does the 'States' mean in this enum statement?

enum States { S_IDLE, S_LEDON, S_WAITON, S_LEDOFF, S_WAITOFF, S_TURNOFF, S_ALARMTRIG };

Could I just leave it out?

enum { S_IDLE, S_LEDON, S_WAITON, S_LEDOFF, S_WAITOFF, S_TURNOFF, S_ALARMTRIG };

It names the enum. You can leave it out if you want .

And: most sketches I come across on the internet store small numbers such as pin numbers in integers:

const int buttonPin = 4;

Would it not suffice to store small numbers like that in bytes?

const byte buttonPin = 4;

When you declare a variable as 'const' the compiler is clever enough to optimize it away so it really doesn't take up any global space whether it is an int or a byte or a long...

Got it - thanks everyone!

blh64:
You really should not declare variables inside your case statement.

That is utter nonsense. There is absolutely NOTHING wrong with declaring variables within a case statement. It is perfectly valid, and perfectly legal. If you declare the SAME variable within multiple cases, you will have to enclose those cases in {} to avoid a multiple declaration error, but there is otherwise no issue whatsoever with the practice. It also follows the best practice of minimizing the scope of all variables to the greatest extent possible to avoid undesired interactions between different parts of the code.
Regards,
Ray L.

RayLivingston:
That is utter nonsense. There is absolutely NOTHING wrong with declaring variables within a case statement. It is perfectly valid, and perfectly legal. If you declare the SAME variable within multiple cases, you will have to enclose those cases in {} to avoid a multiple declaration error, but there is otherwise no issue whatsoever with the practice. It also follows the best practice of minimizing the scope of all variables to the greatest extent possible to avoid undesired interactions between different parts of the code.
Regards,
Ray L.

Technically true but you do get warnings from the compiler and it is prone to issues, as you point out

Again some questions with regards to variable scoping.

The code has been updated again to include parts to write to LCD et cetera - it's getting quite long now :slight_smile:
(I have included only the first part of the code not to exceed the forum's character limit)

#include "pitches.h"
#include <LiquidCrystal.h>
//-----------------------------------------------
// initializeer GSM-library
#include <GSM.h>
#define PINNUMBER "6315" // voer hier de PIN-code van de SIM-kaart in
GSM gsmAccess; // include a 'true' parameter to enable debug
GSM_SMS sms;
//-----------------------------------------------
// States definiëren voor de 'state machine'
enum { S_IDLE, S_LEDON, S_WAITON, S_LEDOFF, S_WAITOFF, S_TURNOFF, S_ALARMTRIG };
byte state = S_IDLE;  // beginstate voor de case-loop
//-----------------------------------------------
// Pins definiëren
const int buttonPin = A0; // digitale pin waar drukknop aanhangt
const int ledPin = 13;  // digitale pin voor de LED (ingebouwde weerstand in Arduino)
const int buzzerPin = 11;
bool textOnLCD = LOW;
LiquidCrystal lcd(10, 9, 8, 6, 5, 4); // lcd pins: rs, en, d4, d5, d6, d7
//-----------------------------------------------

void setup()
{
  Serial.begin(9600); // start seriële communicatie met computer
  lcd.begin(16, 2);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Boot up");
  delay(500);
  //Serial.println("Setup-loop");

  //------Pin modes definiëren-----
  pinMode(buttonPin, INPUT_PULLUP);
  pinMode(ledPin, OUTPUT);
  pinMode(buzzerPin, OUTPUT);
  digitalWrite(ledPin, HIGH);
  //-----------------------------------------------

  //------Maak verbinding met het GSM-netwerk-----
  Serial.println("Verbinding aan het maken...");
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Verbinde");
  lcd.setCursor(0, 1);
  lcd.print("n GSM..");
  lcd.blink();

  bool nietVerbonden = true;  // check of er verbinding is met het GSM-netwerk
  while (nietVerbonden)
  {
    if (gsmAccess.begin(PINNUMBER) == GSM_READY) { // zodra er verbinding is wordt GSM_READY verzonden
      nietVerbonden = false;
    }
    else
    {
      Serial.println("Geen verbinding");
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Verbindi");
      lcd.setCursor(0, 1);
      lcd.print("ng NOK  ");
      delay(1000);
    }
  }
  Serial.println("Verbinding met het netwerk - klaar voor gebruik");
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Verbonde");
  lcd.setCursor(0, 1);
  lcd.print("n GSM:OK");
  lcd.noBlink();

  delay(1000);
  //-----------------------------------------------

  digitalWrite(ledPin, LOW);
  //Serial.println("Setup-loop done");
}

void loop()
{
  static bool ledState = LOW;         // the current state of the output pin
  static bool lastButtonState = HIGH;   // the previous reading from the input pin
  static bool buttonState;             // the current reading from the input pin
  int reading; // de uitlezing van de button-pin
  unsigned long lastDebounceTime = 0;  // het laatste moment dat de output-pin getoggled was
  unsigned long debounceDelay = 500;    // debounce tijd
  static unsigned long ts; // om de huidige tijd voor pauzes in te bewaren

  switch (state)
  {
    case S_IDLE: // tijdens deze status wordt er ge'luisterd' of er op de knop wordt gedrukt
      //Serial.println(state);
      if (textOnLCD == LOW) {
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("Standby");
        lcd.blink();
        textOnLCD = HIGH;
      }
      reading = digitalRead(buttonPin);
      if (reading != lastButtonState) {
        lastDebounceTime = millis();
      }

      if ((millis() - lastDebounceTime) > debounceDelay) {
        if (reading != buttonState) {
          buttonState = reading;
          if (buttonState == LOW) {
            ledState = !ledState;
            textOnLCD = LOW;
            state = S_ALARMTRIG;  // als er voldoende lang op de knop wordt gedrukt gaan we in alarm
          }
        }
      }
      digitalWrite(ledPin, ledState);
      lastButtonState = reading;
      break;

    case S_LEDON:
      digitalWrite(ledPin, HIGH); // schakel LED in
      tone(buzzerPin, NOTE_A5, 1000); // laat de buzzer weerklinken
      ts = millis(); // noteer de huidige tijd
      state = S_WAITON; // ga naar de volgende state
      break;

    case S_WAITON:
      //  Serial.println(state);
      if (textOnLCD == LOW) {
        lcd.clear();
        lcd.setCursor(0, 1);
        lcd.print("1       ");
        textOnLCD = HIGH;
      }

      if (sms.available()) {  // controleer of er een SMS ontvangen werd
        recvSMS();            // zo ja, draai de subroutine om sms uit te lezen
      }
      //-------- Routine om alarm af te zetten met druk op knop ---------------
      /*
            reading = digitalRead(buttonPin);
            if (reading != lastButtonState) {
              lastDebounceTime = millis();
            }

            if ((millis() - lastDebounceTime) > 50) {
              if (reading != buttonState) {
                buttonState = reading;
                if (buttonState == LOW) {
                  ledState = !ledState;
                  textOnLCD = LOW;
                  state = S_TURNOFF;
                }
              }
            }
            lastButtonState = reading;
      */
      //-----------------------------------------------

      if (millis() > ts + 1000)
      {
        textOnLCD = LOW;
        state = S_LEDOFF;
      }
      break;

    case S_LEDOFF:
      digitalWrite(ledPin, LOW); // schakel LED uit
      noTone(buzzerPin); // schakel buzzer uit
      ts = millis(); // noteer de huidige tijd
      state = S_WAITOFF;
      break;

    case S_WAITOFF:
      //Serial.println(state);
      if (textOnLCD == LOW) {
        lcd.clear();
        lcd.setCursor(0, 1);
        lcd.print("0       ");
        textOnLCD = HIGH;
      }

      // zodra er 1 seconde verstreken is , ga naar terug naar fase S_LEDON
      if (millis() > ts + 1000)
      {
        textOnLCD = LOW; // de LCD mag nieuwe tekst krijgen
        state = S_LEDON;
      }
      break;

    case S_TURNOFF: // om het alarm uit te schakelen
      Serial.println();
      Serial.println("Alarm uitgeschakeld");
      ledState = LOW;
      textOnLCD = LOW;
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Alarm ui");
      lcd.setCursor(0, 1);
      lcd.print("t       ");
      delay(1000);
      state = S_IDLE;  // ga terug naar de 'IDLE' state
      break;

    case S_ALARMTRIG: // het alarm wordt getriggerd
      lcd.clear();
      lcd.noBlink();
      lcd.setCursor(0, 0);
      lcd.print("Alarm ac");
      lcd.setCursor(0, 1);
      lcd.print("tivated!");
      textOnLCD = LOW;
      playMelody();
      sendSMS();  // draai de subroutine om een SMS te sturen
      state = S_LEDON;  // ga naar de LEDON state
      break;

    default:
      state = S_IDLE;
      break;
  }
}

In the loop() function I've declared some static variables to make sure they aren't re-set when looping the code over and over again:

void loop()
{
  static bool ledState = LOW;         // the current state of the output pin
  static bool lastButtonState = HIGH;   // the previous reading from the input pin
  static bool buttonState;             // the current reading from the input pin
  int reading; // de uitlezing van de button-pin
  unsigned long lastDebounceTime = 0;  // het laatste moment dat de output-pin getoggled was
  unsigned long debounceDelay = 500;    // debounce tijd
  static unsigned long ts; // om de huidige tijd voor pauzes in te bewaren

Would it make more sense to declare these variables globally (before setup() ) or doesn't that matter? Are there any (dis-)advantages to keeping the scope restricted to the loop() function?

Would it make more sense to declare these variables globally

No. Limiting the scope is better.

Are there any (dis-)advantages to keeping the scope restricted to the loop() function?

There are no disadvantages. The advantage is that loop() is the only functions, this way, that can change the values in the variables (deliberately, that is).

By the way, you do not need three variables, reading, buttonState, and lastButtonState, to determine that the state of the pin is, or is not, the same as it was last time. You only need two.

None of the variables needs 16 bits to hold 0 or 1.