KalyanChakravarthy.net

Thoughts, stories and ideas.

Automated Plant Watering System - Part 3 - the software

This is a follow up to Part 2 and Part 1 of my Mark2 design and build of Plant watering system.

Learning

  1. The 3.7v LiPo was unable to deliver the required current of about 350 mA for running 2 motors simultaneously. This lead to the batteries dying out prematurely while being perfectly capable of squeezing out juice for just one motor.
  2. Different plants have different water consumption characteristics. For example with the current setup my Basil plant was thriving while the Coriander plant withered away due to over-watering
  3. The use of a push button without a pull down resistor makes it unusable and therefore less useful.

Fortunately the first two learnings can be incorporated by some software changes. Following are the goals I set out to accomplish in the new firmware and the code to achieve it.

Goals

  1. Drive motors independently for different "ON" and interval duration.
  2. Drive motors sequentially, for overlapping run-times/run-intervals. The assumption is its ok if a run is delayed by few seconds.
  3. Support for multiple motors to make the code re-usable for Mark3 prototype.

Solution

  1. Motors are configured by the use of an array of MotorConf structs.
  2. whosNext() computes which motor is to be activated. It does so by checking if the delta time from last run exceeded the runtime+waittime.
  3. The watchdog timer is set to wake up the loop every 1 second. However the clock is prescaled to 1/16th the speed. The combined effect should reduce further power consumption.
#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <avr/power.h>

#define adc_disable() (ADCSRA &= ~(1<<ADEN)) // disable ADC (before power-off)
#define adc_enable()  (ADCSRA |=  (1<<ADEN)) // re-enable ADC

#define led 4
#define MPIN_1 1
#define MPIN_2 0
#define MPIN_EXT_1 3
#define MPIN_EXT_2 2

#define setHigh(pin) pinMode(pin, OUTPUT); digitalWrite(pin, HIGH);
#define setLow(pin) pinMode(pin, OUTPUT); digitalWrite(pin, LOW);

unsigned int lpCntr=0;
unsigned int isOn=0;

struct MotorConf {
  byte pin;
  byte onInterval;
  unsigned short offInterval;
  unsigned long lastUsed;
}


const unsigned int offInterval_2hours = 2 * 60 * 60; //(60 * 60 * 6) - onInterval; //round it off to 6 hours
const unsigned int offInterval_4hours = 4 * 60 * 60; //(60 * 60 * 6) - onInterval; //round it off to 6 hours
const unsigned int onInterval_2secs = 2; // 2 sleep cycles
const unsigned int repInterval = onInterval_2secs + offInterval_2hours;


struct MotorConf motorState[] = {
  { 1, 2, offInterval_2hours, 0 },
  { 0, 2, offInterval_4hours, 0 },
}


unsigned int currMotor = -1;
unsigned int maxMotors = 2;

void setup() {
  clock_prescale_set(clock_div_16);

  for(int i=0; i<6; i++) {
    pinMode(i, INPUT);
    digitalWrite(i, LOW);
  }

  pinMode(led, OUTPUT);
  for(int i=0; i<2; i++) {
    digitalWrite(led, HIGH);
    delay(50);
    digitalWrite(led, LOW);
    delay(50);
  }
  digitalWrite(led, LOW);

  adc_disable();
  wdt_reset();          //watchdog reset
  wdt_enable(WDTO_1S);  //1s watchdog timer
  WDTCR |= _BV(WDIE);   //Interrupts watchdog enable
  sei();                //enable interrupts
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
}

int whosNext() {
  unsigned long currTime = millis() / 1000;
  for(int i=0; i<maxMotors; i++) {
    struct MotorConf currConf = motorState[i];
    if( (currTime - currConf.lastUsed) > (currConf.onInterval+currConf.offInterval) ) {
      return i;
    }
  }
  return -1;
}

void loop() {
  // Main loop

  int nextMotor = whosNext();

  if( nextMotor >= 0 && nextMotor <= maxMotors ) {
    struct MotorConf currConf = motorState[i];
    setHigh(currConf.pin);
    delay(currConf.onInterval);
    setLow(currConf.pin);
    currConf.lastUsed = millis() / 1000;
  }

  sleep_enable();
  sleep_cpu();

}

ISR (WDT_vect) {
  WDTCR |= _BV(WDIE);
}

Future improvements

Mark 2 provided fair amount of learnings. However it was plagued with limitations. Here are some improvmenets planned in Mark 3 re-design

  • Use ESP8266 along with MCP23017 GPIO Expander.
    • ESP8266 will aid in improving the configurability as well as observability.
    • MCP23017 I2C GPIP Expander will dramatic increase in number of IO ports to 16.
  • Ports for 8x Motors, 8x Soil input sensors
  • NodeMCU compatibility along with support for ESP8266 12-E bare IC. The former helps with programming, the later with power consumption.