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.
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.
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 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).
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