#include <OneWire.h>
#include <LCDi2cW.h>
#include <Arduino.h>
#include <Wire.h>
#include <RTClib.h>
#include <PWM.h> //used to change the default PWM frequency and resolution
//#include <avr/wdt.h> //watchdog timer
#include <Time.h>
RTC_DS1307 RTC; //intiialize real time clock from RTClib.h
/*
Arduino Reef Controller
Analog Pin 0 =
Analog Pin 1 =
Analog Pin 2 = Relay 6 Refugium Light
Analog Pin 3 = Relay 5 Skimmer
Analog Pin 4 = SDA for I2C
Analog Pin 5 = SCL for I2C
Digital Pin 0 = Temp Sensor
Digital Pin 1 = Relay 1 Heater
Digital Pin 2 = Relay 2 Day Light
Digital Pin 3 = timer 2 8bit PWM Light - Blue stripe - Channel 1 - 3 Hyper Violet and 2 CREE XT-E Royal Blue (Actinic channel) (18v @ 700mA)
Digital Pin 4 = Relay 3 Return Pump
Digital Pin 5 = timer 0 8bit PWM Light - Green - Channel 2 - Royal Blue (base blue spectrum) (16.5V @ 1500mA)
Digital Pin 6 = timer 0 8bit PWM Light - Orange Stripe - Channel 3 - 3 XT-E Netural White and 2 XT-E Warm White (base white color) (16.5V @ 1500mA)
Digital Pin 7 = Relay 4 Fan
Digital Pin 8 = Alarm
Digital Pin 9 = timer 1 16bit PWM Light - Brown Stripe - Channel 4 - Turquoise / CREE XPE2 Blue (enhanced color spectrum) (17.2V @ 700mA)
Digital Pin 10 = timer 1 16bitPWM Light - Brown - Channel 5 - - 3 Hyper Violet and 2 CREE XT-E Royal Blue (Actinic channel) (18v @ 700mA)
Digital Pin 11 = timer 2 8bit PWM fan - Orange
Digital Pin 12 = Relay 7 Power Head #2
Digital Pin 13 = Relay 8 Powerhead in sump
Keypad controlls:
A-normal mode 100
B-feed mode 101
C-Reset LCD Screen 102
D-Reset temp history 103
*/
LCDi2cW lcd = LCDi2cW(4,20,0x4C,1); //[# of lines on LCD],[address of serial LCD controller on I2C]
OneWire ds(0); // Initialize DS18B20 Temp Sensor on pin 2
//****************** Elements and functions for graphing history*****************************************************************
int temp_hist[34]; //define temperature history array for bar graph
byte custChar[8] = { //initialize custom character 0 is off and 1 is on.
B00000,
B00000,
B00000,
B00000,
B00000,
B00000,
B00000,
B00000};
int setpoint (int row, int col) { //sets a point in the custom character
row = 7 - row;
//col = 4 - col;
bitSet (custChar[row], col);
return 1;
}
/* Unused
int clearpoint (int row, int col) { //clears a previously drawn point in the custom character
row = 7 - row;
col = 4 - col;
bitClear (custChar[row], col);
return 1;
}
*/
void ResetcustChar () { //resets the custom character
int counti = 0;
while (counti < 8) {
custChar[counti] = B00000;
counti++;
}
}
int graphHeight (int temp_hist) { //sets the height of each bar in the graph
int bar_height;
if(temp_hist <= 7800){bar_height = -1;}
if(temp_hist > 7850){bar_height = 0;}
if(temp_hist > 7900){bar_height = 1;}
if(temp_hist > 7950){bar_height = 2;}
if(temp_hist > 8000){bar_height = 3;}
if(temp_hist > 8050){bar_height = 4;}
if(temp_hist > 8100){bar_height = 5;}
if(temp_hist > 8150){bar_height = 6;}
if(temp_hist > 8200){bar_height = 7;}
if(temp_hist > 8250){bar_height = 8;}
return bar_height;
}
//**************************************************************************************************************************************
void setup(void) {
InitTimersSafe(); //initialize timers
//SetPinFrequencySafe(led, 100); //sets the frequency in Hz for the specified pin
Timer1_SetTop(65535); //set the resolution of timer 1
for (int i = 0; i < 35; i = i + 1){ //set all values in temp history array to zero
temp_hist[i] = 0;
}
Wire.begin(); //initialize the I2C bus
RTC.begin(); //initialize the RTC
// RTC.adjust(DateTime(__DATE__, __TIME__)); //set the time to the computer's current date and time
lcd.init(); //initialize LCD
//Digital Relay Pins
pinMode(1,OUTPUT);
pinMode(2,OUTPUT);
pinMode(4,OUTPUT);
pinMode(7,OUTPUT);
pinMode(12,OUTPUT);
pinMode(13,OUTPUT);
pinMode(A2,OUTPUT);
pinMode(A3,OUTPUT);
//PWM
pinMode(3, OUTPUT); //Light Channel 1
pinMode(5, OUTPUT); //Light Channel 2
pinMode(6, OUTPUT); //Light Channel 3
pinMode(9, OUTPUT); //Light Channel 4
pinMode(10, OUTPUT); //Light Channel 5
pinMode(11, OUTPUT); //Fan
int i;
for (i = 0; i < 35; i = i + 1) {
temp_hist[i] = 0;
}
digitalWrite(2, HIGH); // turn on power supply for lights
digitalWrite(7, HIGH); // turn on chiller
digitalWrite(4, HIGH); // turn on Return Pump
digitalWrite(13, HIGH); // turn on Powerhead in sump
digitalWrite(A3, HIGH); // turn on Powerhead in tank
// wdt_enable (WDTO_4S); // reset watchdog after four seconds, if no "pat the dog" received
} //end setup
int now_temp = 0; //sets the current temp to zero
int High = 0; //sets an initial value for high temp (set low so it will be reset)
int Low = 9999; //sets an initial value for low temp (set high so it will be reset)
int mode = 100; //sets initial keypad mode to normal operation
time_t feedtime = 0; //sets initial feedtime to zero (used for feed mode)
time_t manualtime = 0; //sets initial manualtime to zero (used for manual mode)
time_t start_up_time = 0; //sets initial start up time to zero (used for feed mode)
int bar_height = 0; //sets initial bar height to zero (used for graphing temp)
int day_time = 1200; //time for daytime lights to come on military time
int night_time = 2130; //time for daytime lights to go off military time
void loop(void){
/* Check for keypad input *************************************************************************/
if(lcd.keypad() != -1){
mode = lcd.keypad();
}
if (mode == 102){
lcd.clear();
}
/* Get Time *************************************************************************************/
DateTime now = RTC.now();
int minutes, hours, seconds, mil_time, years, months, days;
time_t unix_epoch;
hours = now.hour(); //This is in military time [0,23]
minutes = now.minute();
seconds = now.second();
years = now.year();
months = now.month();
days = now.day();
unix_epoch = now.unixtime();
mil_time = (hours * 100) + minutes; //create military time output [0000,2400)
/* Get Temperature ******************************************************************************/
byte i;
byte present = 0;
byte data[12];
byte addr[8];
if ( !ds.search(addr)) {
ds.reset_search();
return;
}
ds.reset();
ds.select(addr);
ds.write(0x44,1); // start conversion, with parasite power on at the end
delay(750); // maybe 750ms is enough, maybe not
present = ds.reset();
ds.select(addr);
ds.write(0xBE); // Read Scratchpad
for ( i = 0; i < 9; i++) { // we need 9 bytes
data[i] = ds.read();
}
int HighByte, LowByte, TReading, SignBit, Tc_100, Whole, Fract;
LowByte = data[0];
HighByte = data[1];
TReading = (HighByte << 8) + LowByte;
SignBit = TReading & 0x8000; // test most sig bit
if (SignBit) // negative
{
TReading = (TReading ^ 0xffff) + 1; // 2's comp
}
Tc_100 = (6 * TReading) + TReading / 4; // multiply by (100 * 0.0625) or 6.25
Tc_100 = (Tc_100 * 9/5) + 3200; //Convert to fahrenheit, comment this out to display in celcius
Whole = Tc_100 / 100; // separate off the whole and fractional portions
Fract = Tc_100 % 100;
/* Use time and temp to control lights, heater, and fan in normal mode*****************************************/
/*LED Light declarations
*/
int on_time_hours, on_time_minutes, off_time_minutes, off_time_hours, intensity, intfan;
float pwmbrightnessch1, pwmbrightnessch2, pwmbrightnessch3, pwmbrightnessch4, pwmbrightnessch5, minutes_into_cycle, total_time;
//if daytime
if(day_time <= mil_time && mil_time < night_time){
/*LED Light calculations
(z/100)(4*w(/y))(-x/y)(x-y) Quadratic
pwmbrightnessch1 = ((intensity - 10) * .01)*(4.0 * 255.0 / total_time)*(-1 * minutes_into_cycle/total_time)*(minutes_into_cycle - total_time);
pwmbrightnessch2 = (intensity * .01)*(4.0 * 255.0 / total_time)*(-1 * minutes_into_cycle/total_time)*(minutes_into_cycle - total_time);
pwmbrightnessch3 = ((intensity + 30) * .01)*(4.0 * 255.0 / total_time)*(-1 * minutes_into_cycle/total_time)*(minutes_into_cycle - total_time);
pwmbrightnessch4 = (intensity * .01)*(4.0 * 65535.0 / total_time)*(-1 * minutes_into_cycle/total_time)*(minutes_into_cycle - total_time);
pwmbrightnessch5 = ((intensity - 10) * .01)*(4.0 * 65535.0 / total_time)*(-1 * minutes_into_cycle/total_time)*(minutes_into_cycle - total_time);
or
(z/100)*w*sin(pi*x/y)
w = total number of PWM steps
x = number of minutes elapsed following the time the lights turned on today
y = total number of minutes that the light will be on today
z = light intensity max from 1-100
*/
intensity = 80; //set base intentity as an integer percentage
total_time = (night_time / 100 - day_time / 100) * 60.0 - day_time % 100 + night_time % 100; //total number of minutes that the light will be on today
minutes_into_cycle = (hours - day_time / 100.0) * 60.0 + minutes - day_time % 100; //total number of minutes that the light has been on today
const float pi = 3.142;
pwmbrightnessch1 = (intensity) * .01 * 255.0 * sin(pi * minutes_into_cycle/total_time);
pwmbrightnessch2 = (intensity * .01) * 255.0 * sin(pi * minutes_into_cycle/total_time);
pwmbrightnessch3 = ((intensity + 20) * .01) * 255.0 * sin(pi * minutes_into_cycle/total_time);
pwmbrightnessch4 = (intensity * .01) * 65535.0 * sin(pi * minutes_into_cycle/total_time);
pwmbrightnessch5 = (intensity * .01) * 65535.0 * sin(pi * minutes_into_cycle/total_time);
if(pwmbrightnessch1 <= 0){pwmbrightnessch1 = 1;} //durring the day, set the minimum intensity
if(pwmbrightnessch2 <= 0){pwmbrightnessch2 = 1;}
if(pwmbrightnessch3 < 0){pwmbrightnessch3 = 0;}
if(pwmbrightnessch4 < 0){pwmbrightnessch4 = 0;}
if(pwmbrightnessch5 < 0){pwmbrightnessch5 = 0;}
if(pwmbrightnessch1 > 255){pwmbrightnessch1 = 255;} //durring the day, set the maximum intensity
if(pwmbrightnessch2 > 255){pwmbrightnessch2 = 255;}
if(pwmbrightnessch3 > 255){pwmbrightnessch3 = 255;}
if(pwmbrightnessch4 > 65535){pwmbrightnessch4 = 65535;}
if(pwmbrightnessch5 > 65535){pwmbrightnessch5 = 65535;}
//set fan intensity - min 150then linear up to 255 max
if(pwmbrightnessch3 <= 50){
intfan = 150;
}
if(pwmbrightnessch3 > 50){
intfan = 100 + pwmbrightnessch3;
}
if(pwmbrightnessch3 > 155){
intfan = 255;
}
pwmWrite(11, intfan);
} //end if
else {
float total_night_time = (11 - night_time / 100) * 60 + night_time % 100 + 420; //total number of minutes that the light will be on at night; night time lasts from the time the light turns off until 7am
float minutes_into_night = minutes + (hours - night_time / 100) * 60;;
if(mil_time < night_time){
minutes_into_night = minutes_into_night + minutes + hours * 60;
}
int days_in_month;
switch (months) {
case 1:
days_in_month = 31;
break;
case 2:
days_in_month = 28;
break;
case 3:
days_in_month = 31;
break;
case 4:
days_in_month = 30;
break;
case 5:
days_in_month = 31;
break;
case 6:
days_in_month = 30;
break;
case 7:
days_in_month = 31;
break;
case 8:
days_in_month = 31;
break;
case 9:
days_in_month = 30;
break;
case 10:
days_in_month = 31;
break;
case 11:
days_in_month = 30;
break;
case 12:
days_in_month = 31;
break;
}
const float pi = 3.142;
float night_intensity = sin(pi * days/ days_in_month);
pwmbrightnessch1 = 0;
pwmbrightnessch2 = 0;
pwmbrightnessch3 = 0;
pwmbrightnessch4 = (night_intensity * .0005) * 65535.0 * sin(pi * minutes_into_night/total_night_time + days);
pwmbrightnessch5 = (night_intensity * .001) * 65535.0 * sin(pi * minutes_into_night/total_night_time + days);
if(pwmbrightnessch4 < 0){pwmbrightnessch4 = -pwmbrightnessch4;}
if(pwmbrightnessch5 < 0){pwmbrightnessch5 = -pwmbrightnessch5;}
pwmWrite(11, 0); //set fan intensity
digitalWrite(2, HIGH); //turn on led light power
} //end else
if(mil_time < 1100){
digitalWrite(A2, HIGH); //refugium light on from midnight to until 10AM
}
else{
digitalWrite(A2, LOW);
}
//if temp is < 78.5 deg
if(Tc_100 < 7850){
digitalWrite(7, LOW); //chiller
digitalWrite(8, HIGH); //alarm
digitalWrite(1, HIGH); //heater
}
//if 78.5 <= temp < 80.0
if(Tc_100 >= 7850 && Tc_100 < 8000){
digitalWrite(8, LOW); //alarm
digitalWrite(1, HIGH); //heater
}
//if 81.0 <= temp < 82.0
if(Tc_100 >= 8100 && Tc_100 < 8200){
digitalWrite(7, HIGH); //chiller
digitalWrite(1, LOW); //heater
digitalWrite(8, LOW); //alarm
}
//if 82.0 <= temp < 82.5
if(Tc_100 >= 8200 && Tc_100 < 8250){
}
//if 83.0 <= temp
if(Tc_100 >= 8300){
digitalWrite(8, HIGH); //alarm
}
//set the pwm values for each led channel
pwmWrite(3, pwmbrightnessch1); //need to use analogWrite because this timer is not initialized
analogWrite(5, pwmbrightnessch2); //need to use analogWrite because this timer is not initialized
analogWrite(6, pwmbrightnessch3); //need to use pwmwWrite because this timer is initialized
pwmWriteHR(9, pwmbrightnessch4); //need to use pwmwWrite because this timer is set to high resolution
pwmWriteHR(10, pwmbrightnessch5); //need to use pwmwWrite because this timer is set to high resolution
/*
pwmWrite(3, 200); //need to use analogWrite because this timer is not initialized
analogWrite(5, 200); //need to use analogWrite because this timer is not initialized
analogWrite(6, 200); //need to use pwmwWrite because this timer is initialized
pwmWriteHR(9, 200); //need to use pwmwWrite because this timer is set to high resolution
pwmWriteHR(10, 200); //need to use pwmwWrite because this timer is set to high resolution
*/
/* Powerheads & Skimmer ***************************************************************************/
if (mode == 101){
feedtime = unix_epoch;
}
if (mode == 100){
feedtime = 0;
}
/*
if (unix_epoch - feedtime < 600){
digitalWrite(A3, LOW); // Powerhead #1
}
else{
digitalWrite(A3, HIGH); // Powerhead #1
}
*/
if (unix_epoch - feedtime < 1000){
digitalWrite(4, LOW); // Return Pump
}
else{
digitalWrite(4, HIGH); // Return Pump
}
/* Unused
if (unix_epoch - feedtime < 1800){
digitalWrite(A2, LOW); // Powerhead #2
}
else{
digitalWrite(A2, HIGH); // Powerhead #2
}
*/
if (unix_epoch - feedtime < 2400){
digitalWrite(12, LOW); // Skimmer
}
else{
digitalWrite(12, HIGH); // Skimmer
}
/* Display all information on LCD *******************************************************************/
//Display current Temp.
lcd.setCursor(3,8);
if (SignBit) // If its negative
{lcd.print("-");}
lcd.print("Now = ");
lcd.print(Whole);
lcd.print(".");
if (Fract < 10){lcd.print("0");}
lcd.print(Fract);
lcd.write(0xDF);
//lcd.print("F ");
int j;
// Reset temp_hist array if mode chosen
/*
if(mode == 103){
for (j = 0; j < 35; j = j + 1){
temp_hist[j] = 0;
}
}
*/
//Display High Temp (values in the temp_hist array)
lcd.setCursor(1,0);
for (j = 0; j < 35; j = j + 1){
if(temp_hist[j] > High){High = temp_hist[j];}
}
Whole = (High / 100); // separate off the whole and fractional portions
Fract = (High % 100);
lcd.print("High = ");
lcd.print(Whole, DEC);
lcd.print(".");
if (Fract < 10){lcd.print("0");}
lcd.print(Fract, DEC);
lcd.write(0xDF);
lcd.print(" ");
//Display Low Temp (values in the temp_hist array)
lcd.setCursor(2,0);
for (j = 0; j < 35; j = j + 1){
if(temp_hist[j] < Low && temp_hist[j] >5000){Low = temp_hist[j];}
}
Whole = (Low / 100); // separate off the whole and fractional portions
Fract = (Low % 100);
lcd.print("Low = ");
lcd.print(Whole, DEC);
lcd.print(".");
if (Fract < 10){lcd.print("0");}
lcd.print(Fract, DEC);
lcd.write(0xDF);
lcd.print(" ");
//Display what relays are on******************************************************************************************
/*
lcd.setCursor(2,0);
if(digitalRead(13) == HIGH){lcd.print("S1 ");}
else{lcd.print("S0 ");}
if(digitalRead(6) == HIGH){lcd.print("H1 ");}
else{lcd.print("H0 ");}
if(digitalRead(7) == HIGH){lcd.print("L1 ");}
else{lcd.print("L0 ");}
if(digitalRead(8) == HIGH){lcd.print("R1 ");}
else{lcd.print("R0 ");}
if(digitalRead(9) == HIGH){lcd.print("F1 ");}
else{lcd.print("F0 ");}
if(digitalRead(10) == HIGH){lcd.print("P1 ");}
else{lcd.print("P0 ");}
*/
// Print feed time remaining if in feed mode
if (unix_epoch - feedtime < 2400){
lcd.setCursor(0,0);
lcd.print(2400 - (unix_epoch - feedtime));
lcd.print(" ");
}
else{
lcd.setCursor(0,0);
lcd.print(" ");
}
// Print custom graph
if(start_up_time == 0){start_up_time = unix_epoch; temp_hist[0] = Tc_100;} //first time through, set stat_time to current time
if((unix_epoch - start_up_time) > 3600){ //if a hour has passed, move all of the array values right one space
for (i = 34; i > 0; i = i - 1){
temp_hist[i] = temp_hist[(i-1)];
}
start_up_time = unix_epoch; // set the starup time to the current time
temp_hist[0] = Tc_100; //set the first value to the new temp value
}
ResetcustChar ();
for (i = 0; i < 5; i = i + 1){ //set new point for data points 0-4
setpoint (graphHeight(temp_hist[i]), i); //set column to new value
}
lcd.load_custom_character(1, custChar);
lcd.setCursor(3,6);
lcd.write(1);
ResetcustChar ();
for (i = 5; i < 10; i = i + 1){ //set new point for data points 0-4
setpoint (graphHeight(temp_hist[i]), i-5); //set column to new value
}
lcd.load_custom_character(2, custChar);
lcd.setCursor(3,5);
lcd.write(2);
ResetcustChar ();
for (i = 10; i < 15; i = i + 1){ //set new point for data points 0-4
setpoint (graphHeight(temp_hist[i]), i-10); //set column to new value
}
lcd.load_custom_character(3, custChar);
lcd.setCursor(3,4);
lcd.write(3);
ResetcustChar ();
for (i = 15; i < 20; i = i + 1){ //set new point for data points 0-4
setpoint (graphHeight(temp_hist[i]), i-15); //set column to new value
}
lcd.load_custom_character(4, custChar);
lcd.setCursor(3,3);
lcd.write(4);
ResetcustChar ();
for (i = 20; i < 25; i = i + 1){ //set new point for data points 0-4
setpoint (graphHeight(temp_hist[i]), i-20); //set column to new value
}
lcd.load_custom_character(5, custChar);
lcd.setCursor(3,2);
lcd.write(5);
ResetcustChar ();
for (i = 25; i < 30; i = i + 1){ //set new point for data points 0-4
setpoint (graphHeight(temp_hist[i]), i-25); //set column to new value
}
lcd.load_custom_character(6, custChar);
lcd.setCursor(3,1);
lcd.write(6);
ResetcustChar ();
for (i = 30; i < 35; i = i + 1){ //set new point for data points 0-4
setpoint (graphHeight(temp_hist[i]), i-30); //set column to new value
}
lcd.load_custom_character(7, custChar);
lcd.setCursor(3,0);
lcd.write(7);
// Print Time
lcd.setCursor(0,12);
if(now.hour() > 12){
if((now.hour() - 12) < 10){lcd.print(" ");}
lcd.print(now.hour() - 12);
lcd.print(":");
if(now.minute() < 10){lcd.print("0");}
lcd.print(now.minute());
lcd.print("PM");
}
else{
if(now.hour() < 10){lcd.print(" ");}
lcd.print(now.hour());
lcd.print(":");
if(now.minute() < 10){lcd.print("0");}
lcd.print(now.minute());
lcd.print("AM");
}
//Manual Mode ********************************************************************************
//Excluded to meet reefcentral character limits
} //end main loop