*linear* PWM LED fade with arduino

Arduino comes with a simple program to make an LED fade on  or off, however, as far as I can tell, the apparent brightness of the LED does not change linearly with the wpm duty cycle. After some research I found that this is because humans percieve brightness logarithmicly, not linearly. (Also, LEDs don't change brightness linearly with current, but sisnce I'm using PWM, i don't think that comes in to effect.)

I found a simple look-up table to correct for this on this page.

255, 180, 128, 90, 64, 45, 32, 23, 16, 12, 8, 6, 4, 3, 2, 1,0

This seems to work very well, but I don't know where this comes from. Does anyong know the ectual equation to get the % duty cycle from the %brightness? I like to abstractise things.

sort by: active | newest | oldest
eggchipsand5 years ago
I understand this is an old thread but...

using zunzun.com to perform curve fitting I have created the following c function which converts a required percentage (0..100) to a pwm value (255). Tested on an arduino uno it appears quite smooth.

I hope it is of some use to fellow faffers.

// returns a pwm value (0..255) for a required percentage (0..100)
// as to provide a linear fade as perceived by eye
int linearPWM(int percentage){
// coefficients
double a = 9.7758463166360387E-01;
double b = 5.5498961535023345E-02;
return floor((a * exp(b*percentage)+.5))-1;
Time your implementation over the look up method.....
gmoon6 years ago
That looks like a function of the square root of 2.

This is a VERY common non-linear relationship in nature. Look up the "inverse square law", regarding light intensity, sound etc.

The sqr root of 2 = ~1.414, so just multiply the preceding number in the ascending sequence (or for descending use half, or  * 0.707).

Those numbers are integers, of course. You'll either have to use a "float" variable and cast it as an integer, or you'll have to use "fixed point" arithmetic (look it up).

(you like to "abstractise" words, too... ;-)
Vick Jr (author)  gmoon6 years ago
Ah, yes. I should have known. It's basically duty cycle=sqrt(2)^x except scaled so that it gives the right domain for whatever range you want to use. (in this case scaled by about 1.408 or 255/sqrt(2)^16. You could use the form PWM=(range/sqrt(2)^domain)*sqrt(2)^x simplified PWM = range* 2^((x-domain)/2) I think for now I'll just calculate data for a larger domain and use that. I could have it calculate in real time, but look-up tables are faster and I already have it implemented. Thank you!
Stumbled on your post, looking to do the same thing but confused with your formula. I get that range is the full scale of PWM (255 for 8bit), I am assuming that X represents the brightness you want, just confused what domain means? Could you help?

Thanks! :D
gmoon Vick Jr6 years ago
Absolutely, you can easily scale this to the resolution of the PWM register.

Tables work well, but some uC compilers automatically copy all flash ROM tables to RAM even if they are static, rather than accessing the ROM directly. Depends on the chip and the compiler, of course. If it's a large table, that could be an issue.