LED Candle – Lights, Flickers, Blows Out, Smokes, and Smells

About This Candle & How to Read This Instructable

Here’s my take on a LED candle that offers the full experience of a real candle. Start to finish.

Be sure to watch the YouTube video below!

Background:

I bought my first Arduino about 3 years ago with a LED candle project in mind. I wanted to make a candle that you could light with a match, then blow it out. I’ve been fine-tuning various aspects of it ever since. About a month ago, I realized that adding smoke was a very real possibility. Since then, I’ve been finalizing the candle, recording videos, and preparing content for this Instructable.

How To Read This Instructable

This candle is a very technical candle, so you and I must proceed carefully for the sake of clarity.

[ Brackets ] will surround secondary information like tips, tricks, and details that are totally OK to skip your first time through this. Then, if you end up actually doing this Instructable, I think you’ll find the content in those brackets worth reading.

I Won’t Micromanage Your Soldering Tip

I built this candle around various things I had available. Notably, the vase. Chances are, my vase is a different size than your vase. Therefore, I’m not going to tell you exactly how to lay out the components and where to solder what. But I will give you clear Fritzing diagrams so you have a clear idea of what connections you need to make.

[Hopefully this helps keep this content insightful while reducing the inapplicable.]

See the brackets? You would have been totally OK skipping over that.

You Like Videos? Me too.

I uploaded this 12-minute video to YouTube as a supplement to this Instructable: Be sure to check it out.

Additional Resources

GitHub Repo

KeithsTestGarage.com – additional details will be posted here over time.

Step 1: Three Milestones

The whole project is pretty big. I’m splitting it into 3 bite-sized milestones as is very natural, as well as providing a Fritzing diagram and full code for each one. This should be less daunting for both of us.

Milestone 1: Built-In LED

  • Lighting with a match
  • Blowing out with your breath

Milestone 2: + NeoPixels

  • Lighting with a match
  • Blowing out with your breath
  • Full NeoPixel implementation

Milestone 3: + Smoke

  • Lighting with a match
  • Blowing out with your breath
  • Full NeoPixel implementation
  • Smoke

Step 2: Gather Parts

Core Parts

I figure you have a lot of parts on hand, and I’m having trouble trying to figure out how granular to be with my parts list. I think rather than trying to psychoanalyze you from afar, I’ll just post links to the items I bought. Then, you can decide what you need, what you don’t, and where to deviate from my list.

Milestone 1

Milestone 2

Milestone 3

Project Etc.

  • Fiberglass PCB boards. I like this variety kit. Good range of sizes and cheap enough to use them without worrying too much about the cost.
  • Connectors – I used a mixture of connectors, but, in the future, I’d definitely only use spring terminals for everything. They pull apart and combine into longer strips. (that would have saved me a lot of desoldering.)

[* About the Arduino: I’m listing an Arduino Uno, but my photos show an Arduino Pro MIni. They have the same pin mappings. I figure you’ll probably build it on an Uno as I did, and then move to something smaller when you’re ready.]

[** I used a IRFZ44N in the pics/video, which seems to work great, but it’s not ideal. Get the P30N06LE instead.]

[Notice: I use affiliate links. Here is my stance on how I will and won’t use them. In a nutshell, I’m not going to let affiliate revenue dictate what content I create. Any revenue from your clicks will go towards an oscilloscope, because I can’t seem to rationalize pulling $500 out of our regular envelopes for one.]

Step 3: Milestone 1: Simple Test

This is a simple step to help you configure the IR and microphone sensors. Once done, you’ll be toggling on and off that built-in LED in style.

Note for Newbies

I expect this project to be for more advanced users, so I don’t plan on giving entry level advice about how to use an Arduino. But, this step is pretty straightforward, so if you’re here for the fun, I’d recommend going through this guide first: https://www.arduino.cc/en/Guide/ArduinoUno.

Then, come on back!

The Code

[ View prettier code at: https://github.com/keith204/KeithsLEDCandle ]

I’ve attached this file to this Instructable.

<p>// FIRST TEST OF MIC AND FLAME SENSOR<br>// Keith Kelly 2019-03-04 <a href="http://www.KeithsTestGarage.com"> www.KeithsTestGarage.com
</a>
// Milestone 1 of 3</p><p>#define MIC_PIN A1   // Microphone Pin
#define FLAME_PIN A3 // IR Sensor Pin</p><p>#define FLAME_DURATION 2000 // millis- duration of flame before lighting candle
#define FLAME_THRESHOLD 800 // 0...1023 - analog reading of FLAME value to detect
#define MIC_THRESHOLD 950 // 0...1023 - analog reading of MIC value to detect</p><p>// Vars to keep track of things
bool smoking = false;
bool flaming = false;</p><p>bool lit = false;
int flame_val = 0;
int mic_val = 0;</p><p>// Vars for tracking pseudo-async times
unsigned long previousMillis = 0;        // will store last time LED was updated
unsigned long flameStartedMillis = 0;    // will store last time LED was updated</p><p>void setup(){</p><p>    Serial.begin(9600); // uncomment this to figure sensor readings.</p><p>    // initialize pins
    pinMode(FLAME_PIN, INPUT);
    pinMode(MIC_PIN, INPUT);
    pinMode(LED_BUILTIN, OUTPUT); </p><p>    digitalWrite(LED_BUILTIN, LOW); // Turn OFF built-in LED</p><p>    blowOutCandle();  // Make sure candle is off (but don't smoke)</p><p>}</p><p>void loop(){
    checkSensors(); // Check for fire and strong winds
}</p><p>// Checks for fire and strong winds every 50 milliseconds
void checkSensors(){
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= 50) { // only check every 50ms
    flame_val = analogRead( FLAME_PIN );
    mic_val = analogRead( MIC_PIN );
    previousMillis = currentMillis;
    
    //  Uncomment below to check sensor readings.  
    Serial.print(mic_val);
    Serial.print(" : ");
    Serial.println(flame_val);
  }</p><p>  if (lit && mic_val > MIC_THRESHOLD){ // MIC REGISTERED SOUND.  BLOW OUT.
    blowOutCandle(); // Turn off light (and start SMOKING!)
    lit = false;
  }</p><p>  if (!lit){ // if candle isn't lit
    if (flame_val > FLAME_THRESHOLD)  { // if flame is detected
        
        // record the starting time (if flame not yet detected)
        if (!flaming){  // if not flaming
          flaming = true; 
          flameStartedMillis = millis();  // record time that flaming begins
        }
        else if (checkFlame()){ // has flame been flaming long enough?          
          lightCandle(); // ok finally light it up. 
          lit = true; 
        }
    }
    else{ // no flame detected
      flaming = false; 
    }    
  }  
}</p><p>// Check if flame has been detected long enough.
bool checkFlame(){    
  unsigned long currentMillis = millis(); // get current time
  if (currentMillis - flameStartedMillis >= FLAME_DURATION) {
    return true;  
  }
  else
    return false;
}</p><p>// (pseudo)ASYNC light the candle by starting the animations
void lightCandle(){
    digitalWrite(LED_BUILTIN, HIGH);
}</p><p>void blowOutCandle(){    
  digitalWrite(LED_BUILTIN, LOW);
}</p>

Open Serial Monitor and Adjust Thresholds

Once you have everything hooked up according to the Fritzing diagram and the code uploaded, open up Serial Monitor and play with some wind & fire.

Your sensors may read different values than mine. Adjust thresholds at the top of the code file where necessary.

That’s it. Wire everything up using the Fritzing diagram above. Nothing special here.

Step 4: Milestone 2: Full Candle W/o Smoke

For The Non-Smokers…

The candle is still really cool without the smoke. That may be all you want. Power with USB or whatever you’d like. Here’s the Fritzing diagram and code for that.

Changes since Milestone 1:

  • No longer using the built-in LED
  • Full NeoPixel functionality included
  • NeoPatterns.h file now included

Note: Now that you’re using NeoPixels, you need to install a Library from within your Arduino IDE’s library manager. The library is: Adafruit_NeoPixel.

Wiring

As you can see in the Fritzing diagram, the right side is new. I don’t think there’s anything notable to say about the wiring at this stage. Follow the diagram and you’ll be good to go.

Code

View code at: https://github.com/keith204/KeithsLEDCandle. I’ve also attached the code to this Instructable.

KeithsCandle_M2_NoSmoke.ino

To keep things simple, I’ve included a bunch of comments in the code to avoid having to explain much here.

// Using technique from here  https://learn.adafruit.com/multi-tasking-the-ardu...//    see link above for a good tutorial on this multi-task-ish way of doing things.// Flicker Realistic Candle
// Keith Kelly 2019-03-04  www.KeithsTestGarage.com

// Milestone 2 of 3#ifdef __AVR__
#include 
#endif// NeoPatterns will require the Adafruit NeoPixel library.  Be sure to install that.  
#include "NeoPatterns.h"#define NEO_PIN 5    // First RGBW Strip Pin
#define NEO2_PIN 9   // Second RGBW Strip Pin
#define NEO_COUNT 3  // First RGBW Strip Count
#define NEO2_COUNT 3 // Second RGBW Strip Count
#define MIC_PIN A1   // Microphone Pin
#define FLAME_PIN A3 // IR Sensor Pin#define FLAME_DURATION 2000 // millis- duration of flame before lighting candle
#define FLAME_THRESHOLD 800 // 0...1023 - analog reading of FLAME value to detect
#define MIC_THRESHOLD 950 // 0...1023 - analog reading of MIC value to detectvoid Strip1Complete(); 
void Strip2Complete();NeoPatterns Strip1(NEO_COUNT, NEO_PIN, NEO_RGBW + NEO_KHZ800, &Strip1Complete);
NeoPatterns Strip2(NEO_COUNT, NEO2_PIN, NEO_RGBW + NEO_KHZ800, &Strip2Complete);uint32_t baseColor; // What is the ON resting color?
uint32_t offColor;  // What is the OFF resting color?// Vars to keep track of things
bool flaming = false;bool lit = false;
int flame_val = 0;
int mic_val = 0;
byte rnd = 0;// Vars for tracking pseudo-async times
unsigned long previousMillis = 0;        // will store last time LED was updated
unsigned long flameStartedMillis = 0;        // will store last time LED was updatedvoid setup(){    //Serial.begin(9600); // uncomment this to figure sensor readings.    // initialize pins
    pinMode(FLAME_PIN, INPUT);
    pinMode(MIC_PIN, INPUT);
    pinMode(LED_BUILTIN, OUTPUT);     // Make sure the following pins are OFF
    digitalWrite(LED_BUILTIN, LOW);     // Initialize NeoPixel Strip 1
    Strip1.begin();
    Strip1.show();
    baseColor = Strip1.Color(2, 110, 0, 100);// GRBW
    Strip1.Color1 = baseColor;    // Initialize NeoPixel Strip 2
    Strip2.begin();
    Strip2.show();
    baseColor = Strip2.Color(2, 110, 0, 100);// GRBW
    Strip2.Color1 = baseColor;
    
    blowOutCandle();  // Make sure candle is off 
}void loop(){ 
    checkSensors(); // Check for fire and strong winds
    Strip1.Update();
    Strip2.Update();
}// Checks for fire and strong winds every 50 milliseconds
void checkSensors(){
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= 50) { // only check every 50ms
    flame_val = analogRead( FLAME_PIN );
    mic_val = analogRead( MIC_PIN );
    previousMillis = currentMillis;
    
    //  Uncomment below to check sensor readings.  
    //    Serial.print(mic_val);
    //    Serial.print(" : ");
    //    Serial.println(flame_val);
  }  if (lit && mic_val > MIC_THRESHOLD){ // MIC REGISTERED SOUND.  BLOW OUT.
    blowOutCandle(); // Turn off light 
    lit = false;
  }  if (!lit){ // if candle isn't lit
    if (flame_val > FLAME_THRESHOLD)  { // if flame is detected
        
        // record the starting time (if flame not yet detected)
        if (!flaming){  // if not flaming
          flaming = true; 
          flameStartedMillis = millis();  // record time that flaming begins
        }
        else if (checkFlame()){ // has flame been flaming long enough?          
          lightCandle(); // ok finally light it up. 
          lit = true; 
        }
    }
    else{ // no flame detected
      flaming = false; 
    }
    
  }  
}// Check if flame has been detected long enough.
bool checkFlame(){    
  unsigned long currentMillis = millis(); // get current time
  if (currentMillis - flameStartedMillis >= FLAME_DURATION) {
    return true;  
  }
  else
    return false;
}//------------------------------------------------------------
//Completion Routines - get called on completion of a pattern
//------------------------------------------------------------// The below configuration routines are the same except for the strip
// being used.  I kept them separate so the strips can be configured
// with different ranges if desired.void Strip1Complete(){
    Strip1.Reverse();    if (Strip1.Direction == REVERSE){ // go back down
        Strip1.Interval = random(5,22); // choose random speed in range
    }
    else{  // pattern ended.  Stop, then trigger re-flicker
        
        /*  This needs an explanation:  
            
              rnd = random(random(45,55),random(80,255));
            You'd think that we could just do random(45,255), right?
            Selecting the low and high randoms first helps make sure that
            the ending random number is *more likely* to fall within a limited range.  
            When observing the flickering of a real candle, the flame generally bounces
            around in a certain range.  But, every once in a while, it spikes high or low.
            This random(random, random) solves that problem!  #proudnerddadmoment
        */
        rnd = random(random(45,55),random(80,255));         Strip1.Pixel = random(0,Strip1.numPixels()); // pick a random Pixel
        Strip1.Interval = 1; 
        Strip1.Color2 = Strip1.Color(2,rnd,0,rnd-10); //GRBW random red and random white-10
    }
}void Strip2Complete(){
    Strip2.Reverse();    if (Strip2.Direction == REVERSE){ // go back down        
        Strip2.Interval = random(5,22); // choose random speed in range
    }
    else{  // pattern ended.  Stop, then trigger re-flicker
        rnd = random(random(45,55),random(80,255)); // see explanation in Strip1Complete().
        Strip2.Pixel = random(0,Strip2.numPixels()); // pick a random Pixel
        Strip2.Interval = 1;
        Strip2.Color2 = Strip2.Color(2,rnd,0,rnd-10); //GRBW random red and random white-10
    }
}// (pseudo)ASYNC light the candle by starting the animations
void lightCandle(){
    Strip1.Flicker(Strip1.Color1, Strip1.Color2, 20, 5);
    Strip2.Flicker(Strip2.Color1, Strip2.Color2, 20, 5);
}void blowOutCandle(){    
  cWipe(offColor, 10); // synchronously turn the candle off.  // We don't want no stinking patterns.
  Strip1.ActivePattern = NONE; 
  Strip2.ActivePattern = NONE;
}// synchronous color wipe
void cWipe(uint32_t c, uint8_t wait) {
  for (uint16_t i = 0; i < Strip1.numPixels(); i++) {
    Strip1.setPixelColor(i, c);
    Strip1.show();
    delay(wait);
  }
  for (uint16_t i = 0; i < Strip2.numPixels(); i++) {
    Strip2.setPixelColor(i, c);
    Strip2.show();
    delay(wait);
  }
}

NeoPatterns.h

This has a few patterns in it from Adafruit, so I’ve omitted most of that content. Grab the file that’s attached or in the GitHub repo for the full version.

Source: LED Candle – Lights, Flickers, Blows Out, Smokes, and Smells

Scroll to Top
Scroll to Top