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 counterintuitive. 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
Author

Fan of making things beep, blink and fly. Created AddOhms. Stream on Twitch. Video Host on element14 Presents and writing for Hackster.IO. Call sign KN6FGY.

24 Comments

  1. I know this was written a few years ago, but it solved my problem. I have used millis() before to blink lights at different rates using classes. I wanted to be able to use it with lights fading as well, but my creativity wasn’t there. This is a great solution and I’m now staying up late trying to code the thing.

  2. Thank you for your website I found it very informative as I’m a bit of a neotype to coding. I would like to ask if there is a way to Fade Up and Down the LED 5 times and in each time increasing the Brightness in increment of 51 to the Brightness level, until at the 5th fade reaches a Brightness of 255. Example of what I’m trying to do. 1st fade 0 ramping to a brightness of 51 and ramping down to 0; 2nd fade 0 ramping to a brightness of 102 and ramping down to 0; 3rd fade 0 ramping to a brightness of 153 and ramping down to 0; 4th fade 0 ramping to a brightness of 204 and ramping down to 0; 5th fade 0 ramp to a brightness of 255 and ramping down to 0. Thanking you in advance for any help or direction you maybe able to provide.

  3. Thank you for making this! I am trying to build an alarm clock with a fading light in it (a wake up light). for the interface with this alarm clock I am using a touch screen. For the fading I want to be able to switch off the alarm during the fading, that is why Millis seem to be ideal.
    I am currently testing the code and it works fine as long as I call upon it in the loop directly. However when I try to use a button on the touch screen (the check button) to start the function the LED goes on, but it does not fade.
    Any idea what the problem could be?
    I pasted the code here: https://pastebin.com/JjLrF6Zv
    Thanks in advance!

  4. Thank you so much for posting this article with a great explanation. I was having trouble getting LEDs, a servo motor, and a couple of other items functioning currently using delay(). This should really help out.

  5. John Shute Reply

    Hello. I need help with the curly brackets. I am sure this is the problem when I attempt to add some code, I’ll try to Explain. From Void doTheFade to Void Loop. The left brackets after the Void statement and the first If statement coincide with the last two closed brackets before Void Loop. The next two if statement’s left brackets coincide withe the two closed ones before the Else. The Else and following if statements brackets coincide with two just before the analogWrite command. I hope this is clear enough. It seems this must be done exactly like this for the sketch to work but I don’t understand why. Please help if you can.

  6. Anyone ever try to use IR Remote to turn on and off this fade sketch? I need help

  7. Hi James.

    I’m brand new to the world of electronics and Arduino. I have rain forest project I would like to construct consisting of an audio track of the rain forest in which at different times there are thunder storms that pass through. I would like to create a lighting affect for the track as follows:

    1. Fade up one set of LEDs to max and stay at max. (Yellow LEDs to simulate the sun)
    2. As the storm approaches fade down to min (and stay at min) the yellow LEDs (to simulate dark clouds approaching)
    3. Next flash another set of white LEDs to simulate lightning
    4. As the storms passes fade up the yellow LEDs to max and stay at max

    The above sequence happens at different times during the 55 min track.

    I have the lightning figured out but I’m unsure of:

    1. How to get the LEDs to fade up or down and stay at max, min, and allow the program to continue to run

    I this is possible I thought that matching the timing using ‘millis’ might be the way forward.

    Any help would be greatly appreciated.

    Regards.

    Robbie.

    • How to get the LEDs to fade up or down and stay at max, min, and allow the program to continue to run

      That’s exactly what this code example does: fade LEDs while letting the program do other stuff.

      As for the details of the patterns you want to achieve, you will need to work out the logic for each and setup conditions to determine what each LED type is doing and when. That’s well beyond the level of project help I can provide.

  8. CLEMENT Vadim Reply

    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.

      • CLEMENT Vadim Reply

        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();
        }
        }

        • CLEMENT Vadim Reply

          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;
          }
          }

  9. JACK C WOLF Reply

    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?

    • Add the function to your code. Then in loop() or any where long loops of code occur, call “doTheFade()”.

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

  11. Great job,thank you for sharing your project,it will be helpful for me,i found interest here,now i am fresher,i learn more from your blog.

  12. I have to get back to doing some arduino coding. Thanks for sharing James and Happy Holidays!

Write A Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.