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");
}
}
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.
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
}
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.
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).
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.
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
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();
}