Entering and exiting a loop or pausing a loop

Hi there,

I am working on an irrigation system which cycles through an on and off for specific intervals for a specific amount of cycle time and then turns off for an extended period of time called the desiccation period, and then goes back to the initial on off irrigation cycle. I am hoping to make the program work as such:
ON
OFF
ON
OFF
[...]
DESICCATION
ON
OFF
ON
etc...

I am struggling to make the program enter and exit the desiccation period. Below is my current code, I've tried various techniques but I can't seem to make it work. I want the code to be time based and not cycle based (like the for(I=0; i<5; i++).
Thank you in advance for your help!

//Setting pin number
int relayPin = 13;

//Changing variables
int valveState = LOW; //valveState used to open and close the valve
long previousMillis = 0; //Stores the last time the valve state was changed
long runtime = 30000; //runtime before entering Desiccation period

//Set the open and closed times for the valve
long OPEN = 2500; // time the valve is open in milliseconds
long CLOSED = 5000; //time the valve is closed in millisseconds
long DESI = 20000; //desiccation time in milliseconds

void setup() {
// put your setup code here, to run once:
pinMode(relayPin, OUTPUT);
Serial.begin(9600); // Serial print is used as an indication of when it is turning on and off
}

void ONOFF() {
unsigned long currentMillis = millis();
if ((valveState == HIGH) && (currentMillis - previousMillis >= OPEN))
{
valveState = LOW; //turn it off
previousMillis = currentMillis; //remember the time (this records the last time the valve state was changed
digitalWrite(relayPin, valveState); //update the actual valve
Serial.print("OFF");
}
else if ((valveState == LOW) && (currentMillis - previousMillis >= CLOSED))
{
valveState = HIGH; //turn it on
previousMillis = currentMillis; //remember time
digitalWrite(relayPin, valveState); //update the actual valve
Serial.print("ON");
}
}

void loop(){
ONOFF();
}

Welcome,

You want runtime to be 30 seconds, on period 2.5 second and off period 5 seconds, so luckily there will be exactly 4 full on/off cycles... But, what if you change any of these values, so that it doesn't result in full on/off cycles ?

For example, supposing you change runtime to 32, then the last state of the pump will be on... This is a problem that you can eliminate easily :

  • Deduce the amount of full on/off cycles by doing uint8_t numCycles = runtime / ( OPEN + CLOSED ) (as the division is made with integers, the result will be truncated). Using this variable will ensure that if you change any of these parameters, the last state of the pump will always be off.

  • Then if you want to wait until runtime has elapsed before desiccation, you can calculate the remaining time where the program will be idling, by doing idleTime = runtime - ( numCycles * ( OPEN + CLOSED ) ), or start the desiccation period immediately if you don't care about this remaining time.

Hi Paul,

When you post code it's REALLY helpful if - whilst still in the IDE - you can select all - do a ctrl+T to format it - then do a ctrl+shift+C to copy it into a forum compatible format before pasting it here - that way we can copy it with 1 click and take a closer look in our IDEs.

With regards to the program - can you give me more information about when the desiccation perioud occurs? With a "[...]" I don't have any way of knowing how many ON / OFF cycles to complete before entering it - or what conditions it's based on.

Here is an example of one way to do it. I did not test this and may not be fully correct/proper. It attempts to use a cooperative super loop via state machine so function is non-blocking.

Interrupt solution also exists. However this is super loop/polling solution, like OP.

//Setting pin number
const int relayPin = 13;

//Changing variables
const long runtime = 30000; //runtime before entering Desiccation period in millisseconds

//Set the open and closed times for the valve
const long OPEN = 2500; // time the valve is open in milliseconds
const long CLOSED = 5000; //time the valve is closed in millisseconds
const long DESI = 20000; //desiccation time in milliseconds

void setup() {
	pinMode(relayPin, OUTPUT);
	Serial.begin(9600);
}

void ONOFF() {
	enum states { ON, OFF, DESIC };
	static states state = OFF;
	static unsigned long previousMillis = millis();
	static unsigned long run_counter = 0;
	unsigned long currentMillis = millis();
	unsigned long time = currentMillis - previousMillis;

	run_counter += time;
	previousMillis = currentMillis;
	
	if (run_counter >= runtime) {
		state = DESIC;
		run_counter = 0;
		digitalWrite(relayPin, LOW); //turn it off
		Serial.print("DESICCATION");
		return;
	}

	switch (state) {
		case OFF:
			if (time >= CLOSED) {
				state= ON; 
				digitalWrite(relayPin, HIGH); //turn it on
				Serial.print("ON");
			}
			break;
		case ON:
			if (time >= OPEN) {
				state = OFF;
				digitalWrite(relayPin, LOW); //turn it off
				Serial.print("OFF");
			}
			break;
		case DESIC: // Currently OFF
			if (time >= DESI) {
				state= ON; 
				digitalWrite(relayPin, HIGH); //turn it on
				Serial.print("ON");
			}
		default:
			break;
	}
}

void loop() {
	ONOFF(); // Non-blocking but cannot be starved
}

Hi,

Thank you, will do in the future :slight_smile:

The amount of cycles is not what I would like to base when the desiccation period happens. I would like for it to be purely based on time. This code will be used in a research project which will require the on/off and desiccation period to change for different plant species and experiments. For example, experiment 1 will run as follows:
During most of the time the system should cycle continuously through:
ON: 8 sec
OFF: 2min
The only time it does not do so is when it enters the desiccation period which should occur every 12hours for a duration of 1.2 hours.
Desiccation meaning the valve is closed.

I hope that makes sense...

Cool - so - how about something like:

  • At midnight start 8sec on / 2 min off cycles and continue until 10:48 then stop

  • At 12 noon resume the 8 sec on / 2 min off cycles until 22:48 and then stop until midnight

  • Rinse and repeat

Times can be set for other plants by changing variables at the top of the sketch.

How does that sound?

That sounds like it could work! Thank you!

RTC will be needed.

Could be done with RTC. However millis would also work here. Drift may occur, however I would not expect this to be very critical.

This can cause a bug sometimes.

Thanks I will give this a go

No worries. You'll need a RTC module, but they're only a few bucks and trivial to impliment over I2C.

Just be aware that there's a very common DS3231 RTC module floating around with a very bad design; it tries to charge a non-rechargable CR2032 battery (or overcharge the equivalent rechargable version) if run at 5V - the fix is easy - just remove the SMD 201 ohm resister in the charging circuit - other than that they're great.

Since you won't need the date side of it to be correct you can just remove the backup battery - power it up at midnight - then reinsert the battery to set the time; it's a bit easier than writing code to set the time (although that works too of course and isn't difficult).

Do you need any help with the code now?

Hi!
I will order an RTC module now! It sounds like the most straight forward way to approach this project. Furthermore, this code will also include lighting control in it in the future, having an RTC will be useful for that too.

I will let you know as soon as I get it. I assume I will need help with the code then.

Thank you!

1 Like

Hi!
I've got myself an RTC, I got the DS 1307, I know it isn't the most accurate but it should not be a problem for this application as long as the on and off time is not affected too much.
The desiccation period can happen at a less precise time and for less precise intervals.

Below is my current code and my system has been running well for the past week. Now I want to include a desiccation period to optimise the growth of the crop.

Could I still use millis to control the ON and OFF times, function called ONOFF, and use the RTC module to jump in and out of the ONOFF loop dependent on the time of day? That way my ON OFF intervals are precise while my desiccation period can veer off the actual time a little.

//Setting pin number
int relayPin = 13;

//Changing variables
int valveState = LOW; //valveState used to open and close the valve
long previousMillis = 0; //Stores the last time the valve state was changed
long runtime = 30000;
int long minute = 60000;
//Set the open and closed times for the valve
long OPEN = 7000; // time the valve is open in milliseconds
long CLOSED = minute * 2; //time the valve is closed in millisseconds
long DESI = minute*90; //desiccation time in milliseconds
long pumpset = 30000; //open valve during set up to get air out of the system

void setup() {
  // put your setup code here, to run once:
  pinMode(relayPin, OUTPUT);
  Serial.begin(9600);
  PUMPSET();
}

void PUMPSET() {
  digitalWrite(relayPin, HIGH );
  Serial.println("Set up");
  delay(pumpset);
}

void ONOFF() {
  // MAIN CODE
  // This code uses millis to time the intervals by doing a simple arythmetic calculation
  // It checks if it's time to change the state of the valve
  // If the difference between the current time and the last time the state of the valve changed
  // is bigger than the OPEN, CLOSE or DESI interval at which you want to change the valve state
  unsigned long currentMillis = millis();

  if ((valveState == HIGH) && (currentMillis - previousMillis >= OPEN))
  {
    valveState = LOW; //turn it off
    previousMillis = currentMillis; //remember the time (this records the last time the valve state was changed
    digitalWrite(relayPin, valveState); //update the actual valve
    Serial.println("OFF");
  }
  else if ((valveState == LOW) && (currentMillis - previousMillis >= CLOSED))
  {
    valveState = HIGH; //turn it on
    previousMillis = currentMillis; //remember time
    digitalWrite(relayPin, valveState); //update the actual valve
    Serial.println("ON");
  }
}

void loop() {
  ONOFF();
}

Could you help me build something like:

  • Start ONOFF function whenever the board is plugged in or reset
  • At 6pm pause ONOFF
  • At 7:30 pm Resume ONOFF
  • At 6am pause ONOFF
  • At 7:30 am start ONOFF

Thank you in advance for your help!

Here is a small section of code which is incomplete. You could make this work with timer interrupt, but may not be worth it really.

Idea is simple, periodically check the RTC to see what the state is. If the state is past, you enable/disable the ONOFF function. Note you could use RTC with interrupt and alarm functions to do this, but it does not look like this is supported. You could also technically do it all with millis, but it may drift.

Point of possible concern is using I2C, which can be a little slow. However for this you will probably be okay. However there can be conflict in sampling and may cause small drifts in ON OFF intervals when it polls. Optimization is possible somewhat.

unsigned long poll_time = 60000;

void loop() {
  bool isEnabled = false;
  static unsigned long past;

  if (millis() - past > poll_time) { // Do not need to poll every time.
    // Poll RTC - libraries exist for this RTC
    if (1) { // Past time
      isEnabled = !isEnabled;
      past = millis(); // This technically loses time but will be fine.
    }
  }
 
  if (isEnabled)
    ONOFF();
}

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