Ya, I didnt think there were any here. Katchupoy (Cesar) is a local and knows a lot about it but I'm ready to go and don't want to wait for potential days for a PM reply to get started.
Thanks for the google suggestion.
I figured you were/are busy.Trido, I apologize for the delay. In and out of state limited access to computer. Can we start from scratch? Is your build similar to mine? Can you post here your sketch? So others can learn too?. Just want to let you know, I'm no expert. Just copied it from somewhere and try to change some.
hope this helps
Would this be like the Reef Angel controller only totally diy?
Version 1 for trido will be done this weekend. 4 channels, concurrent dimming, easy to add more channels.Anyways, Yes my sketch have 2 controllable signals, and I have no idea how to make it 4 channels.
if Brian can help us here, then so much the better so that other people can learn too.
The way I understand it.... based on my sketch.
When channel 1 is on going "on ramp" channel 2 cannot start. Channel 1 needs to finish its program first then 2 will follow.
It would be nice if channel 2 can start while channel 1 is on going. If this is possible then 4, 5, or 6 channel will not be a problem.
And this is what i want to see.
also, i remember that not all pins are PWM so we need to save these for this purpose only.
Version 1 for trido will be done this weekend. 4 channels, concurrent dimming, easy to add more channels.
Version 2 will add a complete menu/control interface through the Serial connection, since he has no buttons.
I'm building these with the new Arduino 1.0 tools
That is very generous for you to take the time to build these. I'm sure they will be useful for ALOT of reefers in the future.
/*
// Typhon firmware
// v0.2 alpha 2010-23-11
// N. Enders, R. Ensminger
// v0.9 alpha 2010-25-02
// Revised by B. Duthie
// This sketch provides firmware for the Typhon LED controller.
// It provides a structure to fade MAX_CHANNELS independent channels of LED lighting
// on and off each day, to simulate sunrise and sunset.
//
// Requires LiquidCrystal, Wire, EEPROM, EEPROMVar libraries.
// EEPROMVar is available here: http://www.arduino.cc/playground/uploads/Profiles/EEPROMVar_01.zip
//
// 02/12/12 to support leds, without use of buttons
*/
// include the libraries:
#include <LiquidCrystal.h>
#include <Wire.h>
#include <EEPROM.h>
#include <EEPROMVar.h>
/**** Define Variables & Constants ****/
/**************************************/
// printing to Serial if debugging
#define DEBUG_ON // General debugging
//#define DEBUG_LED // LED Fade
// ONLY INCLUDE THIS IF YOU ARE CHANGING VALUES STORED IN EEPROM
//#define RESET_EEPROM
// set the RTC's I2C address
#define DS1307_I2C_ADDRESS 0x68
// create the LCD
//LiquidCrystal lcd(8, 7, 5, 4, 16, 2); // Typhon
//int bkl = 6; // backlight pin
LiquidCrystal lcd(12, 8, 7, 6, 5, 4, 2); // Duanes
int bkl = 13; // backlight pin
//create manual override variables
int overSelect = 0; // allow the override menu to remember its current setting
// 0 = Timer, 1 = All On, 2 = All Off, 3 = % Value
int overPercent = 0;
// setting of 60, with internal delays, 0-100 percent takes about 15 seconds (per channel)
int rampDelayUp = 120;
int rampDelayDn = 60;
// Variables making use of EEPROM memory:
EEPROMVar<int> oneStartMins = 480; // minute to start this channel.
EEPROMVar<int> onePhotoPeriod = 780; // photoperiod in minutes for this channel.
EEPROMVar<int> oneMin = 0; // min intensity for this channel, as a percentage
EEPROMVar<int> oneMax = 70; // max intensity for this channel, as a percentage
EEPROMVar<int> oneFadeDuration = 30; // duration of the fade on and off for sunrise and sunset
EEPROMVar<int> twoStartMins = 510;
EEPROMVar<int> twoPhotoPeriod = 720;
EEPROMVar<int> twoMin = 0; // min intensity for this channel, as a percentage
EEPROMVar<int> twoMax = 700;
EEPROMVar<int> twoFadeDuration = 60;
EEPROMVar<int> threeStartMins = 540;
EEPROMVar<int> threePhotoPeriod = 660;
EEPROMVar<int> threeMin = 0; // min intensity for this channel, as a percentage
EEPROMVar<int> threeMax = 170;
EEPROMVar<int> threeFadeDuration = 60;
EEPROMVar<int> fourStartMins = 720;
EEPROMVar<int> fourPhotoPeriod = 240;
EEPROMVar<int> fourMin = 0; // min intensity for this channel, as a percentage
EEPROMVar<int> fourMax = 100;
EEPROMVar<int> fourFadeDuration = 60;
// descriptions need to be 4 characters due to display area
char* oneDesc = "BR ";
char* twoDesc = "RB ";
char* threeDesc = "CW ";
char* fourDesc = "NW";
#define MAX_CHANNELS 4
typedef struct {
char* Desc; // Channel Description
int Led; // channel pin
int StartMins; // minute to start this channel.
int PhotoPeriod; // photoperiod in minutes for this channel.
int ledMin; // min intensity for this channel, as a percentage
int ledMax; // max intensity for this channel, as a percentage
int ledVal; // current intensity for this channel, as a percentage
int FadeDuration; // duration of the fade on and off for sunrise and sunset for this channel.
int RampDelay;
} channelVals_t;
channelVals_t channel[MAX_CHANNELS];
// freeMemory returns the amount of available memory for variables
extern unsigned int __bss_end;
extern unsigned int __heap_start;
extern void *__brkval;
int freeMemory() {
int free_memory;
if((int)__brkval == 0)
free_memory = ((int)&free_memory) - ((int)&__bss_end);
else
free_memory = ((int)&free_memory) - ((int)__brkval);
return free_memory;
}
/****** RTC Functions ******/
/***************************/
// Convert decimal numbers to binary coded decimal
uint8_t decToBcd(byte val)
{
return ( (val/10*16) + (val%10) );
}
// Convert binary coded decimal to decimal numbers
uint8_t 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.send((uint8_t)0);
Wire.send(decToBcd(second));
Wire.send(decToBcd(minute));
Wire.send(decToBcd(hour));
Wire.send(decToBcd(dayOfWeek));
Wire.send(decToBcd(dayOfMonth));
Wire.send(decToBcd(month));
Wire.send(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.send((uint8_t)0);
Wire.endTransmission();
Wire.requestFrom(DS1307_I2C_ADDRESS, 7);
*second = bcdToDec(Wire.receive() & 0x7f);
*minute = bcdToDec(Wire.receive());
*hour = bcdToDec(Wire.receive() & 0x3f);
*dayOfWeek = bcdToDec(Wire.receive());
*dayOfMonth = bcdToDec(Wire.receive());
*month = bcdToDec(Wire.receive());
*year = bcdToDec(Wire.receive());
}
/** Menu Control Functions **/
void showMainMenu () {
Serial.println("");
Serial.println("LED Control Actions");
Serial.println("L+ All Max");
Serial.println("L- All Min");
Serial.println("Lnnn All nnn%");
Serial.println("LT Timer Mode");
Serial.println("");
}
int parseMenuCommand () {
int command;
int value = 0;
int pct = 0;
command = Serial.read();
switch (command) {
case 'l':
case 'L':
if (Serial.available() > 0) {
value = Serial.read();
if (value == 't' || value == 'T') {
overSelect = 0;
Serial.println("Setting Timer Mode");
}
if (value == '+') {
overSelect = 1;
Serial.println("All Max");
}
if (value == '-') {
overSelect = 2;
Serial.println("All Min");
}
if (value >= '0' && value <= '9') {
pct = value - '0';
while (Serial.available() > 0) {
value = Serial.read();
if (value >= '0' && value <= '9')
pct = pct * 10 + value - '0';
else
break;
}
if (overPercent <= 100) {
overSelect = 3;
overPercent = pct;
Serial.print("Setting ");
Serial.print(overPercent);
Serial.println("%");
}
}
}
else { // unrecognized command
}
}
Serial.flush(); // remove all incoming data
return (0);
}
/****** 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 c // current channel
) {
// channel values, just to make code read a little easier
int ledPin = channel[c].Led; // pin for this channel of LEDs
int start = channel[c].StartMins; // start time for this channel of LEDs
int period = channel[c].PhotoPeriod; // photoperiod for this channel of LEDs
int fade = channel[c].FadeDuration; // fade duration for this channel of LEDs
int ledMin = channel[c].ledMin; // min value for this channel
int ledMax = channel[c].ledMax; // max value for this channel
int ledVal = channel[c].ledVal; // current value for this channel
int val = ledMin; // start with the assumption that this channel is off
int pinVal;
// adjust for midnite rollover
if (mins < start && start + period > 1439 && mins <= (start + period) % 1440)
mins += 1440;
//fade up or currently on or currently off
if (mins >= start) {
if (mins - start <= fade) { // fading up
val = map(mins - start, 0, fade, ledMin, ledMax);
}
else if (mins < start + (period - fade)) { // on
val = ledMax;
}
else if (mins > start + period) { // off - nothing to do, but print debug info
}
}
//fade down
if (mins >= start + period - fade && mins <= start + period) {
val = map(mins - (start + period - fade), 0, fade, ledMax, ledMin);
}
// if All On = use ledMax, All Off = use ledMin, otherwise use set percent
switch(overSelect){
case 1: // All On
val = ledMax;
break;
case 2: // All Off
val = ledMin;
break;
case 3: // specific percent
val = overPercent;
break;
}
// Ramp Up/Down slowly -- do nothing if no change
// This will also impact setting of the override % value, and all on/off options
if (channel[c].RampDelay > 0) // avoid using delay() in loop()
--channel[c].RampDelay;
if (channel[c].RampDelay == 0 && val < ledVal) {
--ledVal;
channel[c].RampDelay = rampDelayDn;
}
if (channel[c].RampDelay == 0 && val > ledVal) {
++ledVal;
channel[c].RampDelay = rampDelayUp;
}
if (ledVal != channel[c].ledVal) {
// ledVal is the percentage (0-100) to set for this channel
// pinVal is the value (0-255) to set for the Arduino PWM pin
// comment out the line you don't want to use, or create your own
// pinVal = map(ledVal, 0, 100, 0, 255); // normal linear value for PWM
pinVal = min(255, (pow(2, ledVal/10.0) + 2.99) / 4.0); // value for Meanwell "D"
analogWrite(ledPin, pinVal);
}
#ifdef DEBUG_LED
if (ledVal != channel[c].ledVal) {
if (start + period > 1440)
Serial.println("Midnite rollover ");
Serial.print("mins =");
Serial.print(mins);
Serial.print(", ");
Serial.print(channel[c].Desc);
Serial.print(", start = ");
Serial.print(start);
Serial.print(", period = ");
Serial.print(period);
Serial.print(", fade = ");
Serial.print(fade);
Serial.print(", min = ");
Serial.print(ledMin);
Serial.print(", max = ");
Serial.print(ledMax);
if(overSelect){
Serial.print(", overSelect: ");
Serial.print(val);
}
Serial.print(", ledVal = ");
Serial.print(ledVal);
Serial.print(", PWM = ");
Serial.print(map(ledVal, 0, 100, 0, 255), DEC);
Serial.print(", pin = ");
Serial.println(pinVal, DEC);
}
#endif
return ledVal;
}
/**** Display Functions ****/
void printMDY (byte mm, byte dd, byte yy)
{ if(mm<10){
lcd.print(" ");
}
lcd.print(mm, DEC);
lcd.print("/");
if(dd<10){
lcd.print(" ");
}
lcd.print(dd, DEC);
lcd.print("/");
if(yy<10){
lcd.print("0");
}
lcd.print(2000 + yy, DEC);
}
void printDay (byte dday)
{
char* daynames[8] = {"???","Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
lcd.print(daynames[dday%8]);
}
// format a number of minutes into a readable time (24 hr format)
void showMins(int mins, //time in minutes to print
boolean ampm = false //print am/pm? - future implementation (or not)
) {
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 showHMS (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);
}
/**** Setup ****/
/***************/
void setup() {
#ifdef RESET_EEPROM
oneStartMins = 480; // minute to start this channel.
onePhotoPeriod = 780; // photoperiod in minutes for this channel.
oneMin = 0; // min intensity for this channel, as a percentage
oneMax = 70; // max intensity for this channel, as a percentage
oneFadeDuration = 30; // duration of the fade on and off for sunrise and sunset for
// this channel.
twoStartMins = 510;
twoPhotoPeriod = 720;
twoMin = 0; // min intensity for this channel, as a percentage
twoMax = 70;
twoFadeDuration = 60;
threeStartMins = 540;
threePhotoPeriod = 660;
threeMin = 0; // min intensity for this channel, as a percentage
threeMax = 70;
threeFadeDuration = 60;
fourStartMins = 720;
fourPhotoPeriod = 600;
fourMin = 0; // min intensity for this channel, as a percentage
fourMax = 100;
fourFadeDuration = 30;
#endif
// Initialize channel variables. Set LED channel pin and retrieve values from EEPROM
channel[0].Desc = oneDesc;
channel[0].Led = 11;
channel[0].StartMins = oneStartMins;
channel[0].PhotoPeriod = 780; // onePhotoPeriod;
channel[0].ledMin = oneMin;
channel[0].ledMax = oneMax;
channel[0].ledVal = 0;
channel[0].FadeDuration = oneFadeDuration;
channel[1].Desc = twoDesc;
channel[1].Led = 10;
channel[1].StartMins = twoStartMins;
channel[1].PhotoPeriod = 720; //twoPhotoPeriod;
channel[1].ledMin = twoMin;
channel[1].ledMax = twoMax;
channel[1].ledVal = 0;
channel[1].FadeDuration = twoFadeDuration;
channel[2].Desc = threeDesc;
channel[2].Led = 9;
channel[2].StartMins = threeStartMins;
channel[2].PhotoPeriod = 660; //threePhotoPeriod;
channel[2].ledMin = threeMin;
channel[2].ledMax = threeMax;
channel[2].ledVal = 0;
channel[2].FadeDuration = threeFadeDuration;
channel[3].Desc = fourDesc;
channel[3].Led = 3;
channel[3].StartMins = fourStartMins;
channel[3].PhotoPeriod = fourPhotoPeriod;
channel[3].ledMin = fourMin;
channel[3].ledMax = fourMax;
channel[3].ledVal = 0;
channel[3].FadeDuration = fourFadeDuration;
Serial.begin(57600);
Serial.println("Duanes-Reef");
for (int i = 0; i < MAX_CHANNELS; ++i) {
Serial.print(channel[i].Desc);
Serial.print(", Start: ");
Serial.print(channel[i].StartMins);
Serial.print(", Period: ");
Serial.print(channel[i].PhotoPeriod);
Serial.print(", Min: ");
Serial.print(channel[i].ledMin, DEC);
Serial.print(", Max: ");
Serial.print(channel[i].ledMax, DEC);
Serial.print(", Fade: ");
Serial.print(channel[i].FadeDuration);
Serial.println();
}
Wire.begin();
pinMode(bkl, OUTPUT); // backlight control for the lcd display
lcd.begin(16, 2);
digitalWrite(bkl, HIGH);
lcd.print("Duanes-Reef");
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
getDate(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);
lcd.setCursor(0,1);
printMDY(month, dayOfMonth, year);
lcd.print(" - ");
printDay(dayOfWeek);
delay(5000);
lcd.clear();
updateEEPromVars();
Serial.print("Free Memory = ");
Serial.println(freeMemory());
showMainMenu();
}
/***** Loop *****/
/****************/
void loop() {
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
int minCounter;
getDate(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);
minCounter = hour * 60 + minute;
if (Serial.available() > 0) {
parseMenuCommand();
showMainMenu();
}
for (int i = 0; i < MAX_CHANNELS; i++) { //check & adjust fade durations
if(channel[i].FadeDuration > channel[i].PhotoPeriod/2 && channel[i].PhotoPeriod >0) {
channel[i].FadeDuration = channel[i].PhotoPeriod/2;
}
if(channel[i].FadeDuration < 1)
channel[i].FadeDuration = 1;
}
// set led outputs
for (int i = 0; i < MAX_CHANNELS; i++)
channel[i].ledVal = setLed(minCounter, i);
doMainMenu(hour, minute, second);
}
void doMainMenu(byte hour,
byte minute,
byte second) {
lcd.setCursor(0,0);
showHMS(hour, minute, second);
// display the current intensity for each led channel
showChannelValues();
//debugging function to use the select button to advance the timer by 1 minute
#ifdef DEBUG_ON
if (0) {
byte dayOfWeek, dayOfMonth, month, year;
getDate(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);
++minute;
if (minute > 59) {
minute = 0;
++hour;
}
if (hour > 23)
hour = 0;
setDate(second, minute+1, hour, dayOfWeek, dayOfMonth, month, year);
delay(100);
}
#endif DEBUG_ON
}
void doOverride() {
//Manual Override Menu
lcd.setCursor(0,0);
switch (overSelect){
case 0:
lcd.print("Control by Timer");
break;
case 1:
lcd.print("LED: All Max ");
break;
case 2:
lcd.print("LED: All Min ");
break;
case 3:
lcd.print("All Set: ");
if (overPercent < 10)
lcd.print(" ");
else if (overPercent < 100)
lcd.print(" ");
lcd.print(overPercent,DEC);
lcd.print("% ");
break;
default:
overSelect = 0;
}
// showChannelValues(); // disabled so that changes only start after menu is selected again
}
void showChannelValues() { // display the current intensity for each led channel
lcd.setCursor(9,0);
switch (overSelect) {
case 0:
lcd.print("Timer ");
break;
case 1:
lcd.print("All Max");
break;
case 2:
lcd.print("All Min");
break;
case 3:
lcd.print("All ");
if (overPercent < 10)
lcd.print(" ");
lcd.print(overPercent);
lcd.print("%");
break;
}
lcd.setCursor(0,1);
for (int i = 0; i < MAX_CHANNELS; i++) {
lcd.print(" ");
if (channel[i].ledVal < 10)
lcd.print(" ");
else if (channel[i].ledVal < 100)
lcd.print(" ");
lcd.print(channel[i].ledVal);
}
}
// Actually needs to become a feature of the EEPromVar library -- later
// should just be a function of the assignment operators
void updateEEPromVars() {
// Update EEProms with any values that may have changed
// Only updates values that have changed
#ifdef DEBUG_ON
Serial.println("Updating EEPromVars");
#endif
if (channel[0].StartMins != oneStartMins) {
oneStartMins = channel[0].StartMins;
}
if (channel[0].PhotoPeriod != onePhotoPeriod) {
onePhotoPeriod = channel[0].PhotoPeriod;
}
if (channel[0].ledMin != oneMin) {
oneMin = channel[0].ledMin;
}
if (channel[0].ledMax != oneMax) {
oneMax = channel[0].ledMax;
}
if (channel[0].FadeDuration != oneFadeDuration) {
oneFadeDuration = channel[0].FadeDuration;
}
if (channel[1].StartMins != twoStartMins) {
twoStartMins = channel[1].StartMins;
}
if (channel[1].PhotoPeriod != twoPhotoPeriod) {
twoPhotoPeriod = channel[1].PhotoPeriod;
}
if (channel[1].ledMin != twoMin) {
twoMax = channel[1].ledMax;
}
if (channel[1].ledMax != twoMax) {
twoMax = channel[1].ledMax;
}
if (channel[1].FadeDuration != twoFadeDuration) {
twoFadeDuration = channel[1].FadeDuration;
}
if (channel[2].StartMins != threeStartMins) {
threeStartMins = channel[2].StartMins;
}
if (channel[2].PhotoPeriod != threePhotoPeriod) {
threePhotoPeriod = channel[2].PhotoPeriod;
}
if (channel[2].ledMin != threeMin) {
threeMin = channel[2].ledMin;
}
if (channel[2].ledMax != threeMax) {
threeMax = channel[2].ledMax;
}
if (channel[2].FadeDuration != threeFadeDuration) {
threeFadeDuration = channel[2].FadeDuration;
}
if (channel[3].StartMins != fourStartMins) {
fourStartMins = channel[3].StartMins;
}
if (channel[3].PhotoPeriod != fourPhotoPeriod) {
fourPhotoPeriod = channel[3].PhotoPeriod;
}
if (channel[3].ledMin != fourMin) {
fourMin = channel[3].ledMin;
}
if (channel[3].ledMax != fourMax) {
fourMax = channel[3].ledMax;
}
if (channel[3].FadeDuration != fourFadeDuration) {
fourFadeDuration = channel[3].FadeDuration;
}
}
/** Menu Control Functions **/
void showMainMenu () {
Serial.println("");
Serial.println("LED Control Actions");
Serial.println("L+ All Max");
Serial.println("L- All Min");
Serial.println("Lnnn All nnn%");
Serial.println("LT Timer Mode");
Serial.println("");
}
int parseMenuCommand () {
int command;
int value = 0;
int pct = 0;
command = Serial.read();
switch (command) {
case 'l':
case 'L':
if (Serial.available() > 0) {
value = Serial.read();
if (value == 't' || value == 'T') {
overSelect = 0;
Serial.println("Setting Timer Mode");
}
if (value == '+') {
overSelect = 1;
Serial.println("All Max");
}
if (value == '-') {
overSelect = 2;
Serial.println("All Min");
}
if (value >= '0' && value <= '9') {
pct = value - '0';
while (Serial.available() > 0) {
value = Serial.read();
if (value >= '0' && value <= '9')
pct = pct * 10 + value - '0';
else
break;
}
if (overPercent <= 100) {
overSelect = 3;
overPercent = pct;
Serial.print("Setting ");
Serial.print(overPercent);
Serial.println("%");
}
}
}
else { // unrecognized command
}
}
Serial.flush(); // remove all incoming data
return (0);
}
Enter your email address to join:
Register today and take advantage of membership benefits.
Enter your email address to join: