# Arduino aquarium controller. Have any of you tried it?



## Sargasso (Apr 21, 2010)

For the past few years I've been thinking about setting up an Arduino system to control an aquarium. I just bought a new tank, so I won't be programming for a bit, but I'm wondering if any BCA members have attempted anything with DIY microcontrollers.

Here's a link: Open Aquarium - Aquaponics and Fish Tank Monitoring for Arduino


----------



## MrMan (Feb 23, 2012)

I've got an arduino controlling my tank right now. Controls 4 power outlets, 3 dosing pumps and an ATO. Pretty fun to work with and easy to learn


----------



## Rockman (May 19, 2013)

Yup... I've got two. One's ardunio based and the other uses a Beaglebone black.

The ardunio's my cheapie. I had an old duemilanove (the one using the ATmega 168), ethernet shield and LCD screen laying around. So I got a BNC shield from practical maker and a chinese pH probe off ebay and slapped it all together. Works moderately well. Noise is an issue; I had to ground the **** out of it and it's still not super accurate. It works well enough though. I've got it feeding data to the sparkfun data service (which is pretty handy... works nicely with the less powerful microcontrollers); and I wrote a little web interface that makes graphs using the google charts API (I don't have hosting; so I can't link to it. But it's pretty useful). The 168 was a bit too lightweight for my program (I ended up swapping in a 328 instead; which could run everything properly); but otherwise it all works.

The beaglebone is the new system. I got a new job and threw some serious coin at it. It's still in development; but the core hardware is in place. It measures temperature, pH, ORP and conductivity at the moment, with plans for water depth, filter flow rate, light sensors, etc. I went with the sensors from atlas scientific; which are nice. Way more precise than the old system. Noise was still a bit of a problem (especially with the conductivity sensor... it messed up the readings from the other sensors, as it puts out an AC signal. Also just having the probe in the water caused a ground loop that messed with accuracy); but I fixed that by adding in power and data line isolation. The beaglebone is an awesome platform. Lots of IO, lots of computing power. I'm still working on the programming; but it's gonna be awesome. I've got a background in environmental chemistry; so I can use the extra horsepower to crunch through some models and sqeeze more data from the sensors (I've come up with a way to get KH estimates using the pH and conductivity sensors, for example). It also makes it easy to do things like data logging, remote login, web interfaces, email alerts, etc. I'm programming it in python; which is easy enough (I'm not a programmer... so easy goes a long way for me).

Neither of them controls anything at the moment. I was never comfortable enough with the data coming from the arduino to have it make decisions for me (the chinese probe has a tendancy to lose calibration), and the beaglebone's not done yet). I've got a few things I want to wire up though whenever I get the time.


----------



## Chiumanfu (Oct 30, 2014)

iAqua is pretty advanced but really impressive
iAqua: My Touch Interface Aquarium Controller (Arduino)


----------



## Sargasso (Apr 21, 2010)

Great set of responses, I need to order some sensors and/or make a trip to Lee`s and get this started. I`d also like to explore programmed LED lighting and possibly weather simulation.


----------



## Chiumanfu (Oct 30, 2014)

Lighting control with arduino is easy. I haven't done any weather effects but I might experiment with them when I get a chance. This controller uses high resolution PWM ramping so the transitions are much smoother than simply using the basic analogWrite() function (1024 steps in lighting intensity vs 256 steps)

I used a 12" heatsink and 4 Cree XM-L neutral white LEDs for my 6gal nano. To drive them, I used the Meanwell LDD-1000H. This driver only has 1 amp max output power which sounds like a waste when you consider the XM-L LEDs can be driven up to 3 amps but the light intensity at 1 amp is already way too high. I have to dim them anyways to keep algae at bay. Also, running XM-L LEDs at 1 amp keeps everything nice and cool. You can touch the LEDs without first degree burns and the heatsink only gets slightly warm to the touch. If you need more power, Meanwell just released a LDD-1500L which can push 1.5A but only 30V so your limited to a string of about 9 LEDs.

Power comes from a 24V 5A power supply from eBy. The 24V output feeds the LDD-1000H driver and a small switching power regulator also from eBy. From there I bump the voltage down to 5 volts and power an Arduino Nano V3.0 clone. The Arduino controls the PWM dimming of the light according to the time set in the real time clock module.

Here is the Arduino code. The menu system is pretty simple. Button 1 sets the maximum light level. Button 2 sets the on time. Button 3 sets the off time. Button 4 sets the fade duration. When you are in one of the settings, button one is "up", button 2 is "down", and button 4 is "set".

```
// Aquarium Light Controller by Chiu Fang

// Include Libraries
#include <Wire.h>              // For DS1307 Real Time Clock
#include <DS1307RTC.h>         // For DS1307 Real Time Clock
#include <Time.h>              // For Time functions
#include <LiquidCrystalFast.h> // For 16x1 LCD Display
#include <Keypad.h>            // For Keypad
#include <EEPROMex.h>          // For EEPROM
#include <TimerOne.h>          // For 16-bit PWM

// Set constants
const byte pwmPin = 9;         // PWM light control pin
                               // Pin A4 → I2C SDA, Pin A5 → I2C SCL
LiquidCrystalFast lcd (8, 7, 6, 5, 4, 3, 2); // rs,rw,en1,d4,d5,d6,d7 Init LCD
const byte ROWS = 1; // Keypad one rows
const byte COLS = 4; // Keypad four columns
char keys[ROWS][COLS] = {
  {'1','2','3','4'}
};
byte rowPins[ROWS] = {12}; // Row pinouts of the keypad
byte colPins[COLS] = {14, 15, 16, 17}; // Column pinouts of the keypad (A0, A1, A2, A3)
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS ); // Initialize keypad

// Set Parameter Values
int maxPWM;              // Brightest light level
byte timeOnStart;         // Time that the lights start to ramp on
byte timeOffStart;        // Time that the lights start to ramp off
byte fadeTime;            // Time that the fade takes


// Declare Variables
int valuePWM = 0;
unsigned long pwmDelay = 0;
unsigned long timer = 0;
boolean onFlag = false;
boolean offFlag = false;
boolean onFinishFlag = false;
boolean offFinishFlag = false;
char key;

void setup () {
  maxPWM = EEPROM.readInt(0);
  timeOnStart = EEPROM.readByte(2);
  timeOffStart = EEPROM.readByte(3);
  fadeTime = EEPROM.readByte(4);
  
//  Serial.begin(115200);       // For debug
  lcd.begin(8, 2); //Config LCD
  Timer1.initialize(2000); //500Hz PWM
  Timer1.pwm(pwmPin, valuePWM);
}

void loop () {
  
/***** Real Time Clock *****/

  lcd.setCursor(0, 0);
  tmElements_t tm;           // Calls RTC lib
  if (RTC.read(tm)) {        // Read DS1307
    if (tm.Hour >= 0 && tm.Hour < 10) {
      lcd.print('0');
    }
    lcd.print(tm.Hour);
    lcd.print(':');
    if (tm.Minute >= 0 && tm.Minute < 10) {
      lcd.print('0');
    }
    lcd.print(tm.Minute);
    lcd.print(':');
    if (tm.Second >= 0 && tm.Second < 10) {
      lcd.print('0');
    }
    lcd.print(tm.Second);
    lcd.setCursor(0, 1);
    lcd.print(" ->     ");
    lcd.setCursor(4, 1);
    lcd.print(valuePWM);
  } else {
    if (RTC.chipPresent()) {
      lcd.print(F("RTC Stop"));
    } else {
      lcd.print(F("RTC Err "));
    }
  }
  
//  delay(10);

/***** Lights ON if powered on after timeOnStart *****/

  if (tm.Hour > timeOnStart && tm.Hour < timeOffStart && onFinishFlag == false) {
    valuePWM = maxPWM;
    Timer1.setPwmDuty(pwmPin, valuePWM);
    onFlag = false;
    onFinishFlag = true;
  }


/***** Lights ON *****/
  
  if (tm.Hour == timeOnStart && onFlag == false && onFinishFlag == false) { //tm.Hour
    pwmDelay = (fadeTime * 60000) / maxPWM;    // calculate the correct delay for the ramp time
    onFlag = true;
    onFinishFlag = false;
    valuePWM ++;
    Timer1.setPwmDuty(pwmPin, valuePWM);
    timer = millis();
  }

  if (valuePWM == maxPWM && onFinishFlag == false) {
    onFlag = false;
    onFinishFlag = true;
    offFinishFlag = false;
  }

  if (onFlag == true) {
    if ((millis() - timer) >= pwmDelay){
      valuePWM ++;
      Timer1.setPwmDuty(pwmPin, valuePWM);
      timer = millis();
    }
  }


/***** Lights OFF *****/

  if (tm.Hour == timeOffStart && offFlag == false && offFinishFlag == false && onFinishFlag == true) {
    pwmDelay = (fadeTime * 60000) / maxPWM;    // calculate the correct delay for the ramp time
    offFlag = true;
    offFinishFlag = false;
    valuePWM --;
    Timer1.setPwmDuty(pwmPin, valuePWM);
    timer = millis();
  }

  if (valuePWM == 0 && offFinishFlag == false){
    offFlag = false;
    offFinishFlag = true;
    onFinishFlag = false;
  }
  
  if (offFlag == true) {
    if ((millis() - timer) >= pwmDelay){
      valuePWM --;
      Timer1.setPwmDuty(pwmPin, valuePWM);
      timer = millis();
    }
  }


/***** Menu System *****/

  key = keypad.getKey();
  if(key) {
    switch(key) {
    
    case '1':
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print(F("Max PWM "));
      lcd.setCursor(0, 1);
      lcd.print(F("=       "));
      lcd.setCursor(2, 1);
      lcd.print(maxPWM);
      do {
        key = keypad.waitForKey();
        if(key == '1' && maxPWM < 1023) {
          maxPWM++;
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print(F("Max PWM "));
          lcd.setCursor(0, 1);
          lcd.print(F("=       "));
          lcd.setCursor(2, 1);
          lcd.print(maxPWM);
        } else if(key == '1' && maxPWM >= 1023) {
          maxPWM = 0;
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print(F("Max PWM "));
          lcd.setCursor(0, 1);
          lcd.print(F("=       "));
          lcd.setCursor(2, 1);
          lcd.print(maxPWM);
        } else if(key == '2' && maxPWM > 0) {
          maxPWM--;
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print(F("Max PWM "));
          lcd.setCursor(0, 1);
          lcd.print(F("=       "));
          lcd.setCursor(2, 1);
          lcd.print(maxPWM);
        } else if(key == '2' && maxPWM == 0) {
          maxPWM = 1023;
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print(F("Max PWM "));
          lcd.setCursor(0, 1);
          lcd.print(F("=       "));
          lcd.setCursor(2, 1);
          lcd.print(maxPWM);
        }
      } while(key != '4');
      EEPROM.updateInt(0,maxPWM);
      valuePWM = maxPWM;
      Timer1.setPwmDuty(pwmPin, valuePWM);
      break;

    case '2':
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print(F("On Time "));
      lcd.setCursor(0, 1);
      lcd.print(F("=       "));
      lcd.setCursor(2, 1);
      lcd.print(timeOnStart);
      do {
        key = keypad.waitForKey();
        if(key == '1' && timeOnStart < 23) {
          timeOnStart++;
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print(F("On Time "));
          lcd.setCursor(0, 1);
          lcd.print(F("=       "));
          lcd.setCursor(2, 1);
          lcd.print(timeOnStart);
        } else if(key == '1' && timeOnStart == 23) {
          timeOnStart = 0;
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print(F("On Time "));
          lcd.setCursor(0, 1);
          lcd.print(F("=       "));
          lcd.setCursor(2, 1);
          lcd.print(timeOnStart);
        } else if(key == '2' && timeOnStart > 0) {
          timeOnStart--;
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print(F("On Time "));
          lcd.setCursor(0, 1);
          lcd.print(F("=       "));
          lcd.setCursor(2, 1);
          lcd.print(timeOnStart);
        } else if(key == '2' && timeOnStart == 0) {
          timeOnStart = 23;
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print(F("On Time "));
          lcd.setCursor(0, 1);
          lcd.print(F("=       "));
          lcd.setCursor(2, 1);
          lcd.print(timeOnStart);
        }
      } while(key != '4');
      EEPROM.updateByte(2,timeOnStart);
      break;

    case '3':
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print(F("Off Time"));
      lcd.setCursor(0, 1);
      lcd.print(F(" =      "));
      lcd.setCursor(3, 1);
      lcd.print(timeOffStart);
      do {
        key = keypad.waitForKey();
        if(key == '1' && timeOffStart < 23) {
          timeOffStart++;
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print(F("Off Time"));
          lcd.setCursor(0, 1);
          lcd.print(F(" =      "));
          lcd.setCursor(3, 1);
          lcd.print(timeOffStart);
        }else if(key == '1' && timeOffStart == 23) {
          timeOffStart = 0;
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print(F("Off Time"));
          lcd.setCursor(0, 1);
          lcd.print(F(" =      "));
          lcd.setCursor(3, 1);
          lcd.print(timeOffStart);
        } else if(key == '2' && timeOffStart > 0) {
          timeOffStart--;
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print(F("Off Time"));
          lcd.setCursor(0, 1);
          lcd.print(F(" =      "));
          lcd.setCursor(3, 1);
          lcd.print(timeOffStart);
        } else if(key == '2' && timeOffStart == 0) {
          timeOffStart = 23;
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print(F("Off Time"));
          lcd.setCursor(0, 1);
          lcd.print(F(" =      "));
          lcd.setCursor(3, 1);
          lcd.print(timeOffStart);
        }
      } while(key != '4');
      EEPROM.updateByte(3,timeOffStart);
      break;
    
    case '4':
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print(F("Fade Tim"));
      lcd.setCursor(0, 1);
      lcd.print(F("e =     "));
      lcd.setCursor(4, 1);
      lcd.print(fadeTime);
      do {
        key = keypad.waitForKey();
        if(key == '1') {
          fadeTime++;
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print(F("Fade Tim"));
          lcd.setCursor(0, 1);
          lcd.print(F("e =     "));
          lcd.setCursor(4, 1);
          lcd.print(fadeTime);
        } else if(key == '2') {
          fadeTime--;
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print(F("Fade Tim"));
          lcd.setCursor(0, 1);
          lcd.print(F("e =     "));
          lcd.setCursor(4, 1);
          lcd.print(fadeTime);
        }
      } while(key != '4');
      EEPROM.updateByte(4,fadeTime);
      break;
    }
  }
}
```
Here is the Fritzing diagram showing how everything is wired. The LCD I used is actually a 16x1 and these are not addressed like most other LCDs. The first 8 characters are adddressed as the first line and the last 8 characters are line 2. Keep this in mind if you are customizing the code. Everything else is pretty straight forward.


----------

