Pages: [1]   Go Down
Author Topic: Chiefstons Planted Tank controller proj  (Read 1121 times)
0 Members and 1 Guest are viewing this topic.
Tempe, AZ
Offline Offline
Newbie
*
Karma: 0
Posts: 9
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hey all,
I have gotten started on my own planted tank controller which will control my newest addition, an ADA 60P tank sitting on a custom stand I have built.  As I am a complete newb programmer, and this project is definitely overambitious, I have decided to chronicle my labors here.  Also, if the good people of the community would like to help clean up my newbie code, I would be grateful  smiley-grin  I have been working on this for about a week in my spare time, so some of the things I wish to accomplish are in place.  Others are waiting on parts to arrive, and time to get to them! So...

My basic goals are:
  - Record temperature of the tank, using 2-wire thermistor from adafruit
  - Have 20x4 LCD for display
  - Automate injection of Micro and Macro nutrients on alternating days
    - This will require a peristaltic pump, hall-effect flow meter, and alarms
    -  The flow meter will keep track of injection amount and also amount left in nutrient bottle
  - A small piezo buzzer to warn when nutrient solution is getting low

As mentioned, I have already gotten started.  I intend to update this topic whenever I manage to get a new state completed.  Today I was able to get my nutrient solution levels stored on the DS1307 SRAM, retrieve it, and then post a graphical representation on the LCD.  Doesn't sound like much, but it was a pretty big pain!  Reading others code really only gets you so far, then its all experimentation and slamming your head against the desk  smiley-mad

Well, heres the code in its current form, the next task to handle is allowing myself to set the time using the pushbuttons, and having it update the RTC.  Hope you all enjoy the posts!



Code:
/*
 The LCD circuit for reference
 * LCD RS pin to digital pin 12
 * LCD Enable pin to digital pin 11
 * LCD D4 pin to digital pin 5
 * LCD D5 pin to digital pin 4
 * LCD D6 pin to digital pin 3
 * LCD D7 pin to digital pin 2
 * LCD R/W pin to ground
 */

// include the library code:
#include <LiquidCrystal.h>
#include <Wire.h>
#include <DS1307RTC.h>
#include <Time.h>
#include <Bounce.h>

// Definitions for the thermistor code, copied from learn.adafruit.com
#define THERMISTORPIN A0        
#define THERMISTORNOMINAL 10000      
#define TEMPERATURENOMINAL 25  
#define NUMSAMPLES 10
#define BCOEFFICIENT 3950
#define SERIESRESISTOR 9940

// Global variables, or a convienent place to put stuff
int samples[NUMSAMPLES];  // Used in thermistor calc
float temp;  // Used in thermistor calc
int enterPin = 7;  // Pin def
int movePin = 8;   // Pin def
int screen = 1;  // This is for state machine
int z = 0;  // whatever variable
boolean yesNo = true;  // used for selection menus
int btl1maxlvl = 2000;  // Will hold Micro full bottle mL
int btl2maxlvl = 2000;  // Will hold Macro full bottle mL
byte updatebottles;  // Used in function to update bottles once a min.

//setup debounce functions
Bounce bounceEnter = Bounce(enterPin, 50);
Bounce bounceMove = Bounce(movePin, 50);

// Create custom characters for bottle levels and underline
byte lowLvl[8] = {
  0x0,0x0,0x0,0x0,0x0,0x0,0x1f
};
byte highLvl[8] = {
  0x0,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f
};
byte midLvl[8] = {
  0x0,0x0,0x0,0x1f,0x1f,0x1f,0x1f
};
byte underline[8] = {
  0x1f,0x0,0x0,0x0,0x0,0x0,0x0
};

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);


/***************************
 ******* Setup **************
 ***************************/
void setup() {
  // Setup input pins
  pinMode(enterPin, INPUT);
  pinMode(movePin, INPUT);
  digitalWrite(enterPin, HIGH);
  digitalWrite(movePin, HIGH);


  // setup custom char
  lcd.createChar(0, lowLvl);
  lcd.createChar(1, highLvl);
  lcd.createChar(2, midLvl);
  lcd.createChar(3, underline);

  // set analogRead to 3.3v
  analogReference(EXTERNAL);
  // set up the LCD's number of columns and rows:
  lcd.begin(20, 4);
  lcd.clear();
  
  // setup RTC
  setSyncProvider(RTC.get);
  Wire.begin();

  // Simulating values stored for mL left in fert bottles
  WriteRam(1, 1134);
  WriteRam(2, 1025);
}


/***************************
 ******* Loop ***************
 ***************************/
void loop() {
  
  // State machine
  switch (screen) {
  case 1:
    mainScreen();
    break;
  case 2:
    resetBottle();
    break;
  case 3:
    whichBottle();
    break;
  case 4:
    setAlarm();
    break;
  }
}


/***************************
 ******* MainScreen *********
 ***************************/
void mainScreen() {
  time_t t = now();
  lcd.setCursor(0, 0);
  lcd.print("Temperature :");
  lcd.print(getTemp()); // Get temp from thermistor
  lcd.print((char)223); // Degree symbol

  // Print Time
  lcd.setCursor(6, 1);
  if (hour(t) < 10) {  // Formatting the hour correctly
    lcd.print("0");
  }
  lcd.print(hour(t));
  lcd.print(":");
  if (minute(t) < 10) {  // Formatting the minutes correctly
    lcd.print("0");
  }
  lcd.print(minute(t));
  lcd.print(":");
  if (second(t) < 10) {  // Formatting the seconds correctly
    lcd.print("0");
  }
  lcd.print(second(t));

  // Print fertilizer levels
  SetLevel(second(t));
  
  // Get enter button press
  if (bounceEnter.update()) {
    if (bounceEnter.read() == LOW) {
      lcd.clear();
      screen = 2;  // Enter takes us to ResetBottle screen
    }
  }
}

/***************************
 ****** resetBottle() *******
 ***************************/
void resetBottle() {
  lcd.setCursor(5,0);
  lcd.print("Reset bottle?");
  lcd.setCursor(2, 2);
  lcd.print("Yes     /     No");

  // Print out underline characters for selection purposes
  if (yesNo == true) {
    lcd.setCursor(16,3);
    lcd.print("   ");
    lcd.setCursor(2, 3);
    for (z=0; z<3; z++) {
      lcd.write(byte(3));
    }
  }
  else {
    lcd.setCursor(2,3);
    lcd.print("   ");
    lcd.setCursor(16,3);
    for (z=0; z<2; z++) {
      lcd.write(byte(3));
    }
  }

  // Check button presses and move on
  if (bounceMove.update()) {
    if (bounceMove.read() == LOW) {
      yesNo = !yesNo;
    }
  }
  if (bounceEnter.update()) {
    if (bounceEnter.read() == LOW) {
      if (yesNo == true) {
        screen = 3;
        lcd.clear();
      }
      else {
        screen = 4;
        lcd.clear();
      }
    }
  }
}


/***************************
 ****** whichBottle() *******
 ***************************/

void whichBottle() {
  lcd.setCursor(2,0);
  lcd.print("Which bottle?");
  lcd.setCursor(2,2);
  lcd.print("Micro");
  lcd.setCursor(12,2);
  lcd.print("Macro");

  // Print out underline characters for selection purposes
  if (yesNo == true) {  // Underline Micro and unselect Macro
    lcd.setCursor(12,3);
    lcd.print("     ");  // Clear previous underline
    lcd.setCursor(2,3);
    for (z=0; z<5; z++) {
      lcd.write(byte(3));  // Writing underline characters
    }
  }  
  else {
    lcd.setCursor(2,3);
    lcd.print("     ");  // Clear previous underline
    lcd.setCursor(12, 3);
    for (z=0; z<5; z++) {
      lcd.write(byte(3));  // Writing underline characters
    }
  }

  //Check button presses
  if (bounceMove.update()) {
    if (bounceMove.read() == LOW) {
      yesNo = !yesNo;  // This will change the underline selection
    }
  }
  if (bounceEnter.update()) {
    if (bounceEnter.read() == LOW) {
      if (yesNo == true) {
        screen = 1;  // Currently placeholding
        lcd.clear();
      }
      else {
        screen = 1;  // Currently placeholding
        lcd.clear();
      }
    }
  }
}
« Last Edit: May 15, 2013, 02:21:01 am by Chiefston » Logged

Tempe, AZ
Offline Offline
Newbie
*
Karma: 0
Posts: 9
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

In the future, I will only post updated states.  But for the moment, here is the rest of the code:
Code:
//*********************
//***  setAlarm()   ***
//*********************
void setAlarm(void) {
  lcd.setCursor(2,0);
  lcd.print("Set Dose Alarm?");
  lcd.setCursor(2,2);
  lcd.print("Yes     /     No");
  if (yesNo == true) {
    lcd.setCursor(16,3);
    lcd.print("   ");  // Remove previous selection underline
    lcd.setCursor(2, 3);
    for (z=0; z<3; z++) {
      lcd.write(byte(3)); // Underline new selection
    }
  }
  else {
    lcd.setCursor(2,3);
    lcd.print("   ");  // Remove previous underline
    lcd.setCursor(16,3);
    for (z=0; z<2; z++) {
      lcd.write(byte(3));  // Underline new selection
    }
  }
  if (bounceMove.update()) {
    if (bounceMove.read() == LOW) {
      yesNo = !yesNo;  // Switch selection
    }
  }
  if (bounceEnter.update()) {
    if (bounceEnter.read() == LOW) {
      if (yesNo == true) {
        screen = 1; // Currently placeholding
        lcd.clear();
      }
      else {
        screen = 1;  // Currently placeholding
        lcd.clear();
      }
    }
  }
}




//*********************
//***  getTemp()    ***
//*********************
float getTemp() {
  //Thermistor Stuff
  uint8_t i;
  float average;

  // take N samples in a row, with a slight delay
  for (i=0; i< NUMSAMPLES; i++) {
    samples[i] = analogRead(THERMISTORPIN);
    delay(10);
  }

  // average all the samples out
  average = 0;
  for (i=0; i< NUMSAMPLES; i++) {
    average += samples[i];
  }
  average /= NUMSAMPLES;

  // convert the value to resistance
  average = 1023 / average - 1;
  average = SERIESRESISTOR / average;

  // convert to degrees
  float steinhart;
  steinhart = average / THERMISTORNOMINAL;     // (R/Ro)
  steinhart = log(steinhart);                  // ln(R/Ro)
  steinhart /= BCOEFFICIENT;                   // 1/B * ln(R/Ro)
  steinhart += 1.0 / (TEMPERATURENOMINAL + 273.15); // + (1/To)
  steinhart = 1.0 / steinhart;                 // Invert
  steinhart -= 273.15;   // convert to C
  steinhart = (steinhart * 1.8) + 32;  // convert to F
  return steinhart;
}


//*********************
//***  WriteRam()   ***
//*********************
void WriteRam(int address, int data) {
  byte datalow, datahigh;  // Used to write a 2-byte int to SRAM
  datalow = lowByte(data);
  datahigh = highByte(data);
  Wire.beginTransmission(0x68);
  Wire.write((address * 2) + 8);  // (address * 2) used for 2-byte storage
  Wire.write(datalow);
  Wire.write(datahigh);
  Wire.endTransmission();
}


//*********************
//***   ReadRam()   ***
//*********************
int ReadRam(int address) {
  byte datalow, datahigh;  // Used to read a 2-byte int from SRAM
  word data;  // Used to merge 2 bytes into one word
  Wire.beginTransmission(0x68);
  Wire.write((address * 2) + 8);
  Wire.endTransmission();
  Wire.requestFrom(0x68, 2);
  datalow = Wire.read();
  datahigh = Wire.read();
  data = word(datahigh, datalow);  // Combines low&high bytes into integer.
  return data;
}


//*********************
//***  SetLevel()   ***
//*********************
void SetLevel(byte t) {
  if (t == updatebottles) {  // Used to only update once a second
    return;
  }
  updatebottles = t;
  int btl1, btl2;
  float percent;
  btl1 = ReadRam(1);
  btl2 = ReadRam(2);

  // Get and set bottle 1 display
  percent = (float) btl1 / (float)btl1maxlvl * 10;  // Get percent full
  percent = percent / 2;  // Used because only 5 characters to display fullness
  int blocks = (int) percent;  // How many full blocks to print
  lcd.setCursor(0,4);
  lcd.print("1:");
  for (int y = 0; y < 5; y++) {
    lcd.write(byte(0));  // Write all low characters as starting point
  }
  lcd.setCursor(2,4);
  for (int y = 0; y < blocks; y++) {
    lcd.write(byte(1));  // This writes the full blocks out
  }
  if ((percent - blocks) > .33 && (percent - blocks) < .66) {
    lcd.write(byte(2));  // Determine if half block used
  }
  if ((percent - blocks) >= .66) {
    lcd.write(byte(1));  // Determine if extra full block used
  }

  // Get and set bottle 2 display
  percent = (float) btl2 / (float)btl2maxlvl * 10;
  percent = percent / 2;
  blocks = (int) percent;
  lcd.setCursor(13,3);
  lcd.print("2:");
  for (int y = 0; y < 5; y++) {
    lcd.write(byte(0));
  }
  lcd.setCursor(15,3);
  for (int y = 0; y < blocks; y++) {
    lcd.write(byte(1));
  }
  if ((percent - blocks) > .33 && (percent - blocks) < .66) {
    lcd.write(byte(2));
  }
  if ((percent - blocks) >= .66) {
    lcd.write(byte(1));
  }
}

Logged

Tempe, AZ
Offline Offline
Newbie
*
Karma: 0
Posts: 9
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

So, I managed to get the clock set functions kinda working.  For whatever reason, it will only update the RTC on the 2nd pass through the function tmSet();
It never updates correctly on the 1st pass through, but on the 2nd pass, it updates to the first setting... if that makes sense.  (i.e. if I set the clock to 12:41:00 Sat, then set the clock again to 14:49:00 Sun, when I recycle the power it defaults back to the 12:41:00 Sat setting...)
I have included the appropriate functions below.  Any help?

Code:
/***************************
 ******* Loop ***************
 ***************************/
void loop() {

  // State machine
  switch (screen) {
  case 1:
    mainScreen();
    break;
  case 2:
    resetBottle();
    break;
  case 3:
    whichBottle();
    break;
  case 4:
    setAlarm();
    break;
  case 5:
    printTime();
  case 6:
    tmSet();
  }
}


//*********************
//***  printTime()  ***
//*********************
void printTime() {
  hr = hour(t), mn = minute(t), sc = 0, dy = weekday(t);
 
  // Print Time
  lcd.setCursor(3, 1);
  if (hour(t) < 10) {  // Formatting the hour correctly
    lcd.print("0");
  }
  lcd.print(hour(t));
  lcd.print(":");
  if (minute(t) < 10) {  // Formatting the minutes correctly
    lcd.print("0");
  }
  lcd.print(minute(t));
  lcd.print(":");
  if (second(t) < 10) {  // Formatting the seconds correctly
    lcd.print("0");
  }
  lcd.print(second(t));
  lcd.setCursor(14,1);
  lcd.print(dayName[dy]);
  screen = 6;
}


/***************************
 ******** tmSet() ********
 ***************************/
void tmSet() {
  switch (digit) {
  case 1:    // Set hours
    lcd.setCursor(3,2);    // Set cursor for underline
    lcd.write(byte(0));    // Print underline twice for 2 hour digits
    lcd.write(byte(0));
    lcd.setCursor(3,1);
    if (hr < 10) {  // Formatting the hour correctly
      lcd.print("0");
    }
    lcd.print(hr);

    // Get button presses
    if (bounceMove.update()) {
      if (bounceMove.read() == LOW) {
        hr++;
        if (hr == 24) hr = 0;  // update hr
      }
    }
    if (bounceEnter.update()) {
      if (bounceEnter.read() == LOW) {
        setTime(hr, minute(t), sc, day(t), month(t), year(t));
        digit = 2;
        lcd.setCursor(3,2);  // Erase underline character for next
        lcd.print("  ");
      }
    }
    break;
  case 2:    // Set minutes
    lcd.setCursor(6,2);    // Set cursor for underline character
    lcd.write(byte(0));    // Underline character twice for "12"
    lcd.write(byte(0));
    lcd.setCursor(6,1);
    if (mn < 10) {  // Formatting the hour correctly
      lcd.print("0");
    }
    lcd.print(mn);

    // Get button presses
    if (bounceMove.update()) {
      if (bounceMove.read() == LOW) {
        mn++;
        if (mn == 60) mn = 0;  // update mn
      }
    }
    if (bounceEnter.update()) {
      if (bounceEnter.read() == LOW) {
        setTime(hr, mn, sc, day(t), month(t), year(t));
        digit = 3;
        lcd.setCursor(6,2);    // Erase underline characters
        lcd.print("  "); 
      }
    }
    break;
  case 3:    // Set days
    lcd.setCursor(14,2);  // Set cursor for underline
    lcd.write(byte(0));    // Underline character
    lcd.write(byte(0));    // Used 3 times to underline "Mon"...
    lcd.write(byte(0));
    lcd.setCursor (14,1);
    lcd.print(dayName[dy]);
    // Get button presses
    if (bounceMove.update()) {
      if (bounceMove.read() == LOW) {
        dy++;
        if (dy == 8) dy = 0;  // update dy
      }
    }
    if (bounceEnter.update()) {
      if (bounceEnter.read() == LOW) {
        setTime(hr, mn, sc, dy, 12, 2013);
        RTC.set(t);
        digit = 1;
        lcd.setCursor(14,2);    // Erase underline characters
        lcd.print("   ");
        screen = 1;
        lcd.clear();
      }
    }
    break;
  }
}
Logged

Offline Offline
Newbie
*
Karma: 1
Posts: 14
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Good luck with your project, I'm planning on a very similar project myself. I'm looking to do a basic tank controller, to control lighting and temperature, and possibly to do some filter monitoring with tracking differential pressure across the supply and  return on my canister filter.

You might be able to simplify your project a little and skip the hall effect sensors, peristaltic pumps have very consistent flow rates since they are positive displacement and will pump the same volume per revolution every time. You might be able to attach a simple cam and microswitch to the pump shaft and count off ticks to calculate flow.

I just registered this morning to do research on my project, I'm a complete newbie when it comes to arduino, but I have years of experience in HVAC automation and control, so I might be able to help you out a little.
Logged

Pages: [1]   Go Up
Jump to: