Dawn Simulator (Prototype)

The body is made to wake up with the sunlight. I really hate getting up in the morning when it’s dark in my bedroom. Thats particulary bad in the winter when the sun rises after I should get up, but also in the summer, because I have very light tight curtains.
A dawn simulator is a light that gets slowly on in the morning to simulate a dawn and let the body wake up very smoothly.

There are many dawn simulators out there to buy, but I wanted to build one on my own for quite some time and now I did it.

Schematic

To finally build one was a very spontanious decision, so I had to use parts I had at home for the first prototype.

Microcontroller

I had three µC laying around, one ATmega1284P, a ATtiny45 and an Arduino Pro Mini.
The ATmega1284P is one of the most powerful ATmegas out there, with a lot of flash, SRAM, peripherals and 44 pins. This one is definitely overpowered for a dawn simulator.

The ATtiny45 is the opposite, it has only 8 pins, some flash, some SRAM and not so many timers and stuff. But a dawn simulator doesn’t have to do very much. One timer for the light PWM, an I²C Bus for the display (more on this later) and the other pins for the input. This doesn’t seem too bad.
The display I’m using comes with an Arduino library which is no problem, because you can use an Arduino library without an Arduino, but this library buffers the whole display in the RAM. And because of the size of the display this buffer takes up 512 bytes, which is more RAM than the ATtiny45 has. So it is also not usable.

The Arduino Pro Mini with its ATmega328P is in-between, but I normally don’t use Arduinos so I actually never considered it, but it is the best one for this task.

Display

I have some old mobile phone displays, LCD dot displays and two monochrome OLED displays from Adafruit laying around. The OLED display has the big advantage that the black pixels are really black, so there is no black glowing in the night.
This is the display I’m using: https://www.adafruit.com/products/931
It also comes with an Arduino library: https://github.com/adafruit/Adafruit_SSD1306

Light

For the light I choose two white 5W LEDs. They need a voltage of 17V and the µC controls them with a MOSFET. I found an IRL3803 that is rated with 140A, enough for two LEDs.

Breadboard

I built the circuit on breadboard like this:
Breadboard Schematic

GND wires
+5V
+17V
I²C Bus
Cables to the switches
PWM signal
Other connections

I have an LDO built in my breadboard “housing” and so no need for an extra LDO on the breadboard. All the other circuit is pretty self explaining I think.

Final image of the circuit:
Breadboard Image

Here you can see I put the LEDs on the metal for cooling.

Arduino Code

To bring the circuit to life the µC needs code. I finally ended up using Arduino, its perfect for prototyping because it helps you with a lot of stuff.

Libraries

I used the following libraries:

  1. Adafruit SD1306 https://github.com/adafruit/Adafruit_SSD1306 it requires:
  2. Adafruit GFX https://github.com/adafruit/Adafruit-GFX-Library
  3. Time Library https://www.pjrc.com/teensy/td_libs_Time.html
  4. TimeAlarms https://www.pjrc.com/teensy/td_libs_TimeAlarms.html

Code Parts

The code is split up in five parts:

The core contains the setup() and loop() functions and the time-keeping and alarm code.


[]view raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <TimeAlarms.h>
#include <Time.h>
#include <TimeLib.h>
// The set alarm time
time_t alarmTime = 0;
AlarmID_t alarmID;
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);
void setup() {
int i;
// set the time, the week, month and year are not used in this program
setTime(0,0,0,1,1,2017);
// set the initial duration of the sunrise
sunriseDuration = 20;
// init the blinking, more on that in the gui
blinkTimer = millis();
blinkOn = true;
// set the pin 13 to output, this is where the led of every arduino is
pinMode(13, OUTPUT);
// set the pins for the buttons as input with pullup
for(i=0; i<BUTTON_CNT; i++) {
pinMode(buttonPin[i], INPUT);
digitalWrite(buttonPin[i], HIGH);
}
// init the timer, see pwm
initTimer();
// init the display, set text size and color
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.setTextSize(4);
display.setTextColor(WHITE);
display.display();
}
void loop() {
checkInput();
doSunrise();
runState();
// check if nextState changed
stateChanged = false;
if(state != nextState) {
// if so set the new state and clear the display
stateChanged = true;
state = nextState;
display.clearDisplay();
}
// toggle the blinkOn variable every BLINK_SPEED milliseconds
if(blinkTimer + BLINK_SPEED < millis()) {
blinkTimer = millis();
blinkOn = !blinkOn;
}
// update the display
display.display();
// wait 10ms
// When using the Alarm lib Alarm.delay(10) must be used
Alarm.delay(10);
}
// This function is called when it is time for the alarm
void alarm() {
sunrise = true;
sunriseTime = millis();
}

The input part handles the buttons. It detects button down and a long press.


[]view raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#define BUTTON_CNT 3
// The three buttons that exist
enum Button {
BTN_DOWN,
BTN_UP,
BTN_RIGHT
};
// the pin of each button, saving them is an array makes iterating easier
int buttonPin[BUTTON_CNT] = {7, 8, 9};
// the current state of the buttons
bool buttonState[BUTTON_CNT] = {HIGH, HIGH, HIGH};
// true when the button was clicked between the last loop() run and now
bool buttonClicked[BUTTON_CNT] = {false, false, false};
// the time in millis() the button was clicked (this is needed for long press)
long buttonClickTimes[BUTTON_CNT] = {0, 0, 0};
// true when the button was pressed long between the last loop() run and now
bool buttonLongPressed[BUTTON_CNT] = {false, false, false};
// true if buttonLongPressed was already set true for this press, this is important because
// if I would not have this variable buttonLongPressed would always be true after pressing
// for 1 second, but it should fire only once.
bool buttonLongPressedFired[BUTTON_CNT] = {false, false, false};
// check the buttons
void checkInput() {
int i;
// iterate over all buttons
for(i=0; i<BUTTON_CNT; i++) {
buttonClicked[i] = false;
buttonLongPressed[i] = false;
// button clicked
if(buttonState[i] == HIGH && digitalRead(buttonPin[i]) == LOW) {
buttonClicked[i] = true;
buttonClickTimes[i] = millis();
blinkOn = true;
blinkTimer = millis();
}
// button is pressed
else if(digitalRead(buttonPin[i]) == LOW) {
if(millis() - buttonClickTimes[i] > 1000 && !buttonLongPressedFired[i]) {
//digitalWrite(13, HIGH);
buttonLongPressed[i] = true;
buttonLongPressedFired[i] = true;
}
}
// button is not pressed
else {
buttonLongPressedFired[i] = false;
}
buttonState[i] = digitalRead(buttonPin[i]);
}
}

The program can be in multiple states e.g. show time, change time or change sunrise duration.


[]view raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// This are the states the program can be in
enum State {
STARTUP,
DISPLAY_TIME,
CHANGE_TIME,
DISPLAY_ALARM,
CHANGE_ALARM,
DISPLAY_DURATION,
CHANGE_DURATION
};
// The first state it STARTUP
State state = STARTUP;
// In the program only the nextState variable should be set.
// The loop function then sets the state and does some more stuff.
State nextState = state;
// This is set when a state was changed in the last loop
bool stateChanged = false;
// run the function of the current state
void runState() {
switch(state) {
case STARTUP:
startup();
break;
case DISPLAY_TIME:
displayTime();
break;
case CHANGE_TIME:
stateChangeTime();
break;
case DISPLAY_ALARM:
displayAlarm();
break;
case CHANGE_ALARM:
changeAlarm();
break;
case DISPLAY_DURATION:
displayDuration();
break;
case CHANGE_DURATION:
changeDuration();
break;
};
}
// do nothing here
void startup() {
nextState = DISPLAY_TIME;
}
// This state displays the time
void displayTime() {
// If the button right is clicked go to the state DISPLAY_ALARM
if(buttonClicked[BTN_RIGHT])
nextState = DISPLAY_ALARM;
// If the button down was pressed long go to the state CHANGE_TIME
if(buttonLongPressed[BTN_DOWN])
nextState = CHANGE_TIME;
// show the time
showTime(now(), -1, 0);
}
void stateChangeTime() {
if(stateChanged == true)
// Time to change is set to the current time, when the user has finished changing
// the time, the timeToChange variable changed.
timeToChange = now();
// change time returns true when the user finished changing the time
if(changeTime()) {
// set the current time to the time the user just set
setTime(timeToChange);
// go back to DISPLAY_TIME
nextState = DISPLAY_TIME;
}
}
void displayAlarm() {
if(buttonClicked[BTN_RIGHT])
nextState = DISPLAY_DURATION;
if(buttonLongPressed[BTN_DOWN])
nextState = CHANGE_ALARM;
if(stateChanged == true) {
showTime(alarmTime, -1, 42);
}
}
void changeAlarm() {
if(stateChanged == true)
timeToChange = alarmTime;
if(changeTime(42)) {
alarmTime = timeToChange;
Alarm.free(alarmID);
alarmID = Alarm.alarmRepeat(hour(alarmTime), minute(alarmTime), second(alarmTime), alarm);
nextState = DISPLAY_ALARM;
}
}
void displayDuration() {
if(buttonClicked[BTN_RIGHT])
nextState = DISPLAY_TIME;
if(buttonLongPressed[BTN_DOWN])
nextState = CHANGE_DURATION;
display.setTextSize(1);
display.setCursor(0, 4);
display.print("Duration: ");
display.setTextSize(2);
display.setCursor(25, 18);
display.print(String(sunriseDuration));
}
void changeDuration() {
if(buttonClicked[BTN_DOWN])
sunriseDuration--;
if(buttonClicked[BTN_UP])
sunriseDuration++;
if(buttonClicked[BTN_RIGHT])
nextState = DISPLAY_DURATION;
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0, 4);
display.print("Duration: ");
if(blinkOn) {
display.setTextSize(2);
display.setCursor(25, 18);
display.print(String(sunriseDuration));
}
}

The pwm part initializes the timer and calculates the sunrise.


[]view raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
int sunriseDuration;
#define SUN_PIN 3
// true if the sun is currently rising (light going on), it is set in the alarm() function
bool sunrise = false;
// the time in millis() the time started rising
unsigned long sunriseTime;
// This table is used to let the LED get brighter steady. This is needed because the eye is
// seeing brightness logarithmical. More on this here (german) https://www.mikrocontroller.net/articles/LED-Fading
const int16_t pwmtable[1000] PROGMEM =
{
0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13,
13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 18, 18, 18, 18,
18, 18, 19, 19, 19, 19, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, 26,
26, 26, 26, 27, 27, 27, 28, 28, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32, 32, 32, 33, 33, 33, 34, 34, 34, 35, 35, 36, 36,
36, 37, 37, 38, 38, 38, 39, 39, 40, 40, 40, 41, 41, 42, 42, 43, 43, 44, 44, 45, 45, 46, 46, 47, 47, 48, 48, 49, 49, 50, 50, 51,
51, 52, 52, 53, 54, 54, 55, 55, 56, 56, 57, 58, 58, 59, 60, 60, 61, 62, 62, 63, 64, 64, 65, 66, 66, 67, 68, 69, 69, 70, 71, 72,
72, 73, 74, 75, 76, 76, 77, 78, 79, 80, 81, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101,
102, 103, 104, 105, 106, 108, 109, 110, 111, 112, 114, 115, 116, 117, 119, 120, 121, 122, 124, 125, 126, 128, 129, 131, 132, 133,
135, 136, 138, 139, 141, 142, 144, 145, 147, 149, 150, 152, 153, 155, 157, 158, 160, 162, 164, 165, 167, 169, 171, 173, 174, 176,
178, 180, 182, 184, 186, 188, 190, 192, 194, 196, 198, 201, 203, 205, 207, 209, 212, 214, 216, 219, 221, 223, 226, 228, 231, 233,
236, 238, 241, 243, 246, 249, 251, 254, 257, 260, 262, 265, 268, 271, 274, 277, 280, 283, 286, 289, 292, 295, 299, 302, 305, 308,
312, 315, 318, 322, 325, 329, 332, 336, 340, 343, 347, 351, 355, 358, 362, 366, 370, 374, 378, 382, 386, 390, 395, 399, 403, 408,
412, 416, 421, 426, 430, 435, 439, 444, 449, 454, 459, 464, 469, 474, 479, 484, 489, 495, 500, 505, 511, 516, 522, 527, 533, 539,
545, 551, 557, 563, 569, 575, 581, 587, 594, 600, 607, 613, 620, 626, 633, 640, 647, 654, 661, 668, 675, 683, 690, 697, 705, 713,
720, 728, 736, 744, 752, 760, 768, 776, 785, 793, 802, 811, 819, 828, 837, 846, 855, 865, 874, 883, 893, 902, 912, 922, 932, 942,
952, 963, 973, 983, 994, 1005, 1016, 1027, 1038, 1049, 1060, 1072, 1083, 1095, 1107, 1119, 1131, 1143, 1155, 1168, 1180, 1193,
1206, 1219, 1232, 1246, 1259, 1273, 1286, 1300, 1314, 1328, 1343, 1357, 1372, 1387, 1402, 1417, 1432, 1448, 1463, 1479, 1495,
1511, 1527, 1544, 1561, 1577, 1595, 1612, 1629, 1647, 1665, 1682, 1701, 1719, 1738, 1756, 1775, 1794, 1814, 1833, 1853, 1873,
1893, 1914, 1935, 1955, 1977, 1998, 2020, 2041, 2063, 2086, 2108, 2131, 2154, 2177, 2201, 2224, 2248, 2273, 2297, 2322, 2347,
2373, 2398, 2424, 2450, 2477, 2503, 2530, 2558, 2585, 2613, 2642, 2670, 2699, 2728, 2757, 2787, 2817, 2848, 2878, 2910, 2941,
2973, 3005, 3037, 3070, 3103, 3137, 3171, 3205, 3239, 3274, 3310, 3346, 3382, 3418, 3455, 3492, 3530, 3568, 3607, 3646, 3685,
3725, 3765, 3806, 3847, 3888, 3930, 3973, 4016, 4059, 4103, 4147, 4192, 4237, 4283, 4329, 4376, 4423, 4471, 4519, 4568, 4617,
4667, 4718, 4769, 4820, 4872, 4925, 4978, 5032, 5086, 5141, 5196, 5253, 5309, 5367, 5424, 5483, 5542, 5602, 5663, 5724, 5786,
5848, 5911, 5975, 6040, 6105, 6171, 6237, 6305, 6373, 6442, 6511, 6581, 6652, 6724, 6797, 6870, 6944, 7019, 7095, 7172, 7249,
7328, 7407, 7487, 7567, 7649, 7732, 7815, 7900, 7985, 8071, 8158, 8246, 8335, 8425, 8516, 8608, 8701, 8795, 8890, 8986, 9083,
9181, 9281, 9381, 9482, 9584, 9688, 9793, 9898, 10005, 10113, 10222, 10333, 10444, 10557, 10671, 10786, 10903, 11021, 11140, 11260,
11381, 11504, 11628, 11754, 11881, 12009, 12139, 12270, 12402, 12536, 12672, 12809, 12947, 13087, 13228, 13371, 13515, 13661, 13809,
13958, 14108, 14261, 14415, 14570, 14728, 14887, 15048, 15210, 15374, 15540, 15708, 15878, 16049, 16222, 16398, 16575, 16754, 16935,
17117, 17302, 17489, 17678, 17869, 18062, 18257, 18454, 18653, 18855, 19058, 19264, 19472, 19682, 19895, 20110, 20327, 20546, 20768,
20992, 21219, 21448, 21680, 21914, 22150, 22390, 22631, 22876, 23123, 23372, 23625, 23880, 24138, 24398, 24662, 24928, 25197, 25469,
25744, 26022, 26303, 26587, 26874, 27165, 27458, 27754, 28054, 28357, 28663, 28973, 29285, 29602, 29921, 30244, 30571, 30901, 31235,
31572, 31913, 32257, 32606, 32958, 33314, 33673, 34037, 34405, 34776, 35152, 35531, 35915, 36303, 36695, 37091, 37491, 37896, 38305,
38719, 39137, 39560, 39987, 40419, 40855, 41296, 41742, 42193, 42648, 43109, 43574, 44045, 44520, 45001, 45487, 45978, 46475, 46976,
47484, 47996, 48515, 49039, 49568, 50103, 50644, 51191, 51744, 52303, 52867, 53438, 54015, 54598, 55188, 55784, 56386, 56995, 57611,
58233, 58861, 59497, 60139, 60789, 61445, 62109, 62779, 63457, 64142, 64835, 65535
};
// Set the timer for the PWM
void initTimer() {
// Fast PWM on Pin 10, ICR1 Top
TCCR1A = (1 << COM1B1) | (1 << WGM11); // no prescaler
TCCR1B = (1 << WGM12) | (1 << WGM13) | (1 << CS10);
ICR1 = 0xFFFF;
OCR1B = 0;
pinMode(10, OUTPUT);
}
// This function is called on every loop()
void doSunrise() {
// If the sun is currently not rising do nothing
if(!sunrise)
return;
// If the up button was long pressed turn the light of
if(buttonLongPressed[BTN_UP]) {
sunrise = false;
OCR1B = 0;
return;
}
// Calculate the progress of the sunrise. The result is a value between 0 and 1
float progress = (float) (millis() - sunriseTime) / (sunriseDuration*60.f*1000);
if(progress < 1) {
// Set the timer compare value according to the pwmtable
OCR1B = pgm_read_word(&pwmtable[(int) (999 * progress)]);
}
}

This code draws everything on the display.


[]view raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
void showTime(time_t time, int blink=-1, uint8_t symbol=0);
bool changeTime(uint8_t symbol=0);
// The changeTimeCursorPosition holds what to change
// 0 = hour, 1 = minutes, 0 = seconds
int changeTimeCursorPosition;
// timeToChange is the time that should be changed.
// This must be flexibal because the real time and the alarm time must be changed
time_t timeToChange;
#define BLINK_SPEED 500
unsigned long blinkTimer;
bool blinkOn;
// This function shows the time
// time_t time is the time to display
// int blink is the number that should blink, -1 if no number should blink
// uint8_t symbol is the symbol it can print on the top right corner
void showTime(time_t time, int blink, uint8_t symbol) {
String timestr;
display.clearDisplay();
display.setTextSize(4);
display.setCursor(0, 5);
if(blink == 0 && !blinkOn)
timestr = " ";
else
timestr = l0(hour(time));
display.print(timestr);
display.setCursor(40, 5);
display.print(":");
if(blink == 1 && !blinkOn)
timestr = " ";
else
timestr = l0(minute(time));
display.setCursor(56, 5);
display.print(timestr);
if(blink == 2 && !blinkOn)
timestr = " ";
else
timestr = l0(second(time));
display.setCursor(103, 18);
display.setTextSize(2);
display.print(timestr);
display.setCursor(115, 5);
display.setTextSize(1);
display.write(symbol);
}
// This function converts an integer to a string with a leading 0
String l0(int n) {
return ((n < 10) ? "0" : "") + String(n);
}
// The change time function does everything that lets the user change the time
bool changeTime(uint8_t symbol) {
// At the biginning set the changeTimeCursorPosition = 0
if(stateChanged == true) {
changeTimeCursorPosition = 0;
}
// If the right button was clicked go to the next number to change
if(buttonClicked[BTN_RIGHT]) {
changeTimeCursorPosition++;
blinkOn = false;
}
// finish changing and return true
if(changeTimeCursorPosition == 3) {
return true;
}
// if the button up or down was pressed change the time
if(buttonClicked[BTN_DOWN])
changeTimePart(-1);
if(buttonClicked[BTN_UP])
changeTimePart(1);
// and finally show the time, here the blink argument is used
showTime(timeToChange, changeTimeCursorPosition, symbol);
// changing time is not finished yet -> return false
return false;
}
// This changes the hour, minute or second of timeToChange and handles overflow
// v must be 1 or -1
void changeTimePart(int v) {
TimeElements te;
breakTime(timeToChange, te);
if(changeTimeCursorPosition == 0) {
te.Hour += v;
if(te.Hour == 24)
te.Hour = 0;
if(te.Hour == 0xFF)
te.Hour = 23;
}
if(changeTimeCursorPosition == 1) {
te.Minute += v;
if(te.Minute == 60)
te.Minute = 0;
if(te.Minute == 0xFF)
te.Minute = 59;
}
if(changeTimeCursorPosition == 2) {
te.Second += v;
if(te.Second == 60)
te.Second = 0;
if(te.Second == 0xFF)
te.Second = 59;
}
timeToChange = makeTime(te);
}

Download full code

Video

This video shows the dawn simulator in action.
It is very interesting to see how the camera can’t cope with the pwm of the LEDs.

Prospect

This is just a prototype, I’m already planning the next version. It will come with a internal power supply of which the high voltage (17V) part can be turned of to save power, a rotary encoder for input, a battery powered real time clock to never loose the time, red, yellow and white LEDs for an beautiful gradient and everything in a real casing.
I have already ordered the parts and will publish my progess here.