The Planted Tank Forum banner

Another Arduino LED light schedule sketch

29794 Views 109 Replies 23 Participants Last post by  boxhead1990
Here is the Arduino code I use to control the schedule of my LED lights.

Back story:
I've been redoing my 14G low-tech and wanted to try out LED, so I ordered a 10W ebay-special and planned to hook it up to an Arduino I had lying around. While waiting for it to arrive, I looked around for examples of how others had handled the software side of things. None of the code I found seemed to fit my needs.
I realize a lot of people have done the same, but I wanted something that allowed for individual schedules for any number of channels, including siesta and different length sunrise/sunset.

So in the end, I wrote my own code to control the lights.

Some things that need to be addressed before looking at the code:

1: This is not a complete sketch to control aquarium lights!
While it would work on a setup identical to mine, it wouldn't on much else. The code is meant as a helping hand for those that are not overly comfortable with programming, and find handling more complicated schedules difficult. I have removed all but the essentials in this sketch.

2: This has nothing to do with your hardware!
Ok, that may not be strictly true. But my point is: There are a lot of great guides out there regarding the hardware side of things, I will only be dealing with the software. This is the part that I have found relatively little of on these forums, whereas the hardware gurus seem very active. I provide a way to control the PWM signal, the rest is up to you.

Now, let's get started! :)

- Any number of individual channels (limited by you Arduino's number of PWM pins)
- Pretty much any light schedule that repeats every 24 hours
- Suitable for RGB lights
- Reasonably smooth fading w. a resolution of 1/255 updated every second

Hardware requirements:
- Arduino (Uno in my case)
- RTC module (DS1307 is used in the example)
- PWM outputs wired CORRECTLY to your LED drivers

Other requirements:
- Some (not necessarily a lot) proficiency with Arduino coding

There are a lot of comments in the code, and I hope it is not too difficult to understand.

I have made a considerable rewrite, making it much easier to add more channels.
Concept explained:

A lot of this is explained in the code, but I'll try to summarize it here.
To define a schedule for a channel, you set a series of 'Points'. This should be considered a chart, where the line represents the intensity of light over a 24 hour period.
Intensity is a value in the range 0-255. 0 = OFF, 255 = Fully ON

To make lights turn on in the morning with a 30 minute 'sunrise', you would add 2 Points:
08:00 - Intensity = 0
08:30 - Intensity = 255

From 08:00 to 08:30 channel is going from 0-255

It's pretty simple, really.

:sleep: Enough rambling, here is the code. Let me know if you have questions or find it useful!

Link to source:
New location of source:
  • Like
Reactions: litzel
21 - 40 of 110 Posts

· Registered
19 Posts
Hi benjaf, great job here.

Looking to modify my Arduino sketch to contain more light periods. I want to split the photoperiod in two.

Can I assume that your code here does that?

Channels[0].AddPoint(8, 0, 0);
Channels[0].AddPoint(8, 30, 255);
Channels[0].AddPoint(11, 0, 0);
Channels[0].AddPoint(11, 10, 0);
Channels[0].AddPoint(13, 0, 0);
Channels[0].AddPoint(13, 10, 255);
Channels[0].AddPoint(20, 0, 255);
Channels[0].AddPoint(20, 30, 0);
From my understanding it goes from 0% (0) to 100% (255) from 8:00 to 8:30 and then it's at 0% again at 11. Does that mean that it fades down from 100% at 8:30 to 0% at 11:00? So the fade period there is 30 minutes up and then 2.5 hours down? Or does the light just switch straight off at 11:00?

Why do you also include another 0% marker at 11:10? Is this just redundancy to make sure it's totally off?

Good work though. Simpler than mine and quite nice.

· Registered
19 Posts
Ah, sorry, that is a typo. It was supposed to be (11, 0, 255)! Hope it makes a bit more sense now :)
Yep I understand now. The fade up for the "sunrise" is 30 minutes. Then a quick fade down of 10 minutes for a "midday rest". Fade up of 10 minutes "midday wake" then a final 30 minute fade down for a "sunset".

Nicely done.

Now one thing you may have noticed that I can't really seem to crack is that the fade looks a bit bright early on.

It turns out that human eyes perceive light in the logarithmic scale, yet our fades are linear. As such things get to full bright at around 25% of the fade cycle and stay that way until 100%

You may have noticed this. That there's not really a "dawn breaking" effect. It's more dawn for a few minutes then fully bright for the rest. That's because our eyes perceive the full brightness at around 25% and from then on it's still full brightness.

Here are the correct values for a true logarithmic fade from 100% to 0% for a 1 byte (255) range:

255, 180, 128, 90, 64, 45, 32, 23, 16, 12, 8, 6, 4, 3, 2, 1,0
If you implement these values instead of linearly fading from 100% to 0% (via some sort of lookup array) then you will have a nice fade over time.

I tried to do a calculation that would work out the proper PWM value given a minimum and maximum range (so instead of 100% to 0% if I wanted to fade from 80% to 10%) but I couldn't get one worked out (my maths is poor).

See what you think of the logarithmic fade instead of linear. It's a more gradual effect and I'm sure it's not an issue for the plants. It just means the dimmer values are held a bit longer and so the lights don't look full bright too quickly.

· Registered
19 Posts
So I think I have finalised adding the code to process the linear vs logarithmic scale to my project and thought it could help you and others out.

Here are my results:

With the following values (in case anyone wants to use an array lookup).
Percentage on left, PWM on right:
1	0
2	0
3	0
4	0
5	0
6	0
7	0
8	1
9	1
10	1
11	1
12	1
13	1
14	1
15	1
16	1
17	2
18	2
19	2
20	2
21	2
22	2
23	3
24	3
25	3
26	3
27	3
28	4
29	4
30	4
31	4
32	5
33	5
34	5
35	6
36	6
37	7
38	7
39	8
40	8
41	9
42	9
43	10
44	10
45	11
46	12
47	12
48	13
49	14
50	15
51	16
52	17
53	18
54	19
55	20
56	21
57	22
58	23
59	25
60	26
61	28
62	30
63	31
64	33
65	35
66	37
67	39
68	42
69	44
70	47
71	49
72	52
73	55
74	58
75	62
76	65
77	69
78	73
79	77
80	82
81	87
82	92
83	97
84	102
85	108
86	115
87	121
88	128
89	136
90	143
91	152
92	160
93	169
94	179
95	190
96	200
97	212
98	224
99	237
100	250
I sourced this a while ago from THIS SITE

The formula is as follows. It accepts an integer input (0% - 100%) and will output a byte (0 - 255) which can be written directly to the PWM ports.
byte linearPWM(int percentage)
	// coefficients
	double a = 9.7758463166360387E-01;
	double b = 5.5498961535023345E-02;
	return (byte)(floor((a * exp(b * percentage) + 0.5)) -1);

· Registered
113 Posts
Discussion Starter · #26 ·
I have actually been considering adding this, or at least a slightly simplified version of it for some time.
I have been doing some more rewrites to the project in order to simplify usage even more - and will probably add 'perceived linear' fading as an option for each channel. This code is not yet done, but testing is under way. Thanks for doing some of the numbers!

· Registered
26 Posts's me again. I never got back to thank you for this code, or to let you know I got it up and running without any issues across 9 channels with 3 periods.

I've been running it as my default lighting schedule since the end of May. It's still the older version, but I don't have any issues with it. And I really don't plan to change it.

My question this time is about the timing/intensity matrix.

Is it possible to use a variable within it? Specifically the Intensity?

Current Example:

{0, 0, 9, 45, 0},
{10, 45, 16, 55, 90}, ////Wht_Ch1
{17, 55, 24, 0, 0}

I am trying to pass a variable(without success) from a touch "dimming/blending" screen so I will be able to change it without changing the sketch every time.

Something like this:

{0, 0, 9, 45, 0},
{10, 45, 16, 55, White_Max}, ////Wht_Ch1
{17, 55, 24, 0, 0}

I figured it would be as simple as passing the variable value into the matrix like that, but it has not. I've tried various ways and variables with out success.

Will the schedule only accept a number between 0 and 255?

Thanks for any help or ideas!

EDIT: here is a photo to try and make clear what I am talking about.

This is a control screen where I can currently dim and blend each of my LED colors.
I would like to be able to set maximum value to the lighting schedule from here.


· Registered
113 Posts
Discussion Starter · #28 · (Edited)
You're welcome, happy to see someone is still using it :)
Nice interface by the way!
Anyway, on to what you are trying to do:

Passing a variable that way just passes the value of the variable at that given time.
To change the schedule after initialization you have to change the value directly in the matrix like this:

lightMatrix[Channel][Period][INTENSITY] = Variable;

Assuming you want to change period 2 in the first channel:
lightMatrix[0][1][INTENSITY] = White_Max;
Remember that array indexes start at 0, otherwise results will be rather odd...

EDIT: This must be done every time the variable is changed! (wasn't sure this came across in the original post)

The downside of doing what you do now - any changes will be lost at reset unless you have made some considerable changes to the sketch.
This will be remedied by an upcoming API edition (currently testing) but it will most likely be released as a separate sketch since it has very little in common with the current version..

· Registered
26 Posts
Thanks so much for the quick response, and compliment.

Makes perfect sense to me now. You know...sometimes I get in a rut thinking a specific solution that I can't get by that, and I just need a slap in the head to see things another way.

I'm going to give it a go.

I'm also thinking that I might be able to solve the reset/power loss issue by saving the max lighting value in EEPROM or SD card when the user pushes "enter" on my dimming screen. But, I'll cross that bridge when I get to it.

Another bridge down the road is setting the start time. The total period would be fixed(easier math). Now that I see how I have to designate the variable I hope I can bang that code out too.

Thanks again!!

· Registered
26 Posts
Thanks Benjaf!

I was able to code this quite quickly with your help. EEPROM and all.

I am now able to set the Maximum lighting value from the TFT screen, and I won't lose settings if I lose power, or reset the board.

Now...on to being able to set/alter the start time from the screen.

· Registered
132 Posts
Cool project!

I've done my own Arduino controller specifically for a Current-USA Satellite LED+. It is IR remote controlled, and has separate channels for low-intensity RGBs and a single-channel for higher-intensity white LEDs. It has a bunch of other presets that I am not all that interested in, but the controllability is nice. Hacking the IR protocol was easy, a few other people have detailed it in another thread here in this forum. The Arduino (I used an Uno R3) uses a single PWM channel to drive an IR LED, which could be used to control multiple lights with the same program.

I use an RTC to feed a decimal hour time value to sine functions that control light intensity for each channel. The functions are parametrized such that they are 0 at the specified power-on and power-off times. The amplitude given to the functions determines how fast they ramp, and for how long they plateau at the highest value. Split photoperiods can be done also.

If you're interested in trying to adapt it, I can put up the code, let me know!


· Registered
113 Posts
Discussion Starter · #33 ·
Looks like an interesting solution!
By now I am really just fiddling with the serial communication between Arduino and Python client, the light scheduling is loosely based on my previous version of this sketch. It takes a while to get anywhere since I am learning Python in the process, but that was sort of the point.
The main issue is a lack of time to spend on the project! :icon_conf

· Registered
26 Posts
Hi Benjaf...I'm here to bother you again.

I am now trying to incorporate being able to set a start time from the GUI.

How I am trying to code it is similar to how you showed me to add a variable to the intensity.

For every channel I have called it out like this:

sunMatrix[0][0][Sunset_Hr] = RiseHour;
sunMatrix[0][0][Sunset_Min] = RiseMin;
sunMatrix[0][1][Sunrise_Hr] = RiseHour;
sunMatrix[0][1][Sunrise_Min] = RiseMin;
sunMatrix[0][1][Sunset_Hr] = RiseHour;
sunMatrix[0][1][Sunset_Min] = RiseMin;
sunMatrix[0][2][Sunrise_Hr] = RiseHour;
sunMatrix[0][2][Sunrise_Min] = RiseMin;

And then in the matrix array I am trying something like this:

{0, 0, (RiseHour), (RiseMin+2), 0},
{(RiseHour), (RiseMin+10), (RiseHour+12), (RiseMin+7), RBlue_max}, ////RBlue_Ch1
{(RiseHour+12), (RiseMin+12), 24, 0, 0}
{0, 0, (RiseHour), (RiseMin+8), 0},
{(RiseHour), (RiseMin+14), (RiseHour+12), (RiseMin+2), Blue_max}, ////Blue_Ch1
{(RiseHour+12), (RiseMin+8), 24, 0, 0}

To avoid timing conflicts between Hours and to make sure minutes do not increase above 59.

I am unsure if the Update function is able to pick up which period the schedule is in, or if I can't use the math like I am?

I am hoping you have a suggestion for me.


· Registered
26 Posts
I am 80% sure I can't do the math within the matrix. I have changed the variable naming to:

sunMatrix[CHANNELS][0][Sunset_Hr] = RiseHour;
sunMatrix[CHANNELS][0][Sunset_Min] = RiseMin;
sunMatrix[CHANNELS][1][Sunrise_Hr] = RiseHour;
sunMatrix[CHANNELS][1][Sunrise_Min] = RiseMin;
sunMatrix[CHANNELS][1][Sunset_Hr] = RiseHour;
sunMatrix[CHANNELS][1][Sunset_Min] = RiseMin;
sunMatrix[CHANNELS][2][Sunrise_Hr] = RiseHour;
sunMatrix[CHANNELS][2][Sunrise_Min] = RiseMin;

And I am getting the Blue's....whether they ramp up/down I am unsure. But, I am guessing the white's are not coming on because I have them starting 2 hours after the blues which I designated RiseHour+2 in the matrix.

Getting closer though...

· Registered
113 Posts
Discussion Starter · #36 ·
Actually, to do this you will have to make a few changes.
First of all, you will have to move the initialization of the matrix into a function so you can call it as many times as you want. If you do that, then you can keep it like you described above:

int lightMatrix[CHANNELS][MAXPERIODS][5];

void UpdateMatrix() {
lightMatrix = {
{0, 0, (RiseHour), (RiseMin+2), 0},
{(RiseHour), (RiseMin+10), (RiseHour+12), (RiseMin+7), RBlue_max}, ////RBlue_Ch1
{(RiseHour+12), (RiseMin+12), 24, 0, 0}

Keep in mind that you have to have the definition outside the function like above!

Any time one of the variables are changed, you reinitialize the entire matrix in the easiest possible way:


This makes sure everything is always updated, and you have to do as little work as possible!
Hope this helps, always happy to help :)

· Registered
26 Posts
Thanks for the help!!

I am having an issue though. It's probably due to my lack of experience. A year ago I didn't even know what Arduino was, and never had any experience writing code until 10 months ago.
So, I don't understand the compilation error I am receiving. What it means that is. My lack of vocabulary with all of this.

I made the adjustments you recommended, but it won't compile. It is giving me an error on this line


"expected primary expression before {"

I'm unsure what this means...I kind of hunt and peck a few things, but no success.

· Registered
26 Posts
I'm checking that out.

Something odd happening. I woke up in the middle of the night and all the blue LEDs were on?
This morning I turned on the LEDs to find all channels on??

Maybe Sunday I'll have time to actually sit and sift through all the code.
At least I know something is happening. Lol!

· Registered
26 Posts
Hi Benjaf,

I've been wrestling around with code, and I don't think I am any closer.

I posted the code on the Arduuino forum seeking help, and those guys are so f'n cryptic with their direction. The best I got was that "you can't initialize an array in that way"
"Your Declaration is correct, but your initialization is not".

Why can't they just show me??
21 - 40 of 110 Posts
This is an older thread, you may not receive a response, and could be reviving an old thread. Please consider creating a new thread.