M.V.P.
Published

ATtiny85 Plant Watering Autonomous System

Your plant will have the needed water for months. Displayed: humidity and time since last pumped water! Cheap and only a few components!

BeginnerFull instructions provided12 hours12,700
ATtiny85 Plant Watering Autonomous System

Things used in this project

Hardware components

Attiny85 module
×1
SparkFun Soil Moisture Sensor (with Screw Terminals)
SparkFun Soil Moisture Sensor (with Screw Terminals)
×1
Pololu Mini MOSFET Slide Switch with Reverse Voltage Protection, SV
×1
8x2 monochrome LCD
×1
12V 280L/H Water Pump
×1
LCD 1602 Adapter I2C
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Schematics

Fritzing schematics

Code

Arduino code for Attiny-plant-care

Arduino
Just upload it in Attiny85 and run it.
/* Sketch for ATtiny85. Based on the Digispark (Use Digispark Default 16.5 MHz), no port select.
 *  Compile, Upload and then coonect ATtiny85 to USB.
 * {ATtiny85 alone pins: 1=PB5, 2=PB3,ADC3, 3=PB4,ADC2, 4=GND, 5=PB0,MOSI,SDA, 6=PB1,MISO, 7=PB2,SCK,SCL, 8=VCC}
 * ATtiny Pin 5 = PB0 (P0 on ATTiny board) = SDA 
 * ATtiny Pin 7 = PB2 (P2 on ATTiny board) = SCK=SCL 
 * ATtiny PB1 = to SWITCH power of a POLOLU MOSFET: power the Pump (6-12V)
 * ATtiny PB3 = Humidity sensor output= analog read
 * ATtiny PB4 = Power for the sensor (pin: 20mA = sufficient) 
*   Uses 26 mA for CPU, Sensor + Mosfet on + Display
 *  Uses 20 mA for CPU + Display (between readings)
 *  Uses 8 mA in deep sleep (Display on only)
 * Between reads: deep sleep. Protection against over-watering by humidity sensor. 
 */


#include <TinyWireM.h>                  // I2C Master lib for ATTinys which use USI
#include <LiquidCrystal_attiny.h>       // for LCD w/ GPIO MODIFIED for the ATtiny85
#define GPIO_ADDR 0x27                  // the address i2c for this 8x2 LCD
LiquidCrystal_I2C lcd(GPIO_ADDR, 8, 2); // set the I2C address & 8/16 chars / 2 lines
// Utility sleep macros
#include <avr/sleep.h>
#include <avr/wdt.h>                    //Needed to enable/disable watch dog timer
#define adc_disable() (ADCSRA &= ~(1<<ADEN)) // disable ADC (before power-off)
#define adc_enable()  (ADCSRA |=  (1<<ADEN)) // re-enable ADC

#define LED_BUILTIN PB5           // Change PB5 to PB1 only for testing delays
#define powsen PB4                // PB4 provides 4.1V to power the humidity sensor
#define sensor PB3                // Sensor data pin for analog read
#define pump PB1                  // Pin for LED & MOSFET feeding the Pump


// Reading humidity once in 30min...1hour is sufficient:
int D, H, nr=100;                 // Attiny85 will sleep nr*9 (sec.) (E.g. 200=>30min; 400=>1h))
unsigned long psec, corr=nr*300; // time counter since start or watering

//This runs each time the watch dog wakes us up from sleep
ISR(WDT_vect) {
  //Don't do anything. This is just here for wake up.
}

void setup() {
 lcd.init(); lcd.backlight();  lcd.clear();
 pinMode(powsen,OUTPUT);      // Power to the sensor by powsen PIN
 pinMode(sensor, INPUT);      // Sensor read value from sensor PIN
 pinMode(pump, OUTPUT);       // Pump control PIN
 pinMode(LED_BUILTIN, OUTPUT);
 
 set_sleep_mode(SLEEP_MODE_PWR_DOWN);  // sleep mode is set here
 sleep_enable();                   // enables the sleep bit in the mcucr register, so sleep is possible
 psec=0;                              // Starting moment
}

void loop() {
  adc_enable();
  digitalWrite(LED_BUILTIN, HIGH);      // turn the LED on (HIGH is the voltage level)     
  digitalWrite(powsen,HIGH);           // Power on the sensor 
  int humidity=analogRead(sensor);     // Read sensor data
  delay(200);                          // A short time, just to read the sensor!
  digitalWrite(powsen,LOW);            // Power off the humidity sensor
  digitalWrite(LED_BUILTIN, LOW);       // turn the LED off by making the voltage LOW
  // Convert analog values from sensor to humidity. Tested: free and short-circuit. 
  humidity = constrain(humidity, 85, 660);   // accept values between these limits for 4.8V on sensor 
  humidity = map(humidity, 85, 660, 0, 100); // and map them between 0 and 100%

  // The pump is started if humidity drops below a level determined for each plant!
  // Then, the pump cannot restart before 'pause', waiting for water to diffuse in the pot.
  // Set below the DRY Limit for Your Plant (E.G.: 95):
  if (humidity<=95) {                   
    digitalWrite(pump,HIGH);        // Power the pump through a Pololu-LV-MOSFET
    delay(15000);                   // Time [ms] to pump the tested REQUIRED volume of WATER to the plant!!
    digitalWrite(pump,LOW);         // Power down the pump through the MOSFET
    delay(1000);
    psec=0;                        // Reset timer of water pumping. 
    // Test that after [nr*9] seconds (15min in this case), water was absorbed and sensor is above threshold.
    // Otherwise, the pump will start again after. Warning: too much water can be bad for your plant!   
  }

  // Show the results on the screen
  lcd.setCursor(0, 0);  lcd.print("Mimosa:");
  if (humidity>95)
    if (humidity>97)
       lcd.print(")");
     else
       lcd.print("|");
  else
    lcd.print("(");
  
  lcd.setCursor(0, 1);  
  lcd.print(humidity); lcd.print("%");      // Write on LCD the humidity (%) and
  D=psec/86400;                            //  days since last watering/reset ...
  H=(psec%86400)/3600;                     //    and hours
  lcd.print(D); lcd.print("d");
  lcd.print(H); lcd.print("h ");
  
  // Most of the time, go to sleep for 'nr' multiples of 8 seconds + opp. time         
  adc_disable(); // ADC uses ~320uA
  for (int i=0; i<nr; i++){
    setup_watchdog(9); //Setup watchdog to go off after 9->8s sec + opp. time
    sleep_mode(); //Go to sleep! Wake up n sec later and work
  }
  delay(corr);                    // Timer correction since sleep is less than nr*9 sec.
  psec+=nr*9;                      // Update timer after deep sleep for Half an hour
}

// ===================================================================
void setup_watchdog(int timerPrescaler) {
    //Sets the watchdog timer to wake up, but not reset
    //0=16ms, 1=32ms, 2=64ms, 3=128ms, 4=250ms, 5=500ms
    //6=1sec, 7=2sec, 8=4sec, 9=8sec
    //From: http://interface.khm.de/index.php/lab/experiments/sleep_watchdog_battery/
  if (timerPrescaler > 9 ) timerPrescaler = 9; //Limit incoming amount to legal settings
  byte bb = timerPrescaler & 7; 
  if (timerPrescaler > 7) bb |= (1<<5); //Set the special 5th bit if necessary
  //This order of commands is important and cannot be combined
  MCUSR &= ~(1<<WDRF);            //Clear the watchdog reset
  WDTCR |= (1<<WDCE) | (1<<WDE);  //Set WD_change enable, set WD enable
  WDTCR = bb;                     //Set new watchdog timeout value
  WDTCR |= _BV(WDIE);  //Set the interrupt enable, will keep unit from resetting after each int
}

New version

Arduino
The improved Arduino code. Just compile for Digispark Default 16.5 MHz
/* Sketch for ATtiny85. Based on the Digispark (Use Digispark Default 16.5 MHz), no port select.
 *  Compile, Upload and then coonect ATtiny85 to USB.
 * {ATtiny85 alone pins: 1=PB5, 2=PB3,ADC3, 3=PB4,ADC2, 4=GND, 5=PB0,MOSI,SDA, 6=PB1,MISO, 7=PB2,SCK,SCL, 8=VCC}
 * ATtiny Pin 5 = PB0 (P0 on ATTiny board) = SDA 
 * ATtiny Pin 7 = PB2 (P2 on ATTiny board) = SCK=SCL 
 * ATtiny PB1 = to SWITCH power of a POLOLU MOSFET: power the Pump (6-12V)
 * ATtiny PB3 = Humidity sensor output= analog read
 * ATtiny PB4 = Power for the sensor (20mA is sufficient) 
 * Uses 26 mA for CPU, Sensor + Mosfet
 * Uses 20 mA for CPU only (IN delay)
 * Uses 8 mA in deep sleep
 * Between reads, deep sleep. Protection against over-watering. 
 */


#include <TinyWireM.h>                  // I2C Master lib for ATTinys which use USI
#include <LiquidCrystal_attiny.h>       // for LCD w/ GPIO MODIFIED for the ATtiny85
#define GPIO_ADDR 0x27                  // the address i2c for this 8x2 LCD
LiquidCrystal_I2C lcd(GPIO_ADDR, 8, 2); // set the I2C address & 8/16 chars / 2 lines
// Utility sleep macros
#include <avr/sleep.h>
#include <avr/wdt.h>                    //Needed to enable/disable watch dog timer
#define adc_disable() (ADCSRA &= ~(1<<ADEN)) // disable ADC (before power-off)
#define adc_enable()  (ADCSRA |=  (1<<ADEN)) // re-enable ADC

#define LED_BUILTIN PB5           // Change PB5 to PB1 only for testing delays
#define powsen PB4                // PB4 provides 4.1V to power the humidity sensor
#define sensor PB3                // Sensor data pin for analog read
#define pump PB1                  // Pin for LED & MOSFET feeding the Pump


// Reading humidity once in 30min...1hour is sufficient:
int D, H, nr=100;                 // Attiny85 will sleep nr*9 (sec.) (E.g. 200=>30min; 400=>1h))
unsigned long psec, corr=nr*300; // time counter since start or watering

//This runs each time the watch dog wakes us up from sleep
ISR(WDT_vect) {
  //Don't do anything. This is just here for wake up.
}

void setup() {
 lcd.init(); lcd.backlight();  lcd.clear();
 pinMode(powsen,OUTPUT);      // Power to the sensor by powsen PIN
 pinMode(sensor, INPUT);      // Sensor read value from sensor PIN
 pinMode(pump, OUTPUT);       // Pump control PIN
 pinMode(LED_BUILTIN, OUTPUT);
 
 set_sleep_mode(SLEEP_MODE_PWR_DOWN);  // sleep mode is set here
 sleep_enable();                       // enables the sleep bit in the mcucr register, so sleep is possible
 psec=0;                              // Starting moment
}

void loop() {
  adc_enable();
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)     
  digitalWrite(powsen,HIGH);           // Power on the sensor 
  int humidity=analogRead(sensor);     // Read sensor data
  delay(200);                          // A short time, just to read the sensor!
  digitalWrite(powsen,LOW);            // Power off the humidity sensor
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  // Convert analog values fro sensor to humidity (%. Tested: free and short-circuit. 
  humidity = constrain(humidity, 85, 660);   // accept values between these limits for 4.8V on sensor 
  humidity = map(humidity, 85, 660, 0, 100); // and map them between 0 and 100%

  // Show the results on the screen
  lcd.setCursor(0, 0);  lcd.print("Mimosa:");
  if (humidity>93)
    if (humidity>96)
       lcd.print(")");
     else
       lcd.print("|");
  else
    lcd.print("(");
  
  lcd.setCursor(0, 1);  
  lcd.print(humidity); lcd.print("%");      // Write on LCD the humidity (%) and
  D=psec/86400;                            //  days since last watering/reset ...
  H=(psec%86400)/3600;                     //    and hours
  lcd.print(D); lcd.print("d");
  lcd.print(H); lcd.print("h ");
  
  // The pump is started if humidity drops below a level determined for each plant!
  // Then, the pump cannot restart before 'pause', waiting for water to diffuse in the pot.
  // Set below the DRY Limit for Your Plant (E.G.: 80):
  if (humidity<=93) {                   
    digitalWrite(pump,HIGH);        // Power the pump through a Pololu-LV-MOSFET
    delay(15000);                   // Time [ms] to pump the tested REQUIRED volume of WATER to the plant!!
    digitalWrite(pump,LOW);         // Power down the pump through the MOSFET
    delay(1000);
    psec=0;                        // Reset timer of water pumping. 
    // Test that after [nr*9] seconds (30min in this case), water was absorbed and sensor is above 80%
    // Otherwise, the pump will start again after 30 min. Too much water can be bad for the plant!   
  }
  
  // Most of the time, go to sleep for 'nr' multiples of 8 seconds + opp. time         
  adc_disable(); // ADC uses ~320uA
  for (int i=0; i<nr; i++){
    setup_watchdog(9); //Setup watchdog to go off after 9->8s sec + opp. time
    sleep_mode(); //Go to sleep! Wake up n sec later and work
  }
  delay(corr);                    // Timer correction since sleep is less than nr*9 sec.
  psec+=nr*9;                      // Update timer after deep sleep for Half an hour
}

// =======================================================================
void setup_watchdog(int timerPrescaler) {
    //Sets the watchdog timer to wake us up, but not reset
    //0=16ms, 1=32ms, 2=64ms, 3=128ms, 4=250ms, 5=500ms
    //6=1sec, 7=2sec, 8=4sec, 9=8sec
    //From: http://interface.khm.de/index.php/lab/experiments/sleep_watchdog_battery/
  if (timerPrescaler > 9 ) timerPrescaler = 9; //Limit incoming amount to legal settings

  byte bb = timerPrescaler & 7; 
  if (timerPrescaler > 7) bb |= (1<<5); //Set the special 5th bit if necessary

  //This order of commands is important and cannot be combined
  MCUSR &= ~(1<<WDRF);            //Clear the watchdog reset
  WDTCR |= (1<<WDCE) | (1<<WDE);  //Set WD_change enable, set WD enable
  WDTCR = bb;                     //Set new watchdog timeout value
  WDTCR |= _BV(WDIE);             //Set the interrupt enable, this will keep unit from resetting after each int
}

Latest version

Arduino
This version woks now with the Mimosa in the pot.
/* Water pumping device for a plant (Mimosa pudica) in a pot. 
 * Water is pumped as function of soil humidity & time intervals.
 *  Sketch for ATtiny85. Based on the Digispark (Use Digispark Default 16.5 MHz), no port select.
 *  Compile, Upload and then connect ATtiny85 module to USB cable.
 * {ATtiny85 alone pins: 1=PB5, 2=PB3,ADC3, 3=PB4,ADC2, 4=GND, 5=PB0,MOSI,SDA, 6=PB1,MISO, 7=PB2,SCK,SCL, 8=VCC}
 * ATtiny Pin 5 = PB0 (P0 on ATTiny board) = SDA 
 * ATtiny Pin 7 = PB2 (P2 on ATTiny board) = SCK=SCL 
 * ATtiny PB1 = to SWITCH power of a POLOLU MOSFET: power the Pump (6-12V)
 * ATtiny PB3 = Humidity sensor output= analog read
 * ATtiny PB4 = Power for the sensor (pin: 20mA = sufficient) 
 * Uses 26 mA for CPU, Sensor + Mosfet 
 * Uses 20 mA for CPU + Display (between readings)
 * Uses 8 mA in deep sleep (Only LCD display ON)
 * Between reads: deep sleep. Protection against over-watering by humidity sensor. 
 */


#include <TinyWireM.h>                  // I2C Master lib for ATTinys which use USI
#include <LiquidCrystal_attiny.h>       // for LCD w/ GPIO MODIFIED for the ATtiny85
#define GPIO_ADDR 0x27                  // the address i2c for this 8x2 LCD
LiquidCrystal_I2C lcd(GPIO_ADDR, 8, 2); // set the I2C address & 8/16 chars / 2 lines
// Utility sleep macros
#include <avr/sleep.h>
#include <avr/wdt.h>                    //Needed to enable/disable watch dog timer
#define adc_disable() (ADCSRA &= ~(1<<ADEN)) // disable ADC (before power-off)
#define adc_enable()  (ADCSRA |=  (1<<ADEN)) // re-enable ADC

#define LED_BUILTIN PB5           // Change PB5 to PB1 only for testing delays
#define powsen PB4                // PB4 provides 4.1V to power the humidity sensor
#define sensor PB3                // Sensor data pin for analog read
#define pump PB1                  // Pin for LED & MOSFET feeding the Pump


// Reading humidity once every 15 min...1 hour is sufficient:
// Set here the humidity threshhold (lim):
int D, H, Q, lim=75, npump=0, nr=100;// Attiny85 will sleep nr*9 (sec.) (E.g. 100=>15 min; 200=>30min; 400=>1h))
int humidity; 
unsigned long psec, corr=nr*300;  // time counter since start or watering

//This runs each time the watch dog wakes us up from sleep
ISR(WDT_vect) {
  //Don't do anything. This is just here for wake up.
}

void setup() {
 lcd.init(); lcd.backlight();  lcd.clear();
 pinMode(powsen,OUTPUT);      // Power to the sensor by powsen PIN
 pinMode(sensor, INPUT);      // Sensor read value from sensor PIN
 pinMode(pump, OUTPUT);       // Pump control PIN
 pinMode(LED_BUILTIN, OUTPUT);
 
 set_sleep_mode(SLEEP_MODE_PWR_DOWN);  // sleep mode is set here
 sleep_enable();                       // enables the sleep bit in the mcucr register, so sleep is possible
 psec=0;                              // Starting moment
}

void loop() {
  adc_enable();
  digitalWrite(LED_BUILTIN, HIGH);      // turn the LED on (HIGH is the voltage level)     
  digitalWrite(powsen,HIGH);           // Power on the sensor
  delay(200);                          // Sensor stabilization 
  humidity=analogRead(sensor);     // Read sensor data
  delay(200);                          // A short time, just to read the sensor!
  digitalWrite(powsen,LOW);            // Power off the humidity sensor
  
  // Convert analog values from sensor to humidity. Tested: free and short-circuit. 
  humidity = constrain(humidity, 85, 660);   // accept values between these limits for 4.8V on sensor 
  humidity = map(humidity, 85, 660, 0, 100); // and map them between 0 and 100%


  // Show the results on the screen
  lcd.setCursor(0, 0);  
  lcd.print("Min="); lcd.print(lim); lcd.print("%");
  lcd.setCursor(0, 1);  
  lcd.print("NOW=");lcd.print(humidity); lcd.print("%");      // Write on LCD the humidity (%)
  delay(3000);


  if (humidity==0){
    lcd.setCursor(0, 0);  
    lcd.print("S-ERROR!");
    delay(10000);
  }
  
  
  //===============================================================================
  if (H>=8)
    npump=0;                      // After 8 Hours: reset the number of water pumpings: new pump possible.. 
                                  //  if humidity <= lim
  //===============================================================================
  // The pump is started if humidity drops below a lim level determined for each plant, 
  //  and in any case 2 days after the last watering (if the sensor is in wet mud and the rest is dry!)
  // The pump is activated for max 2 times at psec interval, waiting for water to diffuse in the pot.
  // Set below the DRY Limit for Your Plant (E.G.: 80 to 95):
  if ((humidity <= lim || D >= 2) && npump <=2) {                   
    digitalWrite(pump,HIGH);        // Power the pump through a Pololu-LV-MOSFET
    delay(14000);                   // Pumping time 10 -14 [s] to pump the tested REQUIRED volume of WATER to the plant!!
    digitalWrite(pump,LOW);         // Power down the pump through the MOSFET
    psec=0;                         // Reset timer of water pumping.
    npump++;                        // Count the pumping. Max 3/ 24H to avoid flooding and have uniform humidity. 
    // Test that after [nr*9] seconds (15min in this case), water was absorbed and sensor is above threshold.
    // Otherwise, the pump will restart too soon. Warning: too much water can be bad for your plant! 
    // To avoid flooding, no more than 2 (npump) pumpings in 12 Hours are allowed!

    // After a few seconds, read again the humidity:
    delay(3000);
    digitalWrite(powsen,HIGH);           // Power on the sensor
    delay(200);                          // Sensor stabilization 
    humidity=analogRead(sensor);         // Read sensor data
    delay(200);                          // A short time, just to read the sensor!
    digitalWrite(powsen,LOW);            // Power off the humidity sensor
    // Convert analog values from sensor to humidity. Tested: free and short-circuit. 
    humidity = constrain(humidity, 85, 660);   // accept values between these limits for 4.8V on sensor 
    humidity = map(humidity, 85, 660, 0, 100); // and map them between 0 and 100% 
    lcd.setCursor(0, 1);  
    lcd.print(humidity); lcd.print("%");      // Write on LCD the humidity (%)
  }
  
  lcd.setCursor(0, 0);
  lcd.print("Mimosa>");
  if (humidity>=lim)
    if (humidity>lim+2)
       lcd.print(")");
     else
       lcd.print("|");
  else
    lcd.print("(");
  if (humidity==0){
    lcd.setCursor(0, 0);  
    lcd.print("S-ERROR!");
    delay(10000);
  } 


  lcd.setCursor(0, 1);  
  lcd.print(humidity); lcd.print("%");      // Write on LCD the humidity (%) and mark
  D=psec/86400;                            //  days since last watering/reset ...
  H=(psec%86400)/3600;                     //    and hours
  Q=((psec%86400)%3600)/900;               //    and quarter of hours
  lcd.print(D); lcd.print("d");
  lcd.print(H); lcd.print("h");
  lcd.print(Q*15);lcd.print("m");
  
  digitalWrite(LED_BUILTIN, LOW);       // turn the LED off by making the voltage LOW  
  
  // Most of the time, go to sleep for 'nr' multiples of 8 seconds + opp. time         
  adc_disable();                        // ADC uses ~320uA
  for (int i=0; i<nr; i++){
    setup_watchdog(9);                  //Setup watchdog to go off after 9->8s sec + opp. time
    sleep_mode();                       //Go to sleep! Wake up n sec later and work
  }
  delay(corr);                          // Timer correction since sleep is less than nr*9 sec.
  psec+=nr*9;                           // Update timer after deep sleep for Quarter/Half an hour
}

// =======================================================================
void setup_watchdog(int timerPrescaler) {
    //Sets the watchdog timer to wake up, but not reset
    //0=16ms, 1=32ms, 2=64ms, 3=128ms, 4=250ms, 5=500ms
    //6=1sec, 7=2sec, 8=4sec, 9=8sec
    //From: http://interface.khm.de/index.php/lab/experiments/sleep_watchdog_battery/
  if (timerPrescaler > 9 ) timerPrescaler = 9; //Limit incoming amount to legal settings

  byte bb = timerPrescaler & 7; 
  if (timerPrescaler > 7) bb |= (1<<5); //Set the special 5th bit if necessary

  //This order of commands is important and cannot be combined
  MCUSR &= ~(1<<WDRF);            //Clear the watchdog reset
  WDTCR |= (1<<WDCE) | (1<<WDE);  //Set WD_change enable, set WD enable
  WDTCR = bb;                     //Set new watchdog timeout value
  WDTCR |= _BV(WDIE);             //Set the interrupt enable, this will keep unit from resetting after each int
}

Credits

M.V.P.

M.V.P.

7 projects • 46 followers
Arduino: Amazing, useful and rewarding hobby for some!

Comments