Write code that is responsive to buttons ALL the time in parallel to multiple actions happening at the same time state-machine / non-blocking timing

Hello everybody,

this is an introductional tutorial for newcomers to understand how writing code can be done to achieve:

  • buttons beeing responsive to buttonpresses all the time
  • blinking multiple LEDs with different frequencies
  • executing multiple actions of any kind in (almost) parallel

by using state-machines and non-blocking timing.

You need quite some knowledge and more than one concept to do this.
Because of this amount of knowledge and the mutliple concepts,
it takes more time that just 5 minutes to understand this.

If you are searching for a quick solution - I'm serious - skip this tutorial and try your luck to find a ready to use code. Though the probability to find what you need exactly is very unlikely.

Right from the start: get a clear picture of the different way of code-execution.
It will help a lot if you get a clear picture how code-execution proceeds

differently

from line to line if you code state-machines and non-blocking timing.
one attempt is to look at this video of a mechanical animated lego-scene

There is one single crank-wheel that keeps rotating to make all actions go on all the time. The crank wheel drives everything
There is one single "motor" that drives everything.

In translated to programming there is one single function that keeps all details proceeding.

This one single function is void loop() itself.
As long as void loop() is looping everything is in motion.

In the lego-scene there are three chains that are driven by the one single crank-wheel.
(picture missing yet)

all three chains are driven by the one single crank-wheel.
There are levers mounted on the chain. If the chain keeps on moving the levers push/pull other parts of the mechanic making other parts do some action. These actions are done only from time to time in a periodic manner. There are pauses between these actions
while the crank-wheel keeps spinning all the time.

This is what state-machines combined with non-blocking timing does:

void loop() is looping FAST all the time but actions only occur if certain conditions become true.

This Lego-example shall show the "it is always spinning = void loop() is always looping

And of course this example has its limits and in coding some things are different.
Using delay() in your code equivalents to stop spinning the crank-wheel.

If you would stop spinning the crank-wheel the whole animation stops.
And this is the reason why you can't use delay() in non-blocking code

for the same reason you can't use while-loops or for-loops.
A while-loop or a for-loop is like you disconnect the rest of the mechanic driven by the crank-wheel to make just one chain moving and only after finishing the while-loop reconnect the rest of the mechanic again. The rest of the mechanic would stay stopped until you re-connect the rest.

3 Likes

I will use the actions shown in the lego-scene for some kind of pseudo-code

So what actions does the animated lego-scene show:

  • fish "swimming along the aquarium glass wall in circles"
  • foot of the fishingman tapping from time to time
  • pulling up the mans body and the fishing-rod from time to time
  • head of the cat looking up in parallel to the hook moving up
  • tail of the cat moving from time to time

First important thing:
these actions are driven by a continiously rotated crank-wheel
the fish is moving all the time but the other actions happen only from time to time

The chain moving all the time but pulling up the fishing-rod happens only if the lever moves across a certain point.

The coding equivalent to this behaviour is non-blocking timing.

The basic principle of non-blocking timing is calculating how much time has passed by since a time-reference-point. This means a difference between time-reference-point and actual time is calculated. This difference is "how much time has passed by. And then this difference is checked "has a certain amount of time passed by (since time-reference-point)

If this certain amount of time has really passed by then take action!

Non-blocking timing is a thing on its own which I have described in the tutorial mentioned below.
I'm aware that linking to this other tutorial is sub-optimal. It is what I have so far.

1 Like

If you remember: the crank-wheel is rotated all the time continiously and this makes the driving axles, the driving toothwheels and the chains rotate ALL the time.

The equivalent in programming is time is passing by continiously.
In Coding there comes in a new aspect or maybe I could say a certain code-bevaviour.

quickly jump-in / jump-out

As a very simple example that shows the principle look at this short loop()

void loop() {
  myCounter++;               // increment variable named "myCounter" by 1

  Serial.println(myCounter); // print value of variable "myCounter"  to the serial monitor
}

This would print to the serial monitor

1
2
3
4
...

because loop() is looping.

Quickly jump_in / jump_out
The following variation of this code shall demonstrate what is meant with quickly jump_in / jump_out


void IncrementAndPrint() {
  myCounter++;               // increment variable named "myCounter" by 1  
  Serial.println(myCounter); // print value of variable "myCounter" to the serial monitor 
}


void loop() {
  IncrementAndPrint();
}

function loop() has a single line

  IncrementAndPrint();

and this single line is executed repeatedly all the time over and over and over again.

The code jumps quickly into the function named "IncrementAndPrint()"
execute the line of code

  myCounter++;               // increment variable named "myCounter" by 1  

then execute the line of code

  Serial.println(myCounter); // print value of variable "myCounter" to the serial monitor 

and then jumps out of the function named "IncrementAndPrint()"

here is a variant that adds more serial printing to make this visible in the serial monitor

int myCounter;

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


void IncrementAndPrint() {
  Serial.println("Entering function IncrementAndPrint");
  myCounter++;               // increment variable named "myCounter" by 1
  Serial.println(myCounter); // print value of variable "myCounter" to the serial monitor
  Serial.println("leaving function IncrementAndPrint");
}


void loop() {
  Serial.println("Top of loop");
  IncrementAndPrint();
  Serial.println("Bottom of loop");
}

The serial output looks like this

14:58:32.986 -> Top of loop
14:58:32.986 -> Entering⸮Setup-Start
14:58:32.986 -> Top of loop
14:58:32.986 -> Entering function IncrementAndPrint
14:58:32.986 -> 1
14:58:32.986 -> leaving function IncrementAndPrint
14:58:32.986 -> Bottom of loop
14:58:32.986 -> Top of loop
14:58:32.986 -> Entering function IncrementAndPrint
14:58:32.986 -> 2
14:58:32.986 -> leaving function IncrementAndPrint
14:58:32.986 -> Bottom of loop
14:58:32.986 -> Top of loop
14:58:32.986 -> Entering function IncrementAndPrint
14:58:32.986 -> 3

This is the meaning of quickly jump_in / jump_out behaviour.
This example is too short to make really sense in a real application. It is just used to explain the principle.

Now we are going back to the lego-scene.
Important difference: Code-execution always happens one line after each other
line for line sequentially.
As microcontrollers can execute code very fast you can make it look like things are done in parallel. The reality is the things that should look like done in parallel are proceeding in very tiny steps sequentially and this makes it look like being done in parallel.

To enable repeating of executing steps of multiple functions one after each other sequentially but in a fast manner requires that each and every function does
quickly jump_in / jump_out

action_1: quickly jump_in - and proceed a small step - quickly jump_out
action_2: quickly jump_in - and proceed a small step - quickly jump_out
action_3: quickly jump_in - and proceed a small step - quickly jump_out
....
in this way all actions proceed and it looks like if the actions take place in parallel.

void loop() {

  fishSwimming(); // quickly jump in move fish a little bit jump_out
  footTapping();  // quickly jump in from time to time move foot jump_out
  FishingRodUpDown(); // quickly jump_in from time to time move fishing-rod a little bit jump_out
  CatLookingUpDown(); // quickly jump_in from time to time move head a little bit jump_out
  catTailMoving();    // quickly jump_in from time to time move cat-tail a little bit jump_out
  
}

again rffu

again rffu

again rffu (reserved for future use)

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