A Demo-Code explaining the switch-case state-machine and how to do things (almost) in parallel

Here is another variant that tries to explain how state-machines work and how state-machines enable to keep the code responsive even if you want to execute some parts of the code only from time to time (once every second or once every 10 minutes or whatever time-interval)

Realising fast responsivnes in combination with executing code only from time to time is a medium complex task. It requires non-blocking timing which is a extra part.
If you are not yet familiar with non-blocking timing I recommend that you read this tutorial first

read this first to get the overview

The code that demonstrates this has quite a lot of lines of code.
I highly recommend that you continue reading this text to get introduced to it through getting the overview.

The code below shows 4 lines in the serial monitor
similar to this example

left  Z/A changes 20 times every 0,5 seconds
right X/O changes 10 times every 1,0 seconds
state that blinks A / Z on the left
Z:5#44998015X:0

It might be that the lines are jumping up/down a little bit.
This is caused by the special way the serial monitor is used here similar to an LC-Display.

A state-machine has mutliple modes of operation which are called the "states"
The 3rd lines shows in which state the state-machine is at the moment.
The 4th line has 5 parts

  1. a letter on the left changing between "A" and "Z" once every 0,5 seconds

  2. Then there is a number starting at 0 counting up until 20

  3. in the middle the is a number that counts up very fast

  4. then there is a letter "X" or "O" changing once per second

  5. and a number counting up from 0 to 10

This visualises multiple things

the number in the middle that counts up very fast shows how fast (10000 times per second) the loop is running

The letter A/Z and the number show how things can be executed only from time to time in parallel to the very fast incrementing of the number in the middle

The letter O/X and the number show how things can be executed only from time to time and how you can change between different states like "blinking A/Z" and "blinking "X/O"

To understand all details will take some time.
Especially as a beginner you are invited to ask as many questions as you like if you don't understand something not or if you have difficulties to adapt this demo-code to your own application.

I explicitly want to improve this explanation based on beginners questions.
So please feel free to ask whatever you like

Introduction to the code. First have look at
void loop()

void loop() {      // function loop is looping VERY fast
  // the code-lines below are executed 10000 times per second or even faster
  // you can see this through the values of myCounter
  myCounter++;     
  myStateMachine();
  printVisualisation();
}

void loop() is pretty short. It works that way that each function is called very often.
Jumping into the function myStateMachine(); and after a very short time jump out again
Then the same happends with printVisualisation();
this repeats 10000 times per second.

This basic principle is absolutely crucial
it has to jump in / jump out very quickly to make it work
And it is diametral different to linear-sequential coding based on delay()

I call this circular-repeating coding.

You can imagine the state-machine similar to a car engine.
You can start the engine and the engine will rotate at idling with 600 rotations per minute
= 10 rotations per second. Pretty fast.

The engine is already running without any further action.
Now you can initiate all kinds of "actions" :

switching on the lights
switching on direction lights to blink
pushing the clutch then engaging first gear
releasing the clutch slowly to start driving etc.

each of these "actions" is similar to a state of the state-machine.
engine running is similar to jump in / jump out the function that is the state-machine

Certain things can initiate a new state:
switching direction-lights on to blink left
you push the blink-lever => direction-light start to blink

Some condition becomes true ==> which sets state-machine to a certain state executing the lines of code belonging to that state mutually exclusive.

The code of all other states is not executed.
This behaviour is caused by the break;-statements.

Now you are prepared to examine the code. There will still be questions and it will take time to understand the code.

#define dbg(myFixedText, variableName) \
  Serial.print( F(#myFixedText " "  #variableName"=") ); \
  Serial.println(variableName);
// usage: dbg("1:my fixed text",myVariable);
// myVariable can be any variable or expression that is defined in scope

#define dbgi(myFixedText, variableName,timeInterval) \
  { \
    static unsigned long intervalStartTime; \
    if ( millis() - intervalStartTime >= timeInterval ){ \
      intervalStartTime = millis(); \
      Serial.print( F(#myFixedText " "  #variableName"=") ); \
      Serial.println(variableName); \
    } \
  }

char A_or_Z = 'A';
char X_or_O = 'X';

unsigned long myCounter;

byte Count_AZ_Changes = 0;
byte Count_XO_Changes = 0;

const byte Idling = 0;
const byte BlinkLetterOnTheLeft  = 1;
const byte BlinkLetterOnTheRight = 2;
const byte ResetCounterToZero    = 3;
const byte WaitBeforeRepeating   = 4;

byte myStateVar;

unsigned long BlinkTimer; // variable used
unsigned long WaitTimer;


void setup() {
  Serial.begin(115200);
  Serial.print( F("\n Setup-Start  \n") );
  myCounter = 0;
  Count_AZ_Changes = 0;
  Count_XO_Changes = 0;
  myStateVar = WaitBeforeRepeating; // initialise statevariable named "myStateVar"
  WaitTimer = millis();      // store snapshot of time
}


void myStateMachine() {

  switch (myStateVar) {

    case Idling:
      Serial.println();
      Serial.println();
      Serial.println();
      dbgi("I just do nothing", myCounter, 100); // once every 100 milliseconds print
      break; // immidiately jump down to END-OF-SWITCH


    case BlinkLetterOnTheLeft:
      if ( TimePeriodIsOver(BlinkTimer, 500) ) { // check if 500 milliseconds have passed by
        // if 500 milliseconds REALLY have passed by
        Change_AZ();
        Count_AZ_Changes++;  // increment counter-variable by 1
      }

      if (Count_AZ_Changes >= 20) {   // check if 20 changes have been done
        // if 20 changes REALLY have been done
        //Count_AZ_Changes = 0;               // reset AZ_Counter
        //Count_XO_Changes = 0;               // reset XO_Counter
        BlinkTimer = millis();              // store snapshot of time
        myStateVar = BlinkLetterOnTheRight; // change to next state
      }
      break; // immidiately jump down to END-OF-SWITCH


    case BlinkLetterOnTheRight:
      if ( TimePeriodIsOver(BlinkTimer, 1000) ) { // check if 1000 milliseconds have passed by
        // if 1000 milliseconds REALLY have passed by
        Change_XO();
        Count_XO_Changes++;  // increment counter-variable by 1
      }
      if (Count_XO_Changes >= 10) {   // check if 10 changes have been done
        // if 10 changes REALLY have been done
        //Count_AZ_Changes = 0;               // reset AZ_Counter
        //Count_XO_Changes = 0;               // reset XO_Counter
        WaitTimer = millis();               // store snapshot of time
        myStateVar = WaitBeforeRepeating;   // change to next state
      }
      break; // immidiately jump down to END-OF-SWITCH


    case WaitBeforeRepeating:
      if ( TimePeriodIsOver(WaitTimer, 4000) ) { // check if 4000 milliseconds have passed by
        BlinkTimer = millis();              // store snapshot of time
        Count_AZ_Changes = 0;               // reset AZ_Counter
        Count_XO_Changes = 0;               // reset XO_Counter
        myStateVar = BlinkLetterOnTheLeft;
      }
      break; // immidiately jump down to END-OF-SWITCH

  } // END-OF-SWITCH
}


void loop() {      // function loop is looping VERY fast
  // the code-lines below are executed 10000 times per second or even faster
  // you can see this through the values of myCounter
  myCounter++;     
  myStateMachine();
  printVisualisation();
}


// several functions with the details what has to be done
void Change_AZ() {
  if (A_or_Z == 'A') { // check if character is an 'A'
    A_or_Z = 'Z';      // change to 'Z'
  }
  else {
    A_or_Z = 'A';      // change to 'A'
  }
}


void Change_XO() {
  if (X_or_O == 'X') { // check if character is an 'X'
    X_or_O = 'O';      // change to 'O'
  }
  else {
    X_or_O = 'X';      // change to 'X'
  }
}

void clearSerialMonitor() {
  for (int i = 0; i < 50; i++) { // print 50 empty lines to clear screen
    Serial.println();
  }
}

unsigned long MySlowDownTimer;

void printStateInfo() {
    if ( myStateVar == BlinkLetterOnTheLeft) {
      Serial.println("state that blinks A / Z on the left");      
    }

    if ( myStateVar == BlinkLetterOnTheRight) {
      Serial.println("state that blinks X / O on the right");      
    }

    if ( myStateVar == WaitBeforeRepeating) {
      Serial.println("waiting 4 seconds before new cycle starts");      
    }  
}

void printVisualisation() {
  if ( TimePeriodIsOver(MySlowDownTimer, 50) ) { // check if 50 milliseconds have passed by
    // if 50 milliseconds HAVE passed by
    clearSerialMonitor();
    Serial.println("left  Z/A changes 20 times every 0,5 seconds");
    Serial.println("right X/O changes 10 times every 1,0 seconds");
    printStateInfo();
    
    Serial.print(A_or_Z);
    Serial.print(":");
    Serial.print(Count_AZ_Changes);
    Serial.print("#");
    Serial.print(myCounter);
    Serial.print("#");
    Serial.print(X_or_O);
    Serial.print(":");
    Serial.print(Count_XO_Changes);
    Serial.println();
  }
}

// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - startOfPeriod >= TimePeriod ) {
    // more time than TimePeriod has elapsed since last time if-condition was true
    startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    return true;
  }
  else return false;            // actual TimePeriod is NOT yet over
}
1 Like