Light Projects

A collection LED control examples for Arduino


Low-voltage DC Lamps
LED Strip Control
Addressable LEDs
  NeoPixel Library Quickstart
  Making Electronic Candles
Fading
Chromaticity
Color Spaces
Spectrometers
Light Rendering Indices
Inventory
Pattern Making

This project is maintained by tigoe

Fading Light

Light doesn’t change in a linear fashion, and the human eye doesn’t respond to light in a linear fashion. Light, like all forms of radiant energy, decreases with the square of the distance from the source. Furthermore, the human eye responds to light in a logarithmic fashion. In 1931, the International Commission on Illumination (CIE) released a study of psychometric colorimetry, or the measurement of how we perceived changes in light. The landmark study resulted in the CIE 1931 standard colorimetric system, a standardized way of describing perceived color. As part of that, they defined a formula for lightness as we perceive it. In the CIE 1931 formula, lightness varies linearly from 0 to 8 percent, and on a 1/3 power function from 8 percent to 100 percent.

Add to this the fact that different diffusers and reflectors affect the perceived perceived lightness of a source, and you’ll see that lightness is a complex matter. As a result, it’s helpful to have a few non-linear formulas for fading light when you’re controlling light from a computer. Depending on the effect you’re looking for, you might want a light source to start fading slowly and then speed up, or slow down at the end of its fade.

Fade Curve Examples

The fade curve section of this repository contains examples of a number of different fade curves:

Each of these curves has a different effect, described below. The graphs below show you the fade curves for each example as well.

Figure 1. Graph of the simple linear fade

Figure 1. Graph of simple linear fade

Figure 1 shows a graph of the the simple linear fade. The peak and the trough are sharp changes, and since the power output doesn’t match the lightness output, the light seems steady for most of the time, with very quick fades up and down at the ends of the fade.

Figure 2. Graph of the sine fade curve

Figure 2. Graph of sine fade curve.

Figure 2 shows a graph of a sine fade curve. The slope of the curve flattens out at the top and bottom, so the change seems slightly more smooth.

Figure 3. Graph of the xSquared fade curve

Figure 3. Graph of x squared fade curve.

Figure 3 shows a graph of the xSquared fade curve. The fade feels reasonably even here, particularly at the bottom, but the peak is sharp, so the change from going up to going down feels a little fast.

Figure 4. Graph of an exponential fade curve

Figure 4. Graph of an exponential fade curve.

Figure 4 shows a graph of an exponential fade curve derived by Diarmuid Mac Namara. Mac Namara attempted to correct for the logarithmic change in perceived brightness by trial and error, and came up with the following formula:

x = x^(2/r) - 1

Figure 5. Graph of the CIE1931 fade curve

Figure 5. Graph of the CIE1931 fade curve.

Figure 5 shows a graph of the CIE1931 fade curve derived from the CIE1931 lightness formula. It looks pretty close to the x squared curve, but it’s a little different in the last 8 percent, where it’s linear. This is probably not visible in this graph, but it’s noticeable when you run the fade.

Pre-calculating the Fade Curve

In the examples above, you’ll see that the values for the fade curves are calculated in the setup() function and placed in an array. In the loop(), the sketch gets an input from 0 to 255 and then looks up the corresponding value in the array. Calculating complex math formulas like sine, or raising a value to an exponent, takes longer than looking up an array element, so if the formula’s not going to change, it makes sense to pre-calculate your fade curve. If you want to use formulas other than the ones shown here, a generic sketch might look something like this:

byte levelTable[256];    // pre-calculated PWM levels
int change = 1;          // resolution of one fade step
int fadeIncrement = 10;  // time of one fade step, in ms

void setup() {
  // pre-calculate the PWM levels from the formula:
  fillLevelTable();
}

void loop() {
  // change the current level:
  currentLevel += change;

  //PWM output the result:
  analogWrite(5, levelTable[currentLevel]);
  // delay by the fadeIncrement:
  delay(fadeIncrement);
}

void fillLevelTable() {
  // set the range of values:
  float maxValue = 255;

  // iterate over the array and calculate the right value for it:
  for (int l = 0; l <= maxValue; l++) {
    // calculate the light level here:
    byte lightLevel = (some formula);
    levelTable[l] = lightLevel;
  }
}

Changing the formula will change the fade curve, of course, and changing the fade increment will change the total time of the fade.

Fade Curves with Interactive Input

You don’t always want your fades to be automated. The most common human input control for fading light is a dimmer switch or rotary knob, usually a potentiometer. To use these fade curves with an external input, all you have to do is to map the input to the range of your fade curve steps (0-255 in all the examples here, because that’s the PWM resolution of an Arduino), and look up the corresponding value in the level table. Examples are included for the simple fade with input, the sine fade with input, and the CIE1931 fade with input, but you can use these to derive your own as well.

Effects of Input Controls

When you start writing fade curves and you notice unpleasant jumps in your fades or other outputs you don’t expect, don’t automatically assume your fade curve is to blame. Test with the non-interactive fades first, to make sure the curve is not the problem, then check your inputs. For example, is your potentiometer a linear pot or an audio taper (logarithmic) pot? Since pots are so commonly used to control audio and lighting, they are often designed to change their resistance on a logarithmic curve rather than a linear. That could affect your perception of the fade.

Also consider how noisy the output of your potentiometer is. Write a simple example to test the output of the pot and see if the values change when you’re not touching it. If so, check your wiring to make sure everything is stable. If the pot is still noisy, consider adding a smoothing function or a Kalman filter to stabilize the pot’s readings. The CIE1931 Fade with Kalman Input example shows how you can use the SimpleKalmanFilter library to smooth your potentiometer readings.

References