Blue LED Dawn Simulator for Soleil Sun Alarm
by ewilhelm in Living > Life Hacks
56838 Views, 90 Favorites, 0 Comments
Blue LED Dawn Simulator for Soleil Sun Alarm
This add-on to a Soleil Sun alarm lets the clock control the brightness of a bank of LEDs. A microcontroller adjusts the power of the LEDs so they appear to dim at the same rate as any incandescent lights you may have attached to the alarm.
Background
We run a tight ship here at Squid Labs. Partners are expected at their desks, pencils ready, at 7:00 AM sharp. Given the 4-hour each way commutes typical of the Bay Area and 12-hour works-days, there's not alot of time left for sleeping! So, anything to simulate a natural existence, like a sunrise, is greatly appreciated.
I use a Soleil Sun alarm with its incandescent light controller to simulate sunrises in the morning. It works fairly well and often I wake while the lights are about half brightness, before the radio comes on. Recently, a few close friends, some with seasonal affective disorder, have started using a blue light box -- the Golite -- and swear by it's effectiveness.
Despite having timers on the Golite, it doesn't have an alarm setting and won't turn the LEDs on automatically. Worse, in darkness, the lowest light setting of 10% isn't that different than 100%, and compared to a "sunrise" driven by incandescents, the difference between 0% and 10% is quite jarring. I wanted to try blue light and so decided to build my own LED light source integrated with my alarm clock.
I use a Soleil Sun alarm with its incandescent light controller to simulate sunrises in the morning. It works fairly well and often I wake while the lights are about half brightness, before the radio comes on. Recently, a few close friends, some with seasonal affective disorder, have started using a blue light box -- the Golite -- and swear by it's effectiveness.
Despite having timers on the Golite, it doesn't have an alarm setting and won't turn the LEDs on automatically. Worse, in darkness, the lowest light setting of 10% isn't that different than 100%, and compared to a "sunrise" driven by incandescents, the difference between 0% and 10% is quite jarring. I wanted to try blue light and so decided to build my own LED light source integrated with my alarm clock.
Parts and Materials
Here's what I used and where I got it.
Blue LED light box - click for Instructable
Atmel ATMEGA8-16PI (buy a few in case you burn one out) - Jameco.com
Optoisolator 4N35 - Jameco.com
5 volt regulator, LM341T-5.0 for example - RadioShack
lying around the shop but available at RadioShack or Jameco:
wires
1 kOhm resistor
470 Ohm resistor
Blue LED light box - click for Instructable
Atmel ATMEGA8-16PI (buy a few in case you burn one out) - Jameco.com
Optoisolator 4N35 - Jameco.com
5 volt regulator, LM341T-5.0 for example - RadioShack
lying around the shop but available at RadioShack or Jameco:
wires
1 kOhm resistor
470 Ohm resistor
About the Soleil Alarm Clock
The manual of the alarm clock claims the "data port" spits out a 0-5 volt signal that is read by the external 300 W controller. With dreams of reusing parts of the PWM motor controller described here, I probed the output.
Turns out it's a 190 Hz 0-5 volt PWM signal with a duty cycle that varies in 44 increments of 120 us each.
Turns out it's a 190 Hz 0-5 volt PWM signal with a duty cycle that varies in 44 increments of 120 us each.
About the LEDs and Driver
You might, as I did, think that you could add a transistor to flip the duty cycle of the alarm clock and plug it straight into the external control of the BuckPuck (5V equals off, so if the driver is used without electronics its default state is on). This works, but the lowest brightness level from the clock (one 120 us long pulse every 5.2 ms) looks nearly the same as full blast. Incandescent lights only appeared as bright as the LEDs midway through the cycle.
So, I brought in a microcontroller to generate a PWM signal with greater resolution. (This project is totally doable without a microcontroller -- in fact, while getting up to speed on the Atmels used here, I used the LEDs under direct control of the alarm clock.) At 130 Hz, a pulse 1 us long does not turn the LEDs on; a 2 us long pulse just barely turns them on. So, 16 bit PWM appeared to be enough.
So, I brought in a microcontroller to generate a PWM signal with greater resolution. (This project is totally doable without a microcontroller -- in fact, while getting up to speed on the Atmels used here, I used the LEDs under direct control of the alarm clock.) At 130 Hz, a pulse 1 us long does not turn the LEDs on; a 2 us long pulse just barely turns them on. So, 16 bit PWM appeared to be enough.
Logarithmic Ramp
Again, due to the nature of the LEDs, a linear increase of the PWM signal did not look right. Knowing that the clock only has 44 discrete steps, I shaped an exponential function that would hit these approximate values:
f(0) = 65534 (65535 = 5 volts = off)
f(2) = 65533 (just barely on)
f(13) = 99.84% of 216 (somewhere around 20% of full on)
f(44) = 5% of 216 (full power)
A plain old exponential of the type f(x) = A(1-exp(Bx+C)) couldn't do it. The rate of change needed to change over the range of x, so I tried:
f(x) = A(1-exp( (Bx + D)x + C) )
Solving it directly proved too hard, so I made a spreadsheet and adjusted the parameters by hand.
This could also be done with a lookup table. However, when I started, I was convinced I could find the exact function, and so a lookup table seemed like a huge cop-out.
f(0) = 65534 (65535 = 5 volts = off)
f(2) = 65533 (just barely on)
f(13) = 99.84% of 216 (somewhere around 20% of full on)
f(44) = 5% of 216 (full power)
A plain old exponential of the type f(x) = A(1-exp(Bx+C)) couldn't do it. The rate of change needed to change over the range of x, so I tried:
f(x) = A(1-exp( (Bx + D)x + C) )
Solving it directly proved too hard, so I made a spreadsheet and adjusted the parameters by hand.
This could also be done with a lookup table. However, when I started, I was convinced I could find the exact function, and so a lookup table seemed like a huge cop-out.
Downloads
Build Circuit
Build the driver circuit.
Setup to Program Microcontrollers
I choose Atmel Mega8s because they are rapidly becoming the microcontroller of choice around Squid Labs. The Mega8s have some nice features that make them a great choice for this project including interrupts synced to external signals, 16-bit PWM, and an 8 Mhz clock. The one drawback is that you need an Atmel programmer. Here is a description of programming Atmels using the parallel port, which could be adapted to this project.
I use Context to edit my code, and avr-gcc from WinAVR to compile it. I couldn't get the programmer bundled with WinAVR to work with my programmer, so I used AVR Studio 4 instead.
When you open AVR Studio, press the small "AVR" button shaped like a microchip to get to the programmer menu.
I use Context to edit my code, and avr-gcc from WinAVR to compile it. I couldn't get the programmer bundled with WinAVR to work with my programmer, so I used AVR Studio 4 instead.
When you open AVR Studio, press the small "AVR" button shaped like a microchip to get to the programmer menu.
Wire Up Programmer
If you're like me, you'll need to compile and try the code many times before it actually works. Taking the Atmels in and out of the programmer can get really annoying, so I ran wires directly into the breadboard.
Run wires between the programmer and the ATMEGA8 for pins 1, 7 (VCC), 8 (GND), 17, 18, 19, 20 (AVCC), 21 (AREF), and 22 (GND).
Run wires between the programmer and the ATMEGA8 for pins 1, 7 (VCC), 8 (GND), 17, 18, 19, 20 (AVCC), 21 (AREF), and 22 (GND).
Program Microcontroller
The code and makefile are written to work with avr-gcc. Briefly, it calculates the duty cycle from the clock, calculates the desired LED control level, and spits out a PWM signal. There's some averaging of the input and output values to reduce undesired flashing of the LEDs (like when they are just starting a sunrise). Essentially, the microcontroller acts as a nonlinear PWM filter.
Code: (download the .c file rather than cut and pasting this text; there are some formatting issues with the syntax)
Code: (download the .c file rather than cut and pasting this text; there are some formatting issues with the syntax)
/* LED microcontroller dimmer for use with Soleil Sun AlarmWritten for Atmel ATMega8 and avr-gccEric J. WilhelmSquid Labs, LLCAttribution-NonCommercial-ShareAlike 2.5You are free: * to copy, distribute, display, and perform the work * to make derivative worksUnder the following conditions:by Attribution. You must attribute the work in the manner specified by the author or licensor.Noncommercial. You may not use this work for commercial purposes.Share Alike. If you alter, transform, or build upon this work, you may distribute the resulting work only under a license identical to this one. * For any reuse or distribution, you must make clear to others the license terms of this work. * Any of these conditions can be waived if you get permission from the copyright holder.*/#include <inttypes.h>#include <avr/io.h>#include <avr/interrupt.h>#include <avr/signal.h>#include <math.h># define OC1 PB1# define DDROC DDRB# define OCR OCR1Avolatile uint16_t xtimer;volatile uint16_t timer0;volatile uint8_t status;SIGNAL(SIG_OVERFLOW0){ timer0++; TCNT0=96; // preload the timer with 96 to make this interrupt occur every 20 us.}SIGNAL(SIG_OVERFLOW1){ //The interrupts don't work properly without this definition.}// falling edge PWM signal (rising edge at clock; reversed due to optoisolator)SIGNAL(SIG_INTERRUPT1) { // Zero timer0 to count the length of the positive pulse timer0=0; status=1;}//rising edge PWM signal (falling edge at clock; reversed due to optoisolator)SIGNAL(SIG_INTERRUPT0) { //record the length of the positive pwm signal in xtimer // if timer0 is greater than approximately 263 (at 20 us per interrupt) than the pulse was missed if(timer0<270) xtimer=timer0; status=0;}voidioinit (void){ // Timer1 does ~16-bit PWM TCCR1A = _BV (COM1B1) | _BV (WGM11) | _BV (COM1A1); TCCR1B = _BV (WGM13) | _BV (WGM12) | _BV (CS10); ICR1 = 65535; //Timer0 counts TCCR0 = _BV (CS00); TCNT0 = 96; timer0=0; // set PWM value to 0 OCR = 0; //enable OC1 as output DDROC = _BV (OC1); //enable external interrupts GICR = _BV (INT1) | _BV (INT0); //INT0 is rising edge, INT1 is falling edge MCUCR = _BV (ISC01) | _BV (ISC00) | _BV(ISC11); xtimer=0; status=0; //enable timers timer_enable_int (_BV (TOIE1) | _BV (TOIE0) ); // enable interrupts sei ();}intmain (void){ #define B -0.00325 #define C -11.09 #define D 0.396503 int i,on=0, oncounter=0; unsigned int x(100), y; long t(100), u; ioinit (); for (;;) { //average xtimer over samples because it jumps around alot for(i=99;i>0;i--) { x(i) = x(i-1); } x(0) = xtimer; y=0; for(i=0;i<100;i++) { y = y+x(i); } y=y/100; //divide by 5 because the clock has 120 us resolution on its PWM y=y/5; // average the output so it doesn't jump around for(i=99;i>0;i--) { t(i) = t(i-1); } //determine what to do if(timer0>270 && status == 1 && on == 1) { //Turn light on t(0) = 0; on=1; } else if(timer0>270 && status == 0) { //Turn light off t(0) = 65535; xtimer=0; on=0; oncounter=0; } else if(timer0<270){ t(0) = 65535*(1-exp((B*y + D)*y + C)); if(t(0)>65535) t(0) = 65535; if(t(0)<0) t(0) = 0; } //oncounter prevents the light from turning on suddenly from an off state if timer0>270, but there's a positive pulse on the PWM //this happens during the very start of a sunrise, when the clock's PWM hasn't quite turned on at the right frequency else if(timer0>270 && status == 1) { if(++oncounter==5) { on = 1; oncounter=0; } } // average the output so it doesn't jump around u=0; for(i=0;i<100;i++) { u=u+t(i); } //Change the output PWM OCR = u/100; } return (0);}
Build Connection Cable
Use two male 1/8 plugs and a length of wire to make a cable.
Connect LED Light Box to Clock
Connect the LED light box to your clock and enjoy gradual blue LED sunrises.