Fading LED: analogWrite millis() Example

Add a fading LED without delay()

Fading LED millis() Example

It’s a well-known fact of engineering: LEDs make everything look better. And that means a Fading LED is even better. Using Arduino’s analogWrite(), fading a LED is just a matter of a loop. If you use delay(), you can’t easily add other actions. What can you do? Well, Fading a LED with millis() is pretty simple. Here’s the code to do it and a quick explanation.

//pwm-fade-with-millis.ino
// Example Fading LED with analogWrite and millis()
// See baldengineer.com/fading-led-analogwrite-millis-example.html for more information
// Created by James Lewis

const byte pwmLED = 5;


// define directions for LED fade
#define UP 0
#define DOWN 1

// constants for min and max PWM
const int minPWM = 0;
const int maxPWM = 255;

// State Variable for Fade Direction
byte fadeDirection = UP;

// Global Fade Value
// but be bigger than byte and signed, for rollover
int fadeValue = 0;

// How smooth to fade?
byte fadeIncrement = 5;

// millis() timing Variable, just for fading
unsigned long previousFadeMillis;

// How fast to increment?
int fadeInterval = 50;

void setup() {
  // put pwmLED into known state (off)
  analogWrite(pwmLED, fadeValue); 
}

void doTheFade(unsigned long thisMillis) {
  // is it time to update yet?
  // if not, nothing happens
  if (thisMillis - previousFadeMillis >= fadeInterval) {
    // yup, it's time!
    if (fadeDirection == UP) {
      fadeValue = fadeValue + fadeIncrement;  
      if (fadeValue >= maxPWM) {
        // At max, limit and change direction
        fadeValue = maxPWM;
        fadeDirection = DOWN;
      }
    } else {
      //if we aren't going up, we're going down
      fadeValue = fadeValue - fadeIncrement;
      if (fadeValue <= minPWM) {
        // At min, limit and change direction
        fadeValue = minPWM;
        fadeDirection = UP;
      }
    }
    // Only need to update when it changes
    analogWrite(pwmLED, fadeValue);  

    // reset millis for the next iteration (fade timer only)
    previousFadeMillis = thisMillis;
  }
}

void loop() {
  // get the current time, for this time around loop
  // all millis() timer checks will use this time stamp
  unsigned long currentMillis = millis();
   
  doTheFade(currentMillis);

}

Global Variables for Fading LED

Constants

The constant “pwmLED” is the pin that gets faded. (Make that pin supports analogWrite().) The two #define statements, UP and DOWN, are simple states for the LED. They make the code easier to read. The “maxPWM” value is set to 255 for 8-bit AVR boards like the Uno. If you’re using the ESP8266, set this value to 1023. (You can also use the constant “PWMRANGE.”)

One side note. While this fading LED code is linear, the LED’s brightness is not. For uniform intensity, the fade value needs a correction factor. This is more evident on bright blue LEDs than on dim red ones. But that’s a subject for another post.

States

Next, let’s look at how to keep track of which way the LED is fading.

The variable “fadeDirection” keeps track of the fading state. It is a simple flag variable. The code starts with the fade going UP, or turning on. I created it as a byte instead of bool. (This type allows you to add more states in the future.) The variable “fadeValue” keeps track of the LED’s intensity. You can modify the smoothness of the fade using “fadeIncrement.” While changing “fadeInterval” controls the speed. Keep in mind that “fadeInterval” is the number of milliseconds between states.

previousMillis

As with all millis() code, you need to track the time since the timer event occurred. In my millis() examples, I use the variable “previousMillis.” But each timer event needs a unique copy of the previous millis(). In this code, I’ve given the previousMillis variable a unique name, “previousFadeMillis.” When combining this fading LED example with your code, that unique name will prevent a collision.

Also, you might want to create multiple interval variables.

setup()

The analogWrite() called in setup() is optional. Since you don’t need to call pinMode() with analogWrite(), I put an analogWrite() in setup(). Here’s why.

Calling pinMode() implies the pin’s function is a digital input or output. Since we want the PWM function, that is counter intuitive. At the same time, I like being able to glance at pin assignments by looking just at setup(). So when I see an analogWrite(), I know I’m using the pin “pwmLED” as a PWM pin.

doTheFade()

The actual PWM fading happens with the code in this function. By isolating it to a function, it’s much easier to add the fade to existing code. The function’s argument “thisMillis” is the millis() timestamp from loop(). The first if-statement is the standard reset millis() check. If it’s time to update the LED fading, it happens. Otherwise, the function just exits.

In conclusion, this example can be used to do a simple fade without delay(). Normally you’ll call doTheFade() from loop(). Anytime you have lots of processing, give doTheFade() a quick call. If it isn’t time to fade yet, it’ll just return because there is nothing to do yet. This function will give the appearance of your Arduino program multitasking!

Checkout other millis() Examples

Long comments, URLs, and code tend to get flagged for spam moderation. No need to resubmit.

Leave a comment

13 thoughts on “Fading LED: analogWrite millis() Example

  1. Hello, thank you for your code. But I have a little worry. I have 15 led connected to a TLC5940, I would like to turn on 3 leds (this may be more or less) and turn off with fade effect. All with the 4 variable random() function (2 for fadeup and 2 for fadedown) to select the outputs of the TLC5940. Fade up work (not fine but work) but fade down don’t work at all. All led stay on after fadeup..

    Thanks for your help 🙂

    • Fade up work (not fine but work) but fade down don’t work at all. All led stay on after fadeup..

      Without seeing your code, I have absolutely no idea what you are doing wrong.

      Check line 15, 43, 1, and maybe 6.

      • Thank you for your reply.

        I was able to find an alternative with a function included in the TLC5940 library.

        On the other hand I have another worry, I have an IR remote control, when I press the ON button the LEDs fade in. And stay on indefinitely. When I press the OFF button, the LEDs must be faded out.

        The problem is that I can not leave the LEDs on. The LEDs light up in a loop. And if I put a condition the buttons of the remote control no longer respond.

        (Here’s the code, it’s a little rough draft sorry)
        By pressing ON the LEDs light up directly without fading and can not be turned off by pressing OFF

        Thanks for your precious help! I loose my mind 🙁

        // LIBRARY DECLERATION //
        // *Include PinChangeInterrupt library* BEFORE IRLremote to acces more pins if needed
        #include “PinChangeInterrupt.h”
        #include “IRLremote.h”
        #include “Tlc5940.h”
        #include “tlc_fades.h”

        // TLC5940 DECLARATION //
        //
        TLC_CHANNEL_TYPE channel;

        // IR REMOTE DECLARATION //
        // *Choose a valid PinInterrupt or PinChangeInterrupt* pin of your Arduino board
        #define pinIR 6
        CHashIR IRLremote;
        // IR KEY SECTION //
        #define LEFT 0x6D3891A1
        #define RIGHT 0x6A23CCA1

        #define OFF 0xE4CFA1
        #define ON 0xFDD00AA1

        #define FLASH 0x521468A1
        #define STROBE 0x87F1F3A1
        #define FADE 0xF1AE5A1
        #define SMOOTH 0x44F870A1

        #define EFFECTR 0xA3161CA1
        #define EFFECTG 0xA00157A1
        #define EFFECTB 0x36C25AA1
        #define EFFECTW 0x33AD95A1

        #define EFFECT1 0xC17CEFA1
        #define EFFECT2 0xBE682AA1
        #define EFFECT3 0x55292DA1

        #define EFFECT4 0xF75A7AA1
        #define EFFECT5 0xF445B5A1
        #define EFFECT6 0x8B06B8A1

        #define EFFECT7 0x7E836CA1
        #define EFFECT8 0x7B6EA7A1
        #define EFFECT9 0x122FAAA1

        #define EFFECT10 0xB460F7A1
        #define EFFECT11 0xB14C32A1
        #define EFFECT12 0x480D35A1

        // LIGHT VALUES DECLARATION //
        const int MINLIGHT = 0; // 0% of 4095
        const int MAXLIGHTSTEP1 = 500; // 10% of 4095
        const int MAXLIGHTSTEP2 = 1000; // 25% of 4095
        const int MAXLIGHTSTEP3 = 2000; // 50% of 4095
        const int MAXLIGHTSTEP4 = 3000; // 75% of 4095
        const int MAXLIGHTSTEP5 = 4095; // 100% of 4095

        // NON-CONSTENT VALUE //
        int setBrightness = MAXLIGHTSTEP1;

        boolean glitterActiv = false;
        boolean startUp = false;
        boolean stayOn = false;
        boolean stopDown = false;

        // TEMP VARIABLE
        // constants for min and max PWM
        // define directions for LED fade
        #define UP 0
        #define DOWN 1
        // constants for min and max PWM
        const int minPWM = 0;
        const int maxPWM = 4095;
        // State Variable for Fade Direction
        byte fadeDirection = UP;
        // Global Fade Value
        // but be bigger than byte and signed, for rollover
        int fadeValue = 0;
        // How smooth to fade?
        byte fadeIncrement = 5;
        // millis() timing Variable, just for fading
        unsigned long previousFadeMillis;
        // How fast to increment?
        int fadeInterval = 3;

        void setup()
        {
        // Start serial debug output
        while (!Serial);
        Serial.begin(9600);
        // Start reading the remote. PinInterrupt or PinChangeInterrupt* will automatically be selected
        if (!IRLremote.begin(pinIR))
        Serial.println(F(“You did not choose a valid pin.”));
        Tlc.init();
        }

        void lightUp() {
        unsigned long thisMillis = millis();
        // is it time to update yet?
        // if not, nothing happens
        if (thisMillis – previousFadeMillis >= fadeInterval) {
        // yup, it’s time!
        if (fadeDirection == UP) {
        fadeValue = fadeValue + fadeIncrement;
        if (fadeValue >= maxPWM) {
        // At max, limit and change direction
        fadeValue = maxPWM;
        fadeValue = minPWM;
        }
        }
        // Only need to update when it changes
        Tlc.setAll(fadeValue);
        Tlc.update();

        // reset millis for the next iteration (fade timer only)
        previousFadeMillis = thisMillis;
        }
        }

        void lightOn() {
        Tlc.setAll(maxPWM);
        Tlc.update();
        }

        void loop()
        {

        auto data = IRLremote.read();

        if(data.command == LEFT) {Serial.println(“LEFT”);}
        if(data.command == RIGHT) {Serial.println(“RIGHT”);}

        if(data.command == OFF) {Serial.println(“OFF”); startUp = false;}
        if(data.command == ON) {Serial.println(“ON”); startUp = true;}

        if(data.command == FLASH) {Serial.println(“FLASH”);}

        if(data.command == STROBE) {Serial.println(“STROBE/GLITTER”);}
        if(data.command == FADE) {Serial.println(“FADE”);}
        if(data.command == SMOOTH) {Serial.println(“SMOOTH”);}

        if(data.command == EFFECTR) {Serial.println(“EFFECT R”); setBrightness = MAXLIGHTSTEP5;}
        if(data.command == EFFECTG) {Serial.println(“EFFECT G”);}
        if(data.command == EFFECTB) {Serial.println(“EFFECT B”);}
        if(data.command == EFFECTW) {Serial.println(“EFFECT W”);}

        if(data.command == EFFECT1) {Serial.println(“EFFECT 1”); setBrightness = MAXLIGHTSTEP4;}
        if(data.command == EFFECT2) {Serial.println(“EFFECT 2”);}
        if(data.command == EFFECT3) {Serial.println(“EFFECT 3”);}

        if(data.command == EFFECT4) {Serial.println(“EFFECT 4”); setBrightness = MAXLIGHTSTEP3;}
        if(data.command == EFFECT5) {Serial.println(“EFFECT 5”);}
        if(data.command == EFFECT6) {Serial.println(“EFFECT 6”);}

        if(data.command == EFFECT7) {Serial.println(“EFFECT 7”); setBrightness = MAXLIGHTSTEP2;}
        if(data.command == EFFECT8) {Serial.println(“EFFECT 8”);}
        if(data.command == EFFECT9) {Serial.println(“EFFECT 9”);}

        if(data.command == EFFECT10) {Serial.println(“EFFECT 10”); setBrightness = MAXLIGHTSTEP1;}
        if(data.command == EFFECT11) {Serial.println(“EFFECT 11”);}
        if(data.command == EFFECT12) {Serial.println(“EFFECT 12”);}

        if(startUp == true) {
        lightUp(); startUp == true; stayOn = true;}
        if(stayOn == true) {
        lightOn();
        }else{
        Tlc.setAll(minPWM);
        Tlc.update();
        }
        }

        • So i made this modification. Works but the fade effect work once. When i push one more time ON button, led directly On without fade.

          // LIBRARY DECLERATION //
          // *Include PinChangeInterrupt library* BEFORE IRLremote to acces more pins if needed
          #include “PinChangeInterrupt.h”
          #include “IRLremote.h”
          #include “Tlc5940.h”
          #include “tlc_fades.h”

          // TLC5940 DECLARATION //
          //
          TLC_CHANNEL_TYPE channel;

          // IR REMOTE DECLARATION //
          // *Choose a valid PinInterrupt or PinChangeInterrupt* pin of your Arduino board
          #define pinIR 6
          CHashIR IRLremote;
          // IR KEY SECTION //
          #define LEFT 0x6D3891A1
          #define RIGHT 0x6A23CCA1

          #define OFF 0xE4CFA1
          #define ON 0xFDD00AA1

          #define FLASH 0x521468A1
          #define STROBE 0x87F1F3A1
          #define FADE 0xF1AE5A1
          #define SMOOTH 0x44F870A1

          #define EFFECTR 0xA3161CA1
          #define EFFECTG 0xA00157A1
          #define EFFECTB 0x36C25AA1
          #define EFFECTW 0x33AD95A1

          #define EFFECT1 0xC17CEFA1
          #define EFFECT2 0xBE682AA1
          #define EFFECT3 0x55292DA1

          #define EFFECT4 0xF75A7AA1
          #define EFFECT5 0xF445B5A1
          #define EFFECT6 0x8B06B8A1

          #define EFFECT7 0x7E836CA1
          #define EFFECT8 0x7B6EA7A1
          #define EFFECT9 0x122FAAA1

          #define EFFECT10 0xB460F7A1
          #define EFFECT11 0xB14C32A1
          #define EFFECT12 0x480D35A1

          // LIGHT VALUES DECLARATION //
          const int MINLIGHT = 0; // 0% of 4095
          const int MAXLIGHTSTEP1 = 500; // 10% of 4095
          const int MAXLIGHTSTEP2 = 1000; // 25% of 4095
          const int MAXLIGHTSTEP3 = 2000; // 50% of 4095
          const int MAXLIGHTSTEP4 = 3000; // 75% of 4095
          const int MAXLIGHTSTEP5 = 4095; // 100% of 4095

          // NON-CONSTENT VALUE //
          int setBrightness = MAXLIGHTSTEP1;

          int mode = 3;
          boolean glitterActiv = false;
          boolean startUp = false;
          boolean stayOn = false;
          boolean stopDown = false;

          // TEMP VARIABLE
          // constants for min and max PWM
          // define directions for LED fade
          // FADE UP/DOWN VARIABLE
          #define UP 0
          #define DOWN 1
          // constants for min and max PWM
          const int minPWM = 0;
          const int maxPWM = 4095;
          // State Variable for Fade Direction
          byte fadeDirection = UP;
          byte fadedownDirection = DOWN;
          // How smooth to fade?
          byte fadeIncrement = 5;
          // How fast to increment?
          int fadeInterval = 3;

          // FADE UP VARIABLE
          // Global Fade Value
          // but be bigger than byte and signed, for rollover
          int fadeupValue = 0; // millis() timing Variable, just for fading
          unsigned long previousFadeupMillis;

          // FADE DOWN VARIABLE
          // Global Fade Value
          // but be bigger than byte and signed, for rollover
          int fadedownValue = 4095; // millis() timing Variable, just for fading
          unsigned long previousFadedownMillis;

          void setup()
          {
          // Start serial debug output
          while (!Serial);
          Serial.begin(9600);
          // Start reading the remote. PinInterrupt or PinChangeInterrupt* will automatically be selected
          if (!IRLremote.begin(pinIR))
          Serial.println(F(“You did not choose a valid pin.”));
          Tlc.init();
          }

          void lightUp(unsigned long thisupMillis) {
          // unsigned long thisupMillis = millis();
          // is it time to update yet?
          // if not, nothing happens
          if (thisupMillis – previousFadeupMillis >= fadeInterval) {
          // yup, it’s time!
          if (fadeDirection == UP) {
          fadeupValue = fadeupValue + fadeIncrement;
          if (fadeupValue >= maxPWM) {
          // At max, limit and change direction
          fadeupValue = maxPWM;
          mode = 1;
          }
          }
          // Only need to update when it changes
          Tlc.setAll(fadeupValue);
          Tlc.update();

          // reset millis for the next iteration (fade timer only)
          previousFadeupMillis = thisupMillis;
          }
          }

          void lightDown() {
          unsigned long thisdownMillis = millis();
          // is it time to update yet?
          // if not, nothing happens
          if (thisdownMillis – previousFadedownMillis >= fadeInterval) {
          // yup, it’s time!
          if (fadedownDirection == DOWN) {
          //if we aren’t going up, we’re going down
          fadedownValue = fadedownValue – fadeIncrement;
          if (fadedownValue <= minPWM) {
          // At min, limit and change direction
          fadedownValue = minPWM;
          mode = 3;
          }
          }
          // Only need to update when it changes
          Tlc.setAll(fadedownValue);
          Tlc.update();

          // reset millis for the next iteration (fade timer only)
          previousFadedownMillis = thisdownMillis;
          }
          }

          void lightOn() {
          Tlc.setAll(maxPWM);
          Tlc.update();
          }

          void lightOff() {
          Tlc.setAll(minPWM);
          Tlc.update();
          }

          void loop()
          {

          auto data = IRLremote.read();

          if(data.command == LEFT) {Serial.println("LEFT");}
          if(data.command == RIGHT) {Serial.println("RIGHT");}

          if(data.command == OFF) {Serial.println("OFF"); mode = 2;}
          if(data.command == ON) {Serial.println("ON"); mode = 0;}

          if(data.command == FLASH) {Serial.println("FLASH");}

          if(data.command == STROBE) {Serial.println("STROBE/GLITTER");}
          if(data.command == FADE) {Serial.println("FADE");}
          if(data.command == SMOOTH) {Serial.println("SMOOTH");}

          if(data.command == EFFECTR) {Serial.println("EFFECT R"); setBrightness = MAXLIGHTSTEP5;}
          if(data.command == EFFECTG) {Serial.println("EFFECT G");}
          if(data.command == EFFECTB) {Serial.println("EFFECT B");}
          if(data.command == EFFECTW) {Serial.println("EFFECT W");}

          if(data.command == EFFECT1) {Serial.println("EFFECT 1"); setBrightness = MAXLIGHTSTEP4;}
          if(data.command == EFFECT2) {Serial.println("EFFECT 2");}
          if(data.command == EFFECT3) {Serial.println("EFFECT 3");}

          if(data.command == EFFECT4) {Serial.println("EFFECT 4"); setBrightness = MAXLIGHTSTEP3;}
          if(data.command == EFFECT5) {Serial.println("EFFECT 5");}
          if(data.command == EFFECT6) {Serial.println("EFFECT 6");}

          if(data.command == EFFECT7) {Serial.println("EFFECT 7"); setBrightness = MAXLIGHTSTEP2;}
          if(data.command == EFFECT8) {Serial.println("EFFECT 8");}
          if(data.command == EFFECT9) {Serial.println("EFFECT 9");}

          if(data.command == EFFECT10) {Serial.println("EFFECT 10"); setBrightness = MAXLIGHTSTEP1;}
          if(data.command == EFFECT11) {Serial.println("EFFECT 11");}
          if(data.command == EFFECT12) {Serial.println("EFFECT 12");}

          unsigned long currentMillis = millis();

          switch(mode) {
          case 0:
          lightUp(currentMillis);
          break;
          case 1:
          lightOn();
          break;
          case 2:
          lightDown();
          break;
          case 3:
          lightOff();
          break;
          }
          }

  2. You mentioned that dotheFade (), is easy to add into other codes. I’m new at arduino, how would you add this to a PIR sketch?

  3. Any idea if it’s possible to do a more elaborate lights show using this?
    By this i mean any fade value between 0-255, not just from 0 to 255 and back, different fade times etc

    • Of course. You would just be building a state machine. Define states as what value for PWM and time at that state. One idea that pops into my head is having a two dimensional array.