/*
// Typhon firmware
// v0.3 alpha 2011-16-11
// N. Enders, R. Ensminger
// Modified by Tim Perkins
// Change EEPROMvar.h to use standard EEPROM (GET and PUT)
// Change to use arrays
// Change to be easier for more than 4 channels
//
// This sketch provides firmware for the Typhon LED controller.
// It provides a structure to fade 4 independent channels of LED lighting
// on and off each day, to simulate sunrise and sunset.
//
// Current work in progress:
// - store all LED variables in EEPROM so they are not reset by a loss of power // - allow for signals to be inverted for buckpucks or other drivers that consider 0 to be "on"
//
// Future developments may include:
// - moon phase simulation
// - storm simulation
// - support for plugin hardware modules for temperature, pH, relay control, etc.
//
// Sketch developed in Arduino-22
// Requires LiquidCrystal, Wire, EEPROM, EEPROMVar, and Button libraries.
// Button is available here:
http://www.arduino.cc/playground/Code/Button
// EEPROMVar is available here:
http://www.arduino.cc/playground/uploads/Profiles/EEPROMVar_01.zip
*/
// include the libraries:
#include <LiquidCrystal.h>
#include <Wire.h>
#include <Button.h>
#include <EEPROM.h>
/**** Define Variables & Constants ****/
/**************************************/
// set the RTC's I2C address
const int DS1307_I2C_ADDRESS = 0x68;
// Number of LED channels
const byte LED_channels = 4;
// create the LCD
LiquidCrystal lcd(8, 7, 5, 4, A2, 2);
// set up backlight
int bkl = 6; // backlight pin
byte bklIdle = 10; // PWM value for backlight at idle
byte bklOn = 50; // PWM value for backlight when on
int bklDelay = 10000; // ms for the backlight to idle before turning off
unsigned long bklTime = 0; // counter since backlight turned on
// create the menu counter
int menuCount = 1;
int menuSelect = 0;
//create the plus and minus navigation delay counter with its initial maximum of 250.
byte btnMaxDelay = 200;
byte btnMinDelay = 25;
byte btnMaxIteration = 5;
byte btnCurrIteration;
//create manual override variables
boolean override = false;
byte overmenu = 0;
int overpercent = 0;
// create the buttons
Button menu = Button(12, PULLDOWN);
Button select = Button(13, PULLDOWN);
Button plus = Button(14, PULLDOWN);
Button minus = Button(15, PULLDOWN);
//Button menu = Button(12);
//Button select = Button(13);
//Button plus = Button(14);
//Button minus = Button(15);
// LED variables. These control the behavior of lighting. Change these to customize behavoir
int minCounter = 0; // counter that resets at midnight.
int oldMinCounter = 0; // counter that resets at midnight.
int LedPIN[LED_channels] = {9, 10, 11, 3}; // pins for channel
int Val[LED_channels] = {0, 0, 0, 0}; // current value for channel
// Variables to be stored in EEPROM memory:
struct settings {
int versionCheck; // Just a number to check to ensure we've read in valid settings!
int StartMins; // minute to start this channel.
int PhotoPeriod; // photoperiod in minutes for this channel.
int Max; // max intensity for this channel, as a percentage
int FadeDuration; // duration of the fade on and off for sunrise and sunset for
}; // this channel.
// Array of LED settings
settings LED[LED_channels];
// variables to invert the output PWM signal,
// for use with drivers that consider 0 to be "on"
// i.e. buckpucks. If you need to provide an inverted
// signal on any channel, set the appropriate variable to true.
boolean Inverted[LED_channels] = {false, false, false, false};
/*
int oneStartMins = 1380; // minute to start this channel.
int onePhotoPeriod = 120; // photoperiod in minutes for this channel.
int oneMax = 100; // max intensity for this channel, as a percentage
int oneFadeDuration = 60; // duration of the fade on and off for sunrise and sunset for
// this channel.
int twoStartMins = 800;
int twoPhotoPeriod = 60;
int twoMax = 100;
int twoFadeDuration = 15;
int threeStartMins = 800;
int threePhotoPeriod = 60;
int threeMax = 100;
int threeFadeDuration = 30;
int fourStartMins = 800;
int fourPhotoPeriod = 120;
int fourMax = 100;
int fourFadeDuration = 60;
*/
/****** RTC Functions ******/
/***************************/
// Convert decimal numbers to binary coded decimal
byte decToBcd(byte val)
{
return ( (val / 10 * 16) + (val % 10) );
}
// Convert binary coded decimal to decimal numbers
byte bcdToDec(byte val)
{
return ( (val / 16 * 10) + (val % 16) );
}
// Sets date and time, starts the clock
void setDate(byte second, // 0-59
byte minute, // 0-59
byte hour, // 1-23
byte dayOfWeek, // 1-7
byte dayOfMonth, // 1-31
byte month, // 1-12
byte year) // 0-99
{
Wire.beginTransmission(DS1307_I2C_ADDRESS);
Wire.write(0);
Wire.write(decToBcd(second));
Wire.write(decToBcd(minute));
Wire.write(decToBcd(hour));
Wire.write(decToBcd(dayOfWeek));
Wire.write(decToBcd(dayOfMonth));
Wire.write(decToBcd(month));
Wire.write(decToBcd(year));
Wire.endTransmission();
}
// Gets the date and time
void getDate(byte *second,
byte *minute,
byte *hour,
byte *dayOfWeek,
byte *dayOfMonth,
byte *month,
byte *year)
{
Wire.beginTransmission(DS1307_I2C_ADDRESS);
Wire.write(0);
Wire.endTransmission();
Wire.requestFrom(DS1307_I2C_ADDRESS, 7);
*second = bcdToDec(Wire.read() & 0x7f);
*minute = bcdToDec(Wire.read());
*hour = bcdToDec(Wire.read() & 0x3f);
*dayOfWeek = bcdToDec(Wire.read());
*dayOfMonth = bcdToDec(Wire.read());
*month = bcdToDec(Wire.read());
*year = bcdToDec(Wire.read());
}
/****** LED Functions ******/
/***************************/
//function to set LED brightness according to time of day
//function has three equal phases - ramp up, hold, and ramp down
int setLed(int mins, // current time in minutes
int ledPin, // pin for this channel of LEDs
int start, // start time for this channel of LEDs
int period, // photoperiod for this channel of LEDs
int fade, // fade duration for this channel of LEDs
int ledMax, // max value for this channel
boolean inverted // true if the channel is inverted
) {
int val = 0;
//fade up
if (mins > start || mins <= start + fade) {
val = map(mins - start, 0, fade, 0, ledMax);
}
//fade down
if (mins > start + period - fade && mins <= start + period) {
val = map(mins - (start + period - fade), 0, fade, ledMax, 0);
}
//off or post-midnight run.
if (mins <= start || mins > start + period) {
if ((start + period) % 1440 < start && (start + period) % 1440 > mins )
{
val = map((start + period - mins) % 1440, 0, fade, 0, ledMax);
}
else
val = 0;
}
if (val > ledMax) {
val = ledMax;
}
if (val < 0) {
val = 0;
}
if (inverted) {
analogWrite(ledPin, map(val, 0, 100, 255, 0));
}
else {
analogWrite(ledPin, map(val, 0, 100, 0, 255));
}
if (override) {
val = overpercent;
}
return val;
}
/**** Display Functions ****/
/***************************/
//button hold function
int btnCurrDelay(byte curr)
{
if (curr == btnMaxIteration)
{
btnCurrIteration = btnMaxIteration;
return btnMaxDelay;
}
else if (btnCurrIteration == 0)
{
return btnMinDelay;
}
else
{
btnCurrIteration--;
return btnMaxDelay;
}
}
// format a number of minutes into a readable time (24 hr format)
void printMins(int mins, //time in minutes to print
boolean ampm //print am/pm?
) {
// 259 ) {
int hr = (mins % 1440) / 60;
int mn = mins % 60;
if (hr < 10) {
lcd.print(" ");
}
lcd.print(hr);
lcd.print(":");
if (mn < 10) {
lcd.print("0");
}
lcd.print(mn);
}
// format hours, mins, secs into a readable time (24 hr format)
void printHMS (byte hr,
byte mn,
byte sec //time to print
)
{
if (hr < 10) {
lcd.print(" ");
}
lcd.print(hr, DEC);
lcd.print(":");
if (mn < 10) {
lcd.print("0");
}
lcd.print(mn, DEC);
lcd.print(":");
if (sec < 10) {
lcd.print("0");
}
lcd.print(sec, DEC);
}
void ovrSetAll(int pct) {
for (int i = 0; i < LED_channels; i++) {
analogWrite(LedPIN
, map(pct, 0, 100, 0, 255));
}
}
/**** Setup ****/
/***************/
void setup() {
readSettings();
Wire.begin();
pinMode(bkl, OUTPUT);
lcd.begin(20, 4);
digitalWrite(bkl, HIGH);
lcd.print("Typhon-Reef");
lcd.setCursor(0, 1);
lcd.print("");
delay(5000);
lcd.clear();
analogWrite(bkl, bklIdle);
btnCurrIteration = btnMaxIteration;
}
void readSettings() {
int EEPROM_address = 0;
for (byte i = 0; i < LED_channels; i++) {
EEPROM.get(EEPROM_address, LED);
if (LED.versionCheck != 9898) {
// Set default values for the settings and save them to EEPROM
LED.versionCheck = 9898;
switch (i) {
case 0:
LED.StartMins = 750;
LED.PhotoPeriod = 720;
LED.Max = 100;
LED.FadeDuration = 60;
break;
case 1:
LED.StartMins = 810;
LED.PhotoPeriod = 600;
LED.Max = 100;
LED.FadeDuration = 60;
break;
case 2:
LED.StartMins = 810;
LED.PhotoPeriod = 600;
LED.Max = 100;
LED.FadeDuration = 60;
break;
case 3:
LED.StartMins = 480;
LED.PhotoPeriod = 510;
LED.Max = 100;
LED.FadeDuration = 60;
break;
}
EEPROM_address = (i * sizeof(settings));
EEPROM.put(EEPROM_address, LED);
}
}
}
/***** Loop *****/
/****************/
void loop() {
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
getDate(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);
oldMinCounter = minCounter;
minCounter = hour * 60 + minute;
//reset plus & minus acceleration counters if the button's state has changed
if (plus.stateChanged())
{
btnCurrDelay(btnMaxIteration);
}
if (minus.stateChanged())
{
btnCurrDelay(btnMaxIteration);
}
//check & set fade durations
for (int i = 0; i < LED_channels; i++) {
char updateFlag = 'N';
if (LED.FadeDuration > LED.PhotoPeriod / 2 && LED.PhotoPeriod > 0) {
LED.FadeDuration = LED.PhotoPeriod / 2;
updateFlag = 'Y';
}
if (LED.FadeDuration < 1) {
LED.FadeDuration = 1;
updateFlag = 'Y';
}
if (updateFlag == 'Y') {
updateFlag = 'N';
int EEPROM_address = (i * sizeof(settings));
EEPROM.put(EEPROM_address, LED);
}
}
//check & set any time functions
//set outputs
if (!override) {
for (int i = 0; i < LED_channels; i++) {
Val = setLed(minCounter, LedPIN, LED.StartMins, LED.PhotoPeriod, LED.FadeDuration, LED.Max, Inverted);
}
}
else {
ovrSetAll(overpercent);
}
//turn the backlight off and reset the menu if the idle time has elapsed
if (bklTime + bklDelay < millis() && bklTime > 0 ) {
analogWrite(bkl, bklIdle);
menuCount = 1;
lcd.clear();
bklTime = 0;
}
//iterate through the menus
if (menu.uniquePress()) {
analogWrite(bkl, bklOn);
bklTime = millis();
if (menuCount < 20) {
menuCount++;
} else {
menuCount = 1;
}
lcd.clear();
}
if (menuCount == 1) {
//main screen turn on!!!
if (minCounter > oldMinCounter) {
lcd.clear();
}
lcd.setCursor(0, 0);
printHMS(hour, minute, second);
lcd.setCursor(0, 1);
lcd.print(Val[0]);
lcd.setCursor(4, 1);
lcd.print(Val[1]);
lcd.setCursor(8, 1);
lcd.print(Val[2]);
lcd.setCursor(12, 1);
lcd.print(Val[3]);
//debugging function to use the select button to advance the timer by 1 minute
//if(select.uniquePress()){setDate(second, minute+1, hour, dayOfWeek, dayOfMonth, month, year);}
}
if (menuCount == 2) {
//Manual Override Menu
lcd.setCursor(0, 0);
lcd.print("Manual Overrides");
lcd.setCursor(0, 1);
lcd.print("All: ");
if (select.uniquePress()) {
if (menuSelect < 3) {
menuSelect++;
}
else {
menuSelect = 0;
}
bklTime = millis();
}
if (menuSelect == 0) {
lcd.print("Timer");
override = false;
}
if (menuSelect == 1) {
lcd.print("ON ");
overpercent = 100;
override = true;
}
if (menuSelect == 2) {
lcd.print("OFF ");
overpercent = 0;
override = true;
}
if (menuSelect == 3) {
override = true;
lcd.print(overpercent, DEC);
lcd.print("% ");
if (plus.isPressed() && overpercent < 100)
{
overpercent++;
delay(btnCurrDelay(btnCurrIteration - 1));
bklTime = millis();
}
if (minus.isPressed() && overpercent > 0)
{
overpercent--;
delay(btnCurrDelay(btnCurrIteration - 1));
bklTime = millis();
}
}
}
// Set start times
if (menuCount == 3 || menuCount == 7 || menuCount == 11 || menuCount == 15) {
int channel;
if (menuCount == 3) {
channel = 0;
}
if (menuCount == 7) {
channel = 1;
}
if (menuCount == 11) {
channel = 2;
}
if (menuCount == 15) {
channel = 3;
}
lcd.setCursor(0, 0);
lcd.print("Channel ");
lcd.print(channel + 1);
lcd.print(" Start");
lcd.setCursor(0, 1);
printMins(LED[channel].StartMins, true);
if (plus.isPressed() && LED[channel].StartMins < 1440) {
LED[channel].StartMins++;
if (LED[channel].PhotoPeriod > 0) {
LED[channel].PhotoPeriod--;
}
else {
LED[channel].PhotoPeriod = 1439;
}
delay(btnCurrDelay(btnCurrIteration - 1));
bklTime = millis();
}
if (minus.isPressed() && LED[channel].StartMins > 0) {
LED[channel].StartMins--;
if (LED[channel].PhotoPeriod < 1439) {
LED[channel].PhotoPeriod++;
}
else {
LED[channel].PhotoPeriod = 0;
}
delay(btnCurrDelay(btnCurrIteration - 1));
bklTime = millis();
}
}
// Set end times
if (menuCount == 4 || menuCount == 8 || menuCount == 12 || menuCount == 16) {
int channel;
if (menuCount == 4) {
channel = 0;
}
if (menuCount == 8) {
channel = 1;
}
if (menuCount == 12) {
channel = 2;
}
if (menuCount == 16) {
channel = 3;
}
lcd.setCursor(0, 0);
lcd.print("Channel ");
lcd.print(channel + 1);
lcd.print(" End");
lcd.setCursor(0, 1);
printMins(LED[channel].StartMins + LED[channel].PhotoPeriod, true);
if (plus.isPressed()) {
if (LED[channel].PhotoPeriod < 1439) {
LED[channel].PhotoPeriod++;
}
else {
LED[channel].PhotoPeriod = 0;
}
delay(btnCurrDelay(btnCurrIteration - 1));
bklTime = millis();
}
if (minus.isPressed()) {
if (LED[channel].PhotoPeriod > 0) {
LED[channel].PhotoPeriod--;
}
else {
LED[channel].PhotoPeriod = 1439;
}
delay(btnCurrDelay(btnCurrIteration - 1));
bklTime = millis();
}
}
// Set fade durations
if (menuCount == 5 || menuCount == 9 || menuCount == 13 || menuCount == 17) {
int channel;
if (menuCount == 5) {
channel = 0;
}
if (menuCount == 9) {
channel = 1;
}
if (menuCount == 13) {
channel = 2;
}
if (menuCount == 17) {
channel = 3;
}
lcd.setCursor(0, 0);
lcd.print("Channel ");
lcd.print(channel + 1);
lcd.print(" Fade");
lcd.setCursor(0, 1);
printMins(LED[channel].FadeDuration, false);
if (plus.isPressed() && (LED[channel].FadeDuration < LED[channel].PhotoPeriod / 2 || LED[channel].FadeDuration == 0)) {
LED[channel].FadeDuration++;
delay(btnCurrDelay(btnCurrIteration - 1));
bklTime = millis();
}
if (minus.isPressed() && LED[channel].FadeDuration > 1) {
LED[channel].FadeDuration--;
delay(btnCurrDelay(btnCurrIteration - 1));
bklTime = millis();
}
}
// Set Max levels
if (menuCount == 6 || menuCount == 10 || menuCount == 14 || menuCount == 18) {
int channel;
if (menuCount == 6) {
channel = 0;
}
if (menuCount == 10) {
channel = 1;
}
if (menuCount == 14) {
channel = 2;
}
if (menuCount == 18) {
channel = 3;
}
lcd.setCursor(0, 0);
lcd.print("Channel ");
lcd.print(channel + 1);
lcd.print(" Max");
lcd.setCursor(1, 1);
lcd.print(LED[channel].Max);
lcd.print(" ");
if (plus.isPressed() && LED[channel].Max < 100) {
LED[channel].Max++;
delay(btnCurrDelay(btnCurrIteration - 1));
bklTime = millis();
}
if (minus.isPressed() && LED[channel].Max > 0) {
LED[channel].Max--;
delay(btnCurrDelay(btnCurrIteration - 1));
bklTime = millis();
}
}
// Run all settings thru EEPROM.put - it'll only actually write to EEPROM if they've changed
if (menuCount > 2 && menuCount < 19) {
for (int i = 0; i < LED_channels; i++) {
int EEPROM_address = (i * sizeof(settings));
EEPROM.put(EEPROM_address, LED);
}
}
if (menuCount == 19) {
//set hours
lcd.setCursor(0, 0);
lcd.print("Set Time: Hrs");
lcd.setCursor(0, 1);
printHMS(hour, minute, second);
if (plus.isPressed() && hour < 23) {
hour++;
delay(btnCurrDelay(btnCurrIteration - 1));
bklTime = millis();
}
if (minus.isPressed() && hour > 0) {
hour--;
delay(btnCurrDelay(btnCurrIteration - 1));
bklTime = millis();
}
setDate(second, minute, hour, dayOfWeek, dayOfMonth, month, year);
}
if (menuCount == 20) {
//set minutes
lcd.setCursor(0, 0);
lcd.print("Set Time: Mins");
lcd.setCursor(0, 1);
printHMS(hour, minute, second);
if (plus.isPressed() && minute < 59) {
minute++;
delay(btnCurrDelay(btnCurrIteration - 1));
bklTime = millis();
}
if (minus.isPressed() && minute > 0) {
minute--;
delay(btnCurrDelay(btnCurrIteration - 1));
bklTime = millis();
}
setDate(second, minute, hour, dayOfWeek, dayOfMonth, month, year);
}
}