/* Created by Rhuanito Soranz Ferrarezi & Marc van Iersel, University of Georgia Contact rhuanito@terra.com.br / mvanier@uga.edu for further assistance Release date: October 1st, 2014 Updates: June 2nd, 2015 (by Rhuanito Soranz Ferrarezi) June 3nd, 2015 (by Rhuanito Soranz Ferrarezi) PROGRAM OBJECTIVE: Read four 10HS sensors (Decagon Devices, Pullman, WA), convert the voltage measurements to volumetric water content (VWC), and control four irrigation plots based on the comparison of readings with thresholds by opening and closing one latching and three regular solenoid valves. We used two types of solenoid valves for demonstration purpose only; one type should be chosen. The system can log the data on a SD card with time stamp and print the result to the Serial Monitor. THE HARDWARE: We used one Arduino open source prototype board (Uno R3; Arduino, Ivrea, Italy), one stackable secure digital (SD) shield with built-in real-time clock (RTC) (SD 2.0; Adafruit, China) and one SD card (1 Gb; SanDisk Corporation, Milpitas, CA) for the logging system. The SD card needs to be formatted to FAT to work properly in the SD shield. The stacked boards were connected to an LED (red), a 5 VDC relay driver board with eight relays (SainSmart, Leawood, KA), four capacitance-type soil moisture sensors (10HS; Decagon Devices, Pullman, WA), one 6 - 18 VDC 1-in latching solenoid valve (GCS 3050/3051; Galcon, San Rafael, CA), three 24 VAC 1-in regular solenoid valves (Rain Bird, Tucson, AZ). The SD shield was used to collect data, because the Arduino itself is not able to log the readings. The SD shield is assembled, with all the components pre-soldered. The user will only need to solder on stacking headers (not included, ordered separately) to attach to the Arduino. WIRING: 1) All indicated colors are suggestions. 2) 10HS sensors: Each data output wire (red ones) of four 10HS sensors (numbered from #1 - #4) was attached to analog pins A0 - A3. The excitation/power wires (white ones) from sensors #1 and #2 were connected together to digital pin D2 and the ones from sensors #3 and #4 to digital pin D3 (connections made using ‘euro-style’ terminal strip). All four ground wires (bare ones) were connected together to ground (GND). 3) The RTC on the AdaFruit datalogging shield uses analog pins A4 and A5, so those cannot be used for other sensor measurements. 4) Latching solenoid valve: They are ideal for applications where power is limited, because continuous power is not required to maintain the energized position. To save power consumption, an internal magnet holds the valve in the energized position when the power supply is applied or removed. These valves also feature fast response times, only requiring a momentary pulse to energize and de-energize the valves (50 ms), what can be achieved using a 9V battery. To open the valve, the red wire from the valve needs to be connected to the positive wire of the power supply (9V battery in this case), and the black wire to the negative one. To close the valve, the polarity needs to be inverted, meaning that the red from the valve needs to go to the negative wire of the power supply and the black to the positive one. We used the relay drivers to do it automatically, building a H-Bridge. 5) Relays: Powered continuously with 5V (using orange wire) and connected to the GND (using black wire). 5.A) Relays to latching solenoid valve: Relays K1 and K4 were connected together to Arduino digital pin D4 (using a yellow wire), and relays K2 and K3 to Arduino digital pin D5 (using a blue wire). We used a H-Bridge in the four-relay board (using one 9V battery) to invert the polarity to open and close the latching solenoid valve: • Relay 1: Latching solenoid valve 1 + (red) on COM, battery + (red) to NO • Relay 2: Battery - (black) to COM, jumper wire from NO to relay 1 COM • Relay 3: Latching solenoid valve 1 - (black) on COM, battery + (red) to NO • Relay 4: Battery - (black) to COM, jumper wire from NO to relay 3 COM 5.B) Relays to regular 24V solenoid valve: Relay K5 was connected to Arduino digital pin D6 (using a gray wire), relay K6 to Arduino digital pin D7 (using a purple wire), relay K7 to Arduino digital pin D8 (using a green wire). One wire from the 24V power supply have to be divided in three wires and be connected to the valves 2, 3, and 4. The other wire from the 24V power supply also have to be split in three wires and be connected to the relays 5, 6 and 7 at COM. • Relay 5: Valve 2 + (actually does not matter, inserted for reference) on NO, 24V power supply + to COM • Relay 6: Valve 3 + (actually does not matter, inserted for reference) on NO, 24V power supply + to COM • Relay 7: Valve 4 + (actually does not matter, inserted for reference) on NO, 24V power supply + to COM 6) Digital pin D10 must be an output in the program 7) Relays should use reverse logic, what means LOW to open and HIGH to close in the program 8) The serial monitor allows users to see information on the computer screen. It can be started by going to Tools > Serial monitor or by clicking Ctrl + Shift + M) UPDATES: June 2nd, 2015) "Return" instruction added to the system verification and RunTime and IrrigTime declared as unsigned long variables. June 3rd, 2015) Increment instruction "i++" was fixed. */ // ========================================================================================== // Call libraries for the SD shield with built-in RTC #include // Arduino built-in library #include // Arduino built-in library, needs to be updated at http://learn.adafruit.com/adafruit-data-logger-shield/downloads #include // Arduino built-in library #include "RTClib.h" // Downloadable at http://learn.adafruit.com/adafruit-data-logger-shield/downloads // Declare variables for four 10HS sensors (numbered from #1 - #4) // The variable numbers in brackets is n+1 due to the counting starts on 0 instead of 1 int sensorValue[5], Counter[5], i; float VWC[5], Threshold[5], SubCalSlope, SubCalIntercept; unsigned long IrrigTime, RunTime; // Call the RTC RTC_DS1307 rtc; // ========================================================================================== // The setup routine runs once when the program first starts or when you press reset void setup () { //**************************************************************************************************************************// // NOTE: THE FOLLOWING SECTION CONTAINS ALL IMPORTANT USER-CHANGEABLE SET POINT // // DO NOT MODIFY OTEHR PARTS OF THE PROGRAM UNLESS YOU KNOW WHAT YOU ARE DOING // //**************************************************************************************************************************// // IRRIGATION TIME: Set according to your need (in seconds). The irrigation time is 60 s (=1 min). You can also set different irrigation times for each plot if needed, just changing the time in each sensor identification # accordingly IrrigTime = 60; // RUN TIME: Set according to your need (in seconds). This program run every 1800 s (=30 min) RunTime = 1800; // IRRIGATION THRESHOLDS: Values used to trigger irrigation when the sensor readings are below a specific VWC (in units of m3/m3 or L/L) Threshold[1] = 0.4; Threshold[2] = 0.4; Threshold[3] = 0.4; Threshold[4] = 0.4; // SUBSTRATE CALIBRATION: You have to convert the voltage to VWC using soil or substrate specific calibration. Decagon has generic calibrations (check the 10HS manual at http://manuals.decagon.com/Manuals/13508_10HS_Web.pdf) or you can determine your own calibration. We used our own calibration for Fafard 1P (peat: perlite, Conrad Fafard, Inc., Agawam, MA) SubCalSlope = 1.1785; SubCalIntercept = -0.4938; //**************************************************************************************************************************// // END OF SECTION WITH USER-CHANGEABLE SETPOINT // // DO NOT MODIFY OTHER PARTS OF THE PROGRAM UNLESS YOU KNOW WHAT YOU ARE DOING // //**************************************************************************************************************************// // Initialize serial communication (over the USB cable connecting the Arduino Uno to a computer) at 57,600 bits per second Serial.begin(57600); // Initialize the WIRE library to run the RTC Wire.begin(); // Initialize the RTC library to run the RTC rtc.begin(); // Check if the RTC is running. If not, show error message on serial monitor if (! rtc.isrunning()) { Serial.println("RTC is NOT running!"); // And set the date and time this sketch (program) was compiled rtc.adjust(DateTime(__DATE__, __TIME__)); } else { // If RTC has been started, send message to serial port Serial.println("Real time clock initialized."); } // Pin to write to SD card, depends on SD board, check manufacturing specs (pin D10 for Arduino Uno) const int chipSelect = 10; // Configure digital pin D10 as output. This pin is used by default for use with the SD shield (chipSelect) pinMode(10, OUTPUT); // See if the SD card is present and can be initialized. First send a message to the screen that the SD card is being initialized. If not, send an error message to the serial port and prevent the program from running Serial.print("Initializing SD card... "); if (! SD.begin()) { Serial.println(); Serial.println(); Serial.println("*******************************************"); Serial.println(" Card failed, or not present "); Serial.println(" WARNING: NO DATA WILL BE COLLECTED! "); Serial.println("CHECK CARD AND ALL CONNECTIONS TO SD SHIELD"); Serial.println("*******************************************"); Serial.println(); } else { // If SD card is available, send message to the computer screen that the SD card is ready for use Serial.println("SD card initialized."); Serial.println(); } // Initialize file and write header File dataFile = SD.open("log.txt", FILE_WRITE); // If the file is available, write headers to it if (dataFile) { dataFile.println(); dataFile.println("Date Time, VWC[1], VWC[2], VWC[3], VWC[4], Counter[1], Counter[2], Counter[3], Counter[4]"); dataFile.println(); dataFile.close(); } // If the file is not open, pop up an error else { Serial.println("error opening data file log.txt"); Serial.println(); } // Configure digital pins D2 and D3 as outputs to apply voltage to all four sensors (D2: sensor 1 and 2; D3, sensor 3 and 4) pinMode(2, OUTPUT); pinMode(3, OUTPUT); // Set digital pins D4 - D8 HIGH. These digital pins control the relays. Setting these pins HIGH assures that the relays are open at the initial startup or when the Arduino is reseted for (i = 4; i < 9; i++) { digitalWrite(i, HIGH); // Then configure the pins as outputs. That means that they can be used to apply 0VDC (LOW) or 5VDC (HIGH) to anything connected to these pins. pinMode(i, OUTPUT); } // Configure the pin that controls the LED (digital pin D9) as output. Setting this pin HIGH will turn on the LED pinMode(9, OUTPUT); // Use the internal 1.1V on the Uno board as the reference for all analog voltage measurements analogReference(INTERNAL); // If you are using a Mega 2560 R3 board you sould use this code //analogReference(INTERNAL1V1); } // ========================================================================================== // The following section (loop) of the program will run until the power is disconnected, at an interval specified by the 'RunTime' void loop() { // Check to make sure that the frequency at which the program runs (RunTime) is at least 5x longer than the irrigation duration (IrrigTime). The program will stop if this condition is not satisfied if (RunTime <= IrrigTime*5) { Serial.println("***********************************************"); Serial.println("WARNING: RunTime is too short. Please increase."); Serial.println("WARNING: THE PROGRAM WILL NOT RUN CORRECTLY!"); Serial.println("***********************************************"); return; } // Call the RTC routine and set the current date and time to those or the RTC DateTime now = rtc.now(); // Turn the LED (connected to D9) off digitalWrite(9, LOW); // Apply power to 10HS sensors #1 and #2 // The white wire of these sensors are connected to digital pin D2 digitalWrite(2, HIGH); // Wait 10 ms, delay(10); // Measure the analog output from sensor #1 and #2 (red wires connected to analog pins A0 and A1) sensorValue[1] = analogRead(0); sensorValue[2] = analogRead(1); // And turn the power to the sensors off digitalWrite(2, LOW); // Repeat the same process for sensors #3 and #4 digitalWrite(3, HIGH); delay(10); sensorValue[3] = analogRead(2); sensorValue[4] = analogRead(3); digitalWrite(3, LOW); //For sensors #1 - #4 for (i=1; i < 5; i++){ // Convert the analog reading (which goes from 0 - 1023) to a voltage (0 - 1.1V) that is used to calculate the VWC using a calibration equation VWC[i] = sensorValue[i] * (1.1/1023.0) * SubCalSlope + SubCalIntercept; } // PRINT THE DATE AND TIME STAMP TO THE SERIAL MONITOR // Note the software has a quirk: Whenever the month/day/hour/minute/second is less then 10, by default it will use a single digit (e.g., '3' instead of '03' That can result in odd formatting of the time or date. Therefore, whenever the value is less than 10, we first write a 0, followed by the single digit. This fixes the formatting // Note: Serial.print commands write information to the same line on the screen. Serial.println prints information and then goes to a new line. To send characters to the screen use '…', and to send text to the screen use "...". The value of variables can be written to screen by using the name of that variable (not in quotation marks) // Write the month to the screen if (now.month() <10) Serial.print('0'); Serial.print(now.month(), DEC); Serial.print('/'); // Write the day to the screen if (now.day() <10) Serial.print('0'); Serial.print(now.day(), DEC); Serial.print('/'); // Write the year to the screen Serial.print(now.year(), DEC); Serial.print(' '); // Write the hour to the screen Serial.print(now.hour(), DEC); Serial.print(':'); // Write the minute to the screen if (now.minute() <10) Serial.print('0'); Serial.print(now.minute(), DEC); Serial.print(':'); // Write the second to the screen if (now.second() <10) Serial.print('0'); Serial.print(now.second(), DEC); Serial.println(" h"); // Print the VWC for plots #1 - #4 to the screen (the variable i goes from 1 - 4) Serial.print("VWC (m3/m3): "); for (i = 1; i < 5; i++) { Serial.print("Plot #"); Serial.print(i); Serial.print(" = "); Serial.print(VWC[i]); Serial.print(", "); } // Write an error message to the screen and turn on the LED when a sensor is reading out of range // for plot #1 - #4 for (i = 1; i < 5; i++) { // Check to see if the VWC reading is below 0 if (VWC[i] < 0.0) { // If so, write an error message to the screen Serial.print("WARNING: Sensor #"); Serial.print(i); Serial.print(" out of range (too low). Current reading: "); Serial.print(VWC[i]); Serial.println(" m3/m3"); // And turn on the LED (which is powered by digital pin D9) digitalWrite(9, HIGH); } // If the measured VWC is > 0.8 if (VWC[i] > 0.2) { // Write an error message to the screen Serial.print("WARNING: Sensor #"); Serial.print(i); Serial.print(" out of range (too high). Current reading: "); Serial.print(VWC[i]); Serial.println(" m3/m3"); // And turn on the LED (which is powered by digital pin D9) digitalWrite(9, HIGH); } } // Print the number of irrigations for plots #1 - #4 to the screen (the variable i goes from 1 - 4) Serial.print("Number of irrigations: "); for (i = 1; i < 5; i++) { Serial.print("Plot #"); Serial.print(i); Serial.print(" = "); Serial.print(Counter[i]); Serial.print(", "); } // IRRIGATION CONTROL SECTION FOR PLOT #1, WHICH IS CONTROLLED USING A LATCHING SOLENOID VALVE // When the VWC in plot #1 is equal to or below the threshold established at the beginning of the program, if (VWC[1] <= Threshold[1]) { // The irrigation needs to be turned on // Setting a digital pin LOW applies 0 VDC to the connected wire. This will close the NO and COM connection at the relays #1 and #4 allowing 9 VDC from the battery to be applied to the latching valve to open the latching solenoid valve, we need to apply 9 VDC to the solenoid for 50 ms digitalWrite(4, LOW); // Wait for 50 ms delay(50); // Add 1 to the irrigation counter for this plot Counter[1] = Counter[1] + 1; // Send a message to the screen that the plot is being irrigated Serial.print("Plot #1 irrigation started. "); // And now open the NO and COM connection at the relays #1 and #4 by applying 5 VDC to pin D4 digitalWrite(4, HIGH); // Now wait for the specified irrigation time. Note that the 'delay' instruction uses time in units of milliseconds so the specified irrigation time is multiplied by 1,000 delay(IrrigTime*1000); // Now the latching solenoid valve needs to be closed. That requires applying 9 VDC with reversed polarity to the solenoid. This is done using relays #2 and #3 which are both controlled using digital pin D5. Setting digital pin D5 low will close the NO and COM connection at both relays digitalWrite(5, LOW);// set the Relay ON // Wait for 50 ms delay(50); // And set digital pin D5 HIGH, which will open the NO and COM connection at the relays #2 and #3 digitalWrite(5, HIGH); // Send a message to the screen that the irrigation is finished Serial.println("Irrigation finished."); } // And if the irrigation does not need to be turned on if (VWC[1] > Threshold[1]) { // The program simply pauses for the specified irrigation time. This is done to assure that the program takes the same amount of time to run regardless of which plots get irrigated delay(IrrigTime*1000); // And send a message to the screen that plot 1 did not need irrigation. Serial.println("Plot #1 does not need irrigation."); } // IRRIGATION CONTROL SECTION FOR PLOTS #2 TO #4, WHICH ARE CONTROLLED USING REGULAR SOLENOID VALVES for (i = 2; i < 5; i++) { // Determine whether the measured VWC is below the threshold (and irrigation is needed) if (VWC[i] <= Threshold[i]) { // If so, turn the digital pin LOW (which closes the NO and COM connection at the corresponding relay and opens the valve) // The i+4 refers to the fact that irrigation in plot #2 is controlled by digital pin 6, plot #3 by pin 7, and plot #4 by pin 8. Those pins are defined at the start of the program digitalWrite(i+4, LOW); // Now write a message to the screen that this plot is being irrigated Serial.print("Plot #"); Serial.print(i); Serial.print(" irrigation started. "); // Add 1 to the counter for that plot Counter[i] = Counter[i]+1; // Wait for the specified irrigation time delay(IrrigTime*1000); // And write a message to the screen that the irrigation is finished Serial.println("Irrigation finished."); // Now set the appropriate digital pin HIGH, which will open the NO and COM connection at the relay and closes the valve digitalWrite(i+4, HIGH); } // And if irrigation is not needed else { // The program simply pauses for the specified irrigation time. This is done to assure that the program takes the same amount of time to run regardless of which plots get irrigated delay(IrrigTime*1000); // And send a message to the screen that the plot does not need irrigation Serial.print("Plot #"); Serial.print(i); Serial.println(" does not need irrigation."); } } // Add some extra lines to the serial monitor screen Serial.println(); Serial.println("***********************************************************************************"); Serial.println(); // THE FOLLOWING SECTION IS FOR SAVING AND COLLECTING DATA ON THE SD CARD // Open the data file on the SD card. The datafile is called 'log.txt' File dataFile = SD.open("log.txt", FILE_WRITE); // If the file is available, start with writing the current date and time to the output file if (dataFile) { dataFile.print(now.month(), DEC); dataFile.print('/'); dataFile.print(now.day(), DEC); dataFile.print('/'); dataFile.print(now.year(), DEC); dataFile.print(' '); dataFile.print(now.hour(), DEC); dataFile.print(':'); dataFile.print(now.minute(), DEC); dataFile.print(':'); dataFile.print(now.second(), DEC); // Now write a comma. This will result in a comma-delimited file, which is easily imported into spreadsheets dataFile.print(", "); // Next, write the substrate VWCs to the output file (4 values) for (i = 1; i < 5; i++) { dataFile.print(VWC[i]); // Now write a comma dataFile.print(", "); } // Write the number of irrigations to the output file (4 values) for (i = 1; i < 5; i++) { dataFile.print(Counter[i]); // Now write a comma dataFile.print(", "); } // Go to the next line in the data file, so that all data from each time get written on one single line dataFile.println(); // And close the file dataFile.close(); } // The next delay is to complete the total time of 30 min (1,800 s or 1,800,000 ms) that the program runs. To get this value, multiply the RunTime and IrrigTime by 1000 to convert from miliseconds to seconds, multiply the IrrigTime by 4 because of the four irrigations. Then substract the duration of four irrigation from the RunTime. We also subtract 120 ms, because our testing has indicated that the program takes about 120 ms to run // When this delay has passed, the program will run again delay(RunTime*1000-(IrrigTime*1000*4)-120); }