pH dosing system, having digital output delay overwrite the analog input reading

Hello sorry I am new here, I hope I am in the right place. I'm sure this is a programming issue.

What I have is a pH dosing system. I am using an Arduino Uno with an Atlantic Scientific pH probe. I am driving 2 12v peristaltic pumps with relays.

If the pH is too low or high and it turns on one of the pumps, the readings on my pH probe end up being the delay that I have set for my pump to remain off. For instance I want to add solution for 500ms and delay 120 seconds to keep getting readings.

If i take the pumps out of the mix I can set the delay for readings on the pH probe to whatever I want, but then if a pump gets triggered it overwrites it's digitalwrite LOW delay value into the analog readings delays.

I really hope that make sense, It seems like I need to isolate the pump code from the analog code somehow, and I am not sure to do that. Thank you in advance for any advice, and I hope I have posted this in the proper format.

/* Once uploaded, open the serial monitor, set the baud rate to 9600 and append "Carriage return"
  The code allows the user to observe real time pH readings as well as calibrate the sensor.
  One, two or three-point calibration can be done.

  Calibration commands:
  low-point: "cal,4"
  mid-point: "cal,7"
  high-point: "cal,10"
  clear calibration: "cal,clear" */


#include "ph_grav.h"                                  //header file for Atlas Scientific gravity pH sensor
#include "LiquidCrystal.h"                            //header file for liquid crystal display (lcd)
#include <LiquidCrystal_I2C.h>

#define pH_up 2
#define interval 1000
#define pH_down 4


// define vars for testing menu
const int timeout = 10000;       //define timeout of 10 sec
char menuOption = 0;
long time0;


// Data Variables
String inputstring = "";                              //a string to hold incoming data from the PC
boolean input_string_complete = false;                //a flag to indicate have we received all the data from the PC
char inputstring_array[10];                           //a char array needed for string parsing
Gravity_pH pH = A0;                                   //assign analog pin A0 of Arduino to class Gravity_pH. connect output of pH sensor to pin A0
LiquidCrystal_I2C pH_lcd(0x27, 20, 4);        //make a variable pH_lcd and assign arduino digital pins to lcd pins (2 -> RS, 3 -> E, 4 to 7 -> D4 to D7)


void setup() {


  //Initialize LCD
  pH_lcd.init();                                      // initialize the lcd
  pH_lcd.backlight();
  Serial.begin(9600);                                 //enable serial port
  while (!Serial) ;
  Serial.println("start");
  pH_lcd.begin(20, 4);  //start lcd interface and define lcd size (20 columns and 4 rows)
  pinMode(pH_up, OUTPUT);
  pinMode(pH_down, OUTPUT);




  // First Row LCD
  pH_lcd.setCursor(7, 0);                //place cursor on screen at column 1, row 1
  pH_lcd.print("#^(^#)");               //display characters


  //Last Row LCD
  pH_lcd.setCursor(0, 3);                             //place cursor on screen at column 1, row 4
  pH_lcd.print("--------------------");               //display characters


  //Row 2 LCD
  pH_lcd.setCursor(5, 1);                             //place cursor on screen at column 6, row 2
  pH_lcd.print("pH Reading");                         //display "pH Reading"
  if (pH.begin()) {
    Serial.println("Loaded EEPROM");
  }
  Serial.println(F("Use commands \"LOW\", \"MID\", and \"HI\" to calibrate the circuit to those respective values"));
  Serial.println(F("Use command \"CAL,CLEAR\" to clear the calibration"));

}


void serialEvent() {                                  //if the hardware serial port_0 receives a char
  inputstring = Serial.readStringUntil(13);           //read the string until we see a <CR>
  input_string_complete = true;                       //set the flag used to tell if we have received a completed string from the PC
}



void loop() {




  int sensorValue = analogRead(A0);
  //  Serial.println(sensorValue);
  delay(1000);


  if (input_string_complete == true) {                //check if data received
    inputstring.toCharArray(inputstring_array, 30);   //convert the string to a char array
    parse_cmd(inputstring_array);                     //send data to pars_cmd function
    input_string_complete = false;                    //reset the flag used to tell if we have received a completed string from the PC
    inputstring = "";                                 //clear the string
  }

  Serial.println(pH.read_ph());                       //output pH reading to serial monitor
  pH_lcd.setCursor(8, 2);                             //place cursor on screen at column 9, row 3
  pH_lcd.print(pH.read_ph());                         //output pH to lcd
  delay(1000);

   if (pH.read_ph() < 5.50) {
     digitalWrite(pH_up, HIGH);
     delay(250);
     digitalWrite(pH_up, LOW);
     delay(1000);
     }

    if (pH.read_ph() > 6.50){
     digitalWrite(pH_down, HIGH);
     delay(250);
     digitalWrite(pH_down, LOW);
     delay(1000);
    } 



}


void parse_cmd(char* string) {                      //For calling calibration functions
  strupr(string);                                   //convert input string to uppercase

  if (strcmp(string, "L") == 0) {               //compare user input string with CAL,4 and if they match, proceed
    pH.cal_low();                                   //call function for low point calibration
    Serial.println("LOW CALIBRATED");
  }
  else if (strcmp(string, "M") == 0) {          //compare user input string with CAL,7 and if they match, proceed
    pH.cal_mid();                                   //call function for midpoint calibration
    Serial.println("MID CALIBRATED");
  }
  else if (strcmp(string, "H") == 0) {         //compare user input string with CAL,10 and if they match, proceed
    pH.cal_high();                                  //call function for highpoint calibration
    Serial.println("HIGH CALIBRATED");
  }
  else if (strcmp(string, "C") == 0) {      //compare user input string with CAL,CLEAR and if they match, proceed
    pH.cal_clear();                                 //call function for clearing calibration
    Serial.println("CALIBRATION CLEARED");
  }
}

Why are you using analogRead on the PH sensor? It looks like read_ph (which you're using) is all that's needed.

you need to get rid of all those delays and use millis() to handle your timing (and possibly have a small "state machine")

check the "Useful links - check here for reference posts / tutorials" pinned at the top of the forum for more info

(sensorValue is not used)

For optimising your code you should analyse how fast does the ph-sensor react on ph-changes.

A microcontroller can run so fast that you could control 50 pairs of pumps with 50 ph-sensors at the same time
if the programming is done right.

There is a programming-technique caled non-blocking timing.

as an allday example with easy to follow numbers
delay() is blocking. As long as the delay is "delaying" nothing else of the code can be executed.
Now there is a technique of non-blocking timing.
The basic principle of non-blocking timing is fundamental different from using delay()
You have to understand the difference first and then look into the code.
otherwise you might try to "see" a "delay-analog-thing" in the millis()-code which it really isn't
Trying to see a "delay-analog-thing" in millis() makes it hard to understand millis()
Having understood the basic principle of non-blocking timing based on millis() makes it easy to understand.

imagine baking a frosted pizza
the cover says for preparation heat up oven to 200°C
then put pizza in.
Baking time 10 minutes

You are estimating heating up needs 3 minutes
You take a look onto your watch it is 13:02 (snapshot of time)
You start reading the newspaper and from time to time looking onto your watch
watch 13:02 not yet time
watch 13:03 not yet time
watch 13:04 not yet time 13:04 - 13:02 = 2 minutes is less than 3 minutes
watch 13:05 when did I start 13:02? OK 13:05 - 13:02 = 3 minutes time to put pizza into the oven

New basetime 13:05 (the snapshot of time)
watch 13:06 not yet time
watch 13:07 not yet time
watch 13:08 not yet time (13:08 - 13:05 = 3 minutes is less than 10 minutes
watch 13:09 not yet time
watch 13:10 not yet time
watch 13:11 not yet time
watch 13:12 not yet time
watch 13:13 not yet time
watch 13:14 not yet time (13:14 - 13:05 = 9 minutes is less than 10 minutes
watch 13:15 when did I start 13:05 OK 13:15 - 13:05 = 10 minutes time to eat pizza (yum yum)

You did a repeated comparing how much time has passed by
This is what non-blocking timing does

In the code looking at "How much time has passed by" is done

currentTime - startTime >= bakingTime

bakingTime is 10 minutes

13:06 - 13:05 = 1 minute >= bakingTime is false
13:07 - 13:05 = 2 minutes >= bakingTime is false
...
13:14 - 13:05 = 9 minutes >= bakingTime is false
13:15 - 13:05 = 10 minutes >= bakingTime is TRUE time for timed action!!

if (currentTime - previousTime >= period) {

it has to be coded exactly this way because in this way it manages the rollover from Max back to zero
of the function millis() automatically

baldengineer.com has a very good tutorial about timing with function millis() too .

There is one paragraph that nails down the difference between function delay() and millis() down to the point:

The millis() function is one of the most powerful functions of the Arduino library. This function returns the number of milliseconds the current sketch has been running since the last reset. At first, you might be thinking, well that’s not every useful! But consider how you tell time during the day. Effectively, you look at how many minutes have elapsed since midnight. That’s the idea behind millis()!

Instead of “waiting a certain amount of time” like you do with delay(), you can use millis() to ask “how much time has passed”?

This means your loop runs through tenthousands of times every second always comparing how much time has passed by since last timed action. And if the time that has passed by becomes bigger than your timeing-constant the next timed action gets executed.

So give it a try with a modified code-version - and - if it does not work yet as expected post your new coe-version and ask questions.
best regards Stefan

1 Like

Thank you so much everyone for these valuable tips and insights. When I get home I can't wait to try to fix it. Forum was down yesterday :frowning:
Really appreciate the help, I will report back!

Hi,
Can you post spec/data link to your ph sensor and pumps please?
Can you post a circuit diagram?
What relays are you using?

Have you got ALL the peristaltic pump wiring away from the input analog wiring?
You may be better off with a MOSFET or optically coupled MOSFET to switch your pumps, even 500ms switching is not very good on a relay.

Tom.. :grinning: :+1: :coffee: :australia:

Hey Tom! Cheers!
here is a link

I have the Atlantic Scientific consumer grade pH
here is a link to it

https://create.arduino.cc/projecthub/atlasscientific/arduino-ph-sensor-calibration-ea1b65

Then I have 2 12vdc peristaltic pumps

https://www.amazon.com/gp/product/B075VN1QZM/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1

Arduino Uno

I do have a couple MOSFETS but wasn't having much success with them. In my inexperience I figured I would be better served with an external 12-volt power supply and relays.

I am not so much having a problem with actuating the pumps, it is when I do actuate the pump and then turn it off, the off delay time on the pump overrides the delay time on the analog read for the pH meter, and will not take another reading until the delay off for the pump expires and the pump turns on again.

Tom I much appreciate your time and comments thank you very much. Please let me know if you have any more ideas from my system.

Mucho Gracias

Stefan,
You have really given me such awesome information. I am working on learning it now.
Thank you so much for this very thorough and thoughtful reply!

Hello All,
I have tried to do some learning and figure this out better.
I think I am really close now, please review my code and let me know what is wrong. Now the pH meter will continue to read even if the pump is on, and the pump turns on great. But now the pump will not turn off.....I have tried monitoring all the millis variables in serial monitor but I am having trouble finding out what it is.

The millis values don't seem to update properly.
For right now my goal is to pump solution for 250ms and then take readings for 120 seconds and if more pH solution is needed engage again for another 250ms.

I am open to any advice on how to do this better! Please review my code below and let me know if I am at least on the right track? Thank you very much in advance!




/* Once uploaded, open the serial monitor, set the baud rate to 9600 and append "Carriage return"
  The code allows the user to observe real time pH readings as well as calibrate the sensor.
  One, two or three-point calibration can be done.

  Calibration commands:
  low-point: "L"
  mid-point: "M"
  high-point: "H"
  clear calibration: "C" */



#include "ph_grav.h"                                  //header file for Atlas Scientific gravity pH sensor
#include "LiquidCrystal.h"                            //header file for liquid crystal display (lcd)
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C pH_lcd(0x27, 20, 4);               //make a variable pH_lcd and assign arduino digital pins to lcd pins (2 -> RS, 3 -> E, 4 to 7 -> D4 to D7)

//======VARIABLES==========

#define interval 30000
#define pH_read 1000
Gravity_pH pH = A0;                                   //assign analog pin A0 of Arduino to class Gravity_pH. connect output of pH sensor to pin A0

const int timeout = 10000;                            //define timeout of 10 sec
char menuOption = 0;
long time0;


String inputstring = "";                              //a string to hold incoming data from the PC
boolean input_string_complete = false;                //a flag to indicate have we received all the data from the PC
char inputstring_array[10];                           //a char array needed for string parsing


//======for the pumps==========
const unsigned long pumpONMillis = 250;
const unsigned long dn_base_interval = 120000;
const unsigned long up_base_interval = 120000;
unsigned long startMillis;
unsigned long phDn_interval = dn_base_interval;
unsigned long phUp_interval = up_base_interval;

byte phup_state = LOW;
byte phup_delta = LOW;
byte phdn_state = LOW;

unsigned long prevphUpMillis;
unsigned long prevphDnMillis;
unsigned long currentMillis;

const byte pHup = 2;
const byte pHdn = 4;



void setup() {

  pinMode(pHup, OUTPUT);
  pinMode(pHdn, OUTPUT);

  startMillis = millis();

  //======Setup LCD==========
  pH_lcd.init();
  pH_lcd.backlight();
  pH_lcd.begin(20, 4);                      //start lcd interface and define lcd size (20 columns and 4 rows)
  pH_lcd.setCursor(7, 0);                  //place cursor on screen at column 1, row 1
  pH_lcd.print("#^(^#)");
  pH_lcd.setCursor(0, 3);                //place cursor on screen at column 1, row 4
  pH_lcd.print("--------------------");
  pH_lcd.setCursor(5, 1);
  pH_lcd.print("pH Reading");             //display "pH Reading"



  //======Setup Serial==========
  Serial.begin(9600);
  while (!Serial) ;
  Serial.println("start");
  if (pH.begin()) {
    Serial.println("Loaded EEPROM");
  }
  Serial.println(F("Use commands \"L\", \"M\", and \"H\" to calibrate the circuit to those respective values"));
  Serial.println(F("Use command \"C\" to clear the calibration"));


}


void loop() {
  currentMillis = millis();
  processSerial();
  maintainpH();

}



//======FUNCTIONS==========================================================================



void processSerial() {
  if (input_string_complete == true) {                //check if data received
    inputstring.toCharArray(inputstring_array, 30);   //convert the string to a char array
    parse_cmd(inputstring_array);                     //send data to pars_cmd function
    input_string_complete = false;                    //reset the flag used to tell if we have received a completed string from the PC
    inputstring = "";                                 //clear the string
  }
  Serial.println(pH.read_ph());                       //output pH reading to serial monitor
  pH_lcd.setCursor(8, 2);                             //place cursor on screen at column 9, row 3
  pH_lcd.print(pH.read_ph());                         //output pH to lcd
  delay(1000);
}

//==========================================================================================
void maintainpH() {


//===============PH UP==============
  currentMillis = millis();
  while (pH.read_ph() < 5.5 ) {

    if (phup_state == LOW) {
      if (currentMillis - prevphUpMillis >= phUp_interval) {
        phup_state = HIGH;
        prevphUpMillis += phUp_interval;

      }
      else {
        if (currentMillis - prevphUpMillis >= pumpONMillis) {
          phup_state = LOW;
          prevphUpMillis = pumpONMillis;

        }
      }

    }

  }


//===============PH DOWN==============  
  
  currentMillis = millis();
  while (pH.read_ph() > 6.5 ) {

    if (phdn_state == LOW) {
      if (currentMillis - prevphDnMillis >= phDn_interval) {
        phdn_state = HIGH;
        prevphDnMillis += phDn_interval;

      }
      else {
        if (currentMillis - prevphDnMillis >= pumpONMillis) {
          phdn_state = LOW;
          prevphDnMillis = pumpONMillis;

        }
      }

    }

  }



//============WRITE PUMP STATE========
  digitalWrite(pHup, phup_state);
  digitalWrite(pHdn, phdn_state);

}



//==========================================================================================
void parse_cmd(char* string) {                      //For calling calibration functions
  strupr(string);                                   //convert input string to uppercase

  if (strcmp(string, "CAL,4") == 0) {               //compare user input string with CAL,4 and if they match, proceed
    pH.cal_low();                                   //call function for low point calibration
    Serial.println("LOW CALIBRATED");
  }
  else if (strcmp(string, "CAL,7") == 0) {          //compare user input string with CAL,7 and if they match, proceed
    pH.cal_mid();                                   //call function for midpoint calibration
    Serial.println("MID CALIBRATED");
  }
  else if (strcmp(string, "CAL,10") == 0) {         //compare user input string with CAL,10 and if they match, proceed
    pH.cal_high();                                  //call function for highpoint calibration
    Serial.println("HIGH CALIBRATED");
  }
  else if (strcmp(string, "CAL,CLEAR") == 0) {      //compare user input string with CAL,CLEAR and if they match, proceed
    pH.cal_clear();                                 //call function for clearing calibration
    Serial.println("CALIBRATION CLEARED");
  }
}
1 Like

Just bumping in the hope this gets seen again.

Those while loops in your maintainpH function are a problem. You don't touch the pumps until they complete, so you're setting variables to define what you want the pumps to do, but they don't come on, so you'll be stuck there forever because you need the pumps active to change PH

Try replacing while with if.

I just have to say kudos on rapidly and cheerfully ditching the first approach and adopting the new approach. Fast learner? :wink:

You are close. I see @wildbill ‘s suggestion and that looks like a good step, your code looks very plausible.

Well done.

a7

Thank you wildbill
I actually had that thought myself and forgot to show the code updated. Here is the code I have now
when I did that I got the pump to turn on after being off for the 120 seconds but once it is on in my maintainph() function the timing doesn't seem to do anything in the else statement after it turns the pump on.

I'm wondering if there is something wrong with using the else statement like that?

It doesn't switch to timing at the 250ms of on time and just keeps on timing without switching the pump state back to 0.

Been beating my head against the wall trying to figure this out haha....

Here is where I am now:




/* Once uploaded, open the serial monitor, set the baud rate to 9600 and append "Carriage return"
  The code allows the user to observe real time pH readings as well as calibrate the sensor.
  One, two or three-point calibration can be done.

  Calibration commands:
  low-point: "L"
  mid-point: "M"
  high-point: "H"
  clear calibration: "C" */



#include "ph_grav.h"                                  //header file for Atlas Scientific gravity pH sensor
#include "LiquidCrystal.h"                            //header file for liquid crystal display (lcd)
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C pH_lcd(0x27, 20, 4);               //make a variable pH_lcd and assign arduino digital pins to lcd pins (2 -> RS, 3 -> E, 4 to 7 -> D4 to D7)

//======VARIABLES==========

#define interval 30000
#define pH_read 1000
Gravity_pH pH = A0;                                   //assign analog pin A0 of Arduino to class Gravity_pH. connect output of pH sensor to pin A0

const int timeout = 10000;                            //define timeout of 10 sec
char menuOption = 0;
long time0;


String inputstring = "";                              //a string to hold incoming data from the PC
boolean input_string_complete = false;                //a flag to indicate have we received all the data from the PC
char inputstring_array[10];                           //a char array needed for string parsing


//======for the pumps==========
const unsigned long pumpONMillis = 250;
const unsigned long dn_base_interval = 120000;
const unsigned long up_base_interval = 120000;
unsigned long startMillis;
unsigned long phDn_interval = dn_base_interval;
unsigned long phUp_interval = up_base_interval;

byte phup_state = LOW;
byte phup_delta = LOW;
byte phdn_state = LOW;

unsigned long prevphUpMillis;
unsigned long prevphDnMillis;
unsigned long currentMillis;

const byte pHup = 2;
const byte pHdn = 4;



void setup() {

  pinMode(pHup, OUTPUT);
  pinMode(pHdn, OUTPUT);

  startMillis = millis();

  //======Setup LCD==========
  pH_lcd.init();
  pH_lcd.backlight();
  pH_lcd.begin(20, 4);                      //start lcd interface and define lcd size (20 columns and 4 rows)
  pH_lcd.setCursor(7, 0);                  //place cursor on screen at column 1, row 1
  pH_lcd.print("#^(^#)");
  pH_lcd.setCursor(0, 3);                //place cursor on screen at column 1, row 4
  pH_lcd.print("--------------------");
  pH_lcd.setCursor(5, 1);
  pH_lcd.print("pH Reading");             //display "pH Reading"



  //======Setup Serial==========
  Serial.begin(9600);
  while (!Serial) ;
  Serial.println("start");
  if (pH.begin()) {
    Serial.println("Loaded EEPROM");
  }
  Serial.println(F("Use commands \"L\", \"M\", and \"H\" to calibrate the circuit to those respective values"));
  Serial.println(F("Use command \"C\" to clear the calibration"));


}


void loop() {
  currentMillis = millis();
  processSerial();
  maintainpH();

}



//======FUNCTIONS==========================================================================



void processSerial() {
  if (input_string_complete == true) {                //check if data received
    inputstring.toCharArray(inputstring_array, 30);   //convert the string to a char array
    parse_cmd(inputstring_array);                     //send data to pars_cmd function
    input_string_complete = false;                    //reset the flag used to tell if we have received a completed string from the PC
    inputstring = "";                                 //clear the string
  }
  Serial.println(pH.read_ph());                       //output pH reading to serial monitor
  pH_lcd.setCursor(8, 2);                             //place cursor on screen at column 9, row 3
  pH_lcd.print(pH.read_ph());                         //output pH to lcd
  delay(1000);
}

//==========================================================================================
void maintainpH() {


  //===============PH UP==============
  currentMillis = millis();
  if (pH.read_ph() < 5.5 ) {

    if (phup_state == LOW) {
      if (currentMillis - prevphUpMillis >= phUp_interval) {
        phup_state = HIGH;
        prevphUpMillis += phUp_interval;

      }
      else {
        if (currentMillis - prevphUpMillis >= pumpONMillis) {
          phup_state = LOW;
          prevphUpMillis = pumpONMillis;

        }
      }

    }

  }


  //===============PH DOWN==============

  currentMillis = millis();
  if (pH.read_ph() > 6.5 ) {

    if (phdn_state == LOW) {
      if (currentMillis - prevphDnMillis >= phDn_interval) {
        phdn_state = HIGH;
        prevphDnMillis += phDn_interval;

      }
      else {
        if (currentMillis - prevphDnMillis >= pumpONMillis) {
          phdn_state = LOW;
          prevphDnMillis = pumpONMillis;

        }
      }

    }

  }



  //============WRITE PUMP STATE========
  digitalWrite(pHup, phup_state);
  digitalWrite(pHdn, phdn_state);

}



//==========================================================================================
void parse_cmd(char* string) {                      //For calling calibration functions
  strupr(string);                                   //convert input string to uppercase

  if (strcmp(string, "CAL,4") == 0) {               //compare user input string with CAL,4 and if they match, proceed
    pH.cal_low();                                   //call function for low point calibration
    Serial.println("LOW CALIBRATED");
  }
  else if (strcmp(string, "CAL,7") == 0) {          //compare user input string with CAL,7 and if they match, proceed
    pH.cal_mid();                                   //call function for midpoint calibration
    Serial.println("MID CALIBRATED");
  }
  else if (strcmp(string, "CAL,10") == 0) {         //compare user input string with CAL,10 and if they match, proceed
    pH.cal_high();                                  //call function for highpoint calibration
    Serial.println("HIGH CALIBRATED");
  }
  else if (strcmp(string, "CAL,CLEAR") == 0) {      //compare user input string with CAL,CLEAR and if they match, proceed
    pH.cal_clear();                                 //call function for clearing calibration
    Serial.println("CALIBRATION CLEARED");
  }
}

Thank you for the kind words :slight_smile: ,
This forum has a plethora of good information and I am grateful it is here to help me educate myself! I definitely understand why this is a much better approach now. Even though I still have a lot to learn haha.

1 Like

OK, I will be looking at your code with an eye to creating something logically equivalent that I can execute.

Right away I see no place where input_string_complete is set to true. This may not be currently relevant.

L8R

a7

Alright guys I am still trying to test this
The pump turns on the value moves to the previous millis like I expect it too but then the state will not change to off. If I go and reupload the sketch it will move the pump on value too the previous millis but it wont execute that function while the pump is on for whatever reason....

else {
if (currentMillis - prevphDnMillis >= pumpONMillis) {
phdn_state = LOW;
prevphDnMillis = pumpONMillis;

The above code in particular doesn't seem to work. Here is my full code right now....ugh this is driving me crazy, any help on figuring out why I can't get the state to change to low on the pump would be greatly appreciated!




/* Once uploaded, open the serial monitor, set the baud rate to 9600 and append "Carriage return"
  The code allows the user to observe real time pH readings as well as calibrate the sensor.
  One, two or three-point calibration can be done.

  Calibration commands:
  low-point: "L"
  mid-point: "M"
  high-point: "H"
  clear calibration: "C" */



#include "ph_grav.h"                                  //header file for Atlas Scientific gravity pH sensor
#include "LiquidCrystal.h"                            //header file for liquid crystal display (lcd)
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C pH_lcd(0x27, 20, 4);               //make a variable pH_lcd and assign arduino digital pins to lcd pins (2 -> RS, 3 -> E, 4 to 7 -> D4 to D7)

//======VARIABLES==========

#define interval 30000
#define pH_read 1000
Gravity_pH pH = A0;                                   //assign analog pin A0 of Arduino to class Gravity_pH. connect output of pH sensor to pin A0

const int timeout = 10000;                            //define timeout of 10 sec
char menuOption = 0;
long time0;


String inputstring = "";                              //a string to hold incoming data from the PC
boolean input_string_complete = false;                //a flag to indicate have we received all the data from the PC
char inputstring_array[10];                           //a char array needed for string parsing


//======for the pumps==========
const unsigned long pumpONMillis = 1250;
//const unsigned long dn_base_interval = 30000;
//const unsigned long up_base_interval = 30000;
unsigned long startMillis;
unsigned long phDn_interval = 30000;
unsigned long phUp_interval = 30000;

byte phup_state = LOW;
byte phup_delta = LOW;
byte phdn_state = LOW;

unsigned long prevphUpMillis;
unsigned long prevphDnMillis;
unsigned long currentMillis;

const byte pHup = 2;
const byte pHdn = 4;



void setup() {

  pinMode(pHup, OUTPUT);
  pinMode(pHdn, OUTPUT);

  startMillis = millis();

  //======Setup LCD==========
  pH_lcd.init();
  pH_lcd.backlight();
  pH_lcd.begin(20, 4);                      //start lcd interface and define lcd size (20 columns and 4 rows)
  pH_lcd.setCursor(7, 0);                  //place cursor on screen at column 1, row 1
  pH_lcd.print("#^(^#)");
  pH_lcd.setCursor(0, 3);                //place cursor on screen at column 1, row 4
  pH_lcd.print("--------------------");
  pH_lcd.setCursor(5, 1);
  pH_lcd.print("pH Reading");             //display "pH Reading"



  //======Setup Serial==========
  Serial.begin(9600);
  while (!Serial) ;
  Serial.println("start");
  if (pH.begin()) {
    Serial.println("Loaded EEPROM");
  }
  Serial.println(F("Use commands \"L\", \"M\", and \"H\" to calibrate the circuit to those respective values"));
  Serial.println(F("Use command \"C\" to clear the calibration"));


}


void loop() {
  currentMillis = millis();
  processSerial();
  maintainpH();

}



//======FUNCTIONS==========================================================================



void processSerial() {
  if (input_string_complete == true) {                //check if data received
    inputstring.toCharArray(inputstring_array, 30);   //convert the string to a char array
    parse_cmd(inputstring_array);                     //send data to pars_cmd function
    input_string_complete = false;                    //reset the flag used to tell if we have received a completed string from the PC
    inputstring = "";                                 //clear the string
  }
  Serial.println("pH = " + String(pH.read_ph()) + "  CurrentMillis = " + String(currentMillis) + "  prevphUpMillis = " + String(prevphUpMillis) + " up_interval = " + String(phUp_interval) + " up_state = " + String(phup_state) + " onMillis" + String(pumpONMillis));
  pH_lcd.setCursor(8, 2);                             //place cursor on screen at column 9, row 3
  pH_lcd.print(pH.read_ph());                         //output pH to lcd
  delay(1000);
}

//==========================================================================================
void maintainpH() {


  //===============PH UP==============
  currentMillis = millis();
  if (pH.read_ph() < 9.5 ) {

    if (phup_state == LOW) {
      if (currentMillis - prevphUpMillis >= phUp_interval) {
        phup_state = HIGH;
        prevphUpMillis += phUp_interval;

      }

      else {
        if (currentMillis - prevphUpMillis >= pumpONMillis) {
          phup_state = LOW;
          prevphUpMillis = pumpONMillis;

        }
      }

    }

  }


  //===============PH DOWN==============

  currentMillis = millis();
  if (pH.read_ph() > 6.5 ) {

    if (phdn_state == LOW) {
      if (currentMillis - prevphDnMillis >= phDn_interval) {
        phdn_state = HIGH;
        prevphDnMillis += phDn_interval;

      }
      else {
        if (currentMillis - prevphDnMillis >= pumpONMillis) {
          phdn_state = LOW;
          prevphDnMillis = pumpONMillis;

        }
      }

    }

  }



  //============WRITE PUMP STATE========
  digitalWrite(pHup, phup_state);
  digitalWrite(pHdn, phdn_state);

}



//==========================================================================================
void parse_cmd(char* string) {                      //For calling calibration functions
  strupr(string);                                   //convert input string to uppercase

  if (strcmp(string, "CAL,4") == 0) {               //compare user input string with CAL,4 and if they match, proceed
    pH.cal_low();                                   //call function for low point calibration
    Serial.println("LOW CALIBRATED");
  }
  else if (strcmp(string, "CAL,7") == 0) {          //compare user input string with CAL,7 and if they match, proceed
    pH.cal_mid();                                   //call function for midpoint calibration
    Serial.println("MID CALIBRATED");
  }
  else if (strcmp(string, "CAL,10") == 0) {         //compare user input string with CAL,10 and if they match, proceed
    pH.cal_high();                                  //call function for highpoint calibration
    Serial.println("HIGH CALIBRATED");
  }
  else if (strcmp(string, "CAL,CLEAR") == 0) {      //compare user input string with CAL,CLEAR and if they match, proceed
    pH.cal_clear();                                 //call function for clearing calibration
    Serial.println("CALIBRATION CLEARED");
  }
}

This doesn't look right:

prevphUpMillis = pumpONMillis;

pumpONMillis is a constant for an interval but you're using it earlier as if it were the time the pump came on.

Hey yall,
I figured out if I remove the
if (pH.read_ph() < 6.5 ) argument from my pump on off logic then it works correctly
with it in it will turn the pump on but the pump will not turn off, so it is blocking the state from changing correctly.
I tried putting that argument in the state off function as well but it isn't working. Can anyone help me find a better way to make sure that this pump on off state logic only happens when the pH is in the right range?

Does not appear in your code.

Did you mean >? Seems obvious but you have to treat us as highly literal and almost as dumb as computers when it comes to mind reading.

You may have been talking 'bout the 9.5…

And if that's the only change, say so. If you've made any other changes, post current code, it's a moving target here!

a7

Thank you all very much for the help and education. Please forgive me for my inexperience.
The code is now working as desired thanks to the great resources here. I am posting my completed code with added comments that look like //******* in the hopes it may help someone else one day.

I now have a new issue in that everytime the pump turns on I lose a tiny but acceptable amount of signal on the pH probe. But I don't believe that to be in the scope of this thread and am working on creating a schematic of how I have it all together to see how if there is anything I can do to improve it. Thanks again!




/* Once uploaded, open the serial monitor, set the baud rate to 9600 and append "Carriage return"
  The code allows the user to observe real time pH readings as well as calibrate the sensor.
  One, two or three-point calibration can be done.

  Calibration commands:
  low-point: "L"
  mid-point: "M"
  high-point: "H"
  clear calibration: "C" */



#include "ph_grav.h"                                  //header file for Atlas Scientific gravity pH sensor
#include "LiquidCrystal.h"                            //header file for liquid crystal display (lcd)
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C pH_lcd(0x27, 20, 4);               //make a variable pH_lcd and assign arduino digital pins to lcd pins (2 -> RS, 3 -> E, 4 to 7 -> D4 to D7)

//======VARIABLES==========

#define interval 30000
#define pH_read 100
Gravity_pH pH = A0;                                   //assign analog pin A0 of Arduino to class Gravity_pH. connect output of pH sensor to pin A0

const int timeout = 10000;                            //define timeout of 10 sec
char menuOption = 0;
long time0;


String inputstring = "";                              //a string to hold incoming data from the PC
boolean input_string_complete = false;                //a flag to indicate have we received all the data from the PC
char inputstring_array[10];                           //a char array needed for string parsing



//===================================
void parse_cmd(char* string) {                      //For calling calibration functions
  strupr(string);                                   //convert input string to uppercase

  if (strcmp(string, "L") == 0) {               //compare user input string with CAL,4 and if they match, proceed
    pH.cal_low();                                   //call function for low point calibration
    Serial.println("LOW CALIBRATED");
  }
  else if (strcmp(string, "M") == 0) {          //compare user input string with CAL,7 and if they match, proceed
    pH.cal_mid();                                   //call function for midpoint calibration
    Serial.println("MID CALIBRATED");
  }
  else if (strcmp(string, "H") == 0) {         //compare user input string with CAL,10 and if they match, proceed
    pH.cal_high();                                  //call function for highpoint calibration
    Serial.println("HIGH CALIBRATED");
  }
  else if (strcmp(string, "C") == 0) {      //compare user input string with CAL,CLEAR and if they match, proceed
    pH.cal_clear();                                 //call function for clearing calibration
    Serial.println("CALIBRATION CLEARED");
  }
}

//======for the pumps==========
const unsigned long pumpONMillis = 500;
const unsigned long pumpDNMillis = 500;
const unsigned long dn_base_interval = 30000;
const unsigned long up_base_interval = 30000;
unsigned long startMillis;
unsigned long phDn_interval = dn_base_interval;
unsigned long phUp_interval = up_base_interval;

byte phup_state = LOW;
byte phup_delta = LOW;
byte phdn_state = LOW;

unsigned long prevphUpMillis;
unsigned long prevphDnMillis;
unsigned long currentMillis;

const byte pHup = 2;
const byte pHdn = 4;



void setup() {

  pinMode(pHup, OUTPUT);
  pinMode(pHdn, OUTPUT);

  startMillis = millis();

  //======Setup LCD==========
  pH_lcd.init();
  pH_lcd.backlight();
  pH_lcd.begin(20, 4);                      //start lcd interface and define lcd size (20 columns and 4 rows)
  pH_lcd.setCursor(7, 0);                  //place cursor on screen at column 1, row 1
  pH_lcd.print("#^(^#)");
  pH_lcd.setCursor(0, 3);                 //place cursor on screen at column 1, row 4
  pH_lcd.print("--------------------");
  pH_lcd.setCursor(5, 1);
  pH_lcd.print("pH Reading");             //display "pH Reading"



  //======Setup Serial==========
  Serial.begin(9600);
  delay(200);
  while (!Serial) ;
  Serial.println("start");
  if (pH.begin()) {
    Serial.println("Loaded EEPROM");
  }
  Serial.println(F("Use commands \"L\", \"M\", and \"H\" to calibrate the circuit to those respective values"));
  Serial.println(F("Use command \"C\" to clear the calibration"));


}


void loop() {
  currentMillis = millis();
  processSerial();
  maintainpH();


}



//======FUNCTIONS==========================================================================



void processSerial() {
  if (input_string_complete == true) {                //check if data received
    inputstring.toCharArray(inputstring_array, 30);   //convert the string to a char array
    parse_cmd(inputstring_array);                     //send data to pars_cmd function
    input_string_complete = false;                    //reset the flag used to tell if we have received a completed string from the PC
    inputstring = "";                                 //clear the string
  }

  Serial.println("pH = " + String(pH.read_ph()) + "  CurrentMillis = " + String(currentMillis) + "  prevphUpMillis = " + String(prevphUpMillis) + " up_interval = " + String(phUp_interval) + " up_state = " + String(phup_state) + " onMillis" + String(pumpONMillis));
  Serial.println("pH = " + String(pH.read_ph()) + "  CurrentMillis = " + String(currentMillis) + "  prevphDnMillis = " + String(prevphDnMillis) + " up_interval = " + String(phDn_interval) + " dn_state = " + String(phdn_state) + " onMillis" + String(pumpONMillis));
  pH_lcd.setCursor(8, 2);                             //place cursor on screen at column 9, row 3
  pH_lcd.print(pH.read_ph());                         //output pH to lcd
  delay(1000);
}

void serialEvent() {                                  //if the hardware serial port_0 receives a char
  inputstring = Serial.readStringUntil(13);           //read the string until we see a <CR>
  input_string_complete = true;                       //set the flag used to tell if we have received a completed string from the PC
}

//==========================================================================================
void maintainpH() {


  //===============PH UP==============
  currentMillis = millis();
  if (pH.read_ph() < 5.5 ) {

    if (phup_state == LOW) {
      if (currentMillis - prevphUpMillis >= phUp_interval) {
        phup_state = HIGH;
        prevphUpMillis += phUp_interval;


      }
    }    //******** this } was at the bottom of the whole if else statement and wouldn't work right until I moved it above the else****

    else {
      if (pH.read_ph() < 5.5 ) {   //******** I also needed to add the if ph.read statement here*****
        if (currentMillis - prevphUpMillis >= pumpONMillis) {
          phup_state = LOW;
          prevphUpMillis += pumpONMillis;

        }
      }

    }


  }

  //===============PH DOWN==============

  currentMillis = millis();
  if (pH.read_ph() > 2.5 ) {

    if (phdn_state == LOW) {
      if (currentMillis - prevphDnMillis >= phDn_interval) {
        phdn_state = HIGH;
        prevphDnMillis += phDn_interval;

      }
    } //******** this } was at the bottom of the whole if else statement and wouldn't work right until I moved it above the else****
    else  {
      if (pH.read_ph() > 2.5 ) { //******** I also needed to add the if ph.read statement here*****
        if (currentMillis - prevphDnMillis >= pumpDNMillis) {
          phdn_state = LOW;
          prevphDnMillis += pumpDNMillis;

        }
      }

    }

  }



  //============WRITE PUMP STATE========
  digitalWrite(pHup, phup_state);
  digitalWrite(pHdn, phdn_state);

}



//==========================================================================================