Ready, Set, Go!

LED candles are very common these days.  It's easy to see the attraction -- there's no fire danger and they never need to be replaced (just batteries).  But there's such a wide variation in price and quality that it's hard to know what to purchase.  Some store-bought candles flicker very realistically, others just seem to blink.  Some use plain white LEDs, some look like real flames.  And there's no way to know how good a candle will look without buying it.

Thankfully, with Adafruit's Gemma microcontroller and an RGB LED pixel, making your own LED candle only takes a couple of minutes.  Because the LED can create any color and the Gemma can control the flickering very precisely, it will look better than any store-bought candle ever could.  Because the parts are so cheap, it will cost much less too!

Parts

Each candle needs one Gemma and one through-hole diffused NeoPixel.  It'll also need a power source, either a battery pack or a USB cable.  You'll also need a soldering iron and a computer.

Assembly

To assemble the candle, bend the pins so they reach the correct connectors on the Gemma:

  • LED data-in to Gemma D0
  • LED 5V to Gemma Vout
  • LED Ground to Gemma GND

The LED's data-out pin is not needed.  You can cut it off or just bend it out of the way.

Put a drop of solder on each connection to hold it.

Be sure to consult the LED's product page to identify the pins -- connecting them incorrectly could damage the LED.

Programming

If you don't already have the Arduino IDE installed and configured for Gemma programming, use this guide to get it going: learn.adafruit.com/introducing-gemma/setting-up-with-arduino-ide

Once it's working, connect the Gemma and upload the latest LED candle code, available here: github.com/samclippinger/ledcandle

(It's also duplicated below)

#include <Adafruit_NeoPixel.h>

// The onboard red LED's pin
#define REDLED_PIN              1
// The data-in pin of the NeoPixel
#define WICK_PIN                0
// Any unconnected pin, to try to generate a random seed
#define UNCONNECTED_PIN         2

// The LED can be in only one of these states at any given time
#define BRIGHT                  0
#define UP                      1
#define DOWN                    2
#define DIM                     3
#define BRIGHT_HOLD             4
#define DIM_HOLD                5

// Percent chance the LED will suddenly fall to minimum brightness
#define INDEX_BOTTOM_PERCENT    10
// Absolute minimum red value (green value is a function of red's value)
#define INDEX_BOTTOM            128
// Minimum red value during "normal" flickering (not a dramatic change)
#define INDEX_MIN               192
// Maximum red value
#define INDEX_MAX               255

// Decreasing brightness will take place over a number of milliseconds in this range
#define DOWN_MIN_MSECS          20
#define DOWN_MAX_MSECS          250
// Increasing brightness will take place over a number of milliseconds in this range
#define UP_MIN_MSECS            20
#define UP_MAX_MSECS            250
// Percent chance the color will hold unchanged after brightening
#define BRIGHT_HOLD_PERCENT     20
// When holding after brightening, hold for a number of milliseconds in this range
#define BRIGHT_HOLD_MIN_MSECS   0
#define BRIGHT_HOLD_MAX_MSECS   100
// Percent chance the color will hold unchanged after dimming
#define DIM_HOLD_PERCENT        5
// When holding after dimming, hold for a number of milliseconds in this range
#define DIM_HOLD_MIN_MSECS      0
#define DIM_HOLD_MAX_MSECS      50

#define MINVAL(A,B)             (((A) < (B)) ? (A) : (B))
#define MAXVAL(A,B)             (((A) > (B)) ? (A) : (B))

Adafruit_NeoPixel *wick;
byte state;
unsigned long flicker_msecs;
unsigned long flicker_start;
byte index_start;
byte index_end;

void set_color(byte index)
  {
  index = MAXVAL(MINVAL(index, INDEX_MAX), INDEX_BOTTOM);
  if (index >= INDEX_MIN)
    wick->setPixelColor(0, index, (index * 3) / 8, 0);
  else if (index < INDEX_MIN)
    wick->setPixelColor(0, index, (index * 3.25) / 8, 0);

  wick->show();
  return;
  }

void setup()
  {
  // There is no good source of entropy to seed the random number generator,
  // so we'll just read the analog value of an unconnected pin.  This won't be
  // very random either, but there's really nothing else we can do.
  //
  // True randomness isn't strictly necessary, we just don't want a whole
  // string of these things to do exactly the same thing at the same time if
  // they're all powered on simultaneously.
  randomSeed(analogRead(UNCONNECTED_PIN));

  // Turn off the onboard red LED
  pinMode(REDLED_PIN, OUTPUT);
  digitalWrite(REDLED_PIN, LOW);

  wick = new Adafruit_NeoPixel(1, WICK_PIN, NEO_RGB + NEO_KHZ800);
  wick->begin();
  wick->show();

  set_color(255);
  index_start = 255;
  index_end = 255;
  state = BRIGHT;

  return;
  }

void loop()
  {
  unsigned long current_time;

  current_time = millis();

  switch (state)
    {
    case BRIGHT:
      flicker_msecs = random(DOWN_MAX_MSECS - DOWN_MIN_MSECS) + DOWN_MIN_MSECS;
      flicker_start = current_time;
      index_start = index_end;
      if ((index_start > INDEX_BOTTOM) &&
          (random(100) < INDEX_BOTTOM_PERCENT))
        index_end = random(index_start - INDEX_BOTTOM) + INDEX_BOTTOM;
      else
        index_end = random(index_start - INDEX_MIN) + INDEX_MIN;

      state = DOWN;
      break;
    case DIM:
      flicker_msecs = random(UP_MAX_MSECS - UP_MIN_MSECS) + UP_MIN_MSECS;
      flicker_start = current_time;
      index_start = index_end;
      index_end = random(INDEX_MAX - index_start) + INDEX_MIN;
      state = UP;
      break;
    case BRIGHT_HOLD:
    case DIM_HOLD:
      if (current_time >= (flicker_start + flicker_msecs))
        state = (state == BRIGHT_HOLD) ? BRIGHT : DIM;

      break;
    case UP:
    case DOWN:
      if (current_time < (flicker_start + flicker_msecs))
        set_color(index_start + ((index_end - index_start) * (((current_time - flicker_start) * 1.0) / flicker_msecs)));
      else
        {
        set_color(index_end);

        if (state == DOWN)
          {
          if (random(100) < DIM_HOLD_PERCENT)
            {
            flicker_start = current_time;
            flicker_msecs = random(DIM_HOLD_MAX_MSECS - DIM_HOLD_MIN_MSECS) + DIM_HOLD_MIN_MSECS;
            state = DIM_HOLD;
            }
          else
            state = DIM;
          }
        else
          {
          if (random(100) < BRIGHT_HOLD_PERCENT)
            {
            flicker_start = current_time;
            flicker_msecs = random(BRIGHT_HOLD_MAX_MSECS - BRIGHT_HOLD_MIN_MSECS) + BRIGHT_HOLD_MIN_MSECS;
            state = BRIGHT_HOLD;
            }
          else
            state = BRIGHT;
          }
        }

      break;
    }

  return;
  }

When power is first connected, the LED should light up solid blue until the Gemma starts running the code.  This is normal!  If the LED doesn't light up blue, it may be dead.  That happens, just try another one.

Power

To power the candle, you can either use a USB cable or a battery pack.  Two CR2032 batteries are enough to light it up, thanks to the Gemma's voltage regulator dropping the power to 3V.  I haven't done any testing to see how long they'll last; you'll know the batteries are getting low when you start seeing green flickering as the candle dims.

Since I've built several of these and I want them side-by-side, I found a cheap powered USB hub that works very nicely.

Diffuser

To make the candle look real, it will need to be covered by a semi-opaque chimney or globe to diffuse the LED's light.  I found some frosted glass chimneys at my local hardware store.  They're supposed to be replacements for broken fixtures, but they look great here.  Almost anything is possible -- consider a structure covered in tissue paper or a white paper bag or even an old wax candle that's been hollowed out.

Final thoughts

In this project, I used one Gemma for each candle, which makes it very easy to run them from batteries and place them anywhere.  But there's no reason why one Gemma couldn't run multiple LEDs if they were wired together.  This could be done to create a string of candles or even a multi-wick LED candle.

This LED candle only flickers a realistic yellow/orange color, but there's no reason it couldn't have different a colored "flame", or even randomly rotate through colors.

One small caveat: because of the way NeoPixels are designed, their three internal LEDs (red, green and blue) constantly flicker at 400 Hz to hold a color.  This is far too fast to be seen with the human eye, so they appear to have a steady, smooth light.  But cameras can see the refresh flicker.  In still photos, the effect creates "banding" in the photo (visible in the photos above).  In videos, the effect creates constantly moving horizontal bands that are very noticable.  DotStar LEDs don't have this problem; hopefully Adafruit will come out with through-hole versions of those soon!

Last updated on 2015-11-04 at 12.39.52 PM Published on 2015-11-04 at 12.41.12 PM