## logarithmic LED brightness function

12 posts / 0 new
Author
Message

I want to take a 0-255 brightness value and convert it to an approximately linear increase in LED brightness for use in PWM. I also wanted to minimize processor usage without needing a lookup table. What I ended up with is the following function, which takes the 9 equidistant control points mapped to powers of two and interpolates linearly between them.

`f(0)=0, f(31)=1, f(63)=3, f(95)=7, f(127)=15, f(159)=31, f(191)=63, f(223)=127, f(255)=255`
```unsigned char led_brightness(unsigned char n) {
return (((1<<(n/32))-1) + ((1<<(n/32))*((n%32)+1)+15)/32);
}```

I am going to see what gcc produces from this and try to squeeze the asm down as far as possible. Your thoughts are appreciated.

If you run out of processing power/time, you could use a lookup table. :D

Good idea, though. Maybe you could post your lookup table when you're done so lazy bums like me don't have to re-invent the wheel. :roll:

If you think education is expensive, try ignorance.

Well, "my" lookup table would be less than ideal. If you're going to dedicate the space to one you would want it to be precise, and my function is off by a measurable percent in the middle of the interpolated areas (particularly the last one). If you want an accurate logarithmic version of the table that would be produced I would be happy to generate one from the spreadsheet I used to work on my approximation.

edit: and here it is, f(n)=round(2**((n+1)/32))-1

```unsigned char linear_brightness_curve[256] = {
0,   0,   0,   0,   0,   0,   0,   0,
0,   0,   0,   0,   0,   0,   0,   0,
0,   0,   1,   1,   1,   1,   1,   1,
1,   1,   1,   1,   1,   1,   1,   1,
1,   1,   1,   1,   1,   1,   1,   1,
1,   1,   2,   2,   2,   2,   2,   2,
2,   2,   2,   2,   2,   2,   2,   2,
2,   3,   3,   3,   3,   3,   3,   3,
3,   3,   3,   3,   3,   4,   4,   4,
4,   4,   4,   4,   4,   4,   5,   5,
5,   5,   5,   5,   5,   5,   6,   6,
6,   6,   6,   6,   6,   7,   7,   7,
7,   7,   8,   8,   8,   8,   8,   9,
9,   9,   9,   9,  10,  10,  10,  10,
11,  11,  11,  11,  12,  12,  12,  12,
13,  13,  13,  14,  14,  14,  15,  15,
15,  16,  16,  16,  17,  17,  18,  18,
18,  19,  19,  20,  20,  21,  21,  22,
22,  23,  23,  24,  24,  25,  25,  26,
26,  27,  28,  28,  29,  30,  30,  31,
32,  32,  33,  34,  35,  35,  36,  37,
38,  39,  40,  40,  41,  42,  43,  44,
45,  46,  47,  48,  49,  51,  52,  53,
54,  55,  56,  58,  59,  60,  62,  63,
64,  66,  67,  69,  70,  72,  73,  75,
77,  78,  80,  82,  84,  86,  88,  90,
91,  94,  96,  98, 100, 102, 104, 107,
109, 111, 114, 116, 119, 122, 124, 127,
130, 133, 136, 139, 142, 145, 148, 151,
155, 158, 161, 165, 169, 172, 176, 180,
184, 188, 192, 196, 201, 205, 210, 214,
219, 224, 229, 234, 239, 244, 250, 255
};```

which is, as predicted, as much as 11 different from my linear interpolated function, in the vicinity of n=240. also, depending on exactly how I planned to use it, I might manually fudge the numbers a bit to smooth out the inconsistent slopes right at the slope transitions (like at 40).

Are you accounting for the fact that perceived intensity varies as the RMS optical power (or so I've been told)? When you control the LED with PWM, the RMS power will be proportional to the square root of your duty factor. This helps out multiplexed displays greatly; an 8-digit display is more than 1/3rd as bright as a 1-digit display (at the same current), rather than 1/8th as bright

Thanks. Have you tried to match the table with the curves that the manufacturer has given in the datasheet?

If you think education is expensive, try ignorance.

Levenkay, that is precisely what this code/table does. If you want a visible brightness of 50% then you look up entry 127 in the table, or feed n=127 to the function, and see that you should feed only 15/255 duty cycle to the LED. If you light an LED at {0,1,3,7,15,31,63,127,255}/255 duty cycle then you will, in my limited experience, see a roughly linear increase in perceived brightness.

emuler, I have not matched to a specific data sheet. There are so many different LEDs out there. This curve produces what appears to me to be a linear increase, with most of the LEDs that I have handy. IT also happens to be mathematically straightforward and easy to calculate. A curve specifically matched to a given set of brightness-matched LEDs would be ideal, but cannot be done in a general case.

That said...
The algorithmic version is far less efficient than I had anticipated. I just discovered that the AVR does not have arbitrary-distance shift instructions, so a simple (a<<b) turns into a loop that eats 2+4*b cycles. Average runtime for the code I posted above is 69 cycles. Since there are only 8 values of "b", I am going to investigate a variation that uses a small lookup table for the first 8 powers of 2, then multiplies, instead of shifting to the left. If the lookup takes 3 cycles, then I can cut get the runtime down to something like 25 cycles, and probably save space overall since the code will be smaller without the loops.

It would be interesting to find out why you have this dislike of lookup tables. If you can arrange the table in such a way that the item to be looked up forms part of the address of the lookup value (say ZL), there is no quicker way. :D

If you think education is expensive, try ignorance.

Speed vs Memory. 256 bytes for a lookup table vs 40 bytes of arithmetic, seems trivial until you have 1kB of flash and 0-128 bytes of RAM to work with, and a lot more logic to cram in*.

* - I have seen a lot of programs applying linear duty cycle increase to LEDs. I want to add this code to those existing programs, some of which already almost fill the available resources.

Hummmm... 256 bytes for lookup table,.... 40 bytes for math... Why not make a 32 or 64 byte lookup table and interpolate (1/8 or 1/4) between the entries?

I don't know what level of precision is required, but taken in account that the first 16 levels of the table is 0 (off?), and the 24 levels that follows are 1, and so on... Are the 256 levels REALLY necessary?

Quote:

I don't know what level of precision is required,

That's what I want to know--what is the app that needs this precision? Certainly not the human eye. An illuminated microscope stage?

Lee

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

My code already has an 8-entry lookup table, effectively, implemented in 12 bytes of code that run in 8 cycles. Doing it with an actual table that size would probably not save anything. Using a larger table makes it slightly more accurate at the expense of using more space and the same amount of time.

For just LEDs we don't need 256 levels, but if you are going to do calculations at that resolution (as most naive approaches do) then being able to convert them to the proper output duty cycles for a 256-tick timer is useful. This is a drop-in for code that already exists and is using 256 linear-duty-cycle levels.

To be honest, for my own code I will likely end up implementing 16 levels of brightness stored in a nybble, using a lookup table, but it never hurts to have too much general purpose code.