SOLVED "SeveralThingsAtTheSameTime" Qs/help sought

I am working on a larger project but in an attempt to better wrap my mind around what it is I'm doing (coding wise) and help me help myself down the line I am doing the logical thing of breaking it down to constituent components. What I am working on will have two loops that run independent of one another so I've naturally gravitated to the "SeveralThings..." post for guidance.

The first loop (where I am working currently) consists of two peristaltic pumps and two gate valves.
The second loop also consists of two peristaltic pumps and four gate valves.

(Some background: Pump1 is offloading dirty fluid through GV1 or returning clean enough fluid to container through GV2. Pump2 will replace lost volume when fluid is being offloaded.)

What I have thus far compiles but where I'm getting bogged down is I need to run 'pump1' every 30 minutes. 2 minutes into its run time take an optical density reading of the fluid in the line then proceed with an if/else statement. The IF/ELSE being IF the optical density reading is less than the threshold, stop running. ELSE keep running and take density readings every 2 minutes. What I am seeing is once I start receiving density readings, they are being printed every time through the loop and not every 2 minutes.
A: Where am I going wrong on the density readings every time through the loop?
B: There is an error on the IF/ELSE that I cannot put my finger on--(sarcasm font: massively helpful on my end).

Guidance would be greatly appreciated.

(For myself, it was easier to go ahead and include the constants I know I'll eventually be using and then add them to the loops as I get to their respective parts of the code)

// ------ LIBRARIES ------

#include <SPI.h>
#include <SD.h>
#include <Arduino.h>
#include <Wire.h>
#include "RTClib.h"
#include <LiquidCrystal.h>
#if defined(ARDUINO_ARCH_SAMD)
#define Serial SerialUSB
#endif
RTC_Millis rtc;


// ------ CONSTANTANTS (won't change--PIN #s) ------

const int RTCgroundpin = 19; // RTC Ground Pin

// *** OD sensor *** //
const int ODSensorVCC=51;
const int ODLogicOutA=47;
const int ODLogicOutB=48;
const int ODCalibrate=49;
const int ODAnalogIN=A14;
int val = 0;
const int ODSensorGND=50;
const int ODSensorPreviousMillis = 0;
const int ODSensorWaitTime = 1200000;   // 2 minutes
int OD_HIGH_threshold = 250;  // SUBJECT TO CHANGE

// *** GATEVALVES *** //
const int GV1=29;
const int GV2=27;
const int GV3=24;
const int GV4=25;
const int GV5=23;
const int GV6=22;

// *** PUMPS *** //
// *** VCC power to motor drivers *** //
const int MotorDriver1 = 52;
const int MotorDriver1STBY = 36;
const int MotorDriver2 = 53;
const int MotorDriver2STBY = 37;

const int P1A=35;
const int P1B=33;
const int P1PWM=8;
const int P2A=39;
const int P2B=41;
const int P2PWM=45;
const int P3A=38;
const int P3B=40;
const int P3PWM=46;
const int P4A=34;
const int P4B=32;
const int P4PWM=7;

// *** PUMP RUN TIMES *** //
const int ODpump1INTERVAL = 1800000;  // 30 min
const int ODpump1RunTime1 = 120000;   // 2 min
const int ODpump1RunTime2 = 300000;   // 5 min
const int ODpump1RunTime3 = 600000;   // 10 min
const int ODpump2RunTime1 = 120000;   // 2 min
const int ODpump2RunTime2 = 300000;   // 5 min
const int ODpump2RunTime3 = 600000;   // 10 min

const int ByProductpump3RunTime1 = 120000;   // 2 min
const int ByProductpump3RunTime2 = 300000;   // 5 min
const int ByProductpump3RunTime3 = 600000;   // 10 min
const int ByProductpump4RunTime1 = 120000;   // 2 min
const int ByProductpump4RunTime2 = 300000;   // 5 min
const int ByProductpump4RunTime3 = 600000;   // 10 min


const int chipSelect = 4;      // delete?
const int rs = 12, en = 11, d4 = 5, d5 = 6, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

 
// ------ VARIABLES (will change) ------
unsigned long currentMillis = 0;  // stores the value of millis() in each iteration of loop
unsigned long PreviousPump1Millis = 0; // stores last time pump_ was updated
unsigned long PreviousPump2Millis = 0;
unsigned long PreviousPump3Millis = 0;
unsigned long PreviousPump4Millis = 0;

void setup() {
  // put your setup code here, to run once:
Serial.begin(9600);
Serial.println("Starting OD LOOP");

pinMode (RTCgroundpin, OUTPUT);

// ***OD Sensor *** //
pinMode (ODSensorVCC, OUTPUT);
pinMode (ODLogicOutA, OUTPUT);
pinMode (ODLogicOutB, OUTPUT);
pinMode (ODCalibrate, OUTPUT);
pinMode (ODAnalogIN, INPUT);
pinMode (ODSensorGND, OUTPUT);

// *** PUMPS *** //
pinMode (MotorDriver1, OUTPUT);
pinMode (MotorDriver1STBY, OUTPUT);
pinMode (MotorDriver2, OUTPUT);
pinMode (MotorDriver2STBY, OUTPUT);
pinMode (P1A, OUTPUT);
pinMode (P1B, OUTPUT); 
pinMode (P1PWM, OUTPUT);
pinMode (P2A, OUTPUT);
pinMode (P2B, OUTPUT);
pinMode (P2PWM, OUTPUT);
pinMode (P3A, OUTPUT);
pinMode (P3B, OUTPUT);
pinMode (P3PWM, OUTPUT);
pinMode (P4A, OUTPUT);
pinMode (P4B, OUTPUT);
pinMode (P4PWM, OUTPUT);

// *** GATEVALVES *** //
pinMode(GV1, OUTPUT);
pinMode(GV2, OUTPUT);
pinMode(GV3, OUTPUT);
pinMode(GV4, OUTPUT);
pinMode(GV5, OUTPUT);
pinMode(GV6, OUTPUT);

}

void loop() {
  // put your main code here, to run repeatedly:
digitalWrite (MotorDriver1, HIGH);
digitalWrite (MotorDriver1STBY, HIGH);
digitalWrite (MotorDriver2, HIGH);
digitalWrite (MotorDriver2STBY, HIGH);
digitalWrite (ODSensorVCC, HIGH);
digitalWrite (RTCgroundpin, LOW);
digitalWrite (ODSensorGND, LOW);

currentMillis = millis();  // capture the latest value of millis()
                           // this is equivalent to noting the time from a clock
digitalWrite (GV1, LOW);  // OPEN
digitalWrite (P1A, LOW);  // CLOSED
digitalWrite (P1B, HIGH);
analogWrite (P1PWM, 200);
ODpump1RunTime1;

if (currentMillis - PreviousPump1Millis >= ODpump1INTERVAL) {
  // Turn Pump1 ON, Open GateValve2 
  digitalWrite (GV1, HIGH);  // CLOSED
  digitalWrite (GV2, HIGH);  // OPEN
  digitalWrite (P1A, LOW);  // Motor Counter Clockwise
  digitalWrite (P1B, HIGH);
  analogWrite (P1PWM, 200);
  
if (currentMillis - ODSensorPreviousMillis >= ODSensorWaitTime ) {
    val = analogRead (ODAnalogIN);
  Serial.println (val);
  }
  PreviousPump1Millis >= ODpump1INTERVAL;
 
 if (ODAnalogIN < OD_HIGH_threshold) {
    digitalWrite (P1A, LOW);   // PUMP 1 OFF
    digitalWrite (P1B, LOW);
    analogWrite (P1PWM, 0);
    digitalWrite (GV1, HIGH);  // CLOSED
    digitalWrite (GV2, LOW);   // CLOSED
  }
  else {
    ODpump1RunTime1;
    digitalWrite (GV1, LOW);  // OPEN --OFFLOADING dirty fluid
    digitalWrite (GV2, LOW);  // CLOSED
    digitalWrite (P1A, LOW);  
    digitalWrite (P1B, HIGH);
    analogWrite (P1PWM, 200);
    digitalWrite (P1A, LOW);
    digitalWrite (P1B, HIGH);
    analogWrite (P2PWM, 200);
    
    if (currentMillis - ODSensorPreviousMillis >= ODSensorWaitTime)
     val = analogRead (ODAnalogIN);
     Serial.println (val);
        
  }
}
}
PreviousPump1Millis >= ODpump1INTERVAL;

I did not look too far into your code before finding this. It is wrong.

Should there be an if there somewhere ?

You need to update your ODSensorPreviousMillis value every time you read the sensor

    if (currentMillis - ODSensorPreviousMillis >= ODSensorWaitTime ) {
      val = analogRead (ODAnalogIN);
      Serial.println (val);
      ODSensorPreviousMillis = currentMills;
    }

Also, lines like these

  ODpump1RunTime1;
  PreviousPump1Millis >= ODpump1INTERVAL;

are completely useless

EFox:
Guidance would be greatly appreciated.

Your code is very poorly laid out so it is very difficult to see what IF and ELSE statements are linked. Use the AutoFormat tool and things will be much easier to read.

This

I need to run 'pump1' every 30 minutes. 2 minutes into its run time take an optical density reading of the fluid in the line then proceed with an if/else statement. The IF/ELSE being IF the optical density reading is less than the threshold, stop running. ELSE keep running and take density readings every 2 minutes. What I am seeing is once I start receiving density readings, they are being printed every time through the loop and not every 2 minutes.

suggests very strongly that a "state machine" approach to the problem is needed - look it up!. State machine is just a fancy name for one or more variables that keep track of what should be happening.

Use the IF/ELSE tests to decide the state of the system and set the value of the state variable(s).

Use the current value of the state variable(s) to determine what actually happens.

This has the added advantage that the code for the actions is separate from the decision making so all the different parts can be tested separately.

Writing your program as a collection of short single purpose functions also makes the development and debugging much easier. See Planning and Implementing a Program

...R

I've partially integrated state machines but I'm a bit confused regarding the coding language/correct equation I need to get the appropriate timing/timing gap I seek.

I'm trying to run pump1 to run every 20 seconds for 10 seconds (these intervals for testing verification)--this I've accomplished. Where I'm going off track is I need pump/motor 2 to start running two seconds after pump1 has started running. I believe my problem is in the terminology/math of the second "if" statement.

My end goal is to have the interval for pump1 to be increased to ~30min and have pump2's running be determined by the input from an optical density sensor.

That being said, I need to crawl before I can walk so the immediate goal is to understand the needed equation/language to make pump2 turn on two seconds after pump1 and have both stop running simultaneously.

#include <Wire.h>
#include "DS3231.h"

RTClib RTC;

// *** CONSTANTS *** //
int ODSensor = A14;
const int ODSensorVCC = 51;
const int ODSensorGND = 50;
const int MotorDriver1 = 52;
const int MotorDriver1STBY = 36;
const int MotorDriver2 = 53;
const int MotorDriver2STBY = 37;
const int P1A = 35;
const int P1B = 33;  // Will be pin that is changed
const int P1PWM = 8;
const int P2A = 39;  // Will be pin that is changed
const int P2B = 41;
const int P2PWM = 45;


// *** VARIABLES *** //
int ODPump1State = LOW;
int ODPump2State = LOW;
unsigned long previousMillisPump1 = 0;
unsigned long previousMillisPump2 = 0;
unsigned long nMillisTimer = 0;

// must be long to prevent overflow
unsigned long ODPump1INTERVAL = 20000; //  (20) time between starting
long ODPump1RunTime = 10000; // 10sec run time
long ODPump2INTERVAL = 12000;
unsigned long currentMillis = 0;

//const long ODpump1RunTime10 = 10000; // 10 seconds

void setup() {
  Serial.begin(9600);
  Wire.begin();
  // put your setup code here, to run once:
  pinMode (ODSensorVCC, OUTPUT);
  pinMode (ODSensorGND, OUTPUT);
  pinMode (ODSensor, INPUT);
  pinMode (MotorDriver1, OUTPUT);
  pinMode (MotorDriver1STBY, OUTPUT);
  pinMode (MotorDriver2, OUTPUT);
  pinMode (MotorDriver2STBY, OUTPUT);
  pinMode (P1A, OUTPUT);
  pinMode (P1B, OUTPUT);
  pinMode (P1PWM, OUTPUT);
  pinMode (P2A, OUTPUT);
  pinMode (P2B, OUTPUT);
  pinMode (P2PWM, OUTPUT);

  //currentMillis = millis();
}

void loop() {
  // put your main code here, to run repeatedly:
  DateTime now = RTC.now();
  digitalWrite (ODSensorGND, LOW);
  digitalWrite (ODSensorVCC, HIGH);
  digitalWrite (MotorDriver1, HIGH);
  digitalWrite (MotorDriver1STBY, HIGH);
  digitalWrite (MotorDriver2, HIGH);
  digitalWrite (MotorDriver2STBY, HIGH);
  currentMillis = millis();

  if (currentMillis - previousMillisPump1 > ODPump1INTERVAL) {
    //store the time of this change
    previousMillisPump1 = ODPump1INTERVAL;
    ODPump1State = (ODPump1State == HIGH) ? LOW : HIGH;
    digitalWrite(P1B, ODPump1State);
    digitalWrite(P1A, LOW);
    analogWrite(P1PWM, 200);
    currentMillis = millis();
  }

    }

  if (currentMillis - previousMillisPump2 > ODPump2INTERVAL) {
    previousMillisPump2 = currentMillis;
    ODPump2State = (ODPump2State == HIGH) ? LOW : HIGH;
    digitalWrite (P2A, ODPump2State);
    digitalWrite (P2B, LOW);
    analogWrite (P2PWM, 200);
    analogRead (ODSensor);
    Serial.println (ODSensor);
  }
}

The sort of state-machine that I have in mind is very different from - but not at all in conflict with what you have done.

In general a state-machine is a system that keeps track of what ought to be happening - it does not necessarily actually cause anything to happen.

I think, for your system I would have these states

pump1stopped
pump1waitingToStart
pump1MayRun
pump1running

pump2stopped
pump2waitingToStart
pump2MayRun
pump2running.

Then, if you want pump2 to start at a specific time after pump1 starts then when the state for pump1 changes from pump1waitingToStart to pump1running the state for pump2 should be changed from pump2stopped to pump2waitingToStart.

In both cases the period of time should be counted from when the state changes to pumpXwaitingToStart. When that time interval expires the state will change to pumpXMayRub

And the code to actually turn the motor on will only trigger when it sees the state pumpXMayRun - which it immediately changes to pumpXrunning as well as setting the start time for the duration of the motor run.

I hope that makes sense. It is not always easy to write down succinctly what is clear in one's mind.

...R

Here are some general style suggestions:

  • Use 'const byte' for pin numbers.
  • Use the 'const' keyword on all constants. It helps the compiler make good code.
  • Use type 'bool' for pin states or any variable that should only contain 0/false/LOW or 1/true/HIGH.
  • currentMillis doesn't need to be global so make it local.
  • Move the initialization at the top of loop() into setup().
  • Move the setting of pins that never change into setup().

Note: I had to comment out your use of RTC since I did not have the same DS3231 library as you.

#include <Wire.h>
#include "DS3231.h"


DS3231 RTC(A4, A5);


// *** CONSTANTS *** //
const byte ODSensor = A14;
const byte ODSensorVCC = 51;
const byte ODSensorGND = 50;
const byte MotorDriver1 = 52;
const byte MotorDriver1STBY = 36;
const byte MotorDriver2 = 53;
const byte MotorDriver2STBY = 37;
const byte P1A = 35;
const byte P1B = 33;  // Will be pin that is changed
const byte P1PWM = 8;
const byte P2A = 39;  // Will be pin that is changed
const byte P2B = 41;
const byte P2PWM = 45;


// *** VARIABLES *** //
bool ODPump1State = LOW;
bool ODPump2State = LOW;
unsigned long previousMillisPump1 = 0;
unsigned long previousMillisPump2 = 0;
unsigned long nMillisTimer = 0;


// must be long to prevent overflow
const unsigned long ODPump1INTERVAL = 20000; //  (20 seconds) time between starting
const unsigned long ODPump1RunTime = 10000; // 10sec run time
const unsigned long ODPump2INTERVAL = 12000;


//const long ODpump1RunTime10 = 10000; // 10 seconds


void setup()
{
  Serial.begin(9600);
  Wire.begin();
  // put your setup code here, to run once:
  pinMode (ODSensorVCC, OUTPUT);
  pinMode (ODSensorGND, OUTPUT);
  pinMode (ODSensor, INPUT);
  pinMode (MotorDriver1, OUTPUT);
  pinMode (MotorDriver1STBY, OUTPUT);
  pinMode (MotorDriver2, OUTPUT);
  pinMode (MotorDriver2STBY, OUTPUT);
  pinMode (P1A, OUTPUT);
  pinMode (P1B, OUTPUT);
  pinMode (P1PWM, OUTPUT);
  pinMode (P2A, OUTPUT);
  pinMode (P2B, OUTPUT);
  pinMode (P2PWM, OUTPUT);

  digitalWrite (ODSensorGND, LOW);
  digitalWrite (ODSensorVCC, HIGH);
  digitalWrite (MotorDriver1, HIGH);
  digitalWrite (MotorDriver1STBY, HIGH);
  digitalWrite (MotorDriver2, HIGH);
  digitalWrite (MotorDriver2STBY, HIGH);


  digitalWrite(P1A, LOW);
  analogWrite(P1PWM, 200);


  digitalWrite(P2B, LOW);
  analogWrite(P2PWM, 200);
}


void loop()
{
  //DateTime now = RTC.now();
  unsigned long currentMillis = millis();


  if (currentMillis - previousMillisPump1 > ODPump1INTERVAL)
  {
    //store the time of this change
    previousMillisPump1 += ODPump1INTERVAL;
    ODPump1State = !ODPump1State; // Toggle
    digitalWrite(P1B, ODPump1State);
  }


  if (currentMillis - previousMillisPump2 > ODPump2INTERVAL)
  {
    previousMillisPump2 += ODPump2INTERVAL;
    ODPump2State = !ODPump2State;  // Toggle
    digitalWrite (P2A, ODPump2State);
    analogRead (ODSensor);
    Serial.println (ODSensor);
  }
}

Robin2:
Then, if you want pump2 to start at a specific time after pump1 starts then when the state for pump1 changes from pump1waitingToStart to pump1running the state for pump2 should be changed from pump2stopped to pump2waitingToStart.

In both cases the period of time should be counted from when the state changes to pumpXwaitingToStart. When that time interval expires the state will change to pumpXMayRub

And the code to actually turn the motor on will only trigger when it sees the state pumpXMayRun - which it immediately changes to pumpXrunning as well as setting the start time for the duration of the motor run.

I hope that makes sense. It is not always easy to write down succinctly what is clear in one's mind.

...R

I absolutely understand the difficulties communicating one's thoughts (particularly via text). Also, I think I'm following and will do more reading on state machines. Thank you.

This is absolutely not me being dismissive of your recommendations Robin2 just my trying to understand how it should work/where I'm going awry regarding the timing in the current incarnation.
I've increased the interval for pump1 from 20sec to 30sec so that its obvious that it's 10s run time isn't that it's state is just bouncing every 10s. But I'm still seeing pump1's state switching every 30s and not the desired LOW for initial 30s, HIGH for 10s, LOW for remaining 20s, HIGH for 10s, lather, rinse, repeat.

Again, thank you for guidance.

Evan

johnwasser:
Here are some general style suggestions:

  • Use 'const byte' for pin numbers.
  • Use the 'const' keyword on all constants. It helps the compiler make good code.
  • Use type 'bool' for pin states or any variable that should only contain 0/false/LOW or 1/true/HIGH.
  • currentMillis doesn't need to be global so make it local.
  • Move the initialization at the top of loop() into setup().
  • Move the setting of pins that never change into setup().

Thank you for the advice. I come from a biology background with zero computer science/IT training and have had a fairly elaborate project for work given to me. As such, what I'm producing at this point is, I'm guessing, pretty ugly!

EFox:
I've increased the interval for pump1 from 20sec to 30sec so that its obvious that it's 10s run time isn't that it's state is just bouncing every 10s.

When you make changes to code you need to post the new version so we can see what you have done - the devil is in the detail.

And a key part of developing a program is having in mind how you will know whether the change you make has the effect you expect so that if it does not, the next change builds on your knowledge rather than being a random "we-must-change-something-anything...." which has no benefit except for the profits of coffee retailers.

...R

I'd like to try and help with a statemachine example but I can't figure out what you're doing here (from your OP):

.
.
.
if (ODAnalogIN < OD_HIGH_threshold) {
    digitalWrite (P1A, LOW);   // PUMP 1 OFF
    digitalWrite (P1B, LOW);
    analogWrite (P1PWM, 0);
    digitalWrite (GV1, HIGH);  // CLOSED
    digitalWrite (GV2, LOW);   // CLOSED
  }
///// else the OD is still too high and do ... this stuff?
  else {
    ODpump1RunTime1;
    digitalWrite (GV1, LOW);  // OPEN --OFFLOADING dirty fluid
    digitalWrite (GV2, LOW);  // CLOSED
    digitalWrite (P1A, LOW);  
    digitalWrite (P1B, HIGH);
    analogWrite (P1PWM, 200);
    digitalWrite (P1A, LOW);
    digitalWrite (P1B, HIGH);
    analogWrite (P2PWM, 200);
    
    if (currentMillis - ODSensorPreviousMillis >= ODSensorWaitTime)
     val = analogRead (ODAnalogIN);
     Serial.println (val);
.
.
.

I do see that this version is just "two things at the same time" so each pump is changing states on it's own timing. I am guessing changing pump2's timing is changing the if equation from "if (currentMillis - previousMillisPump2 > ODPump2INTERVAL) { ...". That is where I am going astray. My instinct says I need an if/else or second if equation nested. Something that is changing pump2's state to HIGH 2sec after pump1's goes HIGH but goes LOW when pump1 goes LOW.

Along the lines of 'if (ODPump1State == HIGH && curentMillis + previousPump1Millis > currentMillis + 2000)'.

#include <Wire.h>
#include "DS3231.h"

RTClib RTC;

// *** CONSTANTS *** //
const byte ODSensor = A14;
const byte ODSensorVCC = 51;
const byte ODSensorGND = 50;
const byte MotorDriver1 = 52;
const byte MotorDriver1STBY = 36;
const byte MotorDriver2 = 53;
const byte MotorDriver2STBY = 37;
const byte P1A = 35;
const byte P1B = 33;  // Will be pin that is changed
const byte P1PWM = 8;
const byte P2A = 39;  // Will be pin that is changed
const byte P2B = 41;
const byte P2PWM = 45;


// *** VARIABLES *** //
bool ODPump1State = LOW;
bool ODPump2State = LOW;
unsigned long previousMillisPump1 = 0;
unsigned long previousMillisPump2 = 0;
unsigned long nMillisTimer = 0;


// must be long to prevent overflow
const unsigned long ODPump1INTERVAL = 30000; //  30sec
const unsigned long ODPump1RunTime = 10000;  //   10sec 
const unsigned long ODPump2INTERVAL = 30000; //  30sec
const unsigned long ODPump2RunTime = 8000;   //    8sec 


void setup()
{
  Serial.begin(9600);
  Wire.begin();
  // put your setup code here, to run once:
  pinMode (ODSensorVCC, OUTPUT);
  pinMode (ODSensorGND, OUTPUT);
  pinMode (ODSensor, INPUT);
  pinMode (MotorDriver1, OUTPUT);
  pinMode (MotorDriver1STBY, OUTPUT);
  pinMode (MotorDriver2, OUTPUT);
  pinMode (MotorDriver2STBY, OUTPUT);
  pinMode (P1A, OUTPUT);
  pinMode (P1B, OUTPUT);
  pinMode (P1PWM, OUTPUT);
  pinMode (P2A, OUTPUT);
  pinMode (P2B, OUTPUT);
  pinMode (P2PWM, OUTPUT);

  digitalWrite (ODSensorGND, LOW);
  digitalWrite (ODSensorVCC, HIGH);
  digitalWrite (MotorDriver1, HIGH);
  digitalWrite (MotorDriver1STBY, HIGH);
  digitalWrite (MotorDriver2, HIGH);
  digitalWrite (MotorDriver2STBY, HIGH);

  digitalWrite(P1A, LOW);
  analogWrite(P1PWM, 185);
  digitalWrite(P2B, LOW);
  analogWrite(P2PWM, 185);
}

void loop()
{
  DateTime now = RTC.now();
  unsigned long currentMillis = millis();

  if (currentMillis - previousMillisPump1 > ODPump1INTERVAL)
  {
    //store the time of this change
    previousMillisPump1 += ODPump1INTERVAL;
    ODPump1State = !ODPump1State; // Toggle
    digitalWrite(P1B, ODPump1State);
  }

  if (currentMillis - previousMillisPump2 > ODPump2INTERVAL) {
    previousMillisPump2 += ODPump2INTERVAL;
    ODPump2State = !ODPump2State;  // Toggle
    digitalWrite (P2A, ODPump2State);
  }
}

To make a State Machine you need a variable that holds the current state. I usually You also need a list of stated that your sketch can be in. In each state the sketch is waiting for something to happen to tell it to go to some other state. When your sketch first starts, what is it waiting for?

Here is a partial example of how I implement a State Machine. The 'State' variable keeps track of the current state. The 'enum States' enumerates all of the states. Each state is assigned a number. The 'switch(State)' executes the code specific to the current state. Setting 'State' to a new value moves the State Machine to a different state.

enum States {WAITING_FOR_MACHINE_NUM, WAITING_FOR_JOB_NUM, READY} State = WAITING_FOR_MACHINE_NUM;

void loop()
{
  byte customKey = customKeypad.getKey();
  
  if (customKey != NO_KEY)
  {
    switch (State)
    {
      case WAITING_FOR_MACHINE_NUM:
        if (customKey >= '0' && customKey <= '9')
        {
          MachineNumber = customKey - '0';
          State = WAITING_FOR_JOB_NUM;
        }
        break;


      case WAITING_FOR_JOB_NUM:
        if (customKey >= '0' && customKey <= '9')
        {
          JobNumber = customKey - '0';
          State = READY;
        }
        break;


      case READY:
        // The Machine Number and Job Number are set.
        break;
    }
  }
}

EFox:
I do see that this version is just "two things at the same time" so each pump is changing states on it's own timing.

You don't seem to have made any attempt to incorporate the suggestion I made in Reply #5.

You are, of course, perfectly entitled to ignore my suggestions. All I can say in my defence is that my programs work :slight_smile:

...R

Have a look at this. It compiles and flashes the LED though I didn't check to see if it turns the motors or not. I've got three separate state machines "doing things at once" here; one blinks the LED, one turns pump 1 on every 4min 30-sec and checks the OD to turn it off (or at least it's supposed to) and the last one just toggles motor 2 (but doesn't do anything with valves etc...)

Hopefully it gives you an idea of at least one way to approach doing more than one thing at a time, segregating tasks into fairly small, quick little actions etc.

#define ODP1PUMP_INTERVAL       270000ul        //4.5-minutes
#define ODP1OD_INTERVAL         120000ul        //2-minutes
#define OD_HIGH_THRESH          250             //arbitrary...

#define ODP2PUMP_INTERVAL       60000ul         //1 minute intervals
#define ODP2_RUNTIME            10000ul         //10-second run time

const byte ODAnalogIN = A14;
const byte P1A = 35;
const byte P1B = 33;
const byte P1PWM = 8;
const byte P2A = 39;
const byte P2B = 41;
const byte P2PWM = 45;

const byte GV1=29;
const byte GV2=27;

#define LED_TOGL_PERIOD         125             //125mS toggle period for LED
const byte pinLED = LED_BUILTIN;

void setup() 
{
    Serial.begin(9600);

    //set up LED pin
    pinMode( pinLED, OUTPUT );
    digitalWrite( pinLED, LOW );

    //set up motor control pins
    pinMode (P1A, OUTPUT);
    pinMode (P1B, OUTPUT);
    pinMode (P1PWM, OUTPUT);
    pinMode (P2A, OUTPUT);
    pinMode (P2B, OUTPUT);
    pinMode (P2PWM, OUTPUT);

    //set motor speeds to 0% duty
    analogWrite( P1PWM, 0 );
    analogWrite( P2PWM, 0 );
    
}//setup

void loop() 
{
    //main loop runs very fast, peeking into each state machine function to see if any need attention
    BlinkLED();
    ODPump1StateMachine();
    ODPump2StateMachine();

}//loop

void BlinkLED( void )
{
    //show multiple things happening by blinking the LED
    static bool
        bLEDState = false;
    static unsigned long
        timeLED = 0;
    unsigned long
        timeNow;

    //get time now
    timeNow = millis();
    //if toggle period has expired...
    if( timeNow - timeLED >= LED_TOGL_PERIOD )
    {
        //set up for the next toggle period
        timeLED = timeNow;
        //toggle the LED state variable by XORing it with true (so it toggles true/false/true/false etc)
        bLEDState ^= true;

        //set the LED to the same state as the state variable
        if( bLEDState )
            digitalWrite( pinLED, HIGH );
        else
            digitalWrite( pinLED, LOW );
            
    }//if
        
}//BlinkLED

//ODP1 state names
#define ST_ODP1_INIT                0
#define ST_ODP1_INTERVAL_TIMING     1
#define ST_ODP1_RUNNING             2
//
void ODPump1StateMachine( void )
{
    int
        ODVal;
    static byte
        stateODP1 = ST_ODP1_INIT;
    static unsigned long
        timeODP1_RunPeriod,
        timeODP1_RunTimer,
        timeODP1_ODTimer;
    unsigned long
        timeNow;

    timeNow = millis();
    
    switch( stateODP1 )
    {
        case    ST_ODP1_INIT:
            //out of reset we start here and just initialize our start timer
            timeODP1_RunTimer = timeNow;
            //then move to the interval timing state
            stateODP1 = ST_ODP1_INTERVAL_TIMING;
            
        break;

        case    ST_ODP1_INTERVAL_TIMING:
            //has the interval time elapsed?
            if( (timeNow - timeODP1_RunTimer) >= ODP1PUMP_INTERVAL )
            {
                //yes; start the pump, and...
                digitalWrite(GV1, HIGH);    // CLOSED
                digitalWrite(GV2, HIGH);    // OPEN
                digitalWrite(P1A, LOW);     // Motor Counter Clockwise
                digitalWrite(P1B, HIGH);
                analogWrite(P1PWM, 200);    //set motor speed             
                
                //...initialize the optical density check timer, and...
                timeODP1_ODTimer = timeNow;

                //...save the motor start time to ensure the correct period is produced after the first pass, and...
                timeODP1_RunPeriod = timeNow;

                //...set state to running
                stateODP1 = ST_ODP1_RUNNING;

                Serial.println( "Pump 1 running..." );
                
            }//if
            
        break;

        case    ST_ODP1_RUNNING:
            //pump is running now; check for optical density check intervals
            if( (timeNow - timeODP1_ODTimer) >= ODP1OD_INTERVAL )
            {
                //check the density
                ODVal = analogRead( ODAnalogIN );
                Serial.print( "Optical density val: " );
                Serial.println( ODVal ); 

                //if less than the threshold, turn things off
                if( ODVal <= OD_HIGH_THRESH )
                {
                    //threshold reached. turn off the motor & valves...
                    digitalWrite (P1A, LOW);   // PUMP 1 OFF
                    digitalWrite (P1B, LOW);
                    analogWrite (P1PWM, 0);
                    digitalWrite (GV1, HIGH);  // CLOSED
                    digitalWrite (GV2, LOW);   // CLOSED                     

                    Serial.println( "Pump 1 off..." );

                    //repeat again
                    timeODP1_RunTimer = timeODP1_RunPeriod;     //set our period based on the last time we started the motor
                    stateODP1 = ST_ODP1_INTERVAL_TIMING;        //go back to interval timing
                    
                }//if                
                else
                {
                    //OD reading still showing too much density; set up for another check in a couple of minutes
                    timeODP1_ODTimer = timeNow;    
                    
                }//else
                
            }//if
            
        break;
        
    }//switch
        
    
}//ODPump1StateMachine

//ODP2 state names
#define ST_ODP2_INIT                0
#define ST_ODP2_INTERVAL_TIMING     1
#define ST_ODP2_RUNNING             2
//this machine just turns the pump 2 motor on and off at regular intervals for demonstration purposes
//similar logic (though simpler) than for pump 1
void ODPump2StateMachine( void )
{
    static byte
        stateODP2 = ST_ODP2_INIT;
    static unsigned long
        timeODP2_RunTimer,
        timeODP2_DurationTimer;
    unsigned long
        timeNow;

    timeNow = millis();
    
    switch( stateODP2 )
    {
        case    ST_ODP2_INIT:            
            timeODP2_RunTimer = timeNow;
            stateODP2 = ST_ODP2_INTERVAL_TIMING;
            
        break;

        case    ST_ODP2_INTERVAL_TIMING:
            if( (timeNow - timeODP2_RunTimer) >= ODP2PUMP_INTERVAL )
            {
                //start the pump, and...
                digitalWrite(P2A, HIGH);        // Motor clockwise
                digitalWrite(P2B, LOW);
                analogWrite(P2PWM, 100);        //set motor speed slower than pump 1 for effect             
                
                //...initialize the pump 2 run time duration timer
                timeODP2_DurationTimer = timeNow;

                //set state to run
                stateODP2 = ST_ODP2_RUNNING;
                
                Serial.println( "Pump 2 running..." );
                
            }//if
            
        break;

        case    ST_ODP2_RUNNING:
            //pump is running now; has its run time expired?
            if( (timeNow - timeODP2_DurationTimer) >= ODP2_RUNTIME )
            {
                analogWrite(P2PWM, 0);          //shut down pump 2
                Serial.println( "Pump 2 off..." );

                //go back and set it to run again
                stateODP2 = ST_ODP2_INIT;
                
            }//if
            
        break;
        
    }//switch
        
}//ODPump2StateMachine

Blackfin:
Hopefully it gives you an idea of at least one way to approach doing more than one thing at a time, segregating tasks into fairly small, quick little actions etc.

Thank you for the guidance. I THINK I'm getting the concept and was copying the second state machine to make an additional state machine that I want to run at regular intervals in addition to the first two state machines. However, I'm encountering the "not declared in this scope" when it appears I've done nothing different than the code you provided.

I have a couple other questions but want to learn where I've gone wrong first.

Mahalo!

//******* OD LOOP *******

#define ODP1PUMP_INTERVAL       600000ul        //10 minutes
#define ODP1OD_INTERVAL         90000ul        // 1.5-minutes
const byte OD_HIGH_THRESH = 500;                // arbitrary...

#define ODP2PUMP_INTERVAL       300000ul        // 5 minute intervals
#define ODP2_RUNTIME            20000ul         // 20-second run time

#define ByProductPUMP3_INTERVAL  600000ul       // 10 min
#define ByProductPUMP3_RUNTIME   120000ul       // 2 min
//#define ByProductPUMP4_INTERVAL               // __ min
//#define ByProductPUMP4_RUNTIME                // __ min

const byte ODAnalogIN = A14;
const byte ODVCC = 51;
const byte ODGND = 50;
const byte P1A = 35;
const byte P1B = 33;
const byte P1PWM = 8;
const byte P2A = 39;
const byte P2B = 41;
const byte P2PWM = 45;
const byte P3A = 38;
const byte P3B = 40;
const byte P3PWM = 46;
const byte P4A = 34;
const byte P4B = 32;
const byte P4PWM = 7;
const byte Driver2STBY = 37;
const byte Driver2VCC = 53;
const byte Driver1STBY = 36;
const byte Driver1VCC = 52;

const byte GV1 = 29;          // #s NEED TO BE VERIFIED
const byte GV2 = 27;
const byte GV3 = 24;
const byte GV4 = 25;
const byte GV5 = 23;
const byte GV6 = 22;


void setup()
{
  Serial.begin(9600);

  //set up OD sensor
  pinMode (ODVCC, OUTPUT);
  pinMode (ODGND, OUTPUT);
  digitalWrite (ODVCC, HIGH);
  digitalWrite (ODGND, LOW);

  //set up motor control pins
  pinMode (P1A, OUTPUT);
  pinMode (P1B, OUTPUT);
  pinMode (P1PWM, OUTPUT);
  pinMode (P2A, OUTPUT);
  pinMode (P2B, OUTPUT);
  pinMode (P2PWM, OUTPUT);
  pinMode (P3A, OUTPUT);
  pinMode (P3B, OUTPUT);
  pinMode (P3PWM, OUTPUT);
  pinMode (P4A, OUTPUT);
  pinMode (P4B, OUTPUT);
  pinMode (P4PWM, OUTPUT);

  pinMode (Driver1STBY, OUTPUT);
  pinMode (Driver1VCC, OUTPUT);
  pinMode (Driver2STBY, OUTPUT);
  pinMode (Driver2VCC, OUTPUT);
  digitalWrite (Driver1STBY, HIGH);
  digitalWrite (Driver1VCC, HIGH);
  digitalWrite (Driver2STBY, HIGH);
  digitalWrite (Driver2VCC, HIGH);

  //set motor speeds to 0% duty
  analogWrite( P1PWM, 0 );
  analogWrite( P2PWM, 0 );

}//setup

void loop()
{
  //main loop runs very fast, peeking into each state machine function to see if any need attention
  ODPump1StateMachine();
  ODPump2StateMachine();
  ByProductPump3StateMachine();
  //  ByProductPump4StateMachine();

}//loop

//ODP1 state names
#define ST_ODP1_INIT                0
#define ST_ODP1_INTERVAL_TIMING     1
#define ST_ODP1_RUNNING             2
//
void ODPump1StateMachine( void )
{
  int
  ODVal;
  static byte
  stateODP1 = ST_ODP1_INIT;
  static unsigned long
  timeODP1_RunPeriod,
  timeODP1_RunTimer,
  timeODP1_ODTimer;
  unsigned long
  timeNow;

  timeNow = millis();

  switch ( stateODP1 )
  {
    case    ST_ODP1_INIT:
      //out of reset we start here and just initialize our start timer
      timeODP1_RunTimer = timeNow;
      //then move to the interval timing state
      stateODP1 = ST_ODP1_INTERVAL_TIMING;

      break;

    case    ST_ODP1_INTERVAL_TIMING:
      //has the interval time elapsed?
      if ( (timeNow - timeODP1_RunTimer) >= ODP1PUMP_INTERVAL )
      {
        //yes; start the pump, and...
        digitalWrite(GV1, HIGH);    // CLOSED
        digitalWrite(GV2, HIGH);    // OPEN
        digitalWrite(P1A, LOW);     // Motor Counter Clockwise
        digitalWrite(P1B, HIGH);
        analogWrite(P1PWM, 150);    //set motor speed

        //...initialize the optical density check timer, and...
        timeODP1_ODTimer = timeNow;

        //...save the motor start time to ensure the correct period is produced after the first pass, and...
        timeODP1_RunPeriod = timeNow;

        //...set state to running
        stateODP1 = ST_ODP1_RUNNING;

        Serial.println( "Pump 1 running..." );

      }//if

      break;

    case    ST_ODP1_RUNNING:
      //pump is running now; check for optical density check intervals
      if ( (timeNow - timeODP1_ODTimer) >= ODP1OD_INTERVAL )
      {
        //check the density
        ODVal = analogRead( ODAnalogIN );
        Serial.print( "Optical density val: " );
        Serial.println( ODVal );

        //if less than the threshold, turn things off
        if ( ODVal >= OD_HIGH_THRESH )
        {
          //threshold reached. turn off the motor & valves...
          digitalWrite (P1A, LOW);   // PUMP 1 OFF
          digitalWrite (P1B, LOW);
          analogWrite (P1PWM, 0);
          digitalWrite (GV1, HIGH);  // CLOSED
          digitalWrite (GV2, LOW);   // CLOSED

          Serial.println( "Pump 1 off..." );

          //repeat again
          timeODP1_RunTimer = timeODP1_RunPeriod;     //set our period based on the last time we started the motor
          stateODP1 = ST_ODP1_INTERVAL_TIMING;        //go back to interval timing

        }//if
        else
        {
          //OD reading still showing too much density; set up for another check in a couple of minutes
          timeODP1_ODTimer = timeNow;

        }//else

      }//if

      break;

  }//switch


}//ODPump1StateMachine

//ODP2 state names
#define ST_ODP2_INIT                0
#define ST_ODP2_INTERVAL_TIMING     1
#define ST_ODP2_RUNNING             2
//this machine just turns the pump 2 motor on and off at regular intervals for demonstration purposes
//similar logic (though simpler) than for pump 1

void ODPump2StateMachine( void )
{
  static byte
  stateODP2 = ST_ODP2_INIT;
  static unsigned long
  timeODP2_RunTimer,
  timeODP2_DurationTimer;
  unsigned long
  timeNow;

  timeNow = millis();

  switch ( stateODP2 )
  {
    case    ST_ODP2_INIT:
      timeODP2_RunTimer = timeNow;
      stateODP2 = ST_ODP2_INTERVAL_TIMING;

      break;

    case    ST_ODP2_INTERVAL_TIMING:
      if ( (timeNow - timeODP2_RunTimer) >= ODP2PUMP_INTERVAL )
      {
        //start the pump, and...
        digitalWrite(P2A, HIGH);        // Motor clockwise
        digitalWrite(P2B, LOW);
        analogWrite(P2PWM, 100);        //set motor speed slower than pump 1 for effect

        //...initialize the pump 2 run time duration timer
        timeODP2_DurationTimer = timeNow;

        //set state to run
        stateODP2 = ST_ODP2_RUNNING;

        Serial.println( "Pump 2 running..." );

      }//if

      break;

    case    ST_ODP2_RUNNING:
      //pump is running now; has its run time expired?
      if ( (timeNow - timeODP2_DurationTimer) >= ODP2_RUNTIME )
      {
        analogWrite(P2PWM, 0);          //shut down pump 2
        Serial.println( "Pump 2 off..." );

        //go back and set it to run again
        stateODP2 = ST_ODP2_INIT;

      }//if

      break;

  }//switch

}//ODPump2StateMachine

//By Product Pump3 state names
#define ST_BPP3_INIT               0
#define ST_BPP3_INTERVAL_TIMING   1
#define ST_BPP3_RUNNING           2

void ByProductPump3StateMachine (void)

static byte
stateBPP3 = ST_BPP3_INIT
            static unsigned long
            timeBPP3_RunTimer,
            timeBPP3_DurationTimer;
unsigned long
timeNow;
timeNow = millis();
switch (stateBPP3)
{
case ST_BPP3_INIT;
  timeBPP3_RunTimer = timeNow;
  stateBPP3 = ST_BPP3_INTERVAL_TIMING;

  break;

case ST_BPP3_INTERVAL_TIMING:
  if ((timeNow - timeBPP3_RunTimer) >= BPP3PUMP_INTERVAL)
  {
    //start the pump, and...
    digitalWrite(GV3, HIGH);
    digitalWrite(P3A, HIGH);
    digitalWrite(P3B, LOW);
    analogWrite(P3PWM, 150);

    //...initialize the pump 3 run time duration timer
    timeBPP3_DurationTimer = TimeNow;

    //set the state to run
    stateBPP3 = ST_BPP3_RUNNING;
    Serial.println("Pump3 running...")
  }
  break;
case ST_BPP3_RUNNING:
  //pump is running now; has its run time expired?
  if ((timeNow - timeBPP3_DurationTimer) >= BPP3_RUNTIME)
  {
    digitalWrite(P3A, LOW);
    analogWrite(P3PWM, 0)
    Serial.println("Pump 3 off...");

    //go back and set it to run again
    stateBPP3 = ST_Bpp3_INIT;

  }

}

EFox:
Thank you for the guidance. I THINK I'm getting the concept and was copying the second state machine to make an additional state machine that I want to run at regular intervals in addition to the first two state machines. However, I'm encountering the "not declared in this scope" when it appears I've done nothing different than the code you provided.

I have a couple other questions but want to learn where I've gone wrong first.

You've got a few syntax errors, missing braces, mis-labeled terms, case-sensitivity problems etc in your last function. Try copy-pasting this over the same part of your code.

(I didn't check your logic, just corrected compilation errors.)

//By Product Pump3 state names
#define ST_BPP3_INIT               0
#define ST_BPP3_INTERVAL_TIMING   1
#define ST_BPP3_RUNNING           2

void ByProductPump3StateMachine (void)
{
  static byte
  stateBPP3 = ST_BPP3_INIT;
  static unsigned long
  timeBPP3_RunTimer,
  timeBPP3_DurationTimer;
  unsigned long
  timeNow;
  timeNow = millis();
  switch (stateBPP3)
  {
    case ST_BPP3_INIT:
      timeBPP3_RunTimer = timeNow;
      stateBPP3 = ST_BPP3_INTERVAL_TIMING;

      break;

    case ST_BPP3_INTERVAL_TIMING:
      if ((timeNow - timeBPP3_RunTimer) >= ByProductPUMP3_INTERVAL)
      {
        //start the pump, and...
        digitalWrite(GV3, HIGH);
        digitalWrite(P3A, HIGH);
        digitalWrite(P3B, LOW);
        analogWrite(P3PWM, 150);

        //...initialize the pump 3 run time duration timer
        timeBPP3_DurationTimer = timeNow;

        //set the state to run
        stateBPP3 = ST_BPP3_RUNNING;
        Serial.println("Pump3 running...");
      }
      break;
    case ST_BPP3_RUNNING:
      //pump is running now; has its run time expired?
      if ((timeNow - timeBPP3_DurationTimer) >= ByProductPUMP3_RUNTIME)
      {
        digitalWrite(P3A, LOW);
        analogWrite(P3PWM, 0);
        Serial.println("Pump 3 off...");

        //go back and set it to run again
        stateBPP3 = ST_BPP3_INIT;

      }

  }
}

face palm Thank you.

Next question:
The last pump has two inputs (via valves) and two outputs (via valves--one valve is shared with pump3). I've attached a pdf schematic description. The goal is the utilize pump3 to move fluid through the resin to capture target compounds and between time points that pump3 runs run pump4 to move MeOH through the resin via GV6. Once a tbd time has passed, close GV6 & open GV5 for a tbd length so that pump4 is now drawing "flush" through the resin. Lastly, reset to pump4 off and GVs 4-6 closed and ready for pump3's state machine's next loop.

That makes sense to me but I'm close to this project and you're not, so I'm not sure.

Flow schematic BW1.pdf (204 KB)

Pumps.pdf (392 KB)

What is a better methodology:

Since I cannot have pump4 running while pump3 is running, would timing code be cleaner to write having both pumps in a single state machine or having each pump have it's own?

If pump3 running and pump4 running are mutually exclusive then they could be different states in the same state machine