Arduino LED Fader – ElectroSchematics.com

I have spent at least an hour almost every day since the 80’s advancing my knowledge in electronics. Recently I brushed up my knowledge on LED fading secrets and attained something pleasant. Now, I’d like to outline my random thoughts on better LED faders.

The twisted road of lightness

I use pulse width modulation (PWM) techniques to control the brightness of light emitting diodes (LEDs) in many of my microcontroller projects. I can set my LEDs to the exact brightness level I like on a 0-100% scale typically with a little button or knob. Look a little closer at the way LEDs in such projects dim, and you will find that in some projects the brightness dim in linear fashion, while in other projects it is a logarithmic approach. Sounds sensible, right?

Recall the last time you tried to dim an LED using pulse width modulation. I am sure you have experienced the relationship between the duty cycle and the perceived brightness is not linear at all. The International Commission on Illumination (CIE) released a study of psychometric colorimetry (the measurement of how we perceived changes in light) in 1931. The landmark study resulted in the CIE 1931 standard colorimetric system, a standardized way of describing color. As part of that, they defined a formula for lightness as we perceive it – the CIE 1931 perceived lightness formula!

Note that humans perceive the brightness change non-linearly. The human eye responds to light in a logarithmic fashion and has a better sensitivity at low luminance than high luminance. If you do not believe that test it yourself quickly through an Arduino and a PWM example sketch. You can clearly see a big brightness change between analogWrite(x,1) and analogWrite(x,2)but can’t see a brightness change between analogWrite(x,244) and analogWrite(x,255). Also see the following reference graph – brightness perception of human eye.

Alive to what’s going on

So, if you want to regulate LED brightness linearly to your eye, it requires some clever thoughts and adaptations. First, upload the following PWM sketch to your Arduino Uno r3/Nano v3 and connect a 10mm water-clear green LED to D9.

const int ledPin = 9;

// Test LED wire to D9




void setup() {

  // Set the pin as a digital output

  pinMode(outputPin, OUTPUT);

}




void loop() {

  // Fade the brightness from 0 (min) to 255 (max)

  for (int brightness = 0; brightness <= 255; brightness++) {

    analogWrite(ledPin, brightness);

    delay(40);

 // 40ms delay per step




  }

}

The PWM sketch works to vary the output linearly, however, the light outputted by the LED does not change in a linear manner. When applying the scientific metric of ‘perceived brightness versus time’ for the given sketch it’d appear similar to the chart shown below for a linear pulse width modulated signal. The naked eye feels an exponential brightening – initially a big jump in brightness but no distinguishable change for the last few seconds!

According to Diarmuid Mac Namara (https://diaarmuid.ie/), we need to set the

input PWM signal to counteract the natural exponential nature of the LED to make the LED look like it’s fading linearly. With trial and error, he found the most pleasing input curve to use can be generated by the formula x = x^(2/r) – 1where x is the step (ie step 56 of the fade), y is the PWM value (0-255) and r is a factor that is calculated based on the number of steps and the required output. And, r is calculated such that 2m/r=pwhere p is the maximum value of the PWM cycle (255 in Arduinos) and m is the number of steps the LED will fade over. If it’s expressed in terms of rthen the rearranged formula is r=(m log 2/log p).

The code provided below will give you a nice natural (visually linear) fading of the light over as long and as many steps as you like.

const int ledPin = 9;

//LED pin D9




// The number of steps between the output being on and off

const int pwmIntervals = 100;




// The r value in the graph equation

float R;




void setup() {

  // set the pin connected to the LED as an output

  pinMode(ledPin, OUTPUT);




  // Calculate the r variable (only needs to be done once at setup)

  R = (pwmIntervals * log10(2))/(log10(255));




}




void loop() {

  int brightness = 0;




  for (int interval = 0; interval <= pwmIntervals; interval++) {

      // Calculate the required PWM value for this interval step

      brightness = pow (2, (interval / R)) - 1;

      // Set the LED output to the calculated brightness

      analogWrite(ledPin, brightness);

      delay(40);

  }

}

See Mac Namara’s input curve. Looks like it takes longer to get bright and drops off a bit faster. Right?

The Simple Kalman Filter

The most common interactive input control for fading LEDs is a standard potentiometer. However, when you start controlling fade curves via the potentiometer wired to one analogue input of the Arduino, you often get displeasing jumps in your fades. If so, you can consider a Kalman Filter to stabilize the knob control/ potentiometer readings.

Quick note: ATmega based Arduino boards (UNO, Nano, Mini, Mega) takes about 100us to read an analogue input, so the maximum reading rate is about 10,000 times second.

Below you can see a “CIE1931 Fade” Arduino sketch by Tom Igoe (https://tigoe.github.io). The sketch takes input from a potentiometer and produces a fair LED fade, using the CIE1931 perceived lightness formula. Additionally, a Kalman filter using the “SimpleKalmanFilter” library (https://github.com/denyssene/SimpleKalmanFilter) smooths the potentiometer input. The wiper of the 10K potentiometer is wired to A0 of Arduino (5V-A0-GND), while anode (A) of the test LED is linked to D5 through a 220Ω-470Ω resistor and cathode (K) to GND.

#include <SimpleKalmanFilter.h>

int currentLevel = 1; // current light level

int change = 1; // change each time you fade

byte cie1931[256]; // pre-calculated PWM levels

SimpleKalmanFilter filter(2, 2, 0.01);

void setup() {

Serial.begin(9600);

// pre-calculate the PWM levels from CIE1931 formulas:

fillCIETable();

}

void loop() {

// read potentiometer:

int sensorReading = analogRead(A0);

// calculate the estimated value with Kalman Filter

float estimate = filter.updateEstimate(sensorReading);

// map to 0-255 range:

int currentLevel = map(estimate, 0, 1023, 0, 255);

// PWM output the result. Get levels from

// the pre-calculated CIE1931 table:

analogWrite(5, cie1931[currentLevel]);

Serial.println(cie1931[currentLevel]);

}

void fillCIETable() {

/*

For CIE, the following formulas have Y as luminance, and

Yn is the luminance of a white reference (basically, max luminance).

This assumes a perceived lightness value L* between 0 and 100,

and a luminance value Y of 0-1.0.

if L* > 8: Y = ((L* + 16) / 116)^3 * Yn

if L* <= 8: Y = L* *903.3 * Yn

*/

// set the range of values:

float maxValue = 255;

// scaling factor to convert from 0-100 to 0-maxValue:

float scalingFactor = 100 / maxValue;

// luminance value:

float Y = 0.0;

// iterate over the array and calculate the right value for it:

for (int l = 0; l <= maxValue; l++) {

// you need to scale L from a 0-255 range to a 0-100 range:

float lScaled = float(l) * scalingFactor;

if ( lScaled <= 8 ) {

Y = (lScaled / 903.3);

} else {

float foo = (lScaled + 16) / 116.0;

Y = pow(foo, 3);

}

// multiply to get 0-maxValue, and fill in the table:

cie1931[l] = Y * maxValue;

}

}

This is a random oscillogram from D5 of Arduino Uno. During experiments, I powered my Arduino setup with a Li-ion 9V/600mAh USB battery.

Quick note: Arduino UNO has three timers – D5-D6 on Timer 0 (PWM ~1kHz), D9-D10 on timer1 (PWM ~500Hz) and D3-D11 on Timer2 (PWM ~500Hz). Further reading https://www.arrow.com/en/research-and-events/articles/arduino-pwm-pulse-width-modulation-in-arduino

Linear & Logarithmic

Note that the brightness output is linear in relation to the digital signal value in linear dimming curves. However, in logarithmic dimming curves, the digital signal value changes slower at deeper dimming levels and faster at the brighter end. Next key thing to note down is the difference between measured light and perceived light. Measured light is the amount of light as shown on a light meter, whereas perceived light is the amount of light that our eye interprets due to dilation. In short, in order to accomplish great dimming performance, the brightness of the light source must be matched to the way our eyes behave!

And there you have it! This primer is just a direct translation of my random thoughts. I hope you found this primer helpful. In the next part, I’ll take a look at more elegant LED dimmer/fader project ideas. But only after a while, so stay tuned »

Credits & References (only those not pointed yet)

https://tigoe.github.io/LightProjects/fading.html

https://ledshield.wordpress.com

https://www.arduino.cc/reference/en/language/functions/analog-io/analogread/

http://eletropit.com

https://www.ledinside.com/

https://en.wikipedia.org/wiki/Logistic_function

Leave a Comment