Delay, millis, interrupts for Hydroponic Set Up Programming Use

Hi Team,

Obviously new to Arduino and programming in general. I'm studying power systems engineering (technician) but doing some fun stuff on the side. Just started learning Arduino in the last few months.

I'm working on a very simple ebb and flow hydroponics set up. There is an LCD display which will have manual options (turn pump on/off, turn heater on/off, etc) then an auto function can be called to start the hydro set up for however long that might be, say 6 weeks.

So far I have been working on the manual aspects with an LCD screen, buttons, sensors (temp, EC, water level).

My idea for the auto function (which I'm still figuring out) is when activated from the menu, it runs through a function which will turn the water pump on for 15 mins, maybe 3 times a day depending on which plants, then be off and overnight with only the water heater going, and maybe a nutrient peristaltic pump. During pumping, the heater turns off, peristaltic dosing pumps are off, and water level sensor is still checking if there is water in the main reservoir, every 5 seconds. The water level will kill the pump if there's no water to keep it submerged.

My question is about timing. I was planning on going with millis because delay shuts everything off. Feel comfortable here. One of the tutors who has been helping me out reckons I should use Interrupts, but it is so esoteric to me just looking at the code. He introduced this idea on Friday to me and was really weirdly insistent. From searching this forum and reading stuff online, It doesn't seem to be what I need.

Can I get some confirmation on this? I think his idea would be during the day time hours, the interrupt would trigger the pumping time, then stop after 15 mins and then when a couple hours passed for draining, would trigger again. Is this possible? From what I read it just changes a state, not necessarily calling up another function. And is insanely accurate which I don't really need either. I also want the LCD to continue to display sensor data during flooding and draining phases.

I'm really passive and don't want to argue with this guy for weeks over it and i'm also too ignorant about it all to be able to back up my argument for using millis.

What would be causing the interrupt?

millis() is a good plan, but as this is a long running project, you may need a stable real-time-clock to stay on the same page every day over many weeks.

2 Likes

I see no purpose for interrupts in your stated project, but a real time clock will be essential. You'll still need millis() and good design, but the RTC element will allow sensible HMI interactions.

1 Like

Interrupts get a no from me as well. They are not useful in this task.

However you can't just use millis because on its own it is identical to using delays. What you have to do is to use millis to implement a state machine which is a simple way of making it look like the computer is doing several things at once. Unfortunately this is not beginners stuff but is intermediate stuff .

Search this forum for
Blink without delay
And
Doing several things at once.

This is a topic that comes up several times a day. Most simple examples involve flashing LEDs so you will have to convert this into motor movement, but that is easy enough to do.

1 Like

Interrupts are almost never needed.

The only time you need to use interrupts is when it is necessary to respond to very fast signals that you cannot afford to miss or when you can not use simple high speed polling of inputs.

You will find that using a DS3231 precision RTC ($3.00) will handle slower time requirements gives excellent results (also has backup battery), the same RTC in projects here are off by only a few seconds per 6 months.

For shorter timing events, a millis( ) based TIMER is very easy to incorporate.

2 Likes

Interrupts would just introduce additional complications without any real benefit.
If you wanted to do something like measure water flow from the pump then interrupts might be needed for a flow sensor, but I can't see using them for anything that you have outlined.

One thing I would definitely implement is a watchdog timer, you do not want the system locking up, since that can easily result in killing off the plants.

An additional benefit of having the RTC is that you don't completely lose track of time from a power interruption or board reset.

1 Like

Thanks for that, all will be helpful when I get there. Taking my time with tutorials and this project.

And maybe have a hydro mechanical fallback system :wink: .

Just Kidding

1 Like

Well, I've seen a few discussions on here with people working on incubators for eggs, where switching the power to a heater/fan/humidifier glitched the power and resulted in either cold or cooked eggs.

2 Likes

I like turmeric with my cooked eggs :slight_smile: .

1 Like

Here is something to play with.

See if you can work your way through the sketch.

Ask questions if you don't fully understand things.


Closing a switch on pin #2 starts pump action.

//********************************************^************************************************
//  URL
//
//  LarryD
//
//  Version    YY/MM/DD    Comments
//  =======    ========    ====================================================================
//  1.00       YY/MM/DD    Running code
//
//
//

//********************************************^************************************************
#define RESTART                    millis()

#define pumpON                     HIGH   //PIN---[220R]---A[LED]K---GND
#define pumpOFF                    LOW

#define LEDon                      HIGH   //PIN---[220R]---A[LED]K---GND
#define LEDoff                     LOW

#define PUSHED                     LOW    //+5V---[Internal 50k]---PIN---[switch]---GND
#define RELEASED                   HIGH

#define CLOSED                     LOW    //+5V---[Internal 50k]---PIN---[switch]---GND 
#define OPEN                       HIGH

#define ENABLED                    true
#define DISABLED                   false

#define YES                        true
#define NO                         false

#define EXPIRED                    true
#define stillTIMING                false


//                          m i l l i s ( )   B a s e d   T I M E R S
//********************************************^************************************************
//
struct makeTIMER
{
  //#define ENABLED       true
  //#define DISABLED      false
  //
  //#define YES           true
  //#define NO            false
  //
  //#define EXPIRED       true
  //#define stillTIMING   false

  unsigned long Time;           //when the TIMER started
  unsigned long Interval;       //delay time in ms which we are looking for
  bool          TimerFlag;      //is the TIMER enabled ? ENABLED/DISABLED
  bool          Restart;        //restart this TIMER ? YES/NO

  //****************************************
  //fuction to check if the TIMER is enabled and check if the TIMER has expired
  bool checkTIMER()
  {
    //*********************
    //is this TIMER enabled and has this TIMER expired ?
    if (TimerFlag == ENABLED && millis() - Time >= Interval)
    {
      //*********************
      //should this TIMER restart again?
      if (Restart == YES)
      {
        //restart this TIMER
        Time = millis();
      }

      //this TIMER is enabled and has expired
      return EXPIRED;
    }

    //this TIMER is disabled and/or has not expired
    return stillTIMING;

  } //END of   checkTime()

}; //END of   struct makeTIMER

//********************************************^************************************************
/*example
  // *******************
  makeTIMER toggleLED =
  {
  0, 500ul, ENABLED/DISABLED, YES/NO  //.Time, .Interval, .TimerFlag, .Restart
  };
*/
//*******************
makeTIMER heartbeatTIMER =
{
  0, 500ul, ENABLED, YES      //.Time, .Interval, .TimerFlag, .Restart
};

//*******************
makeTIMER scanSwitchesTIMER =
{
  0, 50ul, ENABLED, YES       //.Time, .Interval, .TimerFlag, .Restart
};

//*******************
makeTIMER pumpOnTIMER =
{
  //5 minutes
  //0, 5ul * 60 * 1000, DISABLED, NO   //.Time, .Interval, .TimerFlag, .Restart

  //5 seconds for testing
  0, 5ul * 1000, DISABLED, NO   //.Time, .Interval, .TimerFlag, .Restart
};

//*******************
makeTIMER pumpOffTIMER =
{
  //5 hours
  //0, 5ul * 60 * 60 * 1000, DISABLED, NO //.Time, .Interval, .TimerFlag, .Restart

  //10 seconds for testing
  0, 10ul * 1000, DISABLED, NO //.Time, .Interval, .TimerFlag, .Restart
};
//*******************


//********************************************^************************************************
const byte mySwitch              = 2;
const byte waterPump             = 12;
const byte heartbeatLED          = 13;

byte lastMySwitch                = OPEN;


//                                       s e t u p ( )
//********************************************^************************************************
void setup()
{
  Serial.begin(115200);

  pinMode(mySwitch, INPUT_PULLUP);

  pinMode(heartbeatLED, OUTPUT);

  //pump off a powerup
  digitalWrite(waterPump, pumpOFF);
    
  pinMode(waterPump, OUTPUT);

} //END of   setup()


//                                        l o o p ( )
//********************************************^************************************************
void loop()
{
  //************************************************              T I M E R  heartbeatLED
  //when enabled, is it time to toggle the heartbeat LED ?
  if (heartbeatTIMER.checkTIMER() == EXPIRED)
  {
    //toggle the LED
    digitalWrite(heartbeatLED, !digitalRead(heartbeatLED));
  }

  //************************************************              T I M E R  scanSwitches
  //is this TIMER enabled and is it time to scan our switches ?
  if (scanSwitchesTIMER.checkTIMER() == EXPIRED)
  {
    checkSwitches();
  }

  //************************************************              T I M E R  pumpOn
  //is this TIMER enabled and is it time to turn off the pump ?
  if (pumpOnTIMER.checkTIMER() == EXPIRED)
  {
    digitalWrite(waterPump, pumpOFF);

    //we are finished with this TIMER now
    pumpOnTIMER.TimerFlag = DISABLED;

    //enable the OFF TIMER
    pumpOffTIMER.TimerFlag = ENABLED;

    //restart the OFF TIMER
    pumpOffTIMER.Time = RESTART;

  }

  //************************************************              T I M E R  pumpOff
  //is this TIMER enabled and is it time to turn on the pump ?
  if (pumpOffTIMER.checkTIMER() == EXPIRED)
  {
    digitalWrite(waterPump, pumpON);

    //we are finished with this TIMER now
    pumpOffTIMER.TimerFlag = DISABLED;

    //enable the ON TIMER
    pumpOnTIMER.TimerFlag = ENABLED;

    //restart the ON TIMER
    pumpOnTIMER.Time = RESTART;

  }

  //************************************************
  //other non blocking code goes here
  //************************************************

} //END of   loop()


//                               c h e c k S w i t c h e s ( )
//********************************************^************************************************
void checkSwitches()
{
  byte state;

  //************************************************              mySwitch
  state = digitalRead(mySwitch);

  //has there been a change in "mySwitch" state ?
  if (lastMySwitch != state)
  {
    //update to this new state
    lastMySwitch = state;

    //*************************************
    //is the reset switch closed ?
    if (state == CLOSED)
    {
      //water pump
      digitalWrite(waterPump, pumpON);

      //disable the OFF TIMER
      pumpOffTIMER.TimerFlag = DISABLED;

      //enable the ON TIMER
      pumpOnTIMER.TimerFlag = ENABLED;

      //restart the ON TIMER
      pumpOnTIMER.Time = RESTART;
    }

  } //END of this switch

  //************************************************              nextSwitch

} //END of   checkSwitches()



//********************************************^************************************************

EDIT

Fixed a small bug

1 Like

Woah, LarryD!

I'm loving it. This is really great for play with timers. Thanks so much for putting this up.

i will be asking questions probably over the next couple weeks, stay tuned :slight_smile:

I'm not 100% sure but I think he thought that a specific time of day would cause the interrupt. So say 09:00, pump starts for 15 mins then off. Then at 13:00 interrupt starts pump for 15 mins then off.

Might be but then this guy is not aware / does not know of non-blocking timing.

You have function "loop()" that does exactly what the name says: looping.
Repeat over and over again. With non-blocking timing function loop() iterates thousands of times each second which is really fast enough to check if it is 09:00:00,0 AM or any other time of the day.

The timer that @LarryD shows is good for more or less short-time timing relative to a varying starting point.

For timing that is done at always a certain time of the day like 9:00 am, 13:00, 21:45 etc.

It is useful to calculate minute_Of_The_Day or seconds_Of_The_day

which is simply


//example 9:03
// 453            =   9   * 60 +    3                      
minute_Of_The_Day = hours * 60 + minutes;

//example 20:45
// 1245           =  20   * 60 +    45                      
minute_Of_The_Day = hours * 60 + minutes;

using minutes of day reduces timing-conditions to a single number

if (minute_Of_The_Day >= 1245) {
  //doAction();
}

or if you would like to read the time directly in a HH:MM-format you could code

const int switchOffAt20_45 = 20 * 60 + 45;

if (minute_Of_The_Day >= switchOffAt20_45) {
  //switchOff();
}

or

const int switchPumpOffTime = 20 * 60 + 45;

if (minute_Of_The_Day >= switchPumpOffTime) {
  //PumpOff();
}

defining this extra constant needs additionally 2 minutes. But it makes the code self-explaining.

You should think about each name what would be still a short name but self-explaining
so if I come back in one year to modify this code that I will understand the meaning

minute_Of_The_Day

is medium long. Not everybodys case

minOfDay

is pretty short yet still self-explaining

As I write code a lot with mark, copy & paste of variable-names and function-names
it doesn't matter for me if there are 3 characters or 30 characters it is the same amount of typing

jumping at the beginning of the word
crtl-cursor-left,
ctrl-c,

move cursor to target-position

ctrl-v

and there is no chance for a typo
because of the copy & paste

best regards Stefan

1 Like

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