Easy to understand analogy how to code doing things in (almost) parallel = multi-tasking

This tutorial has the aim to explain the

fundamental difference

between purely sequential coding of blocking loops
and how to code non-blocking (= in a multi-tasking way)

This tutorial will use an everyday analogy for this. If you don't like everyday analogy jump over to the millions of abstract "explanations" found accross the internet.

Everyday analogy: preparing a salad with tomatos and onions.
The usual way to prepare such a salad would be to
take a first tomato
cut it into slices

take a second tomato
cut it into slices
... maybe in summary 5 tomatoes

as a pseudo-code this could be written as

for (tomatoNr = 1; tomateNr <= 5; tomateNr++) {
  take_a_tomatoe();
  cut_into_slices();
}

Then
take a first Onion
cut it into slices

take a second Onion
cut it into slices

take a third Onion
cut it into slices

as pseudocode

for (OnionNr = 1; OnionNr <= 3; OnionNr++) {
  take_an_onion();
  cut_into_slices();
}

You would do cutting all 5 tomatoes and then after that cutting all 3 onions

For really preparing such a salad this would be the common way to do it.

In microcontroller-programming you often have the case that things should happen in parallel. Like
blinking a green LED while a stepper-motor shall turn clockwise always checking is endposition is reached or not

if endposition is reached
blinking a red LED while a stepper-motor shall turn counter-clockwise always checking is startposition is reached or not

Something like this can not be coded with sequential for-loops or while-loops.
And this is the fundamental difference of non-blocking step-chains.

Back to the everyday analogon of the tomato-onion-salad.
Imagine some nerdy cook would like to prepare the salad this way:

taking first tomate
taking first onion

cutting one slice from the tomato
measure width of the tomato
if width is smaller than 5 mm
take second tomato and start cutting the second tomato

cutting one slice from the onion
measure width of the onion
if width is smaller than 3 mm
take second onion and start cutting the second onion

which means
cut one slice of tomato
immidiately after that
cut one slice of onion
jump back and forth between cutting tomato and cutting onion

repeat this pattern until all cutting is done.

In reality for preparing the salad this would be in-effective.
For programming doing things in parallel this is the way to go

As a pseudo-code this would look like this

void loop() {
  step_chain_cut_tomato(); // do a SINGLE step of the tomato-cutting
  step_chain_cut_Onion();  // do a SINGLE step of the onion-cutting
}

This gives the overview. Now we are stepping down into the details:
The name "step_chain" IMHO explains much better what the character of such a function is that the common tecnical term (I don't mention here)

It is indeed some kind of a "chain" where multiple "steps" are done.
For making the code much easier to read and easier to understand I use self-explaining names. And I highly recommend that you do this too!

This kind of code needs a constant for each step

const byte sct_starting             = 1;
const byte sct_cutting              = 2;
const byte sct_put_into_bowl        = 3;
const byte sct_wait_for_all_dropped = 4;

all constants start with "sct" which is an abbreviation for step-chain_tomato.
This is a personal naming-convention I use to indicate these words are constants of a step-chain.

Then the function with the step-chain itself. The names are self-explaining.
It is very important to remember that cutting the tomato shall be proceeding

in parallel

to cutting the onions.
The "looping" is done by void loop() itself

void loop() {
  step_chain_cut_tomato(); // do a SINGLE step of the tomato-cutting
  step_chain_cut_Onion();  // do a SINGLE step of the onion-cutting
}

.
.

void step_chain_cut_tomato() {

  switch (step_no) {

    case cst_starting:
      take_a_tomato();
      step_no = sct_cutting;
      break; // immidiately jump down to END-OF-SWITCH

    case cst_cutting:
      stepChain_cut_a_slice();
      dist_to_plate = measure_distance();
      if (dist_to_plate == 0) {
        step_no = sct_put_into_bowl;
      }
      break; // immidiately jump down to END-OF-SWITCH

    case sct_put_into_bowl:
      digitalWrite(PusherPin, HIGH);
      step_no = sct_wait_for_all_dropped;
      break; // imidiately jump down to END-OF-SWITCH

    case sct_wait_for_all_dropped:
      if (two_seconds_are_over) {
        step_no = cst_starting;
      }
      break; // immidiately jump down to END-OF-SWITCH
  } END-OF-SWITCH
}

The step-chain for cutting the onions looks very similar
we need constants for each step

const byte scOnion_starting             = 1;
const byte scOnion_cutting              = 2;
const byte scOnion_put_into_bowl        = 3;
const byte scOnion_wait_for_all_dropped = 4;

.
and a step-chain-function

void step_chain_cut_Onion() {

  switch (Onion_step_no) {

    case scOnion_starting:
      take_a_tomato();
      Onion_ = csOnion_cutting;
      break; // immidiately jump down to END-OF-SWITCH

    case scOnion_cutting:
      stepChain_cut_a_slice();
      dist_to_plate = measure_distance();
      if (dist_to_plate == 0) {
        Onion_step_no = scOnion_put_into_bowl;
      }
      break; // immidiately jump down to END-OF-SWITCH

    case scOnion_put_into_bowl:
      digitalWrite(PusherPin, HIGH);
      Onion_step_no = scOnion_wait_for_all_dropped;
      break; // immidiately jump down to END-OF-SWITCH

    case scOnion_wait_for_all_dropped:
      if (two_seconds_are_over) {
        Onion_step_no = scOnion_starting;
      }
      break; // immidiately jump down to END-OF-SWITCH
  } //END-OF-SWITCH
}

.
.
if you have read the pseudo-code carefully you will have recognised there is a call to a function

stepChain_cut_a_slice();

the name is - guess what - self-explaining what the function does

If we take a closer look at what happens when cutting a tomato
there are multiple steps which could be described with the following steps :

step 1: set knife on to of tomato at a position that will cut a slice of approx 5 mm
step 2: push knife forward
step 3: pull knife back
step 4: take a look if already cutted through (= measure distance to plate below the tomato)
if not yet cutted through
repeat pushing/pulling knife

if cutted through

step 5: put slices into bowl
step 6: hands ! wait until brain gives new commands ;-))

as pseudo-code

again we need a constant for each step and they have self-explaining names

const byte scCut_start           = 1;
const byte scCut_forward         = 2;
const byte scCut_reverse         = 3;
const byte scCut_measure         = 4;
const byte scCut_put_into_Bowl   = 5;
const byte scCut_finished_idling = 6;

byte Cut_stepNo = scCut_start;

.
.

void cut_a_slice() {

  switch (Cut_stepNo) {

    case scCut_start:
      cutting_finished = false;
      set_the_knife();
      Cut_stepNo = scCut_forward;
      break; // immidiately jump down to END-OF-SWITCH


    case scCut_forward:
      digitalWrite(forward_pin, HIGH);
      Cut_stepNo = scCut_reverse;
      break; // immidiately jump down to END-OF-SWITCH


    case scCut_reverse:
      digitalWrite(forward_pin, LOW);
      Cut_stepNo = scCut_measure;
      break; // immidiately jump down to END-OF-SWITCH


    case scCut_measure:
      distance_to_plate = measure_distance();
      if (distance_to_plate > 0) {
        // if not yet cutted through go on cutting
        Cut_stepNo = scCut_forward;
      }
      else {
        // if cutted through
        Cut_stepNo = scCut_put_into_Bowl;
      }
      break; // immidiately jump down to END-OF-SWITCH


    case scCut_put_into_Bowl:
      digitalWrite(pusher_pin, HIGH);
      Cut_stepNo = scCut_finished_idling;
      break; // immidiately jump down to END-OF-SWITCH


    case scCut_finished_idling:
      cutting_finished = true;

  } // END-OF-SWITCH
}

.
The experts reading this will complain that there are some lines of code missing.
Yes this is true.
At this stage of the tutorial I focus on explaining the basic principle.
Hence some details were left out.

The best experts about beginner-difficulties are beginners that take the effort to ask questions.
As an "expert" about the subject I'm partially blind about beginners difficulties and have to become a expert about beginner-difficulties too through beginner-questions.
Of course any experienced user can comment on all this too.

I beg for just one thing: if you are puristic like "the best way to learn programming is to ...."
please start your own thread about this kind of discussion.

So far so good. For a complete tutorial some more postings shall be added in the future with code that has a real functionality.

best regards Stefan

That would be six tomatoes, not five

(What is "analogon"? "Analogy"?)

good catch. Yes this would be 6 I will correct it
And Yes I mean analogy

Four onions, not three.

@anon73444976

leave some questions to the beginners !!!

know your are obliged to write a real working demo-code!
But it must be very easy to undestand!

I find things that are correct, consistent and don't leave more questions are easier to understand.

It is more pratical to use ENUM to specify the "constant" values for switch/case.

enum ScCut {scCut_start=1,scCut_forward,scCut_reverse,scCut_put_into_Bowl,scCut_finished_idling} Cut_stepNo = scCut_start;

more logical names:

enum ScCut {Start=1,Forward,Reverse,OutIntoBowl,finished} Cut_stepNo = Start;

to use named ENUM inside switch/case:

case ScCut::Start: .......

and so on.

Have a nice day and enjoy coding in C++.
Дайте миру шанс!

@paulpaulson as soon as I have seen that you are writing I expected something nerdy like this posting.

IMHO your version adds additional things that must be explained.
Additional explanation = more difficult to understand.
At a later point it is indeed useful to introduce to enumeration.

Me myself with the tiuorials is not entertaining as Tyler DeWitt but he is my role model

best regards Stefan

1 Like

Hello
In your tutorial you have broken almost every rule of tight, efficient coding in C++. If you feel called to write tutorials for beginners, do it properly and correctly. In your attempted example about multitasking, an explanation of control flow and data flow would have been mandatory. Maybe you should start with the explanation of data types and user defined data types.
Have a nice day and enjoy coding in C++.
Дайте миру шанс!

If you are too lazy to explain these rules in an easy to understand way by using everyday analogies: I don't care. :man_in_lotus_position: :bath: :woman_juggling: :sloth: :frog: :hamsa: :put_litter_in_its_place: :wheelchair:

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