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