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

Hi everybody,

if you look at the end of this first posting you see a list of links that show where this tutorial was used as a link. Go through this list to find additional information on how to apply this code

This democode requires only the pure microcontroller without any hardware. You just open the serial monitor to watch the behaviour of the code.

From time to time I enjoy writing demo-codes that want to explain programming-functionalities.
There are a lot of different approachs to explain something. This is meant as add another variant
to offer a wider range of how it can be explained. If you don't like it or don't understand it
just skip and try the next one.

Sooner or later almost every coder comes across that she/he needs a functionality that does
two, three or more things "at the same time".

And/or several steps must be executed in a well defined sequence

or even more complex if A then go on way 1 if B continue with way 2
including but if C happens then do "9" !

In the real world billions of variations of this functionality can be found
so I want to add some typical cases here to make it easier to find this thread

blink LED while motor is running

create 5 pulses ON/OFF then go on (IO-pin LOW / HIGH)

IO-Pin HIGH for 5 seconds then .....

one IO-pin LOW for 2 minutes in parallel blink LED / check for .....

(I will add real cases over time as they appear as questions in the forum)

So here is the demo-code. It uses the serial monitor to show what is going on in the code.
I think this will support to understand the principles. The explanations are medium long
So it will not cover everything but goes beyond "here is the plain code analyse it yourself"

so here is the demo-code

// this demo-program shows how to use the switch-case statement
// to create a functionality as described at the bottom of this file

unsigned long myCounter;

const byte sayHello      = 0;
const byte startCounting = 1;
const byte countTo10     = 2; 
const byte sayGoodbye    = 3;
const byte wait5seconds  = 4;

byte myStateVar;

unsigned long waitingTime = 5000;
unsigned long WaitingTimer; // timer variable for non-blocking timing

unsigned long oneSecondTimer; // timer variable for non-blocking timing

void setup() {
  Serial.begin(115200);
  Serial.print( F("\n Setup-Start  \n") );
  myCounter = 0;
  // initialise state-variable to that state the state-machine 
  // shall start with
  myStateVar = sayHello; 
}


// helper-function for easy to use non-blocking timing
boolean TimePeriodIsOver (unsigned long &StartTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();  
  if ( currentMillis - StartTime >= TimePeriod ){
    StartTime = currentMillis; // store timestamp when the new interval has started
    return true;               // more time than TimePeriod) has elapsed since last time if-condition was true
  } 
  else return false; // return value false because LESS time than TimePeriod has passed by
}


void myStepChain() {
    
  switch (myStateVar) {
    case sayHello:
      Serial.println( F("Hello user!") );
      myStateVar = startCounting;
      break;

    case startCounting:
      myCounter = 0; // reset counter-variable
      Serial.println( F("I start counting very fast") );
      myStateVar = countTo10;
      break;     

    case countTo10:
      // there is no slowing down through non-blocking timing 
      // in this state. These lines get executed very fast
      // counting up to 10 in less than one MILLIsecond
      myCounter++;
      Serial.print( F("I'm counting up counter=") );
      Serial.println(myCounter);

      if (myCounter == 10) {
        myStateVar = sayGoodbye;
      }
      break;

    case sayGoodbye:
      Serial.println( F("goodbye see you next round in 5 seconds") );
      WaitingTimer = millis(); // store timestamp when the waiting STARTS
      myStateVar = wait5seconds;
      break;

    case wait5seconds:
      // this condition changes the state after 5000 milliseconds
      if ( TimePeriodIsOver(WaitingTimer,5000) ) {
        // more time than 5000 milliseconds has passed by
        Serial.println( F("5 seconds waited starting new cycle.. ") );
        Serial.println();
        Serial.println();
        myStateVar = sayHello; // reset to starting step to repeat
      }
      
      myCounter++; // counts up VERY fast because in a state-machine 
                   // code-execution is always fast
                   // if you need to execute only from time to time
                   // this is done by non-blocking timing like coded below
      // non-blocking timing:             
      if ( TimePeriodIsOver(oneSecondTimer,1000) ) {
        // more than 1000 milliseconds have passed by
        Serial.print( F("one second over") );
        Serial.print( F(" value of myCounter=") );
        Serial.println(myCounter);
      }
      break;            
  }    
  //delay(100);
}


void loop() {
  myStepChain();
}



/* pre-ambel:  programming something more complex than  
   switch LED on wait 1 second switch LED off wait 1 second
   requires knowledge. More or less complex knowledge
   And this needs a rather big minimum of words to explain 
   That is the reason why this text has more than two lines
   
in writing programs it is always a good idea to write down
the functionality of the program in NORMAL WORDS.
This code uses the serial monitor to make visible what the code
is doing. So no additional hardware is required

The functionality of this program is 
1. say "Hello"  (printing this message only ONCE)
   
2. inform "I initiate counting" (printing this message only ONCE)

3. counting up to 10 VERY fast (printing each number)

4. say "good bye" (printing this message only ONCE)

5. wait 5 seconds 
  but not only waiting! 
  in PARALLEL to the waiting a counter is counting up very fast
  in PARALLEL to the waiting print a message once per second 

repeat this pattern.

This means the program is doing different things in a defined order
and is able to do a thing A in parallel to a thing B and in parallel 
to a thing C

This basic principle can be transferred to things like
- an LED is blinking while a display shows a message 
  "press button to start"


- a motor is switched on to run while in parallel a LED is blinking
  and the motor stops after X seconds

    
- a motor is switched on to run while in parallel a LED is blinking
  and the motor stops if a button is pressed / or a switch is closed


- a heating is switched on starting to heat and in parallel aquire
  new temperature-measurings until a certain temperature is reached 
  then switch of the heating
  while in parallel a humifier is switched on to increase humidity
  and in parallel new humidity-measurings are aquired until 
  a certain level is reached and then switch off

  where the times it takes do reach the right temperature-level
  and humidity-level can be very different


###### everyday analogon 
  The basic principle behind this is to make a servant watching 
  the scene and when certain things happen take a short action

  servant stay a little aside from the dining table and watch
  all guests dining
  if somebodies glas is empty go over and refill the glass
  keep an eye on the soup-pot if the pot is empty shout to the cook
  "bring a new pot"

  The servant is changing his focus very quickly from task to task
  this could be described as step in (a certain task) step out - repeat
  check if a glas is empty
  change to
  check soup-level
  change to 
  ....
  
  in opposite to: 
  walk over to the soup-pot fix your eyes on the soup-level and if 
  soup-level is low enough shout for the cook to bring a new pot

  This means your program is entering a first "task" 
  does a single step and then quickly leaves the first "task" 
  to enter the second "task"
  does a single step and then quickly leaves the second "task" 
  ....

  the repeating is taken to a higher level

loop() {
  task1(); // enter and quickly leave again 
  task2(); // enter and quickly leave again 
  task3(); // enter and quickly leave again 
  ...  
}

if each task requires multiple steps to be done
this is done by a switch-case-statement

like refilling a glas
- open bottle
- bring bottle into position to pour
- turn neck of the bottle down
- pour wine into the glas checking the level
- if glas is filled 
- turn neck of the bottle up
- close bottle
- move back to watching position

  after opening the bottle and bring bottle into position
  the servant is able to take a quick look to the soup-pot 
  seeing still enough soup
  OK let's just pour the wine
  or if soup-pot is empty shouting "cook bring a new pot with soup"
  
  Which means he is entering and leaving a task quickly to do 
  a small step of another task

  That is how multi-tasking programming works
  and it is done by using one BIG L----O----O----P

loop() {
  task1(); // enter and quickly leave again 
  task2(); // enter and quickly leave again 
  task3(); // enter and quickly leave again 
  ...  
}
  and quickly jumping in/out functions where each function has its
  own switch-case-statement to work through sequential steps
  that must be done in a defined order
*/

best regards Stefan

6 Likes

cool sharing. thanks, I'm sure it will be helpful.

Couple suggestion to "simplify" the code

Replace enum myStates myStateVar; with myStates myStateVar; (enum not needed).

Replace

with

inline boolean oneSecondIsOver(unsigned long StartTime, unsigned long rightNow) {
  return (rightNow - StartTime) >= 1000ul;
}

Side note: given waitingTime is a variable, I would not hardwire 5s in the state name wait5seconds as you could change that to a different duration.

1 Like

Nice example of using states.
For your task's you could use this diagram
multitaskingDiagramSmall

2 Likes

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

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
}

Hello Stefan, thanks very much for this example. I was locking for such function. One question I have: The variable oneSecondTimer is not initialized with millis(), so how does the second call of if ( TimePeriodIsOver(oneSecondTimer,1000) ) work?
Thanks for helping and best regards
Markus

the oneSecondTimer is passed by reference (the & after the type and before the parameter in the function) so the function does modify directly the variable

Hello Jackson, thanks for explaining. Yes called by reference. But what is the contant of oneSecondTimer? Means how is the calculation working for

currentMillis (= actual millis time)  - StartTime(what value?) >= TimePeriod 

Thanks for helping again. Best regards Markus

Hi Markus,

in the step before waiting 5 seconds the timer-variable is initialised with the actual values of millis()

state is changed to "wait5seconds"

TimePeriodIsOver(WaitingTimer,5000)

means
TimePeriodIsOver(12005,5000)

inside function TimePeriodIsOver

currentMillis = millis(); (with millis() now beeing 12006

if ( currentMillis - StartTime >= TimePeriod )
if (12006 - 12005 >= 5000 evaluates to false until
if (17005 - 12005 = 5000 evaluates to true
and then

StartTime = currentMillis;

StartTime = 17005;
as StartTime is called by reference this means

variable WaitingTimer gets updated to 17005
and then everything repeats

best regards Stefan

1 Like

Hello Stefan, now understood. Thanks for detailed explaining.
Best regards Markus

@StefanL38 , yours is th best explanation I've personally seen about using switch/case decision making process in conjunction with things that only need to take some set period of time then bail out. This example you generously shared would make a great Arduino game framework for game stages and in-between stages transitions.

Frankly, I wish examples like these were in the IDE. It should go like: Basic Examples > Examples (as they are) but also: Applied Examples > State Machine > Timer Driven States (or things like that - things that outline how to do the things folks commonly actually try to do with Arduino beyond blinking LEDs for the sake of learning to blink LEDs).

1 Like

Here is another example-code from a real application which uses multiple "waitings"

At the beginning of the code there are 3 macros for comfortable serial debugoutput
For understanding the state-machine you can jump over the macros at first.

You can imagine a statemachine as a machine that has different modes of operation
in this case these mode of operations are coded as constants used by the state-machine.

You should use self -explaining names that describe SPOT-ON what the state is doing

const byte sm_wait_1minute   = 0;
const byte sm_switchPowerOn  = 1;
const byte sm_wait_300ms     = 2;
const byte sm_ReadSendTemp   = 3;
const byte sm_wait_200ms     = 4;
const byte sm_switchPowerOff = 5;

This is the code

// MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START *
// Take it for granted at the moment scroll down to void setup
// start of macros dbg and dbgi
#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) \
  do { \
    static unsigned long intervalStartTime; \
    if ( millis() - intervalStartTime >= timeInterval ){ \
      intervalStartTime = millis(); \
      Serial.print( F(#myFixedText " "  #variableName"=") ); \
      Serial.println(variableName); \
    } \
  } while (false);
// usage: dbgi("2:my fixed text",myVariable,1000);
// myVariable can be any variable or expression that is defined in scope
// third parameter is the time in milliseconds that must pass by until the next time a
// Serial.print is executed
// end of macros dbg and dbgi
//
// print only once when value has CHANGED
#define dbgc(myFixedText, variableName) \
  do { \
    static long lastState; \
    if ( lastState != variableName ){ \
      Serial.print( F(#myFixedText " "  #variableName" changed from ") ); \
      Serial.print(lastState); \
      Serial.print( F(" to ") ); \
      Serial.println(variableName); \
      lastState = variableName; \
    } \
  } while (false);
// MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END *

// the states for the state-machine
const byte sm_wait_1minute   = 0;
const byte sm_switchPowerOn  = 1;
const byte sm_wait_300ms     = 2;
const byte sm_ReadSendTemp   = 3;
const byte sm_wait_200ms     = 4;
const byte sm_switchPowerOff = 5;

byte myState = sm_wait_1minute;

const unsigned long powerOnWaitTime = 300;
const unsigned long FinishSendingWaitTime = 200;
const unsigned long PausingTime = 10000; // increase to 600000 for 10 minutes

unsigned long Waiting60SecsTimer;
unsigned long Waiting_ms_Timer;

void myReadSendTemp() {
  Serial.println("Read and send temperature");
  Serial.println();
}

void SwitchPowerOn() {
  Serial.println("Switching Power on");
  Serial.println();
}

void SwitchPowerOff() {
  Serial.println("Switching Power OFF");
  Serial.println();
}

void myReadSendTempStateMachine() {
  dbgc("statechange",myState);
  
  switch (myState) {
  
    case sm_wait_1minute:
      // only once every 2000 milliseconds print 
      dbgi("waiting ",millis() - Waiting60SecsTimer,2000);
      
      if ( TimePeriodIsOver(Waiting60SecsTimer, PausingTime) ) {
        Serial.println();
        Serial.println();
        Serial.println("next Time to read and send temp has come");
        myState = sm_switchPowerOn;
      }
      break; // immidiately jump down to End-of-Switch


    case sm_switchPowerOn:
      // lines of code to switch power on
      SwitchPowerOn();
      Serial.println("start waiting"); 
      Waiting_ms_Timer = millis();
      myState = sm_wait_300ms;
      break; // immidiately jump down to End-of-Switch

    case sm_wait_300ms:
      // only once every 100 milliseconds print 
      dbgi("waiting",millis() - Waiting_ms_Timer,100);
      
      if ( TimePeriodIsOver(Waiting_ms_Timer, powerOnWaitTime) ) {
        Serial.println("waiting over"); 
        myState = sm_ReadSendTemp;
      }
      break; // immidiately jump down to End-of-Switch


    case sm_ReadSendTemp:
      // code to read and sedn temperatur
      myReadSendTemp();
      Serial.println("start waiting");
      Waiting_ms_Timer = millis();
      myState = sm_wait_200ms;
      break; // immidiately jump down to End-of-Switch


    case sm_wait_200ms:
      // only once every 100 milliseconds print 
      dbgi("waiting",millis() - Waiting_ms_Timer,100);

      if ( TimePeriodIsOver(Waiting_ms_Timer, FinishSendingWaitTime) ) {
        Serial.println("waiting over");
        myState = sm_switchPowerOff;
      }
      break; // immidiately jump down to End-of-Switch


    case sm_switchPowerOff:
      // code to switch power off
      SwitchPowerOff();
      Serial.println("initialise waiting for next sending");
      Serial.println();
      Serial.println();
      Waiting60SecsTimer = millis();
      myState = sm_wait_1minute;
      break; // immidiately jump down to End-of-Switch
  } // End-of-Switch

}


void PrintFileNameDateTime() {
  Serial.println( F("Code running comes from file ") );
  Serial.println( F(__FILE__) );
  Serial.print( F("  compiled ") );
  Serial.print( F(__DATE__) );
  Serial.print( F(" ") );
  Serial.println( F(__TIME__) );
}


// 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
}

unsigned long MyTestTimer = 0;                   // Timer-variables MUST be of type unsigned long
const byte    OnBoard_LED = 13;


void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
  static unsigned long MyBlinkTimer;
  pinMode(IO_Pin, OUTPUT);

  if ( TimePeriodIsOver(MyBlinkTimer, BlinkPeriod) ) {
    digitalWrite(IO_Pin, !digitalRead(IO_Pin) );
  }
}


void setup() {
  Serial.begin(115200);
  Serial.println("Setup-Start");
  PrintFileNameDateTime();
}


void loop() {
  BlinkHeartBeatLED(OnBoard_LED, 100);
  myReadSendTempStateMachine();
}

and this is how the serial output looks like if you have activated the time-stamp in the serial monitor

09:58:43.680 -> Setup-Start
09:58:43.680 -> Code running comes from file 
09:58:43.680 -> F:\myData\Arduino\pilutz-StateMachine-001\pilutz-StateMachine-001.ino
09:58:43.680 ->   compiled Dec 28 2022 09:58:37
09:58:45.679 -> "waiting " millis() - Waiting60SecsTimer=2000
09:58:47.668 -> "waiting " millis() - Waiting60SecsTimer=4000
09:58:49.650 -> "waiting " millis() - Waiting60SecsTimer=6000
09:58:51.671 -> "waiting " millis() - Waiting60SecsTimer=8000
09:58:53.679 -> "waiting " millis() - Waiting60SecsTimer=10000
09:58:53.679 -> 
09:58:53.679 -> 
09:58:53.679 -> next Time to read and send temp has come
09:58:53.679 -> "statechange" myState changed from 0 to 1
09:58:53.679 -> Switching Power on
09:58:53.679 -> 
09:58:53.679 -> start waiting
09:58:53.679 -> "statechange" myState changed from 1 to 2
09:58:53.679 -> "waiting" millis() - Waiting_ms_Timer=6
09:58:53.784 -> "waiting" millis() - Waiting_ms_Timer=103
09:58:53.885 -> "waiting" millis() - Waiting_ms_Timer=203
09:58:53.988 -> waiting over
09:58:53.988 -> "statechange" myState changed from 2 to 3
09:58:53.988 -> Read and send temperature
09:58:53.988 -> 
09:58:53.988 -> start waiting
09:58:53.988 -> "statechange" myState changed from 3 to 4
09:58:53.988 -> "waiting" millis() - Waiting_ms_Timer=6
09:58:54.091 -> "waiting" millis() - Waiting_ms_Timer=103
09:58:54.193 -> waiting over
09:58:54.193 -> "statechange" myState changed from 4 to 5
09:58:54.193 -> Switching Power OFF
09:58:54.193 -> 
09:58:54.193 -> initialise waiting for next sending"statechange" myState changed from 5 to 0
09:58:55.676 -> "waiting " millis() - Waiting60SecsTimer=1484
09:58:57.648 -> "waiting " millis() - Waiting60SecsTimer=3484
09:58:59.654 -> "waiting " millis() - Waiting60SecsTimer=5484
09:59:01.662 -> "waiting " millis() - Waiting60SecsTimer=7484
09:59:03.635 -> "waiting " millis() - Waiting60SecsTimer=9484
09:59:04.188 -> 
09:59:04.188 -> 
09:59:04.188 -> next Time to read and send temp has come
09:59:04.188 -> "statechange" myState changed from 0 to 1
09:59:04.188 -> Switching Power on

I will continiue to collect code-examples here in this thread

best regards Stefan

Isn’t that what enum are for? Also Arduino recommends camelCase if you want to stay within the existing realm

1 Like

enum adds another thing to learn. For didactical reasons I did not use enum

As long as the arduino-team keeps their poorly written poorly explained examples I don't care about consequent using of camelCase.

Complaining about not using camelCase is much much less relevant as complaining about hard to understand examples and examples that guide beginners into the wrong direction
using delay, not using functions etc.

Your opinion is as valid as any other even if I disagree…

Lots of things are sequential in life and when you learn how to program it’s a good starting point. If you feel enum are too complicated then the same is true for state machines and debug macros etc…

Another example with this functionality
You have multiple figures that move towards a gate. If the figure arrives at a certain position a sensor detects the figure.
when figure is detected start a timer which will initiate closing a gate after 7 seconds.
Now if another figure is detected by the sensor the timer shall be reset to 7 seconds again.
And in case a third figure, a fourth figure is detected reset timer again and again.

After closing the gate if a new figure is detected open gate again
here is the code

// MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START *
// Take it for granted at the moment scroll down to void setup
// start of macros dbg and dbgi
#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) \
  do { \
    static unsigned long intervalStartTime; \
    if ( millis() - intervalStartTime >= timeInterval ){ \
      intervalStartTime = millis(); \
      Serial.print( F(#myFixedText " "  #variableName"=") ); \
      Serial.println(variableName); \
    } \
  } while (false);
// usage: dbgi("2:my fixed text",myVariable,1000);
// myVariable can be any variable or expression that is defined in scope
// third parameter is the time in milliseconds that must pass by until the next time a
// Serial.print is executed
// end of macros dbg and dbgi
// print only once when value has changed
#define dbgc(myFixedText, variableName) \
  do { \
    static long lastState; \
    if ( lastState != variableName ){ \
      Serial.print( F(#myFixedText " "  #variableName" changed from ") ); \
      Serial.print(lastState); \
      Serial.print( F(" to ") ); \
      Serial.println(variableName); \
      lastState = variableName; \
    } \
  } while (false);
// MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END *

#include <Servo.h>

Servo servo;
const int analogInPin = A0;
int val = 0;
const byte servoPin = 9;
int angle = 90;
int targetAngle = 90;

unsigned long MyTestTimer = 0;                   // Timer-variables MUST be of type unsigned long
const byte    OnBoard_LED = 2;
unsigned long ServoTimer;
unsigned long myWaitTimer;


// for easy identifiying which code-Version is flashed
void PrintFileNameDateTime() {
  Serial.println( F("Code running comes from file ") );
  Serial.println( F(__FILE__) );
  Serial.print( F("  compiled ") );
  Serial.print( F(__DATE__) );
  Serial.print( F(" ") );
  Serial.println( F(__TIME__) );
}


// 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
}


void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
  static unsigned long MyBlinkTimer;
  pinMode(IO_Pin, OUTPUT);

  if ( TimePeriodIsOver(MyBlinkTimer, BlinkPeriod) ) {
    digitalWrite(IO_Pin, !digitalRead(IO_Pin) );
  }
}

// constnats fo rthe different modes of operation
const byte sm_CheckForFigure  = 0;
const byte sm_openGate        = 1;
const byte sm_waitSomeSeconds = 2;
const byte sm_closeGate       = 3;

// the variable for the mode of operation
byte myState = sm_CheckForFigure;


// move servo slowy in a non.blocking way
void moveServoSlow(byte targetAngle, unsigned long waitingPeriod) {
  int  deltaAngle;
  static unsigned long ServoTimer;

  // dbgc("m0",angle);
  if (angle != targetAngle) {
    if (angle > targetAngle) {
      deltaAngle = -1;
    }

    if (angle < targetAngle) {
      deltaAngle = 1;
    }
    if ( TimePeriodIsOver(ServoTimer, waitingPeriod) ) {
      angle += deltaAngle;
      dbgi("a+da",angle,1000);
      servo.write(angle);
    }
  }

}


void myStateMachine() {
  static unsigned long TimeToWait;
  dbgc("State:",myState);
  val = analogRead(analogInPin);   // This reads the value of the sensor to see if an object is detected then saves in the val variable.
  dbgi("above switch",val,1000);

  switch (myState) {

    case sm_CheckForFigure:
      if (val < 600) {
        Serial.println("figure detected start opening gate");
        targetAngle = 75;
        ServoTimer = millis();
        myState = sm_openGate;
      }
      break; // immidiately jump down to End-of-Switch


    case sm_openGate:
      moveServoSlow(targetAngle, 20);
      dbgc("openG",angle);
      if (angle == targetAngle) {
        Serial.println("gate opened start waiting");
        myWaitTimer = millis();
        myState = sm_waitSomeSeconds;
      }
      break; // immidiately jump down to End-of-Switch

    case sm_waitSomeSeconds:
      TimeToWait = (7000 - (millis() - myWaitTimer) ) / 1000; // print seconds left until closing starts
      dbgi("wait",TimeToWait,1000);

      if (val < 600) { // check for figure
        myWaitTimer = millis(); // if figure ist detected reset timer to start new
      }
      if ( TimePeriodIsOver(myWaitTimer, 7000) ) { // check if waiting time is over
        Serial.println("Waiting-Time over start closing gate");
        targetAngle = 180;
        ServoTimer = millis();
        myState = sm_closeGate;
      }
      break; // immidiately jump down to End-of-Switch

    case sm_closeGate:
      moveServoSlow(targetAngle, 20);
      if (angle == targetAngle) {
        Serial.println("gate closed check for figure");
        myState = sm_CheckForFigure;
      }
      break; // immidiately jump down to End-of-Switch
  } // End-of-Switch
}


void setup() {
  Serial.begin(115200);
  Serial.println("Setup-Start");
  PrintFileNameDateTime();
  servo.attach(servoPin);
  angle = 90;
  targetAngle = 90;
}


void loop() {
  BlinkHeartBeatLED(OnBoard_LED, 250);

  myStateMachine();
}

just a thought - may be for examples as such you could also add a wokwi simulation to play with.
here is one, with similar code to yours (just my take on the spec without the printing)

1 Like

We know the type of individuals who come here.

Maybe show Power/GND connections too :thinking: