Here is the complete tear-down of my DIY lighting fixture for my 6.6 gallon Endlers tank. I used prototyping board for the LEDs and some left over solar panel strips for the bus. I’ve attached the schematic for the power board as well.

Be sure to check the older entries for pics of the SBC.

Here is a photo gallery of the hardware. I used 20 gauge aluminum for the cover, and several stand offs to elevate the board from the acrylic tank cover.

I’m planning on replacing one of the NPN transistor outputs on the uC with a FET, and driving the fan directly from that. The built-in 5 volt regulator has spare capacity, and will allow the uC to control the fan. I may add a temperature and humidity sensor ($3 from ebay) and use it to control the fan on/off timing.

The hole for the serial connection is… a bit ragged. I ended up breaking my DB9 punch, so just reamed the hole with a drill bit. Ugly, but I was on the home stretch and wanted it done.





The board is powered from a standard laptop charger/power supply. It could be purchased for around $30 new, but a used one could likely be found for around $8.

I added an external power switch after the fixture was built to complement the over-ride switches on the side. This allows me to turn the entire fixture off when cleaning the tank. There are three over-ride switches installed as well, to allow manual control of the lighting.

Note the transistor driven relay modules, this allows the 8051 to drive the relays, and is well within its 20mA current limit per pin.

The LEDs are mounted onto two prototyping boards with a heavy copper backplane. These are in turn mounted to an aluminum backplate. The backplate has a scrap piece of heatsink added, overkill as the unit has yet to exceed 90 degrees even in the southern California summer.

The timing of the lighting is set via a terminal program over the comm port of a laptop. The serial port is wired as a null modem, so a standard serial cable is all that is needed. If you only have a null modem cable, you will also need a null modem adapter to re-reverse the TX and RX lines.

The power supply board has a heat-sink mounted across the DC-DC convertors, as they do get warm in the summer, although not dangerously so. There is a piece of acrylic covering one end of the cover to force the airflow to pull in past the top mounted heatsink and through the backplane mounted one. This ensures the components all stay cool and should extend the lifetime of the unit. I estimate it has about 5 years before it needs servicing, due to the MTF (mean time to fail) of 14,000 hours for the converters and the 50,000 hour MTF of the LEDs.

The LEDs are a mix of 6500K and 10000K. I noticed a hair algae growth spurt when just the 6500K LEDs were used. The 10000K have less of the red component and has a better PAR for freshwater plants. For a marine tank I’d use 10000K, 12000K and 16000K as saltwater absorbs the red spectrum more readily, so the blue has better penetration.

I initially used lenses on the LEDs, but due to the shallowness of the tank I decided to remove them, as the dispersal of the light isn’t a problem. Reading the output view a VU meter shows about 1,500 lumens at 8", equivalent to roughly 45 watts of flourescent lighting. Note that it works out to about 7 watts per gallon effective illumination.

The tank has been thriving now for some months. I can get up to 1" a day on some of the plants, and have to do a weekly trim in order to keep some free areas open for the fish.

The main code loop is provided here. The code is intended for use with the Keil C compiler, and the target platform is the AT89C51RD2.

//—————————————

// Example read and write DS1307

// KEIL C51 v7.5

//—————————————

//#include <AT89C51XD2.H> /* special function registers 8052 */

//#include <stdio.h>

//#include “ds1307.h”

//#include “delay.h”



/* All headers moved to config.h */

#include “config.h”



char code menu[] =

“1. Set Time:

”

“2. Set Date:

”

“3: Set Day:

”

“4. Set/Show Timers

”

“5. Initialize RTC

”

“6. Enter ISP mode

”

“7. Exit

”;





char code submenu[] =

“Select desired light strip

”

“1: Front white strip

”

“2: Rear white strip

”

“3: Moonlight strip

”

“4: Display current settings

”;



char code timerInst[] =

“Set start and end times in HH:MM-HH:MM format

”;



unsigned char RTC_ARR[7]; // Buffer for second,minute,…..,year



//unsigned int NVRAM_ARR[12];



sbit blue = P0^0;

sbit whitefront = P0^1;

sbit whiterear = P0^2;



//int checkInitRTC;

int selection,lightselect;

int day, month, year, hour, minute, second; /* int storage for setting time */

//unsigned int wFSh,wFSm,wFEh,wFEm,wRSh,wRSm, wREh,wREm,bSh,bSm,bEh,bEm, FrontStart,FrontEnd,RearStart,RearEnd,BlueStart,BlueEnd;

int wFSh,wFSm,wFEh,wFEm,wRSh,wRSm, wREh,wREm,bSh,bSm,bEh,bEm;

int scanfErrorCheck;





void reset (void)

{

((void (code *) (void)) 0x0000) ();

}





//—————————————

// Initialize serial port

//—————————————

void InitSerial(void)

{

SCON = 0x52; // setup serial port control

TMOD = 0x20; // hardware (9600 BAUD @11.05592MHZ)

TH1 = 0xFD; // TH1

TR1 = 1; // Timer 1 on

}



void initializeRTC(void)

{

ReadRTC(&RTC_ARR[0]);

RTC_ARR[0] = 59 & 0x7F; // enable oscillator (bit 7=0)

RTC_ARR[1] = 0x59; // minute = 59

RTC_ARR[2] = 0x23; // hour = 05 ,24-hour mode(bit 6=0)

RTC_ARR[3] = 0x07; // Day = 1 or sunday

RTC_ARR[4] = 0x31; // Date = 30

RTC_ARR[5] = 0x12; // month = August

RTC_ARR[6] = 0x99; // year = 05 or 2005

WriteRTC(&RTC_ARR[0]); // Set RTC



DelayMs(50);



ReadRTC(&RTC_ARR[0]);

RTC_ARR[0] = bcd(00); //second

RTC_ARR[1] = bcd(00); // minute = 59

RTC_ARR[2] = bcd(14); // hour = 05 ,24-hour mode(bit 6=0)

RTC_ARR[3] = bcd(04); // Day = 1 or sunday

RTC_ARR[4] = bcd(19); // Date = 30

RTC_ARR[5] = bcd(10); // month = August

RTC_ARR[6] = bcd(11); // year = 05 or 2005

WriteRTC(&RTC_ARR[0]);

// SRAM addres 0x22 used to store initialization status, 22 = initialized

WriteBYTE(0x22,22);

}



//—————————————

// Main program

//—————————————

//addresses 0x8h - 0x3Fh are nvram (while batt attached)

void main(void)

{

//Turn off all LEDs

blue = 0;

whiterear = 0;

whitefront = 0;





InitSerial(); // Initialize serial port

RI = 0; // clear UART receive interrupt

DelayMs(10); // delay about 1/10th second





//White front start and end

wFSh= ReadBYTE(0x01);

wFSm= ReadBYTE(0x02);

wFEh = ReadBYTE(0x03);

wFEm = ReadBYTE(0x04);

//White rear start and end

wRSh = ReadBYTE(0x05);

wRSm = ReadBYTE(0x06);

wREh = ReadBYTE(0x07);

wREm = ReadBYTE(0x08);

//moonlight start and end

bSh = ReadBYTE(0x09);

bSm = ReadBYTE(0x10);

bEh = ReadBYTE(0x11);

bEm = ReadBYTE(0x12);



while(1)

{

ReadRTC(&RTC_ARR[0]);

printf(“\033[2J”); //clears terminal DOES WORK

printf(“Day : %s

”,IntDay(decimal(RTC_ARR[3])));

printf(“Time: %02d:%02d:%02d

”,decimal(RTC_ARR[2]),decimal(RTC_ARR[1]),decimal(RTC_ARR[0]));

printf(“Date: %02d-%02d-20%02d

”,decimal(RTC_ARR[5]),decimal(RTC_ARR[4]),decimal(RTC_ARR[6]));





//ReadNVRAM(&NVRAM_ARR[0]);





//FrontStart = (wFSh*100)+wFSm;

//FrontEnd = (wFEh*100)+ wFEh;

//RearStart = (wRSh*100) + wRSm;

//RearEnd = (wREh*100) + wREm;

//BlueStart =(bSh*100) + bSm;

//BlueEnd =(bEh*100) + bEm;

if(((wFSh*100)+wFSm) == ((decimal(RTC_ARR[2])*100)+(decimal(RTC_ARR[1]))))

{

whitefront = 1;

}

if(((wRSh*100)+wRSm) == ((decimal(RTC_ARR[2])*100)+(decimal(RTC_ARR[1]))))

{

whiterear = 1;

}

if(((bSh*100)+bSm) ==((decimal(RTC_ARR[2])*100)+(decimal(RTC_ARR[1]))))

{

blue = 1;

}



if(((wFEh*100)+wFEm) == ((decimal(RTC_ARR[2])*100)+(decimal(RTC_ARR[1]))))

{

whitefront = 0;

}

if(((wREh*100)+wREm) == ((decimal(RTC_ARR[2])*100)+(decimal(RTC_ARR[1]))))

{

whiterear = 0;

}

if(((bEh*100)+bEm) == ((decimal(RTC_ARR[2])*100)+(decimal(RTC_ARR[1]))))

{

blue = 0;

}





if(RI)

{

RI=0; //Clear receive interrupt flag in order to grab new values in buffer

SBUF=0; // clear buffer (needed due to the occasional error when bad data input)

printf(“\033[2J”);

printf(menu);

scanf(“%u”, &selection);

switch(selection)

{

case 1:

printf(“Enter time in HH:MM:SS format and hit enter to update

”);

scanfErrorCheck = scanf(“%d:%d:%d”, &hour, &minute, &second);

if (scanfErrorCheck == 3) //verify input has 3 integers

{

if((hour <=24 && hour >0) && (minute >=0 && minute <=60)&&(second <=60 && second >=0)) // Make sure we have rational inputs

{

ReadRTC(&RTC_ARR[0]); // Update buffer with current values

DelayMs(5);

RTC_ARR[0] = bcd(second);

RTC_ARR[1] = bcd(minute);

RTC_ARR[2] = bcd(hour);

WriteRTC(&RTC_ARR[0]); //Write new values

}

else

{

printf(“Invalid input

”);

SBUF=0;

DelayMs(100);

}



}

else

{

SBUF=0;

}

RI=0; //Clear receive interrupt flag

break;



case 2:

printf(“Enter month, day, and year in MM-DD-YY format and hit enter to update

”);

scanfErrorCheck = scanf(“%d-%d-%d”,&month, &day, &year);

if(scanfErrorCheck == 3)

{

if((month >0 && month <=12)&&(day > 0 && day <= 31)) // Make sure we have rational inputs

{

//printf(“Date:%02d-%02d-%02d”, month, day, year);

ReadRTC(&RTC_ARR[0]); // Update buffer with current values

DelayMs(5);

RTC_ARR[4] = bcd(day);

RTC_ARR[5] = bcd(month);

RTC_ARR[6] = bcd(year);

WriteRTC(&RTC_ARR[0]); //Write new values

printf(“ Set to %s %02d 20%02d

”,IntMonth(month),day,year);

DelayMs(1000);

}

else

{

printf(“Invalid input

”);

SBUF=0;

DelayMs(100);

}

}

else

{

SBUF=0;

}

RI=0; //Clear receive interrupt flag

break;



case 3:

printf(“\033[2J”);

printf(“Enter day of week where 1=Sunday then hit enter to update

”);

scanfErrorCheck = scanf(“%d”, &day);

if(scanfErrorCheck == 1)

{

if(day>0 && day < 8)

{

ReadRTC(&RTC_ARR[0]); // Update buffer with current values

DelayMs(5);

RTC_ARR[3] = bcd(day);

WriteRTC(&RTC_ARR[0]); //Write new values

printf(“Day : %s

”,IntDay(day));

DelayMs(1000);

}

else

{

printf(“Invalid input”);

SBUF=0;

DelayMs(1000);

}

}

else

{

SBUF=0;

}

RI=0;

break;



case 4:

printf(“\033[2J”);

printf(submenu);

scanfErrorCheck = scanf(“%u”, &lightselect);

if(scanfErrorCheck == 1)

{

switch(lightselect)

{

case 1:

printf(timerInst);

scanf(“%d:%d-%d:%d”,&wFSh,&wFSm, &wFEh,&wFEm); //White front start and end

WriteBYTE(0x01,wFSh);

WriteBYTE(0x02,wFSm);

WriteBYTE(0x03,wFEh);

WriteBYTE(0x04,wFEm);

DelayMs(5);

//White front start and end

wFSh= ReadBYTE(0x01);

wFSm= ReadBYTE(0x02);

wFEh = ReadBYTE(0x03);

wFEm = ReadBYTE(0x04);



printf(“Start time:%02d:%02d, End time:%02d:%02d”,wFSh,wFSm,wFEh,wFEm);

DelayMs(500);

RI=0;

break;

case 2:

printf(timerInst);

scanf(“%d:%d-%d:%d”,&wRSh,&wRSm, &wREh,&wREm); //White front start and end

WriteBYTE(0x05,wRSh);

WriteBYTE(0x06,wRSm);

WriteBYTE(0x07,wREh);

WriteBYTE(0x08,wREm);

DelayMs(5);

//White rear start and end

wRSh = ReadBYTE(0x05);

wRSm = ReadBYTE(0x06);

wREh = ReadBYTE(0x07);

wREm = ReadBYTE(0x08);

printf(“Start time: %02d:%02d, End time: %02d:%02d”,wRSh,wRSm,wREh,wREm);

DelayMs(500);

RI=0;

break;



case 3:

printf(timerInst);

scanf(“%d:%d-%d:%d”,&bSh,&bSm, &bEh,&bEm); // Moonlight start and end

WriteBYTE(0x09,bSh);

WriteBYTE(0x10,bSm);

WriteBYTE(0x11,bEh);

WriteBYTE(0x12,bEm);

DelayMs(5);

//moonlight start and end

bSh = ReadBYTE(0x09);

bSm = ReadBYTE(0x10);

bEh = ReadBYTE(0x11);

bEm = ReadBYTE(0x12);

printf(“Start time:%02u:%02u, End time:%02u:%02u”,bSh,bSm,bEh,bEm);

DelayMs(500);

RI=0;

break;



case 4:

//White front start and end

wFSh= ReadBYTE(0x01);

wFSm= ReadBYTE(0x02);

wFEh = ReadBYTE(0x03);

wFEm = ReadBYTE(0x04);

//White rear start and end

wRSh = ReadBYTE(0x05);

wRSm = ReadBYTE(0x06);

wREh = ReadBYTE(0x07);

wREm = ReadBYTE(0x08);

//moonlight start and end

bSh = ReadBYTE(0x09);

bSm = ReadBYTE(0x10);

bEh = ReadBYTE(0x11);

bEm = ReadBYTE(0x12);



printf(“White Front Start time:%02d:%02d

”,wFSh,wFSm);

printf(“White Front End time:%02d:%02d

”,wFEh,wFEm);

printf(“White Rear Start time:%02d:%02d

”,wRSh,wRSm);

printf(“White Rear End time:%02d:%02d

”,wREh,wREm);

printf(“Moonlight Start time:%02d:%02d

”,bSh,bSm);

printf(“Moonlight End time:%02d:%02d



”,bEh,bEm);

DelayMs(2500);

SBUF=0;

RI=0;

break;

default:

printf(“\033[2J”);

printf(“Unrecognized input, exiting”);

SBUF=0;

DelayMs(300);

RI=0; //Clear receive interrupt flag

break;

}

}

else

{

SBUF=0;

}

RI=0; //Clear receive interrupt flag

DelayMs(10);

break;



case 5:

printf(“\033[2J”);

initializeRTC();

printf(“DS1307 Initialized

”);

DelayMs(500);

SBUF=0;

RI=0;

break;



case 6:

printf(“\033[2J”);

printf(“Enter 9 to enter ISP mode, any other key to exit

”);

scanfErrorCheck = scanf(“%d”, &selection);

if(scanfErrorCheck == 1)

{

if(selection == 9)

{

//printf(“Remove serial cable within 5 seconds and then connect via Flip

”);

printf(“Power cycle and then connect via Flip

”);

DelayMs(100);

__api_wr_SBV(0xFC);

__api_wr_BSB(0x55);

__api_set_BLJB();

reset ();

//((void (code *)(void)) 0) (); //May work also

//DelayMs(2500);

}

}

else

{

SBUF=0;

}

SBUF=0;

RI=0;

break;

case 7:

printf(“\033[2J”);

printf(“Exiting

”);

DelayMs(200);

SBUF=0;

RI=0; //Clear receive interrupt flag

break;



default:

printf(“\033[2J”);

printf(“Unrecognized input

”);

SBUF=0;

DelayMs(100);

RI=0; //Clear receive interrupt flag

break;

}

}

DelayMs(800); // delay about 1 second

//PCON |= 0x01; /* Enter IDLE Mode – Wake-up every timer interrupt */

}

}