Logarithmic scaling for LED dimming?

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: Stevens's power law - Wikipedia - 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:

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 - Weber–Fechner law - Wikipedia - 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.