/*
* Name: tank-control.pde
* Author: User "sink" at plantedtank.net forums
* URL: http://bitbucket.org/akl/tank-control
*
* This is control code for an aquarium lighting system. It is intended to be
* run on an Arduino microcontroller board. It allows independant
* high-resolution control of two PWM outputs (normally connected to LED
* drivers) and complete flexibility with respect to intensity, timing
* schedules, and sunrise/sunset.
*
* This code requires the following libraries: Wire, TimerOne, Time, DS1307RTC
*
* The latest version of this code can always be found at above url.
*/
/*
* Copyright (c) 2011, User "sink" at plantedtank.net forums
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/* NOTICE: 4/2/2014- Addtional support for I2C LCD & Dallas OneWire Temperature readout added by O2Surplus.
The original Libraries used in this version of the code were updated for use with Arduino 1.5-r2.
Please make sure to use the proper version of said libraries. */
#include
#include
#include
#include
#include // added to original code by O2Surplus 4/2/2014
#include // added to original code by O2Surplus 4/2/2014
//LiquidCrystal_I2C lcd(0x27,16,2); // set the LCD address to 0x27 for a 16 chars and 2 line display
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // Set the LCD I2C address
//Temperature chip i/o
int DS18S20_Pin = 2; //DS18S20 Signal pin on digital 2
OneWire ds(DS18S20_Pin); // on digital pin 2
int fanEnable = 8;// assigns cooling fan ON/OFF control to digital pin 8.
/*
* IMPORTANT: These *must* be the pins corresponding to the Timer1 timer on
* the ATmega168/328. These are digital pins 9 and 10 on the Uno/Duemilanove.
*/
const int kChan0Pin = 9; // Channel 0 Pin
const int kChan1Pin = 10; // Channel 1 Pin
// All times are in seconds since midnight (valid 0 - 86399)
const long kTurnOn = 32400; // time dawn begins - 0900hrs
const long kTurnOff = 75600; // time sunset begins - 2100hrs
/*
* Light "state" represents the PWM duty cycle for each channel This normally
* dictates light intensity. It is an array { duty_chan_1, duty_chan_2 }.
* Possible values for duty cycle are 0 - 1023.
*/
const int kDayState[] = { 1023,1023 }; // daytime LED state
const int kNightState[] = { 0, 0 }; // nighttime LED state
// duration (in seconds) of sunrise/sunset fade
const long kFadeDuration = 3600; // 60 minutes
long ctr;
/*
* fader -- Determine output state for a given time to provide smooth fade from
* one state to another.
* Args:
* start_time -- time (in seconds) of start of fade
* start_state -- beginning state
* end_state -- ending state
* out -- array to update with state
*/
void fader(long start_time, const int start_state[], const int end_state[], int out[2]) {
float per_second_delta_0 = (float) (end_state[0]-start_state[0])/kFadeDuration;
float per_second_delta_1 = (float) (end_state[1]-start_state[1])/kFadeDuration;
long elapsed = ctr-start_time;
out[0] = start_state[0] + per_second_delta_0 * elapsed;
out[1] = start_state[1] + per_second_delta_1 * elapsed;
}
// return seconds elapsed since midnight
long seconds_since_midnight() {
time_t t = now();
long hr = hour(t);
long min = minute(t);
long sec = second(t);
long total = hr * 3600 + min * 60 + sec;
return total;
}
// set output state
void set_state(const int state[]) {
if (state[0] >= 0 && state[0] <= 1023) Timer1.setPwmDuty(kChan0Pin, state[0]);
if (state[1] >= 0 && state[1] <= 1023) Timer1.setPwmDuty(kChan1Pin, state[1]);
}
/*
* determine_state -- This is where the actual timing logic resides. We
* examine ctr (seconds since midnight) and then set output state accordingly.
* Variable ctr rolls back to 0 at midnight so stages that cross midnight (ie:
* nighttime) are broken up into two stages.
*/
void determine_state() {
if ( ctr >= 0 && ctr < kTurnOn ) { // night
set_state(kNightState);
lcd.setCursor(0,2);
//lcd.print(" ");
lcd.setCursor(5,2);
lcd.print("NIGHT/MODE"); // LCD coding added by O2Surplus 4/2/2014
digitalWrite(fanEnable,LOW);
lcd.setCursor(6,3);
lcd.print("FANS-OFF");
} else if ( ctr >= kTurnOn && ctr <= (kTurnOn+kFadeDuration) ) { // sunrise
int foo[2];
fader(kTurnOn, kNightState, kDayState, foo);
set_state(foo);
lcd.setCursor(0,2);
//lcd.print(" ");
lcd.setCursor(3,2);
lcd.print("SUNRISE/MODE"); // LCD coding added by O2Surplus 4/2/2014
digitalWrite(fanEnable, HIGH);
lcd.setCursor(7,3);
lcd.print("FAN-ON");
} else if ( ctr > (kTurnOn+kFadeDuration) && ctr < kTurnOff ) { // day
set_state(kDayState);
lcd.setCursor(0,2);
//lcd.print(" ");
lcd.setCursor(5,2);
lcd.print("DAY/MODE"); // LCD coding added by O2Surplus 4/2/2014
digitalWrite(fanEnable, HIGH);
lcd.setCursor(7,3);
lcd.print("FAN-ON");
} else if ( ctr >= kTurnOff && ctr <= (kTurnOff+kFadeDuration) ) { // sunset
int foo[2];
fader(kTurnOff, kDayState, kNightState, foo);
set_state(foo);
lcd.setCursor(0,2);
//lcd.print(" ");
lcd.setCursor(5,2);
lcd.print("SUNSET/MODE"); // LCD coding added by O2Surplus 4/2/2014
//lcd.setCursor(11,2);
//lcd.print(" ");
digitalWrite(fanEnable, HIGH);
lcd.setCursor(7,3);
lcd.print("FAN-ON");
} else if ( ctr > (kTurnOff+kFadeDuration) && ctr < 86400 ) { // night
set_state(kNightState);
lcd.setCursor(0,2);
//lcd.print(" ");
lcd.setCursor(5,2);
lcd.print("NIGHT/MODE"); // LCD coding added by O2Surplus 4/2/2014
digitalWrite(fanEnable,LOW);
lcd.setCursor(6,3);
lcd.print("FANS-OFF");
}
}
/*
* Utility function for pretty digital clock time output
* From example code in Time library -- author unknown
*/
void printDigits(int digits) {
Serial.print(":");
if(digits < 10)
Serial.print('0');
Serial.print(digits);
}
void displayDigits(int digits) {
lcd.print(":");
if(digits < 10)
lcd.print('0');
lcd.print(digits);
}
/*
* Display time
* Adapted from example code in Time library -- author unknown
*/
void digitalClockDisplay() {
Serial.print(hour());
printDigits(minute());
printDigits(second());
Serial.print(" ");
Serial.print(month());
Serial.print("/");
Serial.print(day());
Serial.print("/");
Serial.print(year());
Serial.println();
}
void lcdClockDisplay() {
lcd.setCursor(0,0);
lcd.print(hour());
displayDigits(minute());
displayDigits(second());
lcd.print(" ");
lcd.print(month());
lcd.print("/");
lcd.print(day());
lcd.print("/");
lcd.print(year());
}
void setup() {
Serial.begin(115200); // Max for Arduino Uno
setSyncProvider(RTC.get);
setSyncInterval(120);
Wire.begin();
lcd.begin(20,4); // initialize the lcd for 20 chars 4 lines and turn on backlight
Timer1.initialize(6666); // 150Hz PWM
pinMode(kChan0Pin, OUTPUT);
Timer1.pwm(kChan0Pin, 0);
pinMode(kChan1Pin, OUTPUT);
Timer1.pwm(kChan1Pin, 0);
pinMode(fanEnable,OUTPUT); // Fan control coding added by O2Surplus 4/2/2014
}
void loop () {
float temperature = getTemp();// Temperature serial display coding addded by O2Suplus 4/2/2014
Serial.println(temperature);
ctr = seconds_since_midnight();// Original code
lcd.setCursor(0,1); // Temperature to I2C LCD added by O2Surplus 4/2/2014
lcd.print("Temp C = ");
lcd.print(temperature);
determine_state();// Original code
Serial.print("ctr: ");
Serial.print(ctr); // display counter
Serial.println();
digitalClockDisplay(); //display time
Serial.println();
lcd.setCursor(0,0); //Time/Date to I2C display added 4/2/2014 by O2Surplus
lcd.println();
lcdClockDisplay();
delay(1000); // no need to do anything until next second
}// Original Code ends here.
float getTemp(){
//returns the temperature from one DS18S20 in DEG Celsius // Added by O2Suplus 4/2/2014
byte data[12];
byte addr[8];
if ( !ds.search(addr)) {
//no more sensors on chain, reset search
ds.reset_search();
return -1000;
}
if ( OneWire::crc8( addr, 7) != addr[7]) {
Serial.println("CRC is not valid!");
return -1000;
}
if ( addr[0] != 0x10 && addr[0] != 0x28) {
Serial.print("Device is not recognized");
return -1000;
}
ds.reset();
ds.select(addr);
ds.write(0x44,1); // start conversion, with parasite power on at the end
byte present = ds.reset();
ds.select(addr);
ds.write(0xBE); // Read Scratchpad
for (int i = 0; i < 9; i++) { // we need 9 bytes
data[i] = ds.read();
}
ds.reset_search();
byte MSB = data[1];
byte LSB = data[0];
float tempRead = ((MSB << 8) | LSB); //using two's compliment
float TemperatureSum = tempRead / 16;
return TemperatureSum;
}