Beginners: using the switch - case statement

The switch - case statement is a powerful construct that is often under-used by beginners.

Basically it allows you to perform tests on a value (or range of values) and make decisions - a bit like the IF statement.

This tutorial has three examples that progressively introduce some simple ideas to help you use the switch construct in your programming.

You will need an Arduino Uno (the programs will run on most arduinos) three leds - preferably red, yellow, green - some resistors around 220 ohms - 2k2 - and a potentiometer.

  • Example 1 which follows tests a value from the ADC and shows "good" and "alarm" conditions on LEDs
  • Example 2 reads values from the keyboard via the serial monitor and identifies them as capitals , numbers etc.
  • Example 3 uses the switch construct with an EASY example of millis() timing to build a traffic light "state machine". STILL EASY.

Example 1:
Suppose you've read in a value from a potentiometer using the ADC. We will start by dividing it by 64 to get a value "reading" holding a number in the range 0 -- 15.

Now we will use some LEDs to show the state of the value.

0 - red; 1 - red & amber; 2 to 4 - amber; 5 to 14 - green; and 15 - green & amber.

You can easily do this with nested if statements - but here's how to do it with "switch"

switch (reading) {
//here we are testing for a single value
    case 0: { // put the red LED on
        }
      break;

    case 1 : { //red and amber on
      }
      break;

//you can also test for a range of values
    case 2 ... 4 : { // just show amber
      }
      break;

    case 5 ... 14: { // all good - show green
      }
      break;

    case 15: { //bit high - show green and amber
      }
      break;

  }

Note - in specifying a range you must use this exact format

case (low value) space three dots space (high value): as shown above.

OK I hate Fritzing but here is how to connect the potentiometer and leds to your Uno.

swfritz.png

And now the full code for the sketch:

/*
   This simple sketch is an example showing how the switch - case structure can be used to avoid nested if statements
   LEDs are connected to pins 2,3,4 on the uno, with 470 ohm resistors to ground
   a potentiometer between 0V and +V provides a variable input to A0
   J. Errington 23 October 2020
*/

// assign pin numbers for the leds
const byte led_R = 4;
const byte led_Y = 3;
const byte led_G = 2;

//input on analog pin 0
const byte vIn = A0;

int reading;

void setup() {
  Serial.begin(9600);
  pinMode(led_R, OUTPUT);
  pinMode(led_Y, OUTPUT);
  pinMode(led_G, OUTPUT);

  reading = analogRead(vIn); // dummy read to settle ADC
}

void loop() {
  reading = analogRead(vIn);
  Serial.println(reading);
  reading = reading / 64;  //just to get easy numbers
  delay(100);
  // voltage warning lights
  switch (reading) {
    case 0: { //R
        digitalWrite(led_R, HIGH);
        digitalWrite(led_Y, LOW);
        digitalWrite(led_G, LOW);
      }
      break; // try the effect of commenting this out

    case 1 : { // RA
        digitalWrite(led_R, HIGH);
        digitalWrite(led_Y, HIGH);
        digitalWrite(led_G, LOW);
      }
      break;

    case 2 ... 4 : { //A
        digitalWrite(led_R, LOW);
        digitalWrite(led_Y, HIGH);
        digitalWrite(led_G, LOW);
      }
      break;

    case 5 ... 14: { //G
        digitalWrite(led_R, LOW);
        digitalWrite(led_Y, LOW);
        digitalWrite(led_G, HIGH);
      }
      break;

    // if its not any of the above then it must be 15.
    default: { //GA
        digitalWrite(led_R, LOW);
        digitalWrite(led_Y, HIGH);
        digitalWrite(led_G, HIGH);
      }
  }
}//loop

"default" is just a catch-all case for any values that dont match the specified tests.

Example 2: testing characters follows.

swfritz.png

switch_warnvolts.ino (1.69 KB)

1 Like

Example 2: Testing non-numeric values

The switch statement can use any variable of integer type - char, signed or unsigned integer, or enumeration. Here is a simple example that reads in a character and tests what it is.
The example also shows how you can test for a set of specific cases.

if you are using the serial monitor you will need to select "no line ending" else you will see lots of non-print characters.

/*
   This simple sketch is an example showing how the switch - case structure can be used with character values
   J. Errington 28 October 2020
*/

char letter;
bool ready = 1; //flag ready for next character input

void setup() {
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB
  }
}

void loop() {
  if (ready) {
    Serial.println("\n Please enter a character from the keyboard");
    ready = 0;
  }
  // send data only when you receive data:
  if (Serial.available() > 0) {
    ready = 1;
    // read the incoming byte:
    letter = Serial.read();
    delay(10);
    Serial.print("You entered: ");
    Serial.print(letter);

    switch (letter) {
      //because there is no "break" any of the following four cases will result in the serial.println being executed
      case ',':
      case '.':
      case ';':
      case ':': {
          Serial.println(" - its punctuation");
        }
        break;
      case 'A' ... 'Z' : {
          Serial.println(" - its a capital");
        }
        break;
      case 'a' ... 'z' : {
          Serial.println(" - its a miniscule");
        }
        break;
      case '0' ... '9' : {
          Serial.println(" - its a number");
        }
        break;
      default: {
          Serial.println(" - its a symbol or npc");
        }
    }
  }

}//loop

and now - Example 3 a VERY SIMPLE state machine using millis() for timing

switch_chars.ino (1.38 KB)

1 Like

Example 3: A SIMPLE state machine to show the traffic light sequence

A single traffic light is a good example of a system that can have clearly defined states; and the switch construct makes it easy to see how this can be implemented as a state machine.

Use the same arrangement of LEDs (see the fritzing) that you used in the first example.

This code also introduces the ENUM type declaration, and a very easy way to understand millis() timing.

At the start of each state we record the time "timeStart". Then the elapsed time "timeElapsed" in that state is the difference between the time now - milllis() - and the start time.

millis() - timeStart = timeElapsed

When the elapsed time exceeds the time required in that state timeStart is reset and the next state selected.

millis1.png

//*
   This simple sketch is an example showing how the switch - case structure can be used to implementa state machine
   LEDs are connected to pins 2,3,4 on the uno, with 470 ohm resistors to ground
   J. Errington 29 October 2020
*/
// assign pin numbers for the leds
const byte led_G = 2;
const byte led_Y = 3;
const byte led_R = 4;


enum vtlStates {AMBER, RED, RA, GREEN}; // the states a single traffic light can be in
// the above line makes a variable data type "vtlStates" and assigns values 0, 1, 2, 3 to AMBER, RED, RA, GREEN
vtlStates tlState;  //create a variable tlState

/*
*** if youre not happy with enum, this also works ***
  const byte AMBER=0, RED=1, RA=2, GREEN=3;
  byte tlState;
*/

const int timings[] = {3000, 10000, 2000, 10000}; //array to hold the time for each state AMBER, RED, RA, GREEN
unsigned long timeStart, timeElapsed ; //will hold the time the state started and the time elapsed in the current state

void setup() {
  pinMode(led_G, OUTPUT);
  pinMode(led_Y, OUTPUT);
  pinMode(led_R, OUTPUT);
  tlState = AMBER; //start with the amber on
  timeStart = millis();
}

void loop() {
  switch (tlState) {
    case AMBER : {
        //set the lights for this state
        digitalWrite(led_R, LOW);
        digitalWrite(led_Y, HIGH);
        digitalWrite(led_G, LOW);
        //is it time for the next state?
        timeElapsed = millis() - timeStart;
        if (timeElapsed > timings[AMBER]) {
          //restart the timer and change the state
          timeStart = millis();
          tlState = RED;
        }
      }
      break;

    case RED: {
        digitalWrite(led_R, HIGH);
        digitalWrite(led_Y, LOW);
        digitalWrite(led_G, LOW);
        //is it time for the next state?
        timeElapsed = millis() - timeStart;
        if (timeElapsed > timings[RED]) {
          //restart the timer and change the state
          timeStart = millis();
          tlState = RA;
        }
      }
      break;

    case RA: {
        digitalWrite(led_R, HIGH);
        digitalWrite(led_Y, HIGH);
        digitalWrite(led_G, LOW);
        //is it time for the next state?
        timeElapsed = millis() - timeStart;
        if (timeElapsed > timings[RA]) {
          //restart the timer and change the state
          timeStart = millis();
          tlState = GREEN;
        }
      }
      break;

    case GREEN: {
        digitalWrite(led_R, LOW);
        digitalWrite(led_Y, LOW);
        digitalWrite(led_G, HIGH);
        //is it time for the next state?
        timeElapsed = millis() - timeStart;
        if (timeElapsed > timings[GREEN]) {
          //restart the timer and change the state
          timeStart = millis();
          tlState = AMBER;
        }
      }
      break;
  }
}//loop

ENUM and timings[] array explained

This is all very simple. (ROFL)

enum vtlStates {AMBER, RED, RA, GREEN};

this makes a new type of variable. It can only take one of the listed values, and it assigns numbers to them in order, starting at zero.

tlState is a number that we will use to hold the current state; so we COULD declare it as
byte tlState; where byte is the type and tlState the name of the variable.

Instead we declare it as
vtlStates tlState; where vtlStates is the type, and tlState the name of the variable.

This allows us to use meaningful names (AMBER etc) for the system state.

const int timings[] = {3000, 10000, 2000, 10000};

timings[] is an array - a set of integer values (declared as constants) - so in memory it looks like this:

[3000] [10000] [2000] [10000]

The position of a value in array is called its index; starting at zero.
So timings[0] is 3000; timings[1] = 10000; timings[2]=2000 and timings[3] = 10000;

When using arrays you need to be careful not to exceed the region used (the “bounds” of the array); we dont know what we would find in timings[4]!

By using an enumerated type we can get values from the array as
timings[AMBER] - which is the same as timings[0];

it also protects from exceeding the array bounds because we can not have a value of 4 in “tlState”, the only values allowed are AMBER, RED, RA, GREEN corresponding to 0,1,2,3

millis1.png

switch_tl_state.ino (2.77 KB)

2 Likes

I used it this week to display a check screen before starting with the proper program on the second pass through.
It is pretty handy...

I would humbly offer two suggestions to enhance that:

  • Put all the switch..case stuff in a function and call it from loop(). That keeps loop() nice and clean, and it becomes a sort of textual-flow-chart to aid the reader in the logic of the sketch. (In real life there may be many more functions, and it's a good practice (imo) to have loop() doing little else than call them.)
  • Add blink without delay in a function too, also called from loop(). Then (especially for the beginner) it's easy to see that the traffic light timing is indeed delay()-less, since bwod is going at the same time.

I have a state machine template along those lines, whose loop() is:

void loop()
{
  doBWOD();
  manageStates();
} //loop

manageStates() is a minimal but compilable switch..case block to act as a place-holder and I just copy/paste as many cases as I need and fill in the specifics.

Thanks Leandra; can you post your "manageStates" code here please?

Hii, This approach looks good and can I know whether the implementation of this code with other Arduino boards effects anything.

Hi Vishkas; no it should work on any arduino-ish board. I've used it on Uno, Mega, Micro, 32U4 and ESP

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.