Simultaneous While Loops

UPDATED: See end of thread for finished, working code.

I am working on a project that reads pH, pressure, and temperature inputs through analog pins. If those inputs go too low or too high, a while loop is triggered that lights an LED and sustains the light for as long as the threshold continues to be tripped. My problem is once the threshold is tripped and the while loop is entered, none of the other thresholds can be tripped until the while loop ceases to be true. How do I write a code that can trip multiple thresholds based on three different analog inputs?

My code is below:

int pHLowPin=2;
int pHHighPin=3;
int pressureLowPin=4;
int pressureHighPin=5;
int temperatureLowPin=6;
int temperatureHighPin=7;

int pHAnalogPin=A0;
int pressureAnalogPin=A1;
int temperatureAnalogPin=A2;

float pHVal;
float pressureVal;
float temperatureVal;

float pHCalibration=0.112;
float pressureCalibration=0.00;
float temperatureCalibration=0.00;

char n=15;
float h;
float p;
float t;
float pHReading[15];
float pressureReading[15];
float temperatureReading[15];

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

  pinMode(pHAnalogPin,INPUT);
  pinMode(pressureAnalogPin,INPUT);
  pinMode(temperatureAnalogPin,INPUT);

  pinMode(pHLowPin,OUTPUT);
  pinMode(pHHighPin,OUTPUT);
  pinMode(pressureLowPin,OUTPUT);
  pinMode(pressureHighPin,OUTPUT);
  pinMode(temperatureLowPin,OUTPUT);
  pinMode(temperatureHighPin,OUTPUT);
}

void loop() {
  pHVal=pHMath();
  pressureVal=pressureMath();
  temperatureVal=temperatureMath();

  imprimir();

  while(pHVal<5){
  digitalWrite(pHLowPin,HIGH);
  pHVal=pHMath();
  pressureVal=pressureMath();
  temperatureVal=temperatureMath();
    imprimir();
   
  }  
  while(pHVal>9){
    digitalWrite(pHHighPin,HIGH);
    pHVal=pHMath();
    pressureVal=pressureMath();
    temperatureVal=temperatureMath();
    imprimir();
   
  }
  while(pressureVal<30){
    digitalWrite(pressureLowPin,HIGH);
  pHVal=pHMath();
  pressureVal=pressureMath();
  temperatureVal=temperatureMath();
    imprimir();
    
  }
  while(pressureVal>70){
    digitalWrite(pressureHighPin,HIGH);
  pHVal=pHMath();
  pressureVal=pressureMath();
  temperatureVal=temperatureMath();
    imprimir();
   
  }
  while(temperatureVal<100){
    digitalWrite(temperatureLowPin,HIGH);
  pHVal=pHMath();
  pressureVal=pressureMath();
  temperatureVal=temperatureMath();
    imprimir();
  
  }
  while(temperatureVal>150){
    digitalWrite(temperatureHighPin,HIGH);
  pHVal=pHMath();
  pressureVal=pressureMath();
  temperatureVal=temperatureMath();
    imprimir();
  
  }

  digitalWrite(pHLowPin,LOW);
  digitalWrite(pHHighPin,LOW);
  digitalWrite(pressureLowPin,LOW);
  digitalWrite(pressureHighPin,LOW);
  digitalWrite(temperatureLowPin,LOW);
  digitalWrite(temperatureHighPin,LOW);
}

float pHAverage(){
  float pail;
  float promedio;
  for(int h=1;h<=n;h++){
    pHReading[h]=analogRead(pHAnalogPin);
    delay(12);
  }
  pail=0;
  for (int h=1;h<=n;h++){
    pail=pail+pHReading[h];
  }
promedio=pail/n;
return promedio;
}

float pressureAverage(){
  float pail;
  float promedio;
  for(int p=1;p<=n;p++){
    pressureReading[p]=analogRead(pressureAnalogPin);
    delay(12);
  }
  pail=0;
  for (int p=1;p<=n;p++){
    pail=pail+pressureReading[p];
  }
promedio=pail/n;
return promedio;
}

float temperatureAverage(){
  float pail;
  float promedio;
  for(int t=1;t<=n;t++){
    temperatureReading[t]=analogRead(temperatureAnalogPin);
    delay(12);
  }
  pail=0;
  for (int t=1;t<=n;t++){
    pail=pail+temperatureReading[t];
  }
promedio=pail/n;
return promedio;
}

float pHMath(){
  float voltcommpH=pHAverage(); 
  float convertpH=((14./1023.)*voltcommpH);
  float pHMathd=convertpH+pHCalibration;
  return pHMathd;
}

float pressureMath(){
  float voltcommPressure=pressureAverage();
  float convertPressure=((100./1023.)*voltcommPressure);
  float pressureMathd=convertPressure+pressureCalibration;
  return pressureMathd;
}

float temperatureMath(){
  float voltcommTemperature = temperatureAverage(); 
  double tempK = log(10000.0 * ((1024.0 / voltcommTemperature -1)));
  tempK = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * tempK * tempK ))
  * tempK );
  float tempC = tempK - 273.15;
  float tempF = (tempC * 9.0)/ 5.0 + 32.0;
  float convertTemperature=tempF;
  float temperatureMathd=convertTemperature+temperatureCalibration;
  return temperatureMathd;
}

void imprimir(){
  Serial.print("pH = ");
  Serial.print(pHVal);
  Serial.print(".         Pressure = ");
  Serial.print(pressureVal);
  Serial.print(".         Temperature = ");
  Serial.println(temperatureVal);
  delay(250);
}

Hello Matt_Skevensky

That is the task of a while() loop.

You migth redesign the program using if() conditions.

Check it out and give them try.

Have a nice day and enjoy coding in C++.

Hello Paul,

I've replaced the while loops with if statements and seen slightly better results. However, the if statements are triggered in succession and I need them to be triggered simultaneously. For example, I adjusted the pH input to be too high and the pressure input to be too low. The Arduino ran through the void loop and performed the command (lighting an LED) in the pH-too-high if statement, then stopped lighting that LED and performed the command (lighting an LED) in the pressure-too-low statement, then shut everything off, then ran through the loop again. For the purposes of my project, I need the LED to light continuously while the analog value that controls it is over the threshold. How do I do this?

Matt

Hello Matt_Skevensky

Take a search engine of your choice and ask the WWW for 'state change detection / edge detection +arduino' to collect some data to be sorted out to get the needed information.

Hello Paul,

I'm somewhat familiar with the concept of state change detection from watching Paul McWhorter videos on pushbuttons. How would I apply it in my scenario?

Matt

Not possible, especially if you using delay(). However, with careful programming, sequential actions will happen too quickly for you to notice the difference.

To learn how, study the Blink Without Delay and Arduino "How to do several things at once" tutorials.

Add a few global booleans (bool pHlow = false).
Set them true if the condition is met.
Set them false if the condition is not met.
At the end of loop, set LED's accordingly...

if (pHlow) {
   digitalWrite(whateverLedPin, HIGH);
}
else {
   digitalWrite(whateverLedPin, LOW);
}
1 Like

Hello build_1971,

I'm unfamiliar with this strategy. Could you provide an example code and explanation of what it does? If not, how do I program the process of setting the global booleans true or false?

Matt

@Matt_Skevensky what @build_1971 is hinting at and @paulpaulson usually makes point of mentioning is the IPO model of processing.

Every loop you would

Input. Once read all your sensors into variables and do whatever maths they need done.

-> Input can also be a matter of time, that is the time now and the time since you may have done something.

Process. Use the state of the LED and the new readings to determine if the state shoukd change. It's just your logical tests on the sensor values and whether the LED is up or down.

Output. Set the output LED accordingly.

Geek out here IPO model. Or not.The IPO pattern is fairly simple.

You will need. probably, to be familiar with "blink without delay" and "state change detection", both of which have been treated in many different ways. Google and poke around to see if any explanation is a good match for your learning style and your current level of understanding.

I think the Ice Tea guy must teach you about both those things, and probably sooner later will be using, if not explicitly pointing it out, the essence of the IPO model.

HTH

a7

bool pHlow = false;
 
void setup() {
}

void loop() {
    double pH = yourmath(analogRead(somePin));
    if (pH < 5) {
        pHlow = true;
    }
    else {
        pHlow = false;
   }
   
   if (pHlow) { 
        digitalWrite(whateverLedPin, HIGH); 
    } 
   else { 
        digitalWrite(whateverLedPin, LOW); 
   }
}

Did I hear IPO model here? :slight_smile:

Hello build_1971,

Thank you for the example code.

Matt

Hello alto777,

Thank you for the clarification.

Matt

const int pHLowPin = 2;
const int pHHighPin = 3;
const int pressureLowPin = 4;
const int pressureHighPin = 5;
const int temperatureLowPin = 6;
const int temperatureHighPin = 7;

const int pHAnalogPin = A0;
const int pressureAnalogPin = A1;
const int temperatureAnalogPin = A2;

float pHVal;
float pressureVal;
float temperatureVal;

const float pHCalibration = 0.112;
const float pressureCalibration = 0.00;
const float temperatureCalibration = 0.00;
const char n = 15;

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

  pinMode(pHAnalogPin, INPUT);
  pinMode(pressureAnalogPin, INPUT);
  pinMode(temperatureAnalogPin, INPUT);

  pinMode(pHLowPin, OUTPUT);
  pinMode(pHHighPin, OUTPUT);
  pinMode(pressureLowPin, OUTPUT);
  pinMode(pressureHighPin, OUTPUT);
  pinMode(temperatureLowPin, OUTPUT);
  pinMode(temperatureHighPin, OUTPUT);
}

void loop() {
  pHVal = pHMath();
  pressureVal = pressureMath();
  temperatureVal = temperatureMath();

  if (pHVal < 5)digitalWrite(pHLowPin, HIGH);
  if (pHVal > 9)digitalWrite(pHHighPin, HIGH);
  if (pressureVal < 30)digitalWrite(pressureLowPin, HIGH);
  if (pressureVal > 70) digitalWrite(pressureHighPin, HIGH);
  if (temperatureVal < 100) digitalWrite(temperatureLowPin, HIGH);
  if (temperatureVal > 150) digitalWrite(temperatureHighPin, HIGH);

  imprimir();
  digitalWrite(pHLowPin, LOW);
  digitalWrite(pHHighPin, LOW);
  digitalWrite(pressureLowPin, LOW);
  digitalWrite(pressureHighPin, LOW);
  digitalWrite(temperatureLowPin, LOW);
  digitalWrite(temperatureHighPin, LOW);
}

float Average(const byte Pin) {
  float pail = 0.0;

  for (byte i = 0; i < n; i++) {
    pail = pail + (float)analogRead(Pin);
    delay(10);
  }

  return pail / n;
}

float pHMath() {
  float voltcommpH = Average(pHAnalogPin);
  float convertpH = 14. / 1023. * voltcommpH ;

  return convertpH + pHCalibration;
}

float pressureMath() {
  float voltcommPressure = Average(pressureAnalogPin);
  float convertPressure = 100. / 1023. * voltcommPressure ;

  return convertPressure + pressureCalibration;
}

float temperatureMath() {
  float voltcommTemperature = Average(temperatureAnalogPin);
  double temp = log(10000.0 * ((1024.0 / voltcommTemperature - 1)));
  temp = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * tempK * tempK )) * tempK ) - 273.15;
  temp = temp * 9.0 / 5.0 + 32.0 ;

  return temp + temperatureCalibration;
}

void imprimir() {
  Serial.print("pH = ");
  Serial.print(pHVal, 1);
  Serial.print(". Pressure = ");
  Serial.print(pressureVal, 1);
  Serial.print(". Temperature = ");
  Serial.println(temperatureVal, 1);
  delay(250);
}

I see both @Matt_Skevensky and @kolaha set all output LOW after impriming.

I think that those outputs should also probably be able to be done on a logical basis, not just every time.

Typical hysteresis of a single control element usually reads (psedocode):

    if the value is above the upper threshold, turn it off

    if the value is below the lower threshold, turn in on

This is basically your home thermostat algorithm.

If we consider the pairs, perhaps each of the two in a pair could have its thresholds, or use the upper/lower thresholds in opposition to the other.

If there is no need for hysteresis, this

  if (pHVal < 5) digitalWrite(pHLowPin, HIGH);
  else digitalWrite(pHLowPin, LOW);

woukd be a clearer picture of what the code does, and in this case it would be hard to discern that it was different to routinely shutting the output off, briefly, every time around the loop.

Which also makes me suggest a crude loop throttle - there is no need to do this 1000s of times a second. A simple delay(77) woukd work fine. And cut down on the printing, too.

a7

Hi @kolaha,

that's close to my suggestion :wink:

/*
  Forum: https://forum.arduino.cc/t/simultaneous-while-loops/1157056/15
  Wokwi: https://wokwi.com/projects/372695449762438145 
  
*/

int pHLowPin = 2;
int pHHighPin = 3;
int pressureLowPin = 4;
int pressureHighPin = 5;
int temperatureLowPin = 6;
int temperatureHighPin = 7;

int pHAnalogPin = A0;
int pressureAnalogPin = A1;
int temperatureAnalogPin = A2;

float pHVal;
float pressureVal;
float temperatureVal;

float pHCalibration = 0.112;
float pressureCalibration = 0.00;
float temperatureCalibration = 0.00;

char n = 15;
float h;
float p;
float t;
float pHReading[15];
float pressureReading[15];
float temperatureReading[15];

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

  pinMode(pHAnalogPin, INPUT);
  pinMode(pressureAnalogPin, INPUT);
  pinMode(temperatureAnalogPin, INPUT);

  pinMode(pHLowPin, OUTPUT);
  pinMode(pHHighPin, OUTPUT);
  pinMode(pressureLowPin, OUTPUT);
  pinMode(pressureHighPin, OUTPUT);
  pinMode(temperatureLowPin, OUTPUT);
  pinMode(temperatureHighPin, OUTPUT);
}

void loop() {
  pHVal = pHMath();
  pressureVal = pressureMath();
  temperatureVal = temperatureMath();

  imprimir();

  if (pHVal < 5) {
    digitalWrite(pHLowPin, HIGH);
  } else {
    digitalWrite(pHLowPin, LOW);
  }

  if (pHVal > 9) {
    digitalWrite(pHHighPin, HIGH);
  } else {
    digitalWrite(pHHighPin, LOW);
  }

  if (pressureVal < 30) {
    digitalWrite(pressureLowPin, HIGH);
  } else {
    digitalWrite(pressureLowPin, LOW);
  }

  if (pressureVal > 70) {
    digitalWrite(pressureHighPin, HIGH);
  } else {
    digitalWrite(pressureHighPin, LOW);
  }

  if (temperatureVal < 100) {
    digitalWrite(temperatureLowPin, HIGH);
  } else {
    digitalWrite(temperatureLowPin, LOW);
  }

  if (temperatureVal > 150) {
    digitalWrite(temperatureHighPin, HIGH);
  } else {
    digitalWrite(temperatureHighPin, LOW);
  }
}

float pHAverage() {
  float pail;
  float promedio;
  for (int h = 1; h <= n; h++) {
    pHReading[h] = analogRead(pHAnalogPin);
    delay(12);
  }
  pail = 0;
  for (int h = 1; h <= n; h++) {
    pail = pail + pHReading[h];
  }
  promedio = pail / n;
  return promedio;
}

float pressureAverage() {
  float pail;
  float promedio;
  for (int p = 1; p <= n; p++) {
    pressureReading[p] = analogRead(pressureAnalogPin);
    delay(12);
  }
  pail = 0;
  for (int p = 1; p <= n; p++) {
    pail = pail + pressureReading[p];
  }
  promedio = pail / n;
  return promedio;
}

float temperatureAverage() {
  float pail;
  float promedio;
  for (int t = 1; t <= n; t++) {
    temperatureReading[t] = analogRead(temperatureAnalogPin);
    delay(12);
  }
  pail = 0;
  for (int t = 1; t <= n; t++) {
    pail = pail + temperatureReading[t];
  }
  promedio = pail / n;
  return promedio;
}

float pHMath() {
  float voltcommpH = pHAverage();
  float convertpH = ((14. / 1023.) * voltcommpH);
  float pHMathd = convertpH + pHCalibration;
  return pHMathd;
}

float pressureMath() {
  float voltcommPressure = pressureAverage();
  float convertPressure = ((100. / 1023.) * voltcommPressure);
  float pressureMathd = convertPressure + pressureCalibration;
  return pressureMathd;
}

float temperatureMath() {
  float voltcommTemperature = temperatureAverage();
  double tempK = log(10000.0 * ((1024.0 / voltcommTemperature - 1)));
  tempK = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * tempK * tempK ))
               * tempK );
  float tempC = tempK - 273.15;
  float tempF = (tempC * 9.0) / 5.0 + 32.0;
  float convertTemperature = tempF;
  float temperatureMathd = convertTemperature + temperatureCalibration;
  return temperatureMathd;
}

void imprimir() {
  static unsigned long lastPrintTime = 0;
  if (millis()-lastPrintTime >= 250) {
    lastPrintTime = millis();
    Serial.print("pH = ");
    Serial.print(pHVal);
    Serial.print(".         Pressure = ");
    Serial.print(pressureVal);
    Serial.print(".         Temperature = ");
    Serial.println(temperatureVal);
  }  
}

My sketch uses if-else to set the Leds and I have removed the delay(250) in the print function. (exchanged by a millis() function, makes it a little bit more responsive). See Wokwi https://wokwi.com/projects/372695449762438145

There is still a delay of 12 msec x 15 repetitions per physical value measurement. As all readings are done in the same way this could easily be moved together in one routine saving two times 180 msec ...

A sketch that avoids this and is even more responsive is this one

Optimized Measurement
/*
  Forum: https://forum.arduino.cc/t/simultaneous-while-loops/1157056/15
  Wokwi: https://wokwi.com/projects/372695498851525633
  
*/

int pHLowPin = 2;
int pHHighPin = 3;
int pressureLowPin = 4;
int pressureHighPin = 5;
int temperatureLowPin = 6;
int temperatureHighPin = 7;

int pHAnalogPin = A0;
int pressureAnalogPin = A1;
int temperatureAnalogPin = A2;

float pHVal;
float pressureVal;
float temperatureVal;
float pHAverage;
float pressureAverage;
float temperatureAverage;

float pHCalibration = 0.112;
float pressureCalibration = 0.00;
float temperatureCalibration = 0.00;

char n = 15;
float h;
float p;
float t;
float pHReading[15];
float pressureReading[15];
float temperatureReading[15];

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

  pinMode(pHAnalogPin, INPUT);
  pinMode(pressureAnalogPin, INPUT);
  pinMode(temperatureAnalogPin, INPUT);

  pinMode(pHLowPin, OUTPUT);
  pinMode(pHHighPin, OUTPUT);
  pinMode(pressureLowPin, OUTPUT);
  pinMode(pressureHighPin, OUTPUT);
  pinMode(temperatureLowPin, OUTPUT);
  pinMode(temperatureHighPin, OUTPUT);
}

void loop() {
  
  doMeasurements();
  
  imprimir();

  if (pHVal < 5) {
    digitalWrite(pHLowPin, HIGH);
  } else {
    digitalWrite(pHLowPin, LOW);
  }

  if (pHVal > 9) {
    digitalWrite(pHHighPin, HIGH);
  } else {
    digitalWrite(pHHighPin, LOW);
  }

  if (pressureVal < 30) {
    digitalWrite(pressureLowPin, HIGH);
  } else {
    digitalWrite(pressureLowPin, LOW);
  }

  if (pressureVal > 70) {
    digitalWrite(pressureHighPin, HIGH);
  } else {
    digitalWrite(pressureHighPin, LOW);
  }

  if (temperatureVal < 100) {
    digitalWrite(temperatureLowPin, HIGH);
  } else {
    digitalWrite(temperatureLowPin, LOW);
  }

  if (temperatureVal > 150) {
    digitalWrite(temperatureHighPin, HIGH);
  } else {
    digitalWrite(temperatureHighPin, LOW);
  }
}


void doMeasurements() {
  float pail;
  float promedio;
  for (int h = 1; h <= n; h++) {
    pHReading[h] = analogRead(pHAnalogPin);
    pressureReading[h] = analogRead(pressureAnalogPin);
    temperatureReading[h] = analogRead(temperatureAnalogPin);
    delay(12);
  }
  float pailpH = 0;
  float pailPr = 0;
  float pailTmp = 0;
  for (int h = 1; h <= n; h++) {
    pailpH  = pailpH + pHReading[h];
    pailPr  = pailPr + pressureReading[h];
    pailTmp = pailTmp + temperatureReading[h];
  }
  pHAverage = pailpH / n;
  pressureAverage = pailPr / n;
  temperatureAverage = pailTmp / n;
  pHVal = pHMath();
  pressureVal = pressureMath();
  temperatureVal = temperatureMath();
}



float pHMath() {
  float convertpH = ((14. / 1023.) * pHAverage);
  float pHMathd = convertpH + pHCalibration;
  return pHMathd;
}

float pressureMath() {
  float convertPressure = ((100. / 1023.) * pressureAverage);
  float pressureMathd = convertPressure + pressureCalibration;
  return pressureMathd;
}

float temperatureMath() {
  double tempK = log(10000.0 * ((1024.0 / temperatureAverage - 1)));
  tempK = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * tempK * tempK ))
               * tempK );
  float tempC = tempK - 273.15;
  float tempF = (tempC * 9.0) / 5.0 + 32.0;
  float convertTemperature = tempF;
  float temperatureMathd = convertTemperature + temperatureCalibration;
  return temperatureMathd;
}

void imprimir() {
  static unsigned long lastPrintTime = 0;
  if (millis() - lastPrintTime >= 250) {
    lastPrintTime = millis();
    Serial.print("pH = ");
    Serial.print(pHVal);
    Serial.print(".         Pressure = ");
    Serial.print(pressureVal);
    Serial.print(".         Temperature = ");
    Serial.println(temperatureVal);
  }
}

See also on Wokwi: https://wokwi.com/projects/372695498851525633

This second sketch does all the measurements and averaging in one place.

Hello ec2021,

This is great. Thank you for the thorough help and code.

Matt

There is no need for three average functions. So use only one...
And no need to store an int in a float and no need to store 3x15 floats.
And you can also use a[0]....
And it is highly recommended that global values have a name longer than 't'.

I didn't see those.

@Matt_Skevensky might think about replacing the averaging with a simple low pass filter, also known colloquially as a leaky integrator.

Assuming a float alpha, the leaky integration goes like

    average = alpha * average + (1 - alpha) * newReading;

The new average is some of the old average, and a bit of the new reading influencing it.

With alpha ranging from 0 to 1.0. Values closer to 1.0 respect the old average more, and converge to a new reading slower.

Try 0.95 and 0.70 on your data and watch what it does.

You could read the sensors every loop and maintain the averages with that simple equation.


I know the problem is solved, but an enhancement might be to make an LED come on when a corresponding threshold is exceeded, and turn off some time after the condition has back in the normal range.

So even a brief excursion would mean the LED was on for, say, 15 seconds.

a7

I know...

Just made those changes which were required in the first place.

There is still a lot of potential ... if you like don't hesitate... :wink: