How to implement Protothreading or other "multithreading" solution

Hello everyone.
I'm currently working on a Soft Robotics project, and I'm trying to make my own control board using an Arduino Nano (I have Uno, and Mini, and probably more available, if that's relevant).

For 1 actuator, I need to control 2 solenoid valves, and 1 pressure sensor with the Arduino.

The control system consists in 3 different routines - functions.
Function A:
Open Valve X;
Close Valve Y;

Funcion B:
Close Valve X;
Open Valve Y;

Function C:
Read pressure;
Close Valve X;
Close Valve Y;
Read pressure; ) LOOP - checks pressure, if drops, opens until reaches the target pressure.
Open Valve X; )

This is easily achievable for 1 actuator. The thing is, I want to control more than 1 actuator. Ideally, at least 4-5 actuators.

How can I keep preforming function C, continuously, on 1 actuator, and preform other functions, changing from A to B to C on other actuators?

I've searched for a bit, and read this tutorial, and from the comment section I learned about RTOS (freeRTOS?) but didn't understand how does any of that work.

So my main question is, what should I be looking in terms of solutions for this problem? What is the best approach I should take? If it's even feasible to do what I want.

I doubt you need threading. An array of actuators should do it. Keep a state variable for each of them and loop through the array doing what's necessary.

The general idea is to make sure loop does the looping, no while loops, no delay, very quick for loops. Test if something needs doing immediately, if it does, do it and move on to the next thing, if not then don’t wait just move to the next thing.

wildbill:
I doubt you need threading. An array of actuators should do it. Keep a state variable for each of them and loop through the array doing what's necessary.

PerryBebbington:
The general idea is to make sure loop does the looping, no while loops, no delay, very quick for loops. Test if something needs doing immediately, if it does, do it and move on to the next thing, if not then don't wait just move to the next thing.

You're saying the same thing, right?

I'm not sure I understand this, but would it be something like this?

void loop() {
if(decision_var1==true){
function1()
if(decision_var2==true){
function2()
...
if(decision_varN==true){
functionN()
}

without delays between them?

One of my colleagues mentioned the millis() function, and using it. But I didn't understand why

I probably could make just 3 functions, and pass the number of the Digital and Analog pins as an argument. Right?

One of my colleagues mentioned the millis() function, and using it. But I didn't understand why

Take a look at Using millis() for timing. A beginners guide, Several things at the same time and the BlinkWithoutDelay example in the IDE

Another example:

afonsots:
One of my colleagues mentioned the millis() function, and using it. But I didn't understand why

millis lets you control timing without using delay. Use of delay will kill your ability to control multiple actuators simultaneously.

I probably could make just 3 functions, and pass the number of the Digital and Analog pins as an argument.

Sure. Probably better to pack the pin numbers in a simple struct or perhaps a class though.

void loop()  {
if(decision_var1==true){
function1()
if(decision_var2==true){
function2()
...
if(decision_varN==true){
functionN()
}

That’s the general idea.
I prefer to put only functions in loop and put whatever decisions are needed as the very first thing at the top of each function, rather than putting the decisions in loop(), however the principal is the same.

You use millis if the decision you need to make is time dependent, so one of your decisions would be if millis had increased by some amount corresponding to the time interval you are interested in.

For example in my Nextion tutorial I have a simple clock like this:

void loop() {
  // Other stuff
  clock_run();
}

void clock_run() {
  static unsigned long previousMillis;
  unsigned long currentMillis = millis();
  const unsigned long interval = 1000;
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    ++clock.second;
    if (clock.second > 59) {
      clock.second = 0;
      ++clock.minute;
      if (clock.minute > 59) {
        clock.minute = 0;
        ++clock.hour;
        if (clock.hour > 23) {
          clock.hour = 0;
        }
      }
    }
    HMI_display_clock();
  }
}

clock_run is called many times in loop but only does something once per second. Note that the first line of code, after the local variables, is a millis based decision on whether or not to do something.

Also, within the function are more nested decisions for incrementing minutes and hours.

PerryBebbington:
clock_run is called many times in loop but only does something once per second. Note that the first line of code, after the local variables, is a millis based decision on whether or not to do something.

That should be the general principle.

Have a look at how the code is organized in Several Things at a Time

Note how each function runs very briefly and returns to loop() so the next one can be called. None of the functions tries to complete a task in one call. And there may be dozens of calls to a function before it is actually time for it to do anything.

...R

Thanks to everyone. I'll be looking up everything you sent me (sorry, got stuck with a problem with the hardware).

Tomorrow I'll try to implement, and provide feedback.

What triggers each of the functions to be called?

In your IDE library manager search for LC_baseTools in there is the idlers library. Its designed for just this kind of thing.

Here's some notes on idlers in git wiki : idler notes.

-jim lee

afonsots:
So my main question is, what should I be looking in terms of solutions for this problem? What is the best approach I should take? If it’s even feasible to do what I want.

Check out the link in my sig: it’s my take on how to go about doing this.

tl;dr:
1 - wrap everything to do with an actuator in a class
2 - supply that class with a setup() and a loop() method. The loop() method must not block, it must use millis() for timing.
3 - initialize instances of the class with appropriate constructor arguments. Use an array of them where appropriate
4 - invoke the setup() of each instance in the global setup(), invoke the loop() of each instance in the global loop()

jimLee:
Here's some notes on idlers in git wiki : idler notes.

Wow. Presumably that 'sleep' method uses longjmp to do context switching?

Its so much simpler that that. sleep() makes sure you are on the "main thread" then just calls idle() over and over 'till your delay time runs out. Then away your main thread goes again.

-jim lee

jimLee:
Its so much simpler that that. sleep() makes sure you are on the "main thread" then just calls idle() over and over 'till your delay time runs out. Then away your main thread goes again.

Oh - so sleep isn't something you use in an idler to achieve delay, for instance to blink an LED. Got it.

Correct. Sleep() is just for people that want to hold their main loop(). Main loops tend to have long time durations while idlers tend to be quick in/out kinds of things.

What I typically mix in with idlers, are timeObj(s). timeObj is basically a kitchen timer. You set the time you want, start it, and when it goes "ding".. The time is up.

If I want to blink an LED.. I can set a timeObj for pulse width and one for duration. Then in your idle() method all you are doing is checking your timers and turning the LED on/off as you pass through.

Actually. there's a squareWave library in there that does all this already. So a lot of times I'll just use that.

-jim lee

jimLee:
Correct. Sleep() is just for people that want to hold their main loop(). Main loops tend to have long time durations while idlers tend to be quick in/out kinds of things.

It seems to me that a newbie would be faced with learning at least as much to implement your system as it would take to learn to use the technique in Several Things at a Time

And I reckon that the effort of learning the techniques in Several Things would have more general application.

...R

PaulMurrayCbr:
Check out the link in my sig: it's my take on how to go about doing this.

tl;dr:
1 - wrap everything to do with an actuator in a class
2 - supply that class with a setup() and a loop() method. The loop() method must not block, it must use millis() for timing.
3 - initialize instances of the class with appropriate constructor arguments. Use an array of them where appropriate
4 - invoke the setup() of each instance in the global setup(), invoke the loop() of each instance in the global loop()

Please don't take this as negative but I read the above and thought that's way, way too complicated and unnecessary. For me writing non-blocking multi-tasking code is a habit you learn, you learn how to always make everything you write non-blocking. If you do that then everything just naturally fits together with nothing getting in the way of everything else, with no effort at all. Please, I am not for one moment saying your way is wrong or inferior, I'm just saying there's an alternative.

Robin2:
It seems to me that a newbie would be faced with learning at least as much to implement your system as it would take to learn to use the technique in Several Things at a Time

And I reckon that the effort of learning the techniques in Several Things would have more general application.

...R

Robin.. Over the years you have made it abundantly clear, we think differently.

-jim lee