Nutrient doser millis() blocking loop

Hello.

I am having trouble with my project.
The code is meant to do as follows.

-** A solution with a tds reading of 600 is used during test**

-Read tds value and display on lcd.

-If tds value is < 1000 then the relay connected to D3 turns on for 10 seconds then turns off.

-Then it waits for 20 seconds and checks the tds value again.

--If tds value is < 1000 then the relay connected to D3 turns on for 10 seconds then turns off.

-Then it waits for 20 seconds and checks the tds value again.

-This keeps repeating continuously.

-While the tds check part of the code is running the lcd is meant to keep showing the refreshed tds reading every 1 second.

Now I have a few thing wrong with my code.

When it executes the relay (D3) never comes on.
The tds reading on the lcd refreshes every second.

Now I tinkered with the code a bit and worked out if I remove the part below from the void loop() section the relay now comes on for 10 seconds, then turns off BUT the lcd reading stops showing refreshed data during the 10 seconds and then refreshes so it only updates the lcd reading every 10 seconds.

This is the part I removed from the void loop() section-

unsigned long currentMillis = millis();

  while (millis() - currentMillis < 20000) {

    return;

  }

Now with more tinkering I can get the the code to take the tds reading and if it is < 1000 the relay comes on for 10 seconds then turns off.

Then it waits another 10 seconds (meant to be 20 seconds) and then repeats.
I think there is a problem with the way I have written this part because I lose 10 seconds from the relay on time count and the pause time is 10 seconds not 20.

Also the lcd only shows refreshed data every 20 seconds now.

To do this I changed the void relayOn() section at the top of the code to this-

void relayOn()

{
  unsigned long currentMillis = millis();

  while (millis() - currentMillis < 10000) {
    digitalWrite(RELAY, HIGH);
  }
  {
    digitalWrite(RELAY, LOW);
  }

  while (millis() - currentMillis < 20000) {
    digitalWrite(RELAY, LOW);
  }

Thanks for taking the time to read this.

I hope it all makes sense (1st project :slight_smile: ) and somebody can steer me in the right direction.

Here is the full code before tinkering-

#include <EEPROM.h>

#include "GravityTDS.h"

#include <OneWire.h>

#include <DallasTemperature.h>

#include <LiquidCrystal.h>

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

#define ONE_WIRE_BUS 2

#define TdsSensorPin A1

OneWire oneWire(ONE_WIRE_BUS);

GravityTDS gravityTds;

DallasTemperature sensors(&oneWire);

float tdsValue = 0;

int RELAY = 3;

void checkTDS()

{

  if (tdsValue < 1000) {

    relayOn();

  }

}

void relayOn()

{

  unsigned long currentMillis = millis();

  while (millis() - currentMillis < 10000) {

    digitalWrite(RELAY, HIGH);

  }

  {

    digitalWrite(RELAY, LOW);

  }

}

void setup()

{

  Serial.begin(115200);

  lcd.begin(16, 2);

  sensors.begin();

  gravityTds.setPin(TdsSensorPin);

  gravityTds.setAref(5.0);       //reference voltage on ADC, default 5.0V on Arduino UNO

  gravityTds.setAdcRange(1024);  //1024 for 10bit ADC;4096 for 12bit ADC

  gravityTds.begin();            //initialization

  pinMode(RELAY, OUTPUT);

}

void loop()

{

  sensors.requestTemperatures();

  gravityTds.setTemperature(sensors.getTempCByIndex(0));  // set the temperature and execute temperature compensation

  gravityTds.update();                                    //sample and calculate

  tdsValue = gravityTds.getTdsValue();                    // then get the value

  Serial.print(tdsValue, 0);

  Serial.println("ppm");

  Serial.print("Temperature is: ");

  Serial.print(sensors.getTempCByIndex(0));

  lcd.setCursor(0, 0);

  lcd.print("TDS: ");

  lcd.print(tdsValue, 0);

  lcd.print(" PPM");

  lcd.setCursor(0, 1);

  lcd.print("Temp: ");

  lcd.print(sensors.getTempCByIndex(0));

  lcd.print(" C");

  delay(1500);

  unsigned long currentMillis = millis();

  while (millis() - currentMillis < 20000) {

    return;

  }

  checkTDS();

}
  while (millis() - currentMillis < 10000) {
    digitalWrite(RELAY, HIGH);
  }

This is s blocking loop. You might just as well use a delay()

For the correct way to use millis() for non blocking timing see Using millis() for timing. A beginners guide, Several things at the same time and the BlinkWithoutDelay example in the IDE

Yeah I can see that is how it's working :frowning:

I am very new to this and have tried reading the links you posted but I get a bit lost to be honest.

Would you please be able to show me how and where I should write the millis() part of the code?

I will attempt another go over those links too.

Cheers mate.

I would start by listing the states that the system could be in then use switch/case to control which code block is executed for each state

For instance, if the system is in a state where you want the relay pin HIGH for a period then set the pin state and save the value of millis() when the state is entered and each time through loop() test whether the period has ended. If not then keep going round loop() executing any other non blocking code that you want until the period ends. When the period ends take the required actions and change state

Here is a simple example of a state machine that turns an LED on and off

enum states
{
  LED_ON,
  LED_OFF
};

states currentState;
const byte ledPin = 13;

unsigned long periods[] = {1000, 100};

void setup()
{
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);
  currentState = LED_OFF;
  digitalWrite(ledPin, LOW);
}

void loop()
{
  unsigned long currentTime = millis();
  static unsigned long stateStartTime = currentTime;
  switch (currentState)
  {
    case LED_OFF: //execute the code for this state
      if (currentTime - stateStartTime >= periods[currentState]) //test if period ended
      {
        digitalWrite(ledPin, HIGH); //if so turn on LED
        currentState = LED_ON;  //change current state
        stateStartTime = currentTime; //save time this state started
      }
      break;
    case LED_ON: //execute the code for this state
      if (currentTime - stateStartTime >= periods[currentState]) //test if period ended
      {
        digitalWrite(ledPin, LOW); //if so turn off LED
        currentState = LED_OFF;  //change current state
        stateStartTime = currentTime; //save time this state started
      }
      break;
  };
  //other code goes here but it must not block the execution of loop()
}

Thanks Bob.

Using your example I managed to cobble together some code that actually does exactly what I wanted.

I am not sure if it's proper but it seems to work so thanks again for the help!

Here it is-

#include <EEPROM.h>
#include "GravityTDS.h"
#include <OneWire.h>
#include <DallasTemperature.h>
#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

#define ONE_WIRE_BUS 2
#define TdsSensorPin A1

OneWire oneWire(ONE_WIRE_BUS);
GravityTDS gravityTds;
DallasTemperature sensors(&oneWire);

enum states {
  relay_ON,
  relay_OFF,
};

states currentState;
float temperature = 25, tdsValue = 0;
const byte RELAY = 3;
unsigned long periods[] = { 30000, 1800000 };  // relay on time, relay off pause time

void setup() {
  Serial.begin(115200);
  lcd.begin(16, 2);
  sensors.begin();
  gravityTds.setPin(TdsSensorPin);
  gravityTds.setAref(5.0);
  gravityTds.setAdcRange(1024);
  gravityTds.begin();

  pinMode(RELAY, OUTPUT);
  currentState = relay_OFF;
  digitalWrite(RELAY, LOW);
}
void loop() {

  sensors.requestTemperatures();
  gravityTds.setTemperature(temperature);
  gravityTds.update();
  tdsValue = gravityTds.getTdsValue();

  lcd.begin(16, 2);
  lcd.setCursor(0, 0);
  lcd.print("TDS: ");
  lcd.print(tdsValue, 0);
  lcd.print(" PPM");

  lcd.setCursor(0, 1);
  lcd.print("Temp: ");
  lcd.print(sensors.getTempCByIndex(0));
  lcd.print(" C");
  delay(1000);

  unsigned long currentTime = millis();
  static unsigned long stateStartTime = currentTime;
  switch (currentState) {
    case relay_OFF:
      if (currentTime - stateStartTime >= periods[currentState])
        if (tdsValue < 1000) {
          digitalWrite(RELAY, HIGH);
          currentState = relay_ON;
          stateStartTime = currentTime;
        }
      break;
    case relay_ON:
      if (currentTime - stateStartTime >= periods[currentState]) {
        digitalWrite(RELAY, LOW);
        currentState = relay_OFF;
        stateStartTime = currentTime;
      }
      break;
  };
}

As long as it works !

Some suggestions :

  • move the lcd.begin() to setup() where it belongs
  • only print the fixed text once in setup()
  • only print the values if they have changed and not every time through loop()
  • do tdsValue and gravityTds need to be floats or could they be ints or even bytes ?

But if I move them from loop() to setup() won't it only read once then stop?

No idea how to do this?

That flew over my head and hit the garage.

The fixed text such as "TDS: " only needs to be printed once

Read the value first in setup() to give you a starting value for each. Then, in loop() read the values again and print it only if the value has changed since the previous time it was read. Either way save the newly read value ready for the read and compare step next time through loop()

These variables are declared as floating point variables which take 4 bytes of storage and are relatively slow to process.

That is all very well if you need the decimal places but you are not even printing them, so do you really need them ? So why not use an integer data type such as int or byte in the first place, depending on the range of values that may occur ? They would take less memory and be faster to process

the code UKHelibob posted is the most elegant one using most programming-teqniques available in C++.

Me personal I prefer a style that is a little different. And I like adding comments to explain what the code does

#include <EEPROM.h>
#include "GravityTDS.h"
#include <OneWire.h>
#include <DallasTemperature.h>
#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

#define ONE_WIRE_BUS 2
#define TdsSensorPin A1

OneWire oneWire(ONE_WIRE_BUS);
GravityTDS gravityTds;
DallasTemperature sensors(&oneWire);


const byte relay_ON  = 1;
const byte relay_OFF = 2;


byte myRelayState; // variable for switch-case-BREAK-statemenet

float temperature = 25, tdsValue = 0;
const byte RELAY_Pin = 3;
const unsigned long relayOnTime  =  30000;   // SELF-explaining
const unsigned long relayOFFTime = 1800000;  // no comment required
unsigned long LCD_UpdateTimer; // timer variabe for non-blocking timing
unsigned long RelayTimer;      // MUST be of type unsigned long


// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - startOfPeriod >= TimePeriod ) {
    // more time than TimePeriod has elapsed since last time if-condition was true
    startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    return true;
  }
  else return false;            // actual TimePeriod is NOT yet over
}


void setup() {
  Serial.begin(115200);
  Serial.println( F("Setup-Start") );
  lcd.begin(16, 2);
  Serial.println( F("lcd.begin(16, 2) done") );
  sensors.begin();
  Serial.println( F("sensors.begin() onewire done") );
  gravityTds.setPin(TdsSensorPin);
  gravityTds.setAref(5.0);
  gravityTds.setAdcRange(1024);
  gravityTds.begin();
  Serial.println( F("init gravityTds done") );

  pinMode(RELAY_Pin, OUTPUT);
  myRelayState = relay_OFF;
  digitalWrite(RELAY_Pin, LOW);
  LCD_UpdateTimer = millis();
  RelayTimer      = millis();
}

void loop() {

  sensors.requestTemperatures();
  gravityTds.setTemperature(temperature);
  gravityTds.update();
  tdsValue = gravityTds.getTdsValue();

  if ( TimePeriodIsOver(LCD_UpdateTimer, 1000) ) { // check if 1000 milliseconds have passed by
    // if 1000 milliseconds REALLY have passed by
    lcd.begin(16, 2);
    lcd.setCursor(0, 0);
    lcd.print( F("TDS: ") );
    lcd.print(tdsValue, 0);
    lcd.print( F(" PPM" ));

    lcd.setCursor(0, 1);
    lcd.print( F("Temp: ") );
    lcd.print(sensors.getTempCByIndex(0));
    lcd.print( F(" C") );
    // delay(1000); // delay() no longer required
  }

  switch (myRelayState) {

    case relay_OFF:
      if ( TimePeriodIsOver(RelayTimer, relayOFFTime) ) { // check if OFF-time is over
        //if relay-offtime is REALLY over
        if (tdsValue < 1000) {
          digitalWrite(RELAY, HIGH);
          RelayTimer = millis();
          myRelayState = relay_ON;
        }
      }
      break;

    case relay_ON:
      if ( TimePeriodIsOver(RelayTimer, relayONTime) ) { // check if ON-time is over
        //if relay-ON-time is REALLY over
        digitalWrite(RELAY, LOW);
        RelayTimer = millis();
        myRelayState = relay_OFF;
      }
      break;
  }
}

best regards Stefan

Wow, nice one Stefan, thanks for that.

Apart from a upper case n on the relayONTimer and missing the _Pin on the 2 digitalWrite(RELAY that worked straight away.

I will use this code.

Now to work on making the tds < trigger point, relay on, and relay off time adjustable and changeable via the buttons on the lcd shield.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.