ATTiny Candle




The ATTiny Candle is an LED candle.  It uses a high brightness LED and some software to mimic the look of a traditional candle without the dangers associated with an open flame.  I imagine they could be useful as movie props where you cannot afford to have a candle go out during a take or in your home in places not suitable for traditional candles such as in a wall niche or alcove.






I have wanted to make an LED candle for some time now, so when I was approached by a work colleague about making one, I was provided the yeast to give it a go.  I figured the hardest part of this project would be making the flicker look realistic, so I decided to let nature do that part for me.  I made this candle with a Light Detecting Resistor ( LDR ) and a fixed resistor acting as a voltage divider.  This is fed to one of the ATTiny85's ADC inputs and sampled at discrete time intervals.  At this time, the sample rate is 100mS.  These 8-bit light level values are then stored to EEPROM so that the candle can recall the flicker pattern to play back on the LED that is connected to a PWM channel after being turned off.  You only need to program the pattern once, but you can program it over and over again with just the push of a button.

What I have really created here is a datalogger for light levels.  Albeit, a datalogger with fairly small storage space of 500 bytes for the ATTiny85.  Never the less, 500 bytes @ 100mS sample rate gives me a loop of ~50 Seconds.  This is sufficiently long to not see a pattern repeating in the flicker pattern.  I guess there is a transformation that occurs when you jam the whole thing into the middle of a candle it becomes an electronic LED Candle instead.  Below is a picture of the aforementioned transformation process.






I did not mention yet, that I used a perfectly functional LED candle as the housing for my ATTiny Candle.  It was about 50c from the thrift store, so it is a cheap enclosure.  I also scavenged the high brightness LED from the original circuit.  Not knowing the specs on the led I set out to measure the forward voltage.  I lit the LED with a high value resistor in series.  In the picture below you can see I am using an LED tester I made that has probably an R860 or 1k0 resistor in it.  The LED plugs into some female header sockets.  Measuring the voltage across the LED legs shows I have a forward voltage of 2.01v.  I will assume 20mA max and select my series resistor based on 3, 1.5v ( nominal ), AA batteries.  So, ( ( 3*1.5v ) - 2.01Vf ) / 0.02mA = R124.5.  I guess the closest value I had on had was an R220 because that is what I used so the LED current is now ~11mA.






Here is a picture of the schematic and one of the assembled circuit about to be installed ( hot glued ) into the inside of a candle.








//Software &c

Here is a listing of the current working code.  I have several improvements I would like to add in the future, but I will outline them later.


/*
Program Description: This program reads a light detecting resistor thru an internal ADC and stores the value, 
after scaling it, to eeprom.  This ADC value is sent to a PWM channel with attached led.  This is essentially a data logger
for light and replay by LED.  If, if you aim the LDR at a flickering candle during its recording phase, you have a flickering 
led candle.  

A circuit description and other details can be found at http://petemills.blogspot.com

Filename: ATTiny_Candle_v1.0.c
Author: Pete Mills

Int. RC Osc. 8 MHz; Start-up time PWRDWN/RESET: 6 CK/14 CK + 64 ms

*/



//********** Includes **********

#include <avr/io.h>     
#include <util/delay.h>   
#include <avr/eeprom.h>




//********** Definitions **********

// LED for flame simulation

#define LED   PB0  
#define LED_PORT PORTB
#define LED_DDR  DDRB



// Light Detecting Resistor for recording a live flame

#define LDR   PINB3 
#define LDR_PORT PINB
#define LDR_DDR  DDRB



// Tactile Switch Input

#define SW1   PINB4
#define SW1_PORT PINB
#define SW1_DDR  DDRB


#define ARRAY_SIZE 500  // size of the flicker array
#define SAMPLE_RATE 100  // ms delay for collecting and reproducing the flicker



//********** Function Prototypes **********

void setup(void);
void toggle_led(void);
void program_flicker(void);
void led_alert(void);
void eeprom_save_array(void);
void eeprom_read_array(void);
void scale_array(void);
uint8_t get_adc(void);
uint8_t scale( uint8_t input, uint8_t inp_low, uint8_t inp_hi, uint8_t outp_low, uint8_t outp_hi);
uint8_t is_input_low(char port, char channel, uint8_t debounce_time, int input_block);




//********** Global Variables **********

uint8_t flicker_array[ ARRAY_SIZE ] = { 0 };
uint8_t EEMEM ee_flicker_array[ ARRAY_SIZE ] = { 0 };


int main(void)
{

uint16_t replay = 0;

setup();

eeprom_read_array();



 while(1)
 { 
 
  
  
  
  if( is_input_low( SW1_PORT, SW1, 25, 250 ) )
  {
   
   // program the flicker
   // after entering and upon completion, a predetermined flash pattern will occur as described in led_alert()  
   // aim the ldr at a flickering candle or any other light source ( like a laser ) you want to record during this time
   // and upon completion the values are stored to eeprom.  They are played back immediately as well 
   // as being recalled from eeprom upon first start up
   
   led_alert();
   program_flicker();
   scale_array();
   eeprom_save_array();
   led_alert();
  }
  
  
  
  // replay the recorded flicker pattern 
  
  OCR0A = flicker_array[ replay ];
  ++replay;
  
  if( replay >= ( ARRAY_SIZE - 13 ) ) // if the end of the stored array has been reached
  { 
   replay = 0;          // start again from the beginning
   //led_alert();
  }
  
  _delay_ms( SAMPLE_RATE );
  _delay_ms( 3 );    // ADC Conversion time
   
 }
}




//********** Functions **********

void setup(void)
{



 //********* Port Config *********

 LED_DDR |= ( 1 << LED);   // set PB0 to "1" for output 
 LED_PORT &= ~( 1 << LED );   // turn the led off

 LDR_DDR &= ~( 1 << LDR );   // set LDR pin to 0 for input
 LDR_PORT |= ( 1 << LDR );   // write 1 to enable internal pullup

 SW1_DDR &= ~( 1 << SW1 );   // set sw1 pin to 0 for input
 SW1_PORT |= ( 1 << SW1 );   // write a 1 to sw1 to enable the internal pullup



 //********** PWM Config *********
 
 TCCR0A |= ( ( 1 << COM0A1 ) | ( 1 << WGM01 ) | ( 1 << WGM00 ) ); // non inverting fast pwm
 TCCR0B |= ( 1 << CS00 ); // start the timer
 
 
 
 //********** ADC Config **********
 
 ADMUX |= ( ( 1 << ADLAR ) | ( 1 << MUX1 ) | ( 1 << MUX0 ) );  // left adjust and select ADC3
 ADCSRA |= ( ( 1 << ADEN ) | ( 1 << ADPS2 ) | ( 1 << ADPS1 ) ); // ADC enable and clock divide 8MHz by 64 for 125khz sample rate
 DIDR0 |= ( 1 << ADC3D ); // disable digital input on analog input channel to conserve power

}




void toggle_led()
{
    LED_PORT ^= ( 1 << LED );
}




uint8_t is_input_low( char port, char channel, uint8_t debounce_time, int input_block )
{

/*
This function is for debouncing a switch input
Debounce time is a blocking interval to wait until the input is tested again.
If the input tests low again, a delay equal to input_block is executed and the function returns ( 1 )
*/
        
 if ( bit_is_clear( port, channel ) )
 {
  _delay_ms( debounce_time );
   
   if ( bit_is_clear( port, channel ) ) 
   {
    _delay_ms( input_block );
    return 1;
   }
 
 }

 return 0;
}




uint8_t get_adc()
{
 ADCSRA |= ( 1 << ADSC );   // start the ADC Conversion
 
 while( ADCSRA & ( 1 << ADSC ));  // wait for the conversion to be complete
 
 return ~ADCH; // return the inverted 8-bit left adjusted adc val

}




void program_flicker()
{ 
 // build the flicker array
 
 for( int i = 0; i < ARRAY_SIZE; i++ )
 {
  flicker_array[ i ] = get_adc();  
  _delay_ms( SAMPLE_RATE );
 }

}




void led_alert()
{
 // this is a function to create a visual alert that an event has occured within the program
 // it toggles the led 10 times.
 
 for( int i = 0; i < 10; i++ )
 {
  OCR0A = 0;
  _delay_ms( 40 );
  OCR0A = 255;
  _delay_ms( 40 );
 }

}




void eeprom_save_array()
{ 
 for( int i = 0; i < ARRAY_SIZE; i++ )
 {
  eeprom_write_byte( &ee_flicker_array[ i ], flicker_array[ i ] );
  
 }
}




void eeprom_read_array()
{
 for( int i = 0; i < ARRAY_SIZE; i++ )
 {
  flicker_array[ i ] = eeprom_read_byte( &ee_flicker_array[ i ] );
  
 }
}




uint8_t scale( uint8_t input, uint8_t inp_low, uint8_t inp_hi, uint8_t outp_low, uint8_t outp_hi)
{
return ( ( ( input - inp_low ) * ( outp_hi - outp_low ) ) / ( ( inp_hi - inp_low ) + outp_low ) );
}




void scale_array()
{
 uint8_t arr_min = 255;
 uint8_t arr_max = 0;
 uint8_t out_low = 20;
 uint8_t out_high = 255;
 
 
 
 // find the min and max values
 
 for( int i = 0; i < ARRAY_SIZE; i++ )
 {
  if( flicker_array[ i ] < arr_min )
   arr_min = flicker_array[ i ];
   
  if( flicker_array[ i ] > arr_max )
   arr_max = flicker_array[ i ];
 }
 
 
 
 // now that we know the range, scale it
 
 for( int i = 0; i < ARRAY_SIZE; i++ )
 {
  flicker_array[ i ] = scale( flicker_array[ i ], arr_min, arr_max, out_low, out_high );
 }
 
} 


After I programmed a flickering candle to the EEPROM using the above code I read back the data.  Below is 500 bytes of candle flicker data just in case you don't care to have a reprogrammable flicker you could use this static one.


:10000000777B7D7B78BA95535E3E3E4352353E7595
:100010004B657B5263586B5562777287858C5D7A2E
:10002000535D5062556F6758784E55956B6D7D7373
:100030007D5B6B686A6A606B7777987A87605B6BC9
:10004000534A5368453B65679C6067537375638A81
:100050007F8388806358586B7A787B838A878A8508
:1000600083888A8A8A8A8A8C8A8A8A8A8A88837F0B
:100070007D7B7A78777570707270704D416D6860B5
:1000800035353D3B4145525E41535D60656A5048A0
:100090004B4E3535313333363B40504E525D605315
:1000A000564B352D2E2E353838393B383158406077
:1000B0004D505A5D434053585A554E31312B2E33D3
:1000C0003136353638393938404A413B506240364E
:1000D000292D455E5D523E333B433545383531333E
:1000E00036363936383B4136363039332B29335A98
:1000F0006356413D5052556065553B302E303B4E66
:10010000362E2B3B393D4A503D45584E4B4E4A45C5
:10011000584B555D5B56585E60775E385A52464B79
:10012000504A4A354E412E363638524B463B3340C4
:100130004E605A504D434A504B48403D4046525BFA
:100140006263635B52465B43554526353B5B434DDB
:100150004056585A5D50464545413B437287908A08
:100160008F979D9573656B4D464555554156555531
:10017000565A5A5B5E56625565585A62686D6D6B89
:10018000686A6F656D316F55485055675A41555EC5
:100190006065686863606A60676A7F838C8788923D
:1001A0008D8F888C8C85826A4E35231119433B4193
:1001B000674A4A3B2E3045414A5848705B6D72622F
:1001C0007567565A5E554D77532D36415D55404003
:1001D0004040403E415E82928888909488857B634F
:1001E000555356555053550334013A7EFF01603E36
:1001F0003E28018EFFFFFFFFFFFFFFFFFFFFFFFF16
:00000001FF




Here is a video of a LDR programmed candle flicker.  I was blowing gently on the candle flame during recording so that it would be a lively flicker for the video.  It reminds me of a candle on the porch when a storm is coming in.  Of course, it can be reprogrammed with a more subtle flame, the light from a bonfire or fire place, a laser pointer or even your hand shading light from the LDR.  You could take a walk thru the woods and record the sunlight shining thru and shadows coming from the treetops.  I have found the best results when programming the ATTiny Candle in a darkened room.






The list of improvements...

A) I would get the lower power version of the ATTiny85 to allow operation from 2 AA batteries.  I only had ATTiny85-20PU in stock so I had to use 3 batteries.  The black box external to the candle is the battery box.  Unsatisfactory, really.

B) Once the batteries are tucked away and the switch is no longer accessible it may be a good idea to put the candle to sleep instead of switching it off.  It could, for example, turn itself off after an hour or two of operation too.  Then it could wake up with an external interrupt.  And what would interrupt it? Improvement C of course.

C) A "blow" detector to be able to blow "out" and back "on",  the candle.  I did some quick experimentation with a piezo to detect a "blow" ( I guess ), although it worked, I abandoned the idea, saving it for later iterations.  I couldn't find a location for the large piezo speakers I have, so I will have to order some smaller ones.

D) If you have any ideas please leave a comment!

Here are a couple more photos.  Let me know if you have any success building an ATTiny Candle yourself.






Comments

ElectroNick said…
This IS pretty cool! Pretty much everyone else just tries to come up with pseudo-randomness that imitates the actual handle and your project is the first I've ever seen that records the real thing.

As I was reading the project notes, you got me thinking that there's also a spatial aspect to a real candle effect - the flame not only changes brightness but also dances around quite a bit and so the shadows are moving around and supplementing the brightness changes rather nicely. I've started thinking about the best way to record brightness levels from several spots spread around the wick rather than the ambient light level. The recorded values could then be fed back to LEDs positioned in similar points around the center of the candle to re-create the spatial aspect of the flame dance. Well, just an idea at this point, really.
Keep up great work!
Cheers!
Deb and Ted said…
I am looking at the guts of a cheap fan which used persistence of vision to show the name of a resort in L.E.D.s on the soft vinyl blades. As the blade rotates, a spring makes contact with a copper pad, with an interrupter etched in the pad to give a clock pulse. The circuit uses a 24c02 EEPROM and a 4 pin circuit to allow programming of any phrase on the blade.
I found your candle after googling "EEPROM circuits".
I'm just learning this stuff. Thanks for the build info. I'll be in the basement sniffing solder fumes and following you.
Anonymous said…
Great project - lots to learn from this, as I am a novice to avr programming.

One request: Please increase the width of your web page - it's long and narrow, and it makes the code display difficult to comprehend.

Regards
Unknown said…
Hi!
I compiled your code and made the hex file for my atmega48pa-pu. This time I haven't got an LDR at home, so I tested it with an other switch. Switched your original switch on, and made an SOS morse code with my switch. After that I connect a led to it, but it constantly lighted at 1.75V. I measured the output and saw a really interesting output. There was my SOS signal, but the output was 4.99V-5V. Is there something to modify in the code to work with my atmega? Or how can I get more different voltages?
Thank you for your help!

Andrew
Pete said…
Hi andrew, send me an email. My address is in the "about" profile link on the right.

Did you port the code to the atmega? Or just load the at tiny hex to the atmega?

How did you connect your input switch? And which pin on the mega?

How did you measure the output where you could see the Morse code but average voltage was 1.75? Oscilloscope?

-pete