Introduction: Blue LED Dawn Simulator for Soleil Sun Alarm

About: Eric J. Wilhelm is the founder of Instructables. He has a Ph.D. from MIT in Mechanical Engineering. Eric believes in making technology accessible through understanding, and strives to inspire others to learn …

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.

Step 1: 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.

Step 2: 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

Step 3: 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.

Step 4: 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.

Step 5: 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.

Step 6: Build Circuit

Build the driver circuit.

Step 7: 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.

Step 8: 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).

Step 9: 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)

/* 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);}

Step 10: Build Connection Cable

Use two male 1/8 plugs and a length of wire to make a cable.

Step 11: Connect LED Light Box to Clock

Connect the LED light box to your clock and enjoy gradual blue LED sunrises.