An LED-based fire lamp
(Last modified 8 Oct 11)

I call this a fire lamp because the LEDs, driven by an Atmel ATtiny85 MCU, provide a very realistic imitation of a flickering flame about the size of a tennis ball.  Unfortunately, I've never bothered to buy a video camera, so I can't embed any live video.  However, the flickering effect is excellent, without any strobing or blinking.

The firelamp LED project in a table lamp

I used the Liteon LTL912SEKSA Piranha red LEDs for this build.  These LEDs are available (Oct 2011) from Electronic Goldmine for $0.39 each.  Unlike typical 3 mm or 5 mm LEDs, these device will take up to 70 mA each, have a wide dispersion angle of 60 degrees, and put out nearly four lumens.  They are commonly used in automobile brake lights and make a totally excellent light source for light art.


Liteon "Pirhana" LEDs

Closeup of the Liteon Pirhana LEDs.  Note that the pins are in a square pattern, spaced 0.2" apart.




The circuit is slightly more complex than a typical LED driver because of the LED current.  The ATtiny can't source the needed 60 mA per LED, so I added a 2N2222 transistor in each LED control line.  You can use just about any NPN transistor for this if you can't find any 2N2222 or PN2222 devices.  You can check out the schematic here.  (firelamp.pdf)

The schematic includes a note for changing resistor values to use smaller (less current) LEDs.

I built the circuit up on a small breadboard, adding a 2-pin power connector for hooking up a wall-wart power supply.  You can find small, 5 VDC switcher wall-warts in a lot of surplus or thrift stores now days; these make excellent power supplies for small projects.  You can tell you're holding a switcher wall-wart because it will be very lightweight, only a few ounces.  If you hook it up to AC and measure the output, you will see a value from about 4.9 to 5.1 VDC, unlike the 6 to 9 VDC put out by some of the older, unregulated (and heavier) wall-warts.


The firelamp electronics

The circuit fits onto a 2" x 3" protoboard with plenty of room to spare.  The capacitor C1 is shown as 25 uf, but just about anything between 10 and 1000 uf will work.

Using a small wall-wart and an old-fashioned socket plug lets you install the firelamp circuit board directly in a table lamp socket.  This gives you a table lamp that looks like it has a small fire in it, rather than a bulb (which explains the project's name).

Firelamp board plugged into a lamp

Here is the firelamp PCB and wall-wart, plugged into a socket plug and installed in a table lamp.  I stuck the PCB onto the top of the wall-wart using foam tape, but hot-glue or RTV would probably work, as well.  I didn't shorten the power cord on the wall-wart, just wound it around the body and tied it in place.

The firmware for this project was written in C in Atmel's AVR Studio4, then pushed into the ATtiny using an AVRISP mkII programmer.  The only tricky part of the firmware is the technique for doing pulse-width modulation (PWM) for the LEDs.  I am really fussy about LED PWM.  I don't want to see any strobing when my eyes scan past the LEDs.  For this project, I used a PWM clock of 1 MHz / 256, or about 4 kHz.  Each LED is controlled by a 32-bit PWM mask.  At each PWM clock (4000 times per second), the low bit of each LED's PWM mask is written to the output port for that LED.  Intensity of each LED is varied by a different PWM mask from the table of 32 possibilities.  For example, using a PWM mask value of 0x55555555UL will give an LED intensity of about 50%, since the LED is on every other PWM clock.

Feel free to play around with the timing.  You can modify the timer setup to use a compare-match instead of the overflow shown here, which would let you use an even faster PWM clock rate.

Here is a .hex file you can burn directly into an ATtiny85 if you don't want to be bothered compiling.  (firelamp.hex)



/*
 *  firelamp.c      PWM control of LEDs on an ATtiny85
 */

#include  <avr/io.h>
#include  <avr/pgmspace.h>
#include  <avr/interrupt.h>


#define  NUM_LEDS            6
#define  MASK_LEDS            0x3f        /* assumes PB0 - PB5 */

#define  NUM_PWM_STATES        32
#define  MAX_PWM_STATE        (NUM_PWM_STATES-1)

#define  PORT_LEDS            PORTB
#define  DDR_LEDS            DDRB

#define  DELAY                1000UL        /* general delay in tics */




const unsigned long int        pwmvals[NUM_PWM_STATES] PROGMEM =
    {
        0x00000000L,  0x00010000L,  0x00010001L,  0x80200800L,
        0x01010101L,  0x82080820L,  0x84108410L,  0x84112410L,
        0x11111111L,  0x11249111L,  0x12491249L,  0x25225252L,
        0x25252525L,  0x25525522L,  0x25552555L,  0x25555555L,
        0x55555555L,  0x55575555L,  0x57555755L,  0x57575755L,
        0x57575757L,  0x57577775L,  0x57777577L,  0x57777777L,
        0x77777777L,  0xf7777777L,  0xf777f777L,  0xf7f7f777L,
        0xf7f7f7f7L,  0xf7fff7f7L,  0xf7fffff7L,  0xffffffffL
    };





uint8_t                        bright[NUM_LEDS];
uint8_t                        delta[NUM_LEDS];
uint32_t                    pwm[NUM_LEDS];
uint16_t                    delays[NUM_LEDS];
volatile uint16_t            tics[NUM_LEDS];



/*
 *  Local functions
 */

uint16_t                    readtics(uint8_t  cntr);
void                        writetics(uint8_t  cntr, uint16_t  delay);
void                        assignpwm(uint8_t  led);
static long unsigned int      b_random(void);
static long unsigned int      rnd(long unsigned int  val);



int  main(void)
{
    uint8_t                    n;
    unsigned long int        nval;

    TCCR0B = (1<<CS01);                        // /8 prescaler
    TIMSK = (1<<TOIE0);                        // enable interrupt on TOF

    PORT_LEDS = PORT_LEDS & ~MASK_LEDS;        // turn off all LEDs
    DDR_LEDS = DDR_LEDS | MASK_LEDS;        // make all LED drive lines outputs

    for (n=0; n<NUM_LEDS; n++)
    {
        bright[n] = 0;                        // start with all brightness values at 0
        assignpwm(n);                        // make it so
        delays[n] = 200;                    // start with an arbitray delay value
    }

    sei();                                    // turn on interrupts

    while (1)                                // main loop
    {
        for (n=0; n<NUM_LEDS; n++)            // for each LED
        {
            if (readtics(n) == 0)            // if done with current delay for this LED...
            {
                nval = rnd(20) + 11;
                bright[n] = nval & 0xff;    // update the brightness
                assignpwm(n);

                nval = rnd(750) + 250;        // calc a random delay
                writetics(n, nval & 0xffff);
            }
        }
    }
    return  0;
}



void  assignpwm(uint8_t  led)
{
    cli();                                    // do not disturb
    pwm[led] = pgm_read_dword(&pwmvals[bright[led]]);
    sei();
}



uint16_t  readtics(uint8_t  cntr)
{
    uint16_t            t;

    t = tics[cntr];
    if (t != tics[cntr])  t = tics[cntr];
    return  t;
}



void  writetics(uint8_t  cntr, uint16_t  delay)
{
    cli();
    tics[cntr] = delay;
    sei();
}




/*
 *  The following functions try to duplicate the ANSI random() function for 8-bit MCUs such as the
 *  Atmel ATmega1284p.  Seed is fixed at compile time.
 */
static long unsigned int  b_random(void)
{
    static long unsigned int                seed = 12345678L;

    seed = 1664525L * seed + 1013904223L;
    return seed;
}



static long unsigned int  rnd(long unsigned int  val)
{
    long unsigned int                    t;

    t = b_random();                            // compute a 32-bit random number
    val = t % val + 1L;                        // now keep it within requested range
    return  val;
}






SIGNAL(TIM0_OVF_vect)
{
    uint8_t            n;
    uint8_t            mask;
    uint32_t        t;

    mask = 0;
    for (n=0; n<NUM_LEDS; n++)                // for LED 0 through NUM_LED-1...
    {
        t = pwm[n] & 1;                        // get low bit of PWM for this LED
        mask = mask | (t << n);                // move low bit of PWM into proper bit of mask
        pwm[n] = (pwm[n] >> 1);                // move PWM value one bit to the right
        if (t)  pwm[n] = pwm[n] + 0x80000000;    // rotate original low bit into high bit
        if (tics[n]) tics[n]--;                // drop this counter if not yet 0
    }

    PORT_LEDS = PORT_LEDS & ~(MASK_LEDS);    // strip off port lines dedicated to LEDs
    PORT_LEDS = PORT_LEDS | mask;            // turn on the LEDs
}









Home