Natural Reef Aquarium Lighting Controller

Numlock10

New member
Hello,

I just wanted to post an updated control program for people that might be interested. There has been an increase in people that seem to be interested in the old code that was posed a few years back.

Since there was some interest I decided to have a look at the code and see if I could improve on it. I pretty much decided to try and do a re-write and have come up with the following;

This code will simulate and natural sunrise and sunset for any location on the planet at a given date.

All you need to provide the code is the Latitude, Longitude and Time Zone for the position on the earth. Along with the RTC it will calculate the lighting times.

Please have a look and let me know what you think? If there is anything you would like to see added or changed, please let me know. I plan to work on the Lunar cycle along with adding weather functions soon....


PHP:
// Natural Reef Aquarium Lighting V1.0
// 16/06/2013
// Developed by J. Harp (nUm - RTAW Forums, Numlock10 - Reef Central Forums)
// Formulas based off of information from NOAA website for sunrise / sunset times.
// Compiled in Arduino 1.5.2
//
// Future Development:
// Lunar Cycle
// Weather Simulation
//
// Please feel free to use this and modify as you see fit, if you have any comments or suggestions please let me know via messages on the forums listed above.
//

#include <math.h>
#include <Wire.h>
#define DS1307_I2C_ADDRESS 0x68

// RTC variables
byte second, rtcMins, oldMins, rtcHrs, oldHrs, dayOfWeek, dayOfMonth, month, year, psecond;

// LED variables (Change to match your needs)
byte bluePins[]      =  {5, 3, 9};      // PWM pins for blues
byte whitePins[]     =  {10, 11, 6};    // PWM pins for whites

byte blueChannels    =        3;    // how many PWMs for blues (count from above)
byte whiteChannels   =        3;    // how many PWMs for whites (count from above)

int PWMHigh          =       255;   // High value for PWM - if your values are noraml this is 255, if your values are inverted this is 0
int PWMLow           =       0;     // Low value for PWM - if your values are noraml this is 0, if your values are inverted this is 255

// Set for the location of the world you want to replicate.

float latitude = -19.770621;   // + to N  Defualt - (-19.770621) Heart Reef, Great Barrier Reef, QLD, Australia 
float longitude = 149.238532;  // + to E  Defualt - (149.238532)
int TimeZone = 10;             // + to E  Defulat - (10)


float fullSun = 37.5;  // sun elevation in deg that we will assume full sunlight values
int delayTime = 0;     // start time delay in minutes,  - will push the day back, + will bring the day forward


int LedLight (byte _ledPin, byte _year, byte _month, byte _day, byte _hour, byte _min, byte _sec)
{
  float a = floor((14 - _month)/12);
  float y = _year + 4800 - a;
  float m = _month + (12 * a) - 3;
  float AH;
  int result;
  
  float JC = (((_day + floor(((153 * m) + 2) / 5) + (365 * y) + floor(y / 4) - floor(y / 100) + floor(y / 400) - 32045) + ((_hour / 24.0) + (_min / 1444.0) + (_sec / 86400.0))) - 2451545) / 36525;
  
  float GMLS = fmod(280.46646+JC*(36000.76983 + JC * 0.0003032),360);
  
  float GMAS = 357.52911 + JC * (35999.05029 - 0.0001537 * JC);
  
  float EEO = 0.016708634 - JC * (0.000042037 + 0.0000001267 * JC);
  
  float SEoC = sin((GMAS * M_PI)/180)*(1.914602 - JC * (0.004817 + 0.000014 * JC)) + sin(((2 * GMAS) * M_PI) / 180) * (0.019993 - 0.000101 * JC) + sin(((3 * JC) * M_PI) / 180) * 0.000289;
  
  float STL = GMLS + SEoC;
  
  float STA = GMAS + SEoC;
  
  float SRV = (1.000001018 * (1 - EEO * EEO)) / (1 + EEO * cos((STA * M_PI) / 180));
  
  float SAL = STL - 0.00569 - 0.00478 * sin(((125.04 - 1934.136 * JC) * M_PI) / 180);
  
  float MOE = 23 + (26 + ((21.448 - JC * (46.815 + JC * (0.00059 - JC * 0.001813)))) / 60) / 60;
  
  float OC = MOE + 0.00256 * cos(((215.04 - 1934.136 * JC) * M_PI) / 180);
  
  float SD = (asin(sin((OC * M_PI) / 180) * sin((SAL * M_PI) / 180))) * (180 / M_PI);
  
  float vy = tan(((OC / 2) * M_PI) / 180) * tan(((OC / 2) * M_PI) / 180);
  
  float EQoT = (4 * (vy * (sin(2 * ((GMLS * M_PI) / 180)) - 2 * EEO * sin((GMAS * M_PI) / 180) + 4 * EEO * vy * sin((GMAS * M_PI) / 180) * cos( 2 * ((GMLS * M_PI) / 180)) - 0.5 * vy * vy * sin(4 * ((GMLS * M_PI) / 180)) - 1.25 * EEO * EEO * sin(2 * ((GMAS * M_PI) / 180))))) * (180 / M_PI);
  
  float HAS = acos(cos((90.833 * M_PI) / 180) / (cos((latitude * M_PI) / 180) * cos((SD * M_PI) / 180)) - tan((latitude * M_PI) / 180) * tan((SD * M_PI) / 180)) * (180 / M_PI);

  float SN = (720 - 4 * longitude - EQoT + TimeZone * 60);

  float SR = SN - HAS * 4;

  float SS = SN + HAS * 4;
 
  float STD = 8 * HAS;
  
  float TST = fmod((((_hour) + (_min / 60.0) + (_sec / 3600.0)) / 24.0)*1440 + EQoT + 4 * longitude - 60 * TimeZone,1440)+delayTime;
 
  if (TST / 4 < 0)
  {
  AH = ((TST / 4.0) + 180);
  }
  else
  {
  AH = ((TST / 4.0) - 180);  
  }
  
  float SZA = (acos(sin((latitude * M_PI) / 180) * sin((SD * M_PI) / 180) + cos((latitude * M_PI) / 180) * cos((SD * M_PI) / 180) * cos((AH * M_PI) / 180))) * (180 / M_PI);
  
  float SEA = 90 - SZA;
  
  if (SEA < 0)  
  {
   result = 0;    
  }
  
  if (SEA > 0 && SEA < fullSun)
  {
  result = map(SEA,0,fullSun,PWMLow,PWMHigh);
  }
  
  if (SEA > fullSun)  
  {
  result = PWMHigh;
  }

  analogWrite(_ledPin, result);  
  return result;
  
}

/***** RTC Functions *******/
/***************************/
// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val)
{
  return ( (val/10*16) + (val%10) );
}

// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
  return ( (val/16*10) + (val%16) );
}

// Gets the date and time from the ds1307
void getDateDs1307(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());
}

void setup() {
Serial.begin(9600);
Wire.begin();
}

void loop() {
  getDateDs1307(&second, &rtcMins, &rtcHrs, &dayOfWeek, &dayOfMonth, &month, &year);
  if (psecond != second){
    psecond = second;
  Serial.print(rtcHrs);
  Serial.print(":");
  Serial.print(rtcMins);
  Serial.print(":");
  Serial.print(second);
  Serial.print(" ");
  Serial.print(dayOfMonth);
  Serial.print("/");
  Serial.print(month);
  Serial.print("/");
  Serial.println(year);
  update_leds();
  }
}

void update_leds ( void ){
  int i;
  byte value;
  Serial.println("Blue LED's");
  for (i = 0; i < blueChannels; i++)
  {
    value = LedLight(bluePins[i],year,month,dayOfMonth,rtcHrs,rtcMins,second);
    Serial.print(map(value,PWMLow,PWMHigh,0,100));
    Serial.print("% ");
  }
  Serial.println();
  Serial.println("White LED's");
  for (i = 0; i < whiteChannels; i++)
  {
    value = LedLight(whitePins[i],year,month,dayOfMonth,rtcHrs,rtcMins,second);
    Serial.print(map(value,PWMLow,PWMHigh,0,100));
    Serial.print("% ");
  }
  Serial.println();
}
 
Cool! But all that float math will take several seconds to complete.
Is there more than one channel? I don't think that's possible, but it would be interesting to integrate.
 
I have run it on 6 channels in my testing, I haven't had any problems with the calculations taking too long so far.
 
I have updated to include the Lunar cycle into the code;

PHP:
// Natural Reef Aquarium Lighting V2.0
// 16/06/2013
// Developed by J. Harp (nUm - RTAW Forums, Numlock10 - Reef Central Forums)
// Formulas based off of information from NOAA website for sunrise / sunset times.
// Includes Lunar Simulation.
// Compiled in Arduino 1.5.2
//
// Future Development:
// Weather Simulation
//
// Please feel free to use this and modify as you see fit, if you have any comments or suggestions please let me know via messages on the forums listed above.
//

#include <math.h>
#include <Wire.h>
#define DS1307_I2C_ADDRESS 0x68

// RTC variables
byte second, rtcMins, oldMins, rtcHrs, oldHrs, dayOfWeek, dayOfMonth, month, year, psecond;

// LED variables (Change to match your needs)
byte bluePins[]      =  {5, 3, 9};      // PWM pins for blues
byte whitePins[]     =  {10, 11};    // PWM pins for whites
byte moonPins[]      =  {6};            // PWM pins for moonlight

byte blueChannels    =        3;    // how many PWMs for blues (count from above)
byte whiteChannels   =        2;    // how many PWMs for whites (count from above)
byte moonChannels    =        1;    // how many PWMs from moon (count from above)

byte BluePWMHigh[]          =       {255, 255, 255};   // High value for PWM - if your values are noraml this is 255, if your values are inverted this is 0
byte BluePWMLow[]           =       {0, 0, 0};         // Low value for PWM - if your values are noraml this is 0, if your values are inverted this is 255
byte WhitePWMHigh[]         =       {255, 255};        // High value for PWM - if your values are noraml this is 255, if your values are inverted this is 0
byte WhitePWMLow[]          =       {0, 0};            // Low value for PWM - if your values are noraml this is 0, if your values are inverted this is 255
byte MoonPWMHigh[]          =       {255};             // High value for PWM - if your values are noraml this is 255, if your values are inverted this is 0
byte MoonPWMLow[]           =       {0};               // Low value for PWM - if your values are noraml this is 0, if your values are inverted this is 255

// Set for the location of the world you want to replicate.

float latitude = -19.770621;   // + to N  Defualt - (-19.770621) Heart Reef, Great Barrier Reef, QLD, Australia 
float longitude = 149.238532;  // + to E  Defualt - (149.238532)
int TimeZone = 10;             // + to E  Defulat - (10)

// Julian Century Varaiable

float JC;

// Sunlight Variables

float fullSun = 37.5;  // sun elevation in deg that we will assume full sunlight values (Larger = more sunlight)
int delayTime = 0;     // start time delay in minutes,  - will push the day back, + will bring the day forward


int SunLight (byte _ledPin, byte _ledHigh, byte _ledLow, byte _year, byte _month, byte _day, byte _hour, byte _min, byte _sec)
{
  float a = floor((14 - _month)/12);
  float y = _year + 4800 - a;
  float m = _month + (12 * a) - 3;
  float AH;
  int result;
  
  float JC = (((_day + floor(((153 * m) + 2) / 5) + (365 * y) + floor(y / 4) - floor(y / 100) + floor(y / 400) - 32045) + ((_hour / 24.0) + (_min / 1444.0) + (_sec / 86400.0))) - 2451545) / 36525;
  
  float GMLS = fmod(280.46646+JC*(36000.76983 + JC * 0.0003032),360);
  
  float GMAS = 357.52911 + JC * (35999.05029 - 0.0001537 * JC);
  
  float EEO = 0.016708634 - JC * (0.000042037 + 0.0000001267 * JC);
  
  float SEoC = sin((GMAS * M_PI)/180)*(1.914602 - JC * (0.004817 + 0.000014 * JC)) + sin(((2 * GMAS) * M_PI) / 180) * (0.019993 - 0.000101 * JC) + sin(((3 * JC) * M_PI) / 180) * 0.000289;
  
  float STL = GMLS + SEoC;
  
  float STA = GMAS + SEoC;
  
  float SRV = (1.000001018 * (1 - EEO * EEO)) / (1 + EEO * cos((STA * M_PI) / 180));
  
  float SAL = STL - 0.00569 - 0.00478 * sin(((125.04 - 1934.136 * JC) * M_PI) / 180);
  
  float MOE = 23 + (26 + ((21.448 - JC * (46.815 + JC * (0.00059 - JC * 0.001813)))) / 60) / 60;
  
  float OC = MOE + 0.00256 * cos(((215.04 - 1934.136 * JC) * M_PI) / 180);
  
  float SD = (asin(sin((OC * M_PI) / 180) * sin((SAL * M_PI) / 180))) * (180 / M_PI);
  
  float vy = tan(((OC / 2) * M_PI) / 180) * tan(((OC / 2) * M_PI) / 180);
  
  float EQoT = (4 * (vy * (sin(2 * ((GMLS * M_PI) / 180)) - 2 * EEO * sin((GMAS * M_PI) / 180) + 4 * EEO * vy * sin((GMAS * M_PI) / 180) * cos( 2 * ((GMLS * M_PI) / 180)) - 0.5 * vy * vy * sin(4 * ((GMLS * M_PI) / 180)) - 1.25 * EEO * EEO * sin(2 * ((GMAS * M_PI) / 180))))) * (180 / M_PI);
  
  float HAS = acos(cos((90.833 * M_PI) / 180) / (cos((latitude * M_PI) / 180) * cos((SD * M_PI) / 180)) - tan((latitude * M_PI) / 180) * tan((SD * M_PI) / 180)) * (180 / M_PI);

  float SN = (720 - 4 * longitude - EQoT + TimeZone * 60);

  float SR = SN - HAS * 4;

  float SS = SN + HAS * 4;
 
  float STD = 8 * HAS;
  
  float TST = fmod((((_hour) + (_min / 60.0) + (_sec / 3600.0)) / 24.0)*1440 + EQoT + 4 * longitude - 60 * TimeZone,1440)+delayTime;
 
  if (TST / 4 < 0)
  {
  AH = ((TST / 4.0) + 180);
  }
  else
  {
  AH = ((TST / 4.0) - 180);  
  }
  
  float SZA = (acos(sin((latitude * M_PI) / 180) * sin((SD * M_PI) / 180) + cos((latitude * M_PI) / 180) * cos((SD * M_PI) / 180) * cos((AH * M_PI) / 180))) * (180 / M_PI);
  
  int SEA = 90 - SZA;
  
  if (SEA < 0)  
  {
   result = _ledLow;    
  }
  
  if (SEA > 0 && SEA < fullSun)
  {
  result = map(SEA,0,fullSun,_ledLow,_ledHigh);
  }
  
  if (SEA > fullSun)  
  {
  result = _ledHigh;
  }
  
  analogWrite(_ledPin, result);  
  return result;
  
}

int MoonLight (float JC, byte _ledPin, byte _ledHigh, byte _ledLow)
{
 int result;
 
 float MS = fmod((2456318.69458333 - JC),29.530589);
 
 if(MS < 14.7518)
 {
  result = map(MS,0,14.7518,_ledLow,_ledHigh);
 }
 
 if( MS > 14.7518)
 {
   result = map(MS,14.7518,29.530589,_ledHigh,_ledLow);
 }
 Serial.print("Moon - ");
  if(MS >= 0 && MS < 2)
  {
  Serial.println("New Moon");
  }
  if(MS >= 2 && MS < 5.5)
  {
  Serial.println("Waning Crescent");
  }
  if(MS >= 5.5 && MS < 8.5)
  {
  Serial.println("Last Quarter");
  }
  if(MS >= 8.5 && MS < 12)
  {
  Serial.println("Waning Gibbous");
  }
  if(MS >= 12 && MS < 16)
  {
  Serial.println("Full Moon");
  }
  if(MS >= 16 && MS < 20)
  {
  Serial.println("Waxing Gibbous");
  }
  if(MS >= 20 && MS < 24)
  {
  Serial.println("First Quarter");
  }
  if(MS >= 24 && MS < 28)
  {
  Serial.println("Waxing Crescent");
  }
  if(MS >= 28 && MS < 30)
  {
  Serial.println("New Moon");
  }
 analogWrite(_ledPin, result); 
 return result;
}

   
 

/***** RTC Functions *******/
/***************************/
// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val)
{
  return ( (val/10*16) + (val%10) );
}

// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
  return ( (val/16*10) + (val%16) );
}

// Gets the date and time from the ds1307
void getDateDs1307(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());
}

void setup() {
delay(500);
Serial.begin(57600);
Wire.begin();
}

void loop() {
  getDateDs1307(&second, &rtcMins, &rtcHrs, &dayOfWeek, &dayOfMonth, &month, &year);
  if (psecond != second){
    psecond = second;
  Serial.print(rtcHrs);
  Serial.print(":");
  Serial.print(rtcMins);
  Serial.print(":");
  Serial.print(second);
  Serial.print(" ");
  Serial.print(dayOfMonth);
  Serial.print("/");
  Serial.print(month);
  Serial.print("/");
  Serial.println(year);
  update_leds();
  }
}

void update_leds ( void ){
  int i;
  byte value;
  int MS;
  Serial.println("Blue LED's");
  for (i = 0; i < blueChannels; i++)
  {
    value = SunLight(bluePins[i],BluePWMHigh[i],BluePWMLow[i],year,month,dayOfMonth,rtcHrs,rtcMins,second);
    Serial.print(map(value,BluePWMLow[i],BluePWMHigh[i],0,100));
    Serial.print("% ");
  }
  Serial.println();
  Serial.println("White LED's");
  for (i = 0; i < whiteChannels; i++)
  {
    value = SunLight(whitePins[i],WhitePWMHigh[i],WhitePWMLow[i],year,month,dayOfMonth,rtcHrs,rtcMins,second);
    Serial.print(map(value,WhitePWMLow[i],WhitePWMHigh[i],0,100));
    Serial.print("% ");
  }
  Serial.println();
  for (i = 0; i < moonChannels; i ++)
  {
  MS = MoonLight(JC, moonPins[i],MoonPWMHigh[i],MoonPWMLow[i]);
  }
}
 
can you explain a little more about how to hook up the RTC board for this sketch to work with it and also do I need to add resistors to SDA or SCL I am very new to a lot of this
 
There really isn't a whole lot required to hook up the RTC, you just need to hook it up to the 5vcc, Ground, Analog pin 4 for SDA Analog pin 5 for SCL.

You don't need any resistors on the SDA or SCL.

Cheers,

Jason
 
This is great news! I was missing the lunar cycle. I will test it when I get home from work.
Thanks!

//Patrik
 
I have some trouble getting this thing to work and got 2 questions:

1. How do I set the delay time? If I want the light to shut of around 21.00 do I set it to -240?

2. Should I have a empty space between - sign and the number like this: - 240

//Patrik
 
putting -240 in the offset would adjust the start and finish time by 4 hours. you dont put a space between - and the 240.

So right now the lights would turn off around 6pm so if you put the -240 that would make it around 10pm.

Let me know how you go.

Cheers,

Jason
 
Hi!
I've been trying the code now for a couple of days. Everything went well the first day but then I got probleme with the arduino mega bord (actually, it's a fundino mega 2560). It started with that some pwm pin did respond so I fix that by choosing some other. But, in the end, none of the pins did work and the chip get very hot. When trying to clean eeprom the bord aint answering so I guess I have to buy me anoter one.
Now, what kind of board do I need? Is it the mega board that is the best or could I buy cheaper one, like leonardo for running your code? Which one is you using?

The leds are running by using meanwell ldd 700-h and ldd 1000-h drivers and I would like to be able to separeted the leds by 4 channels (1 blue, 2 whites and 1 moonlight)

//Patrik
 
#Patrick

sorry to hear that your board has stuffed up, I use a Arduino Duemilanove with the Atmega328 chip. I have had mine up and running for about 7 days now, but I don't have any LED's setup at the moment so there are not any channels attached to the PWM pins. Hopefully its not an issue with the code, but it does have quite a bit of float math calculations. I might try and see if I can eliminate some of the float math in some of the future updates.

I have attached my most up to date code, it includes a lunar cycle as well as an additional string for UV leds. I could modify further if there are more separate strings required.

PHP:
// Natural Reef Aquarium Lighting V2.3
// 16/06/2013
// Developed by J. Harp (nUm - RTAW Forums, Numlock10 - Reef Central Forums)
// Formulas based off of information from NOAA website for sunrise / sunset times.
// Includes Lunar Simulation.
// Compiled in Arduino 1.5.2
//
// Testing;
// Additonal colour channels
// Unique "fullsun" values for each string
//
// Future Development:
// Weather Simulation
//
// Please feel free to use this and modify as you see fit, if you have any comments or suggestions please let me know via messages on the forums listed above.
//

#include <math.h>
#include <Wire.h>
#define DS1307_I2C_ADDRESS 0x68

// RTC variables
byte second, rtcMins, oldMins, rtcHrs, oldHrs, dayOfWeek, dayOfMonth, month, year, psecond;

// LED variables (Change to match your needs)
byte bluePins[]      =  {3, 9};      // PWM pins for blues
byte whitePins[]     =  {10, 11};    // PWM pins for whites
byte uvPins[]        =  {5};         // PWM pins for UVs
byte moonPins[]      =  {6};            // PWM pins for moonlights

byte blueChannels    =        2;    // how many PWMs for blues (count from above)
byte whiteChannels   =        2;    // how many PWMs for whites (count from above)
byte uvChannels      =        1;    // how many PWMs for uv (count from above)
byte moonChannels    =        1;    // how many PWMs from moon (count from above)

byte BluePWMHigh[]          =       {255, 255};        // High value for Blue PWM each vale is for each string - if your values are noraml this is 255, if your values are inverted this is 0
byte BluePWMLow[]           =       {0, 0};            // Low value for Blue PWM - if your values are noraml this is 0, if your values are inverted this is 255
float BlueFull[]            =       {25, 25};          // Value in degrees (sun angle) that each Blue string will be at max output
byte WhitePWMHigh[]         =       {255, 255};        // High value for White PWM - if your values are noraml this is 255, if your values are inverted this is 0
byte WhitePWMLow[]          =       {0, 0};            // Low value for White PWM - if your values are noraml this is 0, if your values are inverted this is 255
float WhiteFull[]           =       {37.5, 37.5};      // Value in degrees (sun angle) that each White string will be at max output
byte UVPWMHigh[]            =       {255};             // High value for UV PWM - if your values are noraml this is 255, if your values are inverted this is 0
byte UVPWMLow[]             =       {0};               // Low value for UV PWM - if your values are noraml this is 0, if your values are inverted this is 255
float UVFull[]              =       {30};              // Value in degrees (sun angle) that each UV string will be at max output
byte MoonPWMHigh[]          =       {255};             // High value for Moon PWM - if your values are noraml this is 255, if your values are inverted this is 0
byte MoonPWMLow[]           =       {0};               // Low value for Moon PWM - if your values are noraml this is 0, if your values are inverted this is 255

// Set for the location of the world you want to replicate.

float latitude = -19.770621;   // + to N  Defualt - (-19.770621) Heart Reef, Great Barrier Reef, QLD, Australia 
float longitude = 149.238532;  // + to E  Defualt - (149.238532)
int TimeZone = 10;             // + to E  Defulat - (10)

// Julian Century Varaiable

float JC;

// Sunlight Variables

//float fullSun = 37.5;  // sun elevation in deg that we will assume full sunlight values (Larger = more sunlight)
int delayTime = 0;     // start time delay in minutes,  - will push the day back, + will bring the day forward


int SunLight (byte _ledPin, byte _ledHigh, byte _ledLow, float _fullSun, byte _year, byte _month, byte _day, byte _hour, byte _min, byte _sec)
{
  float a = floor((14 - _month)/12);
  float y = _year + 4800 - a;
  float m = _month + (12 * a) - 3;
  float AH;
  int result;
  
  JC = (((_day + floor(((153.0 * m) + 2.0) / 5.0) + (365.0 * y) + floor(y / 4.0) - floor(y / 100.0) + floor(y / 400.0) - 32045.0) + ((_hour / 24.0) + (_min / 1444.0) + (_sec / 86400.0))) - 2451556.08) / 36525.0;
  
  float GMLS = fmod(280.46646+JC*(36000.76983 + JC * 0.0003032),360);
  
  float GMAS = 357.52911 + JC * (35999.05029 - 0.0001537 * JC);
  
  float EEO = 0.016708634 - JC * (0.000042037 + 0.0000001267 * JC);
  
  float SEoC = sin((GMAS * M_PI)/180)*(1.914602 - JC * (0.004817 + 0.000014 * JC)) + sin(((2 * GMAS) * M_PI) / 180) * (0.019993 - 0.000101 * JC) + sin(((3 * JC) * M_PI) / 180) * 0.000289;
  
  float STL = GMLS + SEoC;
  
  float STA = GMAS + SEoC;
  
  float SRV = (1.000001018 * (1 - EEO * EEO)) / (1 + EEO * cos((STA * M_PI) / 180));
  
  float SAL = STL - 0.00569 - 0.00478 * sin(((125.04 - 1934.136 * JC) * M_PI) / 180);
  
  float MOE = 23 + (26 + ((21.448 - JC * (46.815 + JC * (0.00059 - JC * 0.001813)))) / 60) / 60;
  
  float OC = MOE + 0.00256 * cos(((215.04 - 1934.136 * JC) * M_PI) / 180);
  
  float SD = (asin(sin((OC * M_PI) / 180) * sin((SAL * M_PI) / 180))) * (180 / M_PI);
  
  float vy = tan(((OC / 2) * M_PI) / 180) * tan(((OC / 2) * M_PI) / 180);
  
  float EQoT = (4 * (vy * (sin(2 * ((GMLS * M_PI) / 180)) - 2 * EEO * sin((GMAS * M_PI) / 180) + 4 * EEO * vy * sin((GMAS * M_PI) / 180) * cos( 2 * ((GMLS * M_PI) / 180)) - 0.5 * vy * vy * sin(4 * ((GMLS * M_PI) / 180)) - 1.25 * EEO * EEO * sin(2 * ((GMAS * M_PI) / 180))))) * (180 / M_PI);
  
  float HAS = acos(cos((90.833 * M_PI) / 180) / (cos((latitude * M_PI) / 180) * cos((SD * M_PI) / 180)) - tan((latitude * M_PI) / 180) * tan((SD * M_PI) / 180)) * (180 / M_PI);

  float SN = (720 - 4 * longitude - EQoT + TimeZone * 60);

  float SR = SN - HAS * 4;

  float SS = SN + HAS * 4;
 
  float STD = 8 * HAS;
  
  float TST = fmod((((_hour) + (_min / 60.0) + (_sec / 3600.0)) / 24.0)*1440 + EQoT + 4 * longitude - 60 * TimeZone,1440)+delayTime;
 
  if (TST / 4 < 0)
  {
  AH = ((TST / 4.0) + 180);
  }
  else
  {
  AH = ((TST / 4.0) - 180);  
  }
  
  float SZA = (acos(sin((latitude * M_PI) / 180) * sin((SD * M_PI) / 180) + cos((latitude * M_PI) / 180) * cos((SD * M_PI) / 180) * cos((AH * M_PI) / 180))) * (180 / M_PI);
  
  int SEA = 90 - SZA;
  
  if (SEA < 0)  
  {
   result = _ledLow;    
  }
  
  if (SEA > 0 && SEA < _fullSun)
  {
  result = map(SEA,0,_fullSun,_ledLow,_ledHigh);
  }
  
  if (SEA > _fullSun)  
  {
  result = _ledHigh;
  }
  
  analogWrite(_ledPin, result);  
  return result;
  
}

int MoonLight (float JC, byte _ledPin, byte _ledHigh, byte _ledLow)
{
 int result;
 
 float MS = fmod((2456318.69458333 - JC),29.530589);
 
 if(MS < 14.7518)
 {
  result = map(MS,0,14.7518,_ledLow,_ledHigh);
 }
 
 if( MS > 14.7518)
 {
   result = map(MS,14.7518,29.530589,_ledHigh,_ledLow);
 }
 analogWrite(_ledPin, result); 
 return result;
}
 

/***** RTC Functions *******/
/***************************/
// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val)
{
  return ( (val/10*16) + (val%10) );
}

// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
  return ( (val/16*10) + (val%16) );
}

// Gets the date and time from the ds1307
void getDateDs1307(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());
}

void setup() {
delay(500);
Serial.begin(57600);
Wire.begin();
}

void loop() {
  getDateDs1307(&second, &rtcMins, &rtcHrs, &dayOfWeek, &dayOfMonth, &month, &year);
  if (psecond != second){
    psecond = second;
  Serial.print(rtcHrs);
  Serial.print(":");
  Serial.print(rtcMins);
  Serial.print(":");
  Serial.print(second);
  Serial.print(" ");
  Serial.print(dayOfMonth);
  Serial.print("/");
  Serial.print(month);
  Serial.print("/");
  Serial.println(year);
  update_leds();
  }
}

void update_leds ( void ){
  int i;
  byte value;
  int MS;
  Serial.println("Blue LED's");
  for (i = 0; i < blueChannels; i++)
  {
    value = SunLight(bluePins[i],BluePWMHigh[i],BluePWMLow[i],BlueFull[i],year,month,dayOfMonth,rtcHrs,rtcMins,second);
    Serial.print(map(value,BluePWMLow[i],BluePWMHigh[i],0,100));
    Serial.print("% ");
  }
  Serial.println();
  Serial.println("White LED's");
  for (i = 0; i < whiteChannels; i++)
  {
    value = SunLight(whitePins[i],WhitePWMHigh[i],WhitePWMLow[i],WhiteFull[i],year,month,dayOfMonth,rtcHrs,rtcMins,second);
    Serial.print(map(value,WhitePWMLow[i],WhitePWMHigh[i],0,100));
    Serial.print("% ");
  }
  Serial.println();
  Serial.println("UV LED's");
  for (i = 0; i < uvChannels; i++)
  {
    value = SunLight(uvPins[i],UVPWMHigh[i],UVPWMLow[i],UVFull[i],year,month,dayOfMonth,rtcHrs,rtcMins,second);
    Serial.print(map(value,UVPWMLow[i],UVPWMHigh[i],0,100));
    Serial.print("% ");
  }
  Serial.println();
  Serial.println("Moon Value");
  for (i = 0; i < moonChannels; i ++)
  {
  MS = MoonLight(JC, moonPins[i],MoonPWMHigh[i],MoonPWMLow[i]);
  Serial.println(MS);
  Serial.print(map(MS,MoonPWMLow[i],MoonPWMHigh[i],0,100));
  Serial.print("% ");
  }
  Serial.println();
}
 
Finally, I have now bought a new board (Arduino Uno) and also has test driven it in the serial monitor with your code. Everything seems to work as it should.
I, however, a question regarding your sketchbook.
What does float value means?

Ex:
Code:
byte BluePWMHigh[]          =       {255, 255};        // High value for Blue PWM each vale is for each string - if your values are noraml this is 255, if your values are inverted this is 0
byte BluePWMLow[]           =       {0, 0};            // Low value for Blue PWM - if your values are noraml this is 0, if your values are inverted this is 255
float BlueFull[]            =       {25, 25};          // Value in degrees (sun angle) that each Blue string will be at max output

Sorry if it's a noob question but I just can understand that part of the code :hmm2:

//Patrik
 
"Float" is just another term for "decimal" in programming.
A "Double" is like a float, but has twice the precision normally. But in all Arduino boards (except the Due) a double is exactly like a float.

To OP:
have you tried replacing all floats with numbers x 100? I find that is a good way to keep precision, to a point. Not sure if it would work in this situation.
For instance, if you want to get a decimal for 5/3, you do (5 x 1000)/3, and get 1666 (Arduino rounds down). Then, when all calculations are done, you convert it into:
float meep =float(float(1666)/1000.0)
That way, you only need to do one float math in the entire program.
 
"Float" is just another term for "decimal" in programming.
A "Double" is like a float, but has twice the precision normally. But in all Arduino boards (except the Due) a double is exactly like a float.

Thanks for the explanation!

//Patrik
 
Hello!
I have now tried the sketch with some modification. It does not work as it should and I would be grateful if anyone could help me with this.
The problem occurs at sunrise and sunset.
At sunrise the leds start with 100% power. This lasts for a few minutes and then return to normal.
Much the same thing happens at sunset.
At sunset it works as it should and the leds gets weaker and weaker. Then, about when the lights will be switched off completely the lights go up to 100% suddenly. This lasts for a couple of minutes and then closed down totally.

Does anyone has any idea what the probleme can be?

Heres the code that I'm using

Code:
// Natural Reef Aquarium Lighting V2.3
// 16/06/2013
// Developed by J. Harp (nUm - RTAW Forums, Numlock10 - Reef Central Forums)
// Formulas based off of information from NOAA website for sunrise / sunset times.
// Includes Lunar Simulation.
// Compiled in Arduino 1.5.2
//
// Testing;
// Additonal colour channels
// Unique "fullsun" values for each string
//
// Future Development:
// Weather Simulation
//
// Please feel free to use this and modify as you see fit, if you have any comments or suggestions please let me know via messages on the forums listed above.
//

#include <math.h>
#include <Wire.h>
#define DS1307_I2C_ADDRESS 0x68

// RTC variables
byte second, rtcMins, oldMins, rtcHrs, oldHrs, dayOfWeek, dayOfMonth, month, year, psecond;

// LED variables (Change to match your needs)
byte bluePins[]      =  {6};      // PWM pins for blues
byte whitePins[]     =  {10, 11};    // PWM pins for whites
byte uvPins[]        =  {5, 9};         // PWM pins for UVs
byte moonPins[]      =  {3};            // PWM pins for moonlights

byte blueChannels    =        1;    // how many PWMs for blues (count from above)
byte whiteChannels   =        2;    // how many PWMs for whites (count from above)
byte uvChannels      =        2;    // how many PWMs for uv (count from above)
byte moonChannels    =        1;    // how many PWMs from moon (count from above)

byte BluePWMHigh[]          =       {255};        // High value for Blue PWM each vale is for each string - if your values are noraml this is 255, if your values are inverted this is 0
byte BluePWMLow[]           =       {0};            // Low value for Blue PWM - if your values are noraml this is 0, if your values are inverted this is 255
float BlueFull[]            =       {25};          // Value in degrees (sun angle) that each Blue string will be at max output
byte WhitePWMHigh[]         =       {200, 200};        // High value for White PWM - if your values are noraml this is 255, if your values are inverted this is 0
byte WhitePWMLow[]          =       {0, 0};            // Low value for White PWM - if your values are noraml this is 0, if your values are inverted this is 255
float WhiteFull[]           =       {37.5, 37.5};      // Value in degrees (sun angle) that each White string will be at max output
byte UVPWMHigh[]            =       {255, 255};             // High value for UV PWM - if your values are noraml this is 255, if your values are inverted this is 0
byte UVPWMLow[]             =       {0, 0};               // Low value for UV PWM - if your values are noraml this is 0, if your values are inverted this is 255
float UVFull[]              =       {30, 30};              // Value in degrees (sun angle) that each UV string will be at max output
byte MoonPWMHigh[]          =       {150};             // High value for Moon PWM - if your values are noraml this is 255, if your values are inverted this is 0
byte MoonPWMLow[]           =       {0};               // Low value for Moon PWM - if your values are noraml this is 0, if your values are inverted this is 255

// Set for the location of the world you want to replicate.

float latitude = -17.730211;   // + to N  Defualt - (-19.770621) Heart Reef, Great Barrier Reef, QLD, Australia 
float longitude = 177.127218;  // + to E  Defualt - (149.238532)
int TimeZone = 12;             // + to E  Defulat - (10)

// Julian Century Varaiable

float JC;

// Sunlight Variables

//float fullSun = 37.5;  // sun elevation in deg that we will assume full sunlight values (Larger = more sunlight)
int delayTime = -150;     // start time delay in minutes,  - will push the day back, + will bring the day forward


int SunLight (byte _ledPin, byte _ledHigh, byte _ledLow, float _fullSun, byte _year, byte _month, byte _day, byte _hour, byte _min, byte _sec)
{
  float a = floor((14 - _month)/12);
  float y = _year + 4800 - a;
  float m = _month + (12 * a) - 3;
  float AH;
  int result;
  
  JC = (((_day + floor(((153.0 * m) + 2.0) / 5.0) + (365.0 * y) + floor(y / 4.0) - floor(y / 100.0) + floor(y / 400.0) - 32045.0) + ((_hour / 24.0) + (_min / 1444.0) + (_sec / 86400.0))) - 2451556.08) / 36525.0;
  
  float GMLS = fmod(280.46646+JC*(36000.76983 + JC * 0.0003032),360);
  
  float GMAS = 357.52911 + JC * (35999.05029 - 0.0001537 * JC);
  
  float EEO = 0.016708634 - JC * (0.000042037 + 0.0000001267 * JC);
  
  float SEoC = sin((GMAS * M_PI)/180)*(1.914602 - JC * (0.004817 + 0.000014 * JC)) + sin(((2 * GMAS) * M_PI) / 180) * (0.019993 - 0.000101 * JC) + sin(((3 * JC) * M_PI) / 180) * 0.000289;
  
  float STL = GMLS + SEoC;
  
  float STA = GMAS + SEoC;
  
  float SRV = (1.000001018 * (1 - EEO * EEO)) / (1 + EEO * cos((STA * M_PI) / 180));
  
  float SAL = STL - 0.00569 - 0.00478 * sin(((125.04 - 1934.136 * JC) * M_PI) / 180);
  
  float MOE = 23 + (26 + ((21.448 - JC * (46.815 + JC * (0.00059 - JC * 0.001813)))) / 60) / 60;
  
  float OC = MOE + 0.00256 * cos(((215.04 - 1934.136 * JC) * M_PI) / 180);
  
  float SD = (asin(sin((OC * M_PI) / 180) * sin((SAL * M_PI) / 180))) * (180 / M_PI);
  
  float vy = tan(((OC / 2) * M_PI) / 180) * tan(((OC / 2) * M_PI) / 180);
  
  float EQoT = (4 * (vy * (sin(2 * ((GMLS * M_PI) / 180)) - 2 * EEO * sin((GMAS * M_PI) / 180) + 4 * EEO * vy * sin((GMAS * M_PI) / 180) * cos( 2 * ((GMLS * M_PI) / 180)) - 0.5 * vy * vy * sin(4 * ((GMLS * M_PI) / 180)) - 1.25 * EEO * EEO * sin(2 * ((GMAS * M_PI) / 180))))) * (180 / M_PI);
  
  float HAS = acos(cos((90.833 * M_PI) / 180) / (cos((latitude * M_PI) / 180) * cos((SD * M_PI) / 180)) - tan((latitude * M_PI) / 180) * tan((SD * M_PI) / 180)) * (180 / M_PI);

  float SN = (720 - 4 * longitude - EQoT + TimeZone * 60);

  float SR = SN - HAS * 4;

  float SS = SN + HAS * 4;
 
  float STD = 8 * HAS;
  
  float TST = fmod((((_hour) + (_min / 60.0) + (_sec / 3600.0)) / 24.0)*1440 + EQoT + 4 * longitude - 60 * TimeZone,1440)+delayTime;
 
  if (TST / 4 < 0)
  {
  AH = ((TST / 4.0) + 180);
  }
  else
  {
  AH = ((TST / 4.0) - 180);  
  }
  
  float SZA = (acos(sin((latitude * M_PI) / 180) * sin((SD * M_PI) / 180) + cos((latitude * M_PI) / 180) * cos((SD * M_PI) / 180) * cos((AH * M_PI) / 180))) * (180 / M_PI);
  
  int SEA = 90 - SZA;
  
  if (SEA < 0)  
  {
   result = _ledLow;    
  }
  
  if (SEA > 0 && SEA < _fullSun)
  {
  result = map(SEA,0,_fullSun,_ledLow,_ledHigh);
  }
  
  if (SEA > _fullSun)  
  {
  result = _ledHigh;
  }
  
  analogWrite(_ledPin, result);  
  return result;
  
}

int MoonLight (float JC, byte _ledPin, byte _ledHigh, byte _ledLow)
{
 int result;
 
 float MS = fmod((2456318.69458333 - JC),29.530589);
 
 if(MS < 14.7518)
 {
  result = map(MS,0,14.7518,_ledLow,_ledHigh);
 }
 
 if( MS > 14.7518)
 {
   result = map(MS,14.7518,29.530589,_ledHigh,_ledLow);
 }
 analogWrite(_ledPin, result); 
 return result;
}
 

/***** RTC Functions *******/
/***************************/
// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val)
{
  return ( (val/10*16) + (val%10) );
}

// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
  return ( (val/16*10) + (val%16) );
}

// Gets the date and time from the ds1307
void getDateDs1307(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());
}

void setup() {
delay(500);
Serial.begin(57600);
Wire.begin();
}

void loop() {
  getDateDs1307(&second, &rtcMins, &rtcHrs, &dayOfWeek, &dayOfMonth, &month, &year);
  if (psecond != second){
    psecond = second;
  Serial.print(rtcHrs);
  Serial.print(":");
  Serial.print(rtcMins);
  Serial.print(":");
  Serial.print(second);
  Serial.print(" ");
  Serial.print(dayOfMonth);
  Serial.print("/");
  Serial.print(month);
  Serial.print("/");
  Serial.println(year);
  update_leds();
  }
}

void update_leds ( void ){
  int i;
  byte value;
  int MS;
  Serial.println("Blue LED's");
  for (i = 0; i < blueChannels; i++)
  {
    value = SunLight(bluePins[i],BluePWMHigh[i],BluePWMLow[i],BlueFull[i],year,month,dayOfMonth,rtcHrs,rtcMins,second);
    Serial.print(map(value,BluePWMLow[i],BluePWMHigh[i],0,100));
    Serial.print("% ");
  }
  Serial.println();
  Serial.println("White LED's");
  for (i = 0; i < whiteChannels; i++)
  {
    value = SunLight(whitePins[i],WhitePWMHigh[i],WhitePWMLow[i],WhiteFull[i],year,month,dayOfMonth,rtcHrs,rtcMins,second);
    Serial.print(map(value,WhitePWMLow[i],WhitePWMHigh[i],0,100));
    Serial.print("% ");
  }
  Serial.println();
  Serial.println("UV LED's");
  for (i = 0; i < uvChannels; i++)
  {
    value = SunLight(uvPins[i],UVPWMHigh[i],UVPWMLow[i],UVFull[i],year,month,dayOfMonth,rtcHrs,rtcMins,second);
    Serial.print(map(value,UVPWMLow[i],UVPWMHigh[i],0,100));
    Serial.print("% ");
  }
  Serial.println();
  Serial.println("Moon Value");
  for (i = 0; i < moonChannels; i ++)
  {
  MS = MoonLight(JC, moonPins[i],MoonPWMHigh[i],MoonPWMLow[i]);
  Serial.println(MS);
  Serial.print(map(MS,MoonPWMLow[i],MoonPWMHigh[i],0,100));
  Serial.print("% ");
  }
  Serial.println();
}
 
Last edited:
#Patrick

PHP:
//float fullSun = 37.5;  // sun elevation in deg that we will assume full sunlight values (Larger = more sunlight)
int delayTime = 0;     // start time delay in minutes,  - will push the day back, + will bring the day forward

Hi!
I just wonder if the first line "//float fullSun...." should be comment out? I see that in the earlier version you have made that line i comment out.
Maybe thats the reason why I have problem with the code.

//Patrik
 
Hello!
I have now tried the sketch with some modification. It does not work as it should and I would be grateful if anyone could help me with this.
The problem occurs at sunrise and sunset.
At sunrise the leds start with 100% power. This lasts for a few minutes and then return to normal.
Much the same thing happens at sunset.
At sunset it works as it should and the leds gets weaker and weaker. Then, about when the lights will be switched off completely the lights go up to 100% suddenly. This lasts for a couple of minutes and then closed down totally.

Does anyone has any idea what the probleme can be?

Heres the code that I'm using

Code:
// Natural Reef Aquarium Lighting V2.3
// 16/06/2013
// Developed by J. Harp (nUm - RTAW Forums, Numlock10 - Reef Central Forums)
// Formulas based off of information from NOAA website for sunrise / sunset times.
// Includes Lunar Simulation.
// Compiled in Arduino 1.5.2
//
// Testing;
// Additonal colour channels
// Unique "fullsun" values for each string
//
// Future Development:
// Weather Simulation
//
// Please feel free to use this and modify as you see fit, if you have any comments or suggestions please let me know via messages on the forums listed above.
//

#include <math.h>
#include <Wire.h>
#define DS1307_I2C_ADDRESS 0x68

// RTC variables
byte second, rtcMins, oldMins, rtcHrs, oldHrs, dayOfWeek, dayOfMonth, month, year, psecond;

// LED variables (Change to match your needs)
byte bluePins[]      =  {6};      // PWM pins for blues
byte whitePins[]     =  {10, 11};    // PWM pins for whites
byte uvPins[]        =  {5, 9};         // PWM pins for UVs
byte moonPins[]      =  {3};            // PWM pins for moonlights

byte blueChannels    =        1;    // how many PWMs for blues (count from above)
byte whiteChannels   =        2;    // how many PWMs for whites (count from above)
byte uvChannels      =        2;    // how many PWMs for uv (count from above)
byte moonChannels    =        1;    // how many PWMs from moon (count from above)

byte BluePWMHigh[]          =       {255};        // High value for Blue PWM each vale is for each string - if your values are noraml this is 255, if your values are inverted this is 0
byte BluePWMLow[]           =       {0};            // Low value for Blue PWM - if your values are noraml this is 0, if your values are inverted this is 255
float BlueFull[]            =       {25};          // Value in degrees (sun angle) that each Blue string will be at max output
byte WhitePWMHigh[]         =       {200, 200};        // High value for White PWM - if your values are noraml this is 255, if your values are inverted this is 0
byte WhitePWMLow[]          =       {0, 0};            // Low value for White PWM - if your values are noraml this is 0, if your values are inverted this is 255
float WhiteFull[]           =       {37.5, 37.5};      // Value in degrees (sun angle) that each White string will be at max output
byte UVPWMHigh[]            =       {255, 255};             // High value for UV PWM - if your values are noraml this is 255, if your values are inverted this is 0
byte UVPWMLow[]             =       {0, 0};               // Low value for UV PWM - if your values are noraml this is 0, if your values are inverted this is 255
float UVFull[]              =       {30, 30};              // Value in degrees (sun angle) that each UV string will be at max output
byte MoonPWMHigh[]          =       {150};             // High value for Moon PWM - if your values are noraml this is 255, if your values are inverted this is 0
byte MoonPWMLow[]           =       {0};               // Low value for Moon PWM - if your values are noraml this is 0, if your values are inverted this is 255

// Set for the location of the world you want to replicate.

float latitude = -17.730211;   // + to N  Defualt - (-19.770621) Heart Reef, Great Barrier Reef, QLD, Australia 
float longitude = 177.127218;  // + to E  Defualt - (149.238532)
int TimeZone = 12;             // + to E  Defulat - (10)

// Julian Century Varaiable

float JC;

// Sunlight Variables

//float fullSun = 37.5;  // sun elevation in deg that we will assume full sunlight values (Larger = more sunlight)
int delayTime = -150;     // start time delay in minutes,  - will push the day back, + will bring the day forward


int SunLight (byte _ledPin, byte _ledHigh, byte _ledLow, float _fullSun, byte _year, byte _month, byte _day, byte _hour, byte _min, byte _sec)
{
  float a = floor((14 - _month)/12);
  float y = _year + 4800 - a;
  float m = _month + (12 * a) - 3;
  float AH;
  int result;
  
  JC = (((_day + floor(((153.0 * m) + 2.0) / 5.0) + (365.0 * y) + floor(y / 4.0) - floor(y / 100.0) + floor(y / 400.0) - 32045.0) + ((_hour / 24.0) + (_min / 1444.0) + (_sec / 86400.0))) - 2451556.08) / 36525.0;
  
  float GMLS = fmod(280.46646+JC*(36000.76983 + JC * 0.0003032),360);
  
  float GMAS = 357.52911 + JC * (35999.05029 - 0.0001537 * JC);
  
  float EEO = 0.016708634 - JC * (0.000042037 + 0.0000001267 * JC);
  
  float SEoC = sin((GMAS * M_PI)/180)*(1.914602 - JC * (0.004817 + 0.000014 * JC)) + sin(((2 * GMAS) * M_PI) / 180) * (0.019993 - 0.000101 * JC) + sin(((3 * JC) * M_PI) / 180) * 0.000289;
  
  float STL = GMLS + SEoC;
  
  float STA = GMAS + SEoC;
  
  float SRV = (1.000001018 * (1 - EEO * EEO)) / (1 + EEO * cos((STA * M_PI) / 180));
  
  float SAL = STL - 0.00569 - 0.00478 * sin(((125.04 - 1934.136 * JC) * M_PI) / 180);
  
  float MOE = 23 + (26 + ((21.448 - JC * (46.815 + JC * (0.00059 - JC * 0.001813)))) / 60) / 60;
  
  float OC = MOE + 0.00256 * cos(((215.04 - 1934.136 * JC) * M_PI) / 180);
  
  float SD = (asin(sin((OC * M_PI) / 180) * sin((SAL * M_PI) / 180))) * (180 / M_PI);
  
  float vy = tan(((OC / 2) * M_PI) / 180) * tan(((OC / 2) * M_PI) / 180);
  
  float EQoT = (4 * (vy * (sin(2 * ((GMLS * M_PI) / 180)) - 2 * EEO * sin((GMAS * M_PI) / 180) + 4 * EEO * vy * sin((GMAS * M_PI) / 180) * cos( 2 * ((GMLS * M_PI) / 180)) - 0.5 * vy * vy * sin(4 * ((GMLS * M_PI) / 180)) - 1.25 * EEO * EEO * sin(2 * ((GMAS * M_PI) / 180))))) * (180 / M_PI);
  
  float HAS = acos(cos((90.833 * M_PI) / 180) / (cos((latitude * M_PI) / 180) * cos((SD * M_PI) / 180)) - tan((latitude * M_PI) / 180) * tan((SD * M_PI) / 180)) * (180 / M_PI);

  float SN = (720 - 4 * longitude - EQoT + TimeZone * 60);

  float SR = SN - HAS * 4;

  float SS = SN + HAS * 4;
 
  float STD = 8 * HAS;
  
  float TST = fmod((((_hour) + (_min / 60.0) + (_sec / 3600.0)) / 24.0)*1440 + EQoT + 4 * longitude - 60 * TimeZone,1440)+delayTime;
 
  if (TST / 4 < 0)
  {
  AH = ((TST / 4.0) + 180);
  }
  else
  {
  AH = ((TST / 4.0) - 180);  
  }
  
  float SZA = (acos(sin((latitude * M_PI) / 180) * sin((SD * M_PI) / 180) + cos((latitude * M_PI) / 180) * cos((SD * M_PI) / 180) * cos((AH * M_PI) / 180))) * (180 / M_PI);
  
  int SEA = 90 - SZA;
  
  if (SEA < 0)  
  {
   result = _ledLow;    
  }
  
  if (SEA > 0 && SEA < _fullSun)
  {
  result = map(SEA,0,_fullSun,_ledLow,_ledHigh);
  }
  
  if (SEA > _fullSun)  
  {
  result = _ledHigh;
  }
  
  analogWrite(_ledPin, result);  
  return result;
  
}

int MoonLight (float JC, byte _ledPin, byte _ledHigh, byte _ledLow)
{
 int result;
 
 float MS = fmod((2456318.69458333 - JC),29.530589);
 
 if(MS < 14.7518)
 {
  result = map(MS,0,14.7518,_ledLow,_ledHigh);
 }
 
 if( MS > 14.7518)
 {
   result = map(MS,14.7518,29.530589,_ledHigh,_ledLow);
 }
 analogWrite(_ledPin, result); 
 return result;
}
 

/***** RTC Functions *******/
/***************************/
// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val)
{
  return ( (val/10*16) + (val%10) );
}

// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
  return ( (val/16*10) + (val%16) );
}

// Gets the date and time from the ds1307
void getDateDs1307(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());
}

void setup() {
delay(500);
Serial.begin(57600);
Wire.begin();
}

void loop() {
  getDateDs1307(&second, &rtcMins, &rtcHrs, &dayOfWeek, &dayOfMonth, &month, &year);
  if (psecond != second){
    psecond = second;
  Serial.print(rtcHrs);
  Serial.print(":");
  Serial.print(rtcMins);
  Serial.print(":");
  Serial.print(second);
  Serial.print(" ");
  Serial.print(dayOfMonth);
  Serial.print("/");
  Serial.print(month);
  Serial.print("/");
  Serial.println(year);
  update_leds();
  }
}

void update_leds ( void ){
  int i;
  byte value;
  int MS;
  Serial.println("Blue LED's");
  for (i = 0; i < blueChannels; i++)
  {
    value = SunLight(bluePins[i],BluePWMHigh[i],BluePWMLow[i],BlueFull[i],year,month,dayOfMonth,rtcHrs,rtcMins,second);
    Serial.print(map(value,BluePWMLow[i],BluePWMHigh[i],0,100));
    Serial.print("% ");
  }
  Serial.println();
  Serial.println("White LED's");
  for (i = 0; i < whiteChannels; i++)
  {
    value = SunLight(whitePins[i],WhitePWMHigh[i],WhitePWMLow[i],WhiteFull[i],year,month,dayOfMonth,rtcHrs,rtcMins,second);
    Serial.print(map(value,WhitePWMLow[i],WhitePWMHigh[i],0,100));
    Serial.print("% ");
  }
  Serial.println();
  Serial.println("UV LED's");
  for (i = 0; i < uvChannels; i++)
  {
    value = SunLight(uvPins[i],UVPWMHigh[i],UVPWMLow[i],UVFull[i],year,month,dayOfMonth,rtcHrs,rtcMins,second);
    Serial.print(map(value,UVPWMLow[i],UVPWMHigh[i],0,100));
    Serial.print("% ");
  }
  Serial.println();
  Serial.println("Moon Value");
  for (i = 0; i < moonChannels; i ++)
  {
  MS = MoonLight(JC, moonPins[i],MoonPWMHigh[i],MoonPWMLow[i]);
  Serial.println(MS);
  Serial.print(map(MS,MoonPWMLow[i],MoonPWMHigh[i],0,100));
  Serial.print("% ");
  }
  Serial.println();
}

You need to add the following, right before the line analogWrite(_ledPin, result); :

if(result < 0)
{
result = 0;
}
 
You need to add the following, right before the line analogWrite(_ledPin, result); :

if(result < 0)
{
result = 0;
}

Hi and thanks again for your help.

So, to be sure I'm doing it right, what you mean is to change the code from this:

Code:
  if (SEA > _fullSun)  
  {
  result = _ledHigh;
  }
  
  analogWrite(_ledPin, result);  
  return result;
  
}

int MoonLight (float JC, byte _ledPin, byte _ledHigh, byte _ledLow)

So it looks like this

Code:
  if (SEA > _fullSun)  
  {
  result = _ledHigh;
  }
  
if(result < 0)
{
result = 0;
}   
analogWrite(_ledPin, result);  
  return result;
  
}

int MoonLight (float JC, byte _ledPin, byte _ledHigh, byte _ledLow)

//Patrik
 
I think I might have a fix for the light going to 100% before and after the light cycle.

Please let me know if this works.

PHP:
// Natural Reef Aquarium Lighting V2.4
// 16/06/2013
// Developed by J. Harp (nUm - RTAW Forums, Numlock10 - Reef Central Forums)
// Formulas based off of information from NOAA website for sunrise / sunset times.
// Includes Lunar Simulation.
// Compiled in Arduino 1.5.2
//
// Testing;
// Additonal colour channels
// Unique "fullsun" values for each string
// Fix for 100% power on light start and light stop
//
// Future Development:
// Weather Simulation
//
// Please feel free to use this and modify as you see fit, if you have any comments or suggestions please let me know via messages on the forums listed above.
//

#include <math.h>
#include <Wire.h>
#define DS1307_I2C_ADDRESS 0x68

// RTC variables
byte second, rtcMins, oldMins, rtcHrs, oldHrs, dayOfWeek, dayOfMonth, month, year, psecond;

// LED variables (Change to match your needs)
byte bluePins[]      =  {3, 9};      // PWM pins for blues
byte whitePins[]     =  {10, 11};    // PWM pins for whites
byte uvPins[]        =  {5};         // PWM pins for UVs
byte moonPins[]      =  {6};            // PWM pins for moonlights

byte blueChannels    =        2;    // how many PWMs for blues (count from above)
byte whiteChannels   =        2;    // how many PWMs for whites (count from above)
byte uvChannels      =        1;    // how many PWMs for uv (count from above)
byte moonChannels    =        1;    // how many PWMs from moon (count from above)

byte BluePWMHigh[]          =       {255, 255};        // High value for Blue PWM each vale is for each string - if your values are noraml this is 255, if your values are inverted this is 0
byte BluePWMLow[]           =       {0, 0};            // Low value for Blue PWM - if your values are noraml this is 0, if your values are inverted this is 255
float BlueFull[]            =       {25, 25};          // Value in degrees (sun angle) that each Blue string will be at max output
byte WhitePWMHigh[]         =       {255, 255};        // High value for White PWM - if your values are noraml this is 255, if your values are inverted this is 0
byte WhitePWMLow[]          =       {0, 0};            // Low value for White PWM - if your values are noraml this is 0, if your values are inverted this is 255
float WhiteFull[]           =       {37.5, 37.5};      // Value in degrees (sun angle) that each White string will be at max output
byte UVPWMHigh[]            =       {255};             // High value for UV PWM - if your values are noraml this is 255, if your values are inverted this is 0
byte UVPWMLow[]             =       {0};               // Low value for UV PWM - if your values are noraml this is 0, if your values are inverted this is 255
float UVFull[]              =       {30};              // Value in degrees (sun angle) that each UV string will be at max output
byte MoonPWMHigh[]          =       {255};             // High value for Moon PWM - if your values are noraml this is 255, if your values are inverted this is 0
byte MoonPWMLow[]           =       {0};               // Low value for Moon PWM - if your values are noraml this is 0, if your values are inverted this is 255

// Set for the location of the world you want to replicate.

float latitude = -19.770621;   // + to N  Defualt - (-19.770621) Heart Reef, Great Barrier Reef, QLD, Australia 
float longitude = 149.238532;  // + to E  Defualt - (149.238532)
int TimeZone = 10;             // + to E  Defulat - (10)

// Julian Century Varaiable

float JC;

// Sunlight Variables

//float fullSun = 37.5;  // sun elevation in deg that we will assume full sunlight values (Larger = more sunlight)
int delayTime = 0;     // start time delay in minutes,  - will push the day back, + will bring the day forward


int SunLight (byte _ledPin, byte _ledHigh, byte _ledLow, float _fullSun, byte _year, byte _month, byte _day, byte _hour, byte _min, byte _sec)
{
  float a = floor((14 - _month)/12);
  float y = _year + 4800 - a;
  float m = _month + (12 * a) - 3;
  float AH;
  int result;
  
  JC = (((_day + floor(((153.0 * m) + 2.0) / 5.0) + (365.0 * y) + floor(y / 4.0) - floor(y / 100.0) + floor(y / 400.0) - 32045.0) + ((_hour / 24.0) + (_min / 1444.0) + (_sec / 86400.0))) - 2451556.08) / 36525.0;
  
  float GMLS = fmod(280.46646+JC*(36000.76983 + JC * 0.0003032),360);
  
  float GMAS = 357.52911 + JC * (35999.05029 - 0.0001537 * JC);
  
  float EEO = 0.016708634 - JC * (0.000042037 + 0.0000001267 * JC);
  
  float SEoC = sin((GMAS * M_PI)/180)*(1.914602 - JC * (0.004817 + 0.000014 * JC)) + sin(((2 * GMAS) * M_PI) / 180) * (0.019993 - 0.000101 * JC) + sin(((3 * JC) * M_PI) / 180) * 0.000289;
  
  float STL = GMLS + SEoC;
  
  float STA = GMAS + SEoC;
  
  float SRV = (1.000001018 * (1 - EEO * EEO)) / (1 + EEO * cos((STA * M_PI) / 180));
  
  float SAL = STL - 0.00569 - 0.00478 * sin(((125.04 - 1934.136 * JC) * M_PI) / 180);
  
  float MOE = 23 + (26 + ((21.448 - JC * (46.815 + JC * (0.00059 - JC * 0.001813)))) / 60) / 60;
  
  float OC = MOE + 0.00256 * cos(((215.04 - 1934.136 * JC) * M_PI) / 180);
  
  float SD = (asin(sin((OC * M_PI) / 180) * sin((SAL * M_PI) / 180))) * (180 / M_PI);
  
  float vy = tan(((OC / 2) * M_PI) / 180) * tan(((OC / 2) * M_PI) / 180);
  
  float EQoT = (4 * (vy * (sin(2 * ((GMLS * M_PI) / 180)) - 2 * EEO * sin((GMAS * M_PI) / 180) + 4 * EEO * vy * sin((GMAS * M_PI) / 180) * cos( 2 * ((GMLS * M_PI) / 180)) - 0.5 * vy * vy * sin(4 * ((GMLS * M_PI) / 180)) - 1.25 * EEO * EEO * sin(2 * ((GMAS * M_PI) / 180))))) * (180 / M_PI);
  
  float HAS = acos(cos((90.833 * M_PI) / 180) / (cos((latitude * M_PI) / 180) * cos((SD * M_PI) / 180)) - tan((latitude * M_PI) / 180) * tan((SD * M_PI) / 180)) * (180 / M_PI);

  float SN = (720 - 4 * longitude - EQoT + TimeZone * 60);

  float SR = SN - HAS * 4;

  float SS = SN + HAS * 4;
 
  float STD = 8 * HAS;
  
  float TST = fmod((((_hour) + (_min / 60.0) + (_sec / 3600.0)) / 24.0)*1440 + EQoT + 4 * longitude - 60 * TimeZone,1440)+delayTime;
 
  if (TST / 4 < 0)
  {
  AH = ((TST / 4.0) + 180);
  }
  else
  {
  AH = ((TST / 4.0) - 180);  
  }
  
  float SZA = (acos(sin((latitude * M_PI) / 180) * sin((SD * M_PI) / 180) + cos((latitude * M_PI) / 180) * cos((SD * M_PI) / 180) * cos((AH * M_PI) / 180))) * (180 / M_PI);
  
  int SEA = 90 - SZA;
  
  if (SEA <= 0)  
  {
   result = _ledLow;    
  }
  
  if (SEA > 0 && SEA < _fullSun)
  {
  result = map(SEA,0,_fullSun,_ledLow,_ledHigh);
  }
  
  if (SEA >= _fullSun)  
  {
  result = _ledHigh;
  }
  
  analogWrite(_ledPin, result);  
  return result;
  
}

int MoonLight (float JC, byte _ledPin, byte _ledHigh, byte _ledLow)
{
 int result;
 
 float MS = fmod((2456318.69458333 - JC),29.530589);
 
 if(MS <= 14.7518)
 {
  result = map(MS,0,14.7518,_ledLow,_ledHigh);
 }
 
 if( MS > 14.7518)
 {
   result = map(MS,14.7518,29.530589,_ledHigh,_ledLow);
 }
 analogWrite(_ledPin, result); 
 return result;
}
 

/***** RTC Functions *******/
/***************************/
// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val)
{
  return ( (val/10*16) + (val%10) );
}

// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
  return ( (val/16*10) + (val%16) );
}

// Gets the date and time from the ds1307
void getDateDs1307(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());
}

void setup() {
delay(500);
Serial.begin(57600);
Wire.begin();
}

void loop() {
  getDateDs1307(&second, &rtcMins, &rtcHrs, &dayOfWeek, &dayOfMonth, &month, &year);
  if (psecond != second){
    psecond = second;
  Serial.print(rtcHrs);
  Serial.print(":");
  Serial.print(rtcMins);
  Serial.print(":");
  Serial.print(second);
  Serial.print(" ");
  Serial.print(dayOfMonth);
  Serial.print("/");
  Serial.print(month);
  Serial.print("/");
  Serial.println(year);
  update_leds();
  }
}

void update_leds ( void ){
  int i;
  byte value;
  int MS;
  Serial.println("Blue LED's");
  for (i = 0; i < blueChannels; i++)
  {
    value = SunLight(bluePins[i],BluePWMHigh[i],BluePWMLow[i],BlueFull[i],year,month,dayOfMonth,rtcHrs,rtcMins,second);
    Serial.print(map(value,BluePWMLow[i],BluePWMHigh[i],0,100));
    Serial.print("% ");
  }
  Serial.println();
  Serial.println("White LED's");
  for (i = 0; i < whiteChannels; i++)
  {
    value = SunLight(whitePins[i],WhitePWMHigh[i],WhitePWMLow[i],WhiteFull[i],year,month,dayOfMonth,rtcHrs,rtcMins,second);
    Serial.print(map(value,WhitePWMLow[i],WhitePWMHigh[i],0,100));
    Serial.print("% ");
  }
  Serial.println();
  Serial.println("UV LED's");
  for (i = 0; i < uvChannels; i++)
  {
    value = SunLight(uvPins[i],UVPWMHigh[i],UVPWMLow[i],UVFull[i],year,month,dayOfMonth,rtcHrs,rtcMins,second);
    Serial.print(map(value,UVPWMLow[i],UVPWMHigh[i],0,100));
    Serial.print("% ");
  }
  Serial.println();
  Serial.println("Moon Value");
  for (i = 0; i < moonChannels; i ++)
  {
  MS = MoonLight(JC, moonPins[i],MoonPWMHigh[i],MoonPWMLow[i]);
  Serial.println(MS);
  Serial.print(map(MS,MoonPWMLow[i],MoonPWMHigh[i],0,100));
  Serial.print("% ");
  }
  Serial.println();
}
 
Back
Top