Simulteanous opening/closing of valves with a single Arduino

I am working on building a filling machine that will work with 3 containers, as you can see in the image, each tank has an inlet and a discharge solenoid valve.
It is intended to work as follows:
with the "0" button the filling valves of the 3 containers will be opened until each one reaches the liquid level determined by the sensor (in green).
Buttons 1, 2 and 3 release the contenst of each deposit accordingly.
I started writing the code according to these operating principles without stopping to think if it was even possible.
Considering that a delay() function is used for the discharge of each container, I assume that it is not possible to open a discharge valve while another one is discharging. Is there any way to work around this problem? Or would I need to use different arduinos for each set of valves?

Here's the code Ive written so far:

//XKC-Y25-V

const int SENSOR1 = 13;
const int SENSOR2 = 12;
const int SENSOR3 = 11;
const int BUTTON0 = 10;   //button for loading all 3 fillers
const int BUTTON1 =  9;   //filler #1 discharge
const int BUTTON2 =  8;   //filler #2 discharge
const int BUTTON3 =  7;   //filler #3 discharge
const int VAL11 = 6;
const int VAL12 = 5;
const int VAL21 = 4;
const int VAL22 = 3;
const int VAL31 = 2;
const int VAL32 = 1;

bool flag00 = 1;  //flag variable for unloaded fillers
bool flag01 = 0;  //flag variable for load button
bool flag02 = 0;  //flag variable for load button
bool flag03 = 0;  //flag variable for load button


bool flag11 = 0;  //flag variable for load conditions
bool flag21 = 0;  //flag variable for load conditions
bool flag31 = 0;  //flag variable for load conditions

bool flag1L = 0;  //marks the end of loading 1 
bool flag2L = 0;  //marks the end of loading 2
bool flag3L = 0;  //marks the end of loading 3

bool flag12 = 0;  //flag variable for discharge button1
bool flag22 = 0;  //flag variable for discharge button2
bool flag32 = 0;  //flag variable for discharge button3

bool flag1D = 0;  //marks the end of discharge 1 
bool flag2D = 0;  //marks the end of discharge 2
bool flag3D = 0;  //marks the end of discharge 3


unsigned long discharge_time = 40000; //NOTE TO SELF:CHECK FOR ERROR, COULD BE SMALLER

void setup()
{
  Serial.begin(9600);
  //SENSORS
  pinMode(SENSOR1, INPUT);
  pinMode(SENSOR2, INPUT);
  pinMode(SENSOR3, INPUT);
  //BUTTONS
  pinMode(BUTTON0, INPUT);
  pinMode(BUTTON1, INPUT);
  pinMode(BUTTON2, INPUT);
  pinMode(BUTTON3, INPUT);
  // VALVE CONTROL
  pinMode(VAL11, OUTPUT);
  pinMode(VAL12, OUTPUT);
  pinMode(VAL21, OUTPUT);
  pinMode(VAL22, OUTPUT);
  pinMode(VAL31, OUTPUT);
  pinMode(VAL32, OUTPUT);
}

void loop()
{
  // begin with all valves shut
  digitalWrite(VAL11,HIGH);
  digitalWrite(VAL12,HIGH);
  digitalWrite(VAL21,HIGH);
  digitalWrite(VAL22,HIGH);
  digitalWrite(VAL31,HIGH);
  digitalWrite(VAL32,HIGH);
  delay(100);

  // get sensor readings & print
  int level1 = digitalRead(SENSOR1);  //CHECKS FOR LIQUID LEVEL in filler#1
  int level2 = digitalRead(SENSOR2);  //CHECKS FOR LIQUID LEVEL in filler#2
  int level3 = digitalRead(SENSOR3);  //CHECKS FOR LIQUID LEVEL in filler#3
  
  Serial.print("READINGS ");
  Serial.print(level1);
  Serial.print(", ");
  Serial.print(level2);
  Serial.print(", ");
  Serial.println(level3);

  
  // CHECK FOR LOADING CONDITIONS
  int load = digitalRead(BUTTON0);  
  if (load != 1) {
    delay(200);
    flag01 = 1;
    flag02 = 1;
    flag03 = 1;
    
  }
  flag11 = flag01 && flag00
  flag21 = flag02 && flag00
  flag31 = flag03 && flag00
  flag1D = 0
  flag2D = 0
  flag3D = 0

  // open valve11
  if (level1 == 1 && flag11==1) {
    digitalWrite(VAL11, LOW);
    //flag0 = 1;
    //flag3 = 1;
  }
  else {
    digitalWrite(VAL11, HIGH);
    flag11 = 0;
    flag01 = 0;
    flag1L = 1;
  }

  
  // open valve21
  if (level2 == 1 && flag21==1) {
    digitalWrite(VAL21, LOW);
    //flag0 = 1;
    //flag3 = 1;
  }
  else {
    digitalWrite(VAL21, HIGH);
    flag21 = 0;
    flag02 = 0;
    flag2L = 1;
  }

  
  // open valve31
  if (level3 == 1 && flag31==1) {
    digitalWrite(VAL31, LOW);
    //flag0 = 1;
    //flag3 = 1;
  }
  else {
    digitalWrite(VAL31, HIGH);
    flag31 = 0;
    flag03 = 0;
    flag3L = 1;
  }

// DISCHARGE SECTION

 int disch1 = digitalRead(BUTTON1);  //CHECKING BUTTON1
  if (disch1 != 1) {
    delay(200);
    flag12 = 1;
    }
    
 int disch2 = digitalRead(BUTTON2);  //CHECKING BUTTON2
  if (disch2 != 1) {
    delay(200);
    flag22 = 1;
    }
 int disch3 = digitalRead(BUTTON3);  //CHECKING BUTTON3
  if (disch3 != 1) {
    delay(200);
    flag32 = 1;
     }

     
  if (flag12 == 1 && flag1L == 1) {   //DISCHARGES FILLER1 through VAL12
    digitalWrite(VAL12, LOW);
    delay(discharge_time);
    digitalWrite(VAL12, HIGH);
    flag12 = 0;
    flag1L = 0;
    flag1D = 1;

  }
     
  if (flag22 == 1 && flag2L == 1) {   //DISCHARGES FILLER2 through VAL22
    digitalWrite(VAL22, LOW);
    delay(discharge_time);
    digitalWrite(VAL22, HIGH);
    flag22 = 0;
    flag2L = 0;
    flag2D = 1;

  }
 
     
  if (flag32 == 1 && flag3L == 1) {   //DISCHARGES FILLER3 through VAL32
    digitalWrite(VAL32, LOW);
    delay(discharge_time);
    digitalWrite(VAL32, HIGH);
    flag32 = 0;
    flag3L = 0;
    flag3D = 1;

  }
  flag00= flag1D && flag2D && flag3D 
}

image

Do not use delay(). See the "Blink without delay" tutorial

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

What do you want to have happen if the discharge button for a tank is pressed before the level filling has been stopped by its sensor?

You have three identical tanks, you should be able to write one piece of code that woukd handle tank number X, and execute it for tank number 0, tank number 1 and so forth.

If you use the

blink without delay

or

arduino two things at once

or

arduino finite state machine 

style of coding. google, read, learn.

No delay(), as had been said.

You probably shoukd learn about arrays and how they can be exploited to make a pattern of code operate for any number of objects.

A very nice little task for just one Arduino, for sure. Even then it will be waiting on the buttons and sensors 99.99 percent of the time.

a7

If the button is pressed before filling has stopped nothing should happen. Thx for the info!

Will check it out, thx!!

You seem to have an unnecessary number of flag variables and their names are not helpful so it's very hard to see what the code is doing. Consider giving them names that describe them better.

I suspect that what you're trying to do can be done with a lot less code too.

You'll probably want this in setup().   That way it's only done once.

It'd be a good idea, too to format your code with <ctrl> t.  It'll reveal some syntax errors.

You can use a simple state machine.

Here's one state in pseudo code

state : FULL
  do we have a signal to empty the tank?
    no : nothing to do,do nothing.
    yes :
       open the bottom valve
       preset the timer for the emptying time
       advance to state EMPTYING

I see FULL, EMPTYING, EMPTY and FILLING as the four states of your machinery.

Once you have the states and the causes for moving amongst them, the code writes itself.

Give it a go after you do a little reading about FSMs "finite state machines" in the context of Arduino programming.

HTH - I am envious, you get to spend time writing this little program. I have to go to the beach and drink.

a7

put all valves on the same port then you can change their state simultaneously by writing to the port once

lots of separate variables and flags makes the code difficult to follow.

a struct can be used to identify the upper and lower valve and sensor pins. not sure any flag variables are necessary if using a state machine

struct Container {
    const byte  PinValveUpper;
    const byte  PinValveLower;
    const byte  PinSensor;
    const char *desc;
};

would make setup() simpler

    Container *c = containers;
    for (unsigned n = 0; n < N_Container; n++, c++)  {
        digitalWrite (c->PinValveUpper, Close);
        digitalWrite (c->PinValveLower, Close);
        pinMode      (c->PinValveUpper, OUTPUT);
        pinMode      (c->PinValveLower, OUTPUT);
        pinMode      (c->PinSensor,     INPUT_PULLUP);
    }

Container containers [] = {
    { 6, 5, 13, "one" },
    { 4, 3, 12, "two" },
    { 2, 1, 11, "three" },
};

a state machine makes sense, but i think Idle, Refilling and Filling. a switch machine can invoke a function unique to each state none at all (e.g. Ready).

loop() should check for button presses (needs to recognize changes in state), take some action when one is pressed and set some state. button 0 can make sure all the lower valves are closed, open all the upper valves and set the state to Refill.

the Refill state function can check the sensor for each tank and close the upper valve when the sensor becomes active. it could change the state to Idle when all of the sensors report filled

when one of the other buttons is pressed, the corresponding lower value is opened, a timestamp captured and the state set to Fill

The Fill state function may simply waits (using millis()) and then closes all lower valves. (is there a need to keep track of which valve)

i don't know if there should be a Ready state that would allow filling. in other words, goto Idle after filling but don't allow filling until after refilling. Should refilling be done automatically after filling.

But I dont want the bottom valves to open all at the same time. Your suggestion might still be used for the top valves

Appreciate it doug!

This is enlightening, but i want to be able to open discharge valves at different times, would I need new states for each valve (EMPTYING 1, EMPTYING 2, EMPTYING 3)?

I want to go to the beach and drink too :C

Hello. Tbh, I dont understand much of whats going on your example. But I'll definitely look into FSM in arduino, thx for breaking down that part so well !

No no no. That's why you need to learn about arrays.

Whenever you have variables with names like tank1, tank2, tank3, it is a clue that you shoukd be using arrays.

Once you have arrays, you can use indexing. So you can talk about the elements using a loop, for example.

Read

Learn how to use functions. Read

With arrays and functions, you have the basic tools for implementing finite state machines, FSMs, which I hope you have read about.

There are more advanced ways of encapsulating information about objects being modeled in code, but I recommend you start simple with arrays.

There are clever ways to use fewer states in an FSM. In this project, there is an ideal and one-to-one mapping between the states and what the real world tank will be up to. Four states. When you are in one of the states, the open/close condition of the valves is unchanged: EMPTYING would be the tank is emptying, obvsly, and the upper valve is closed and the lower valve is open.

Every aspext of a tank would get an array, like the pin numbers that are for controlling the top valves, the times since tanks started to empty, what state FULL, EMPTY, EMPTYING or FILLING the tank is in, and so forth. Maybe 5 or 6 or 7 array variables, all with three slots, one per tank. Like

int tankState[3];

would reserve space for the current state for three tanks.

Then to talk about the state of a certain tank x, we can just use tankState[x].

So tankState[0] might be FULL, while tankState[1] coukd be EMPTYING.

And a function can have an argument that would make all the code in the function apply to the tank number passed to that function.

Okay, words you may not know in the context of computer programming, so…

Try to defer progress doing things you don't understand and take just a bit of time to get a handle on these basic concepts, either the links here or search terms that have been provided. Give a handful of sites a handful of minutes until you connect with a source that matches your level of knowledge and your learning style.

It will save you time in the long run if you do much in this hobby.

When you know more about arrays and functions, perhaps the things you find googling as I recommended in post #4 above will make more sense.

Or wait. Ppl here will fall all over each other, someone is bound to just write the whole thing for you and you'd be done. :wink:

a7

This is a lot to unpack, but thx for the abundance of resources and depth of your explanation.
Indeed, when I was writing my original code, I thought functions and some way of creating instances of each tank might simplify my life, but I just wasnt sure where to look into. I do have some understanding of the concepts of funcions (and arguments), and arrays, also FSM (but only built with like logic gates), I just didnt know how to bring it all together, and while I understand those concepts I'm not really experienced in writing c++ so sometimes its hard to understand whats going on (code-wise).

While that might be quite convenient, I have found this interesting enough to be willing to learn some more haha

Anyways, thx again for your guidance

Haha, me neither. If I were to call myself a programmer, and was required to qualify it by language specialty, I would have to say I am a C programmer.

It's just where I am, and I get things done. My opinion is that total mastery of C is a good start for expanding into C++. I also feel that C is a better match to programming very small machines.

You will find that opinions vary. :expressionless:

I hope you get or have already had that AHA! moment that comes when a process built on an FSM makes total sense.

Keep reading.

a7

it's not a c++ thing, it's more about understanding what tools arduino provides (e.g. millis()) and how they are best used.

state machines have been around forever. the concept of using millis() to wait for some time to expire is more unique to arduino and other uni-task polled programs

    switch (state)  {
    case St_Fill:
        if (millis() - msecLst > 250)  {
            // potential perform some action and change state
        }
        break;
    ...
    }

Yes. Although I prefer to take a time with millis() at the top of my free-running loop, the time for all time based actions in the rest of the loop. Because nothing in the loop takes any significant time.

unsigned long now = *millis*();

Serious ppl will use names like currentMillis.

Without getting too carried away, you can use nice variable name for times that stuff happens, like

 buttonPressedTime = now;

or whatever.

a7