# Arduino Forum

## Using Arduino => Programming Questions => Topic started by: dtokez on Feb 09, 2013, 03:04 am

Title: Logarithmic scaling for LED dimming?
Post by: dtokez on Feb 09, 2013, 03:04 am
Hi all,

I'm writing some code to control TLC5940's via the internet. Currently in my code, I'm simply subtracting a value (something like 128 or 256) from the TLC cycle of 4096 which reduces the brightness in those steps.

I have heard that brightness is not perceived in this linier fashion so I'm wondering how I would go about implementing it?

the reason I want to do this is because I hardly notice it dimming when the values are high but when they get lower the dimming steps are to great and it wont dim as low as I want it too (only 20 or 30).

Thanks
Title: Re: Logarithmic scaling for LED dimming?
Post by: nickgammon on Feb 09, 2013, 03:12 am
A simple approach would be to have a table (array) of values which you subtract (you could make them logarithmic).

eg.

Code: [Select]
`int vals [] = { 256, 230, 200, 150, 100, 50, 10 };`

So each time you plan to reduce the brightness you subtract a different amount. Getting the values right might require some trial and error.
Title: Re: Logarithmic scaling for LED dimming?
Post by: dtokez on Feb 09, 2013, 04:21 am
Hi Nick,

Thanks, simple is good where I'm concerned :)

So I guess something like the following could work?

Code: [Select]
`int vals [] = { 256, 230, 200, 150, 100, 50, 10 };int numVal = 7; //number of values in arrayfor (int i = 0; i < numVal; i++){ //for the amount of values in the array..brightness = (brightness - vals[i]); //set new brightness level}`

Title: Re: Logarithmic scaling for LED dimming?
Post by: nickgammon on Feb 09, 2013, 07:38 am
That's what I had in mind. And you can get the compiler to work out the number of items in the array:

Code: [Select]
`// number of items in an array#define NUMITEMS(arg) ((unsigned int) (sizeof (arg) / sizeof (arg [0])))int vals [] = { 256, 230, 200, 150, 100, 50, 10 };int numVal = NUMITEMS (vals); //number of values in array`
Title: Re: Logarithmic scaling for LED dimming?
Post by: JimboZA on Feb 09, 2013, 08:32 am
Quote
I have heard that brightness is not perceived in this linier fashion

That's interesting.... worth reading up on just for the hell of it....
Title: Re: Logarithmic scaling for LED dimming?
Post by: robtillaart on Feb 09, 2013, 09:59 am
if you need more steps you can use multimap() to interpolate between the values of the array.

- http://playground.arduino.cc/Main/MultiMap -

multimap() approximates a non linear function with linear pieces. The more points you take the smaller the average/max error.
And be aware that the points do not need to be equidistant. This allows you to add points where the function has the most "dynamics" and to leave out points where the function is "boring linear"

Title: Re: Logarithmic scaling for LED dimming?
Post by: dtokez on Feb 10, 2013, 02:34 am
Hi all.

I've got the code running but I'm noticing some strange behavior, the values in the array (vals) is being added up, and the total value is being subtracted from the brightness each time - (1500 in this case)?

Here is the code, very experimental at the moment and a lot will change. The array is defined in the DEFINES section and the dimming is happening in the void down() function.

Code: [Select]
`////////////////////////INCLUDES//////////////////////#include <SPI.h>#include <Ethernet.h>#include "Tlc5940.h"////////////////////////END OF INCLUDES//////////////////////////////////////////////DEFINES//////////////////////int TLCDelay = 50;  //Delay after setting TLCint brightness = 4095;  //Initial brightness to displayint brightnessStep = 128;  //Dimming steps in// number of items in an array#define NUMITEMS(arg) ((unsigned int) (sizeof (arg) / sizeof (arg [0])))int vals [] = { 1000, 500 };int numVal = NUMITEMS (vals); //number of values in array////////////////////////END OF DEFINES//////////////////////////////////////////////ETHERNET SETUP//////////////////////byte mac[] = {   0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; //physical mac addressbyte ip[] = {   192, 168, 0, 177 }; // ip in lanbyte gateway[] = {   192, 168, 1, 1 }; // internet access via routerbyte subnet[] = {   255, 255, 255, 0 }; //subnet maskEthernetServer server(80); //server portString readString;////////////////////////END OF ETHERNET SETUP//////////////////////////////////////////////START OF MODES//////////////////////void allOn(){  Tlc.clear();  digitalWrite(7, HIGH);  Tlc.setAll(brightness);  Tlc.update();  delay(TLCDelay);}void sides(){  Tlc.clear();  digitalWrite(7, HIGH);  for (int i = 4; i < 12; i++)  {    Tlc.set(i, brightness);  }  Tlc.update();  delay(TLCDelay);}void gradient(){  Tlc.clear();  for (int i = 0; i < 4; i++)    Tlc.set(i, 4095);  for (int i = 4; i < 8; i++)    Tlc.set(i, 1000);  for (int i = 8; i < 12; i++)    Tlc.set(i, 250);  for (int i = 12; i < 16; i++)    Tlc.set(i, 100);  Tlc.update();  delay(TLCDelay);}void frontRow(){  Tlc.clear();  Tlc.set((1, 5, 9), brightness);  Tlc.update();  delay(TLCDelay);}void middle(){  Tlc.clear();  Tlc.set(6, brightness);  Tlc.set(7, brightness);  Tlc.set(8, brightness);  Tlc.update();  delay(TLCDelay);}////////////////////////END OF MODES//////////////////////////////////////////////START OF DIMMING CONTROL//////////////////////void up(){  if ((brightness + brightnessStep) < 4095)    brightness = brightness + brightnessStep;   Serial.println(brightness);  Tlc.setAll(brightness);  Tlc.update();  delay(TLCDelay);}void down(){  for (int i = 0; i < numVal; i++){ //for the amount of values in the array..    brightness = (brightness - vals[i]); //set new brightness level  }  Serial.println(brightness);  Tlc.setAll(brightness);  Tlc.update();  delay(TLCDelay);}////////////////////////END OF DIMMING CONTROL//////////////////////void setup(){  Tlc.init(0);  pinMode(7, OUTPUT); //pin selected to control  //start Ethernet  Ethernet.begin(mac, ip, gateway, subnet);  server.begin();  Serial.begin(9600);}void loop(){  // Create a client connection  EthernetClient client = server.available();  if (client) {    while (client.connected()) {      if (client.available()) {        char c = client.read();        //read char by char HTTP request        if (readString.length() < 100) {          //store characters to string          readString += c;          //Serial.print(c);        }        //if HTTP request has ended        if (c == '\n') {          ///////////////          client.println("HTTP/1.1 200 OK"); //send new page          client.println("Content-Type: text/html");          client.println();          client.println("<HTML>");          client.println("<HEAD>");          client.println("<meta name='apple-mobile-web-app-capable' content='yes' />");          client.println("<meta name='apple-mobile-web-app-status-bar-style' content='black-translucent' />");          client.println("<link rel='stylesheet' type='text/css' href='http://homeautocss.net84.net/a.css' />");          client.println("<TITLE>Home Automation</TITLE>");          client.println("<meta name=\"viewport\" content=\"user-scalable = yes\" />"); //?          client.println("<meta name=\"viewport\" content=\"width=device-width, height=device-height, initial-scale=1, maximum-scale=1\" />");          client.println("</HEAD>");          client.println("<BODY>");          client.println("<H1>Home Automation</H1>");          client.println("<hr />");          client.println("<br />");          client.println("<a href=\"/?lighton\"\">All On</a>");          client.println("<a href=\"/?lightoff\"\">All Off</a><br />");          client.println("<br />");          client.println("<hr />");          client.println("<br />");          client.println("<a href=\"/?sides\"\">Sides</a>");            client.println("<a href=\"/?gradient\"\">Gradient</a><br />");            client.println("<br />");          client.println("<br />");          client.println("<a href=\"/?up\"\">Up</a>");          client.println("<a href=\"/?down\"\">Down</a><br />");          client.println("<br />");          client.println("<br />");          client.println("<a href=\"/?frontRow\"\">Front Row</a>");          client.println("<a href=\"/?middle\"\">Middle</a><br />");          client.println("</BODY>");          client.println("</HTML>");          delay(1);          //stopping client          client.stop();          ///////////////////// control arduino pin          if(readString.indexOf("?lighton") >0)//checks for on          {            allOn();          }          else if(readString.indexOf("?lightoff") >0)//checks for off          {            Tlc.setAll(0);            Tlc.update();            delay(TLCDelay);          }          else if(readString.indexOf("?sides") >0)//checks for off          {            sides();          }          else if(readString.indexOf("?gradient") >0)//checks for off          {            gradient();          }          else if(readString.indexOf("?up") >0)          {            up();          }          else if(readString.indexOf("?down") >0)          {            down();          }          else if(readString.indexOf("?frontRow") >0)          {            frontRow();          }          else if(readString.indexOf("?middle") >0)          {            middle();          }          //clearing string for next read          readString="";        }      }    }  }}/*            digitalWrite(7, LOW); //Tlc.set(1, 0); //Tlc.set(15, 0); for (int i = 4095; i > (0-1) ; i--) { Tlc.set(15, i); Tlc.set(10, i); Tlc.update(); delay(1); } Tlc.set(9, 0); Tlc.update(); delay(100); */`
Title: Re: Logarithmic scaling for LED dimming?
Post by: nickgammon on Feb 10, 2013, 02:46 am
You may be running out of RAM. Try using the F macro, eg. change:

Code: [Select]
`         client.println("<HTML>");          client.println("<HEAD>");          client.println("<meta name='apple-mobile-web-app-capable' content='yes' />");          client.println("<meta name='apple-mobile-web-app-status-bar-style' content='black-translucent' />");          client.println("<link rel='stylesheet' type='text/css' href='http://homeautocss.net84.net/a.css' />");          client.println("<TITLE>Home Automation</TITLE>");          client.println("<meta name=\"viewport\" content=\"user-scalable = yes\" />"); //?          client.println("<meta name=\"viewport\" content=\"width=device-width, height=device-height, initial-scale=1, maximum-scale=1\" />");`

to:

Code: [Select]
`          client.println(F("<HTML>"));          client.println(F("<HEAD>"));          client.println(F("<meta name='apple-mobile-web-app-capable' content='yes' />"));          client.println(F("<meta name='apple-mobile-web-app-status-bar-style' content='black-translucent' />"));          client.println(F("<link rel='stylesheet' type='text/css' href='http://homeautocss.net84.net/a.css' />"));          client.println(F("<TITLE>Home Automation</TITLE>"));          client.println(F("<meta name=\"viewport\" content=\"user-scalable = yes\" />")); //?          client.println(F("<meta name=\"viewport\" content=\"width=device-width, height=device-height, initial-scale=1, maximum-scale=1\" />"));`

And so on.

Quote

the values in the array (vals) is being added up, and the total value is being subtracted from the brightness each time

Well that's what your code is told to do:

Code: [Select]
`  for (int i = 0; i < numVal; i++){ //for the amount of values in the array..    brightness = (brightness - vals[i]); //set new brightness level  }`

You are subtracting every value.
Title: Re: Logarithmic scaling for LED dimming?
Post by: dtokez on Feb 10, 2013, 03:16 am
Thanks Nick, I will use the F macro option!

I'm confused as to how I pull the values to subtract from the 'val' array each time through the loop?

Title: Re: Logarithmic scaling for LED dimming?
Post by: nickgammon on Feb 10, 2013, 04:28 am
You probably don't want to do it each time. You would have a (global) variable that says what part of the array you are up to. After a certain time elapsed (use millis() to find when that is), increment the variable to get the next number from the array to subtract.
Title: Re: Logarithmic scaling for LED dimming?
Post by: tmd3 on Feb 11, 2013, 12:01 am
Here's a method of calculating brightness levels that appear to be equally spaced.  It may be a bit late in the thread's life for this.

Stevens' power law  - see it here: http://en.wikipedia.org/wiki/Stevens_power_law (http://en.wikipedia.org/wiki/Stevens_power_law) - gives a method of determining the relative perceived intensity of a stimulus - in this case, brightness of an LED - as a function of the physical magnitude of the stimulus.  Here's the general form:
P = k * Sa
where P is the perceived intensity, S is the magnitude, and k and a are constants that depend on the type of stimulus and the units of measurement.  The value of a is less than 1 for brightness and loudness; greater than 1 for electric current through fingertips.

The value of a isn't well-characterized, though.  For light intensity, it might be around 0.33, but the actual value will depend on the ambient lighting, color, the color and brightness of the background that it's seen against, and, to some extent, which one of us is looking at the LED.  It'll take some experiments to find an acceptable value.

Dealing with k is easier - in fact, we can forget about it.  Here's why:  the units of P, the perceived brightness, are arbitrary.  They're generic "perceived brightness units," and they don't correspond to any real physical quantity.  So, we can pick the units of P so that the value of k is exactly one, and then we can forget about k.  We'll also select the units of S as PWM ticks - the time-averaged illumination resulting from a PWM code of 1.  The amount of light that comes from the  LED varies nearly linearly with this quantity, so it's a reasonable unit to use.  And, doing so makes the math a lot easier.

To do the experiments, we can pick an a, and calculate an array defining n equal steps.  First, we calculate the maximum perceived brightness in arbitrary units:
Pmax = Smaxa
Smax is the code that corresponds to the maximum brightness at which you want to operate an LED.  It could be anything, but it's easy to select 4095, since that's the maximum brightness code your LED driver IC will accept.  Then, for n=0 to N, where N is the number of steps, calculate Sn like this:
Sn = [Pmax * (n/N)](1/a)
or, in C, rounding the output codes to integers:
Code: [Select]
`levels[n] = int(pow(Pmax * ((float)n/(float)N), 1/a) + 0.5);`
The wiki article suggests that a is between 0.33 and 0.5.  My experiments say that might be true, but they also say that equal step-size is tricky to identify.

At the end, we have an array of codes that will ostensibly yield an equal change in brightness for each step.  That's true if you believe that Stevens' power law accurately describes perceived brightness.  Not everyone does.  The alternative is the Weber-Fechner law - http://en.wikipedia.org/wiki/Weber-Fechner_law  (http://en.wikipedia.org/wiki/Weber-Fechner_law) - which describes a curve that's logarithmic, rather than a power function.  The math is about the same, except that it uses exp() rather than pow(), but the experimentation is harder - it requires you to find a stimulus magnitude that results in a perception of zero, and that stimulus magnitude can't itself be zero.  My rough tests suggest that a PWM code of 1 yields a perceptible brightness, so I think that we'd just be guessing about it.  The power law seems to be reasonably well-accepted, so it'll likely yield acceptable results.
Title: Re: Logarithmic scaling for LED dimming?
Post by: Grumpy_Mike on Feb 11, 2013, 12:24 am
Here is a simple example of using a look up table with the 256 levels given by the normal PWM

Code: [Select]
`/* Change brightness of LED linearly to Human eye 32 step brightness using 8 bit PWM of Arduino brightness step 24 should be twice bright than step 12 to your eye.*/ #include <avr/pgmspace.h> #define CIELPWM(a) (pgm_read_word_near(CIEL8 + a)) // CIE Lightness loopup table function/*5 bit CIE Lightness to 8 bit PWM conversionL* = 116(Y/Yn)^1/3 - 16 , Y/Yn > 0.008856L* = 903.3(Y/Yn), Y/Yn <= 0.008856*/prog_uint8_t CIEL8[] PROGMEM = {0,    1,    2,    3,    4,    5,    7,    9,    12,15,    18,    22,    27,    32,    38,    44,    51,    58,67,    76,    86,    96,    108,    120,    134,    148,    163,180,    197,    216,    235,    255};int brightness = 0;    // initial brightness of LEDint fadeAmount = 1;void setup()  {// declare pin 9 to be an output:pinMode(9, OUTPUT);}void loop()  {// set the brightness of pin 9:, 0-31, 5 bit steps of brightnessanalogWrite(9, CIELPWM(brightness));// change the brightness for next time through the loop:brightness = brightness + fadeAmount;// reverse the direction of the fading at the ends of the fade: if (brightness == 0 || brightness == 31) {fadeAmount = -fadeAmount ;} // wait for 500 milliseconds to see the bightness change delay(500);}`
Title: Re: Logarithmic scaling for LED dimming?
Post by: nickgammon on Feb 11, 2013, 02:42 am
It looks to me like Grumpy_Mike's example lists new values, not differences. That is probably much simpler anyway.
Title: Re: Logarithmic scaling for LED dimming?
Post by: Longinus on Jun 29, 2013, 11:55 pm

Here is a simple example of using a look up table with the 256 levels given by the normal PWM

Hey Mike!

I was just trying your code, and I've noticed something...
Shouldn't the macro read a byte, and not a word?

`#define CIELPWM(a) (pgm_read_word_near(CIEL8 + a))`
`#define CIELPWM(a) (pgm_read_byte_near(CIEL8 + a))`