Deriving the Logarithm of Photoresistor Resistance From Voltage

Photoresistor circuit and equation
Photoresistor circuit and equation

The general idea is a simple light sensor that can cope with typical indoor illumination, brightening and dimming the digits on a clock so that it’s both visible in the daylight and doesn’t light up the room at night.

This circuit produces a voltage that varies more-or-less inversely with the photoresistance R. The “decade resistor” DR acts as a range selector: the output voltage will be 2.5 V when DR = R.

Yesterday I doodled about the voltage-vs-resistance curves and how DR works, showing the equations that spit out V when you know R, which is handy from a circuit-analysis standpoint (if you can say that with a straight face for a two-resistor circuit).

What you want is the equation that spits out R when you know V, because you can actually measure V. Rearranging the equation in the doodle above produces that equation

R = DR * (5 – V) / V

Actually, what you really want is log R, because your sensation of brightness varies logarithmically with the illumination intensity: each doubling of intensity makes the scenery twice as bright. So you could find R, apply a floating-point math package to it, and come up with log R.

There’s a better way.

CdS Resistance vs Voltage
CdS Resistance vs Voltage

Stand yesterday’s graph on its ear and flip it side-to-side to get this view of the same data points. It’s plotted with a log scale on the Y axis, because the resistance varies over such a huge range.

The dots represent the values of R produced by the equation above with DR = 1 kΩ. Those are the actual resistance values, at least according to the circuit model.

The midsection of those dots is pretty straight, so the diagonal line second from the bottom is a straight line “curve fit” with intercepts at

(0 V, log 10000)


(5 V, log 100)

The y = mx + b equation fitting that line is

log R = (-2/5) * V + 4

where the (-2/5) comes from the slope of the line:

(log 10000 – log 100) / (0 – 5)

and the (+ 4) comes from the intercept at (log 10000), as set by the value of DR. In fact, the intercept is 1+ (log DR), because it’s always a factor of 10 higher than the value of DR.

Now, what’s nice about that is the equation spits out log R directly, with just some multiply-divide-add action.

Changing DR by factors of 10 produces the other lines, so (as before) switching DR gives you a low-budget, wide-dynamic-range output.

DR need not be a power of 10, of course. The dashed line near the middle is DR = 3 kΩ, which puts the more-or-less linear region between about 20 kΩ (fairly dim) and 500 Ω (rather bright). That’s a useful range in my house and it might be close enough for my friend.

The dashed line approximating those points has an intercept at 1 + log 3000 = 4.5, so the overall equation is

log R = (-2/5) * V + 4.5

The approximation gets progressively worse below, say, V = 0.5 and above V = 4.5, so the outline of the algorithm is:

  • V < 0.5 = pretty dark: lowest digit intensity
  • V between 0.5 and 4.5: puzzle over log R
  • V > 4.5 = lots o’ light: max digit intensity

The Arduino ADC produces a 10-bit integer: 0 through 1023. Call that Vb (“V binary”), which you can scale to V like this

V = (5/1024) * Vb

Plugging that into the equation produces

log R = (-2/5) * (5/1024) * Vb + 4.5

log R = (-2/1024) * Vb + 4.5

The useful limits on Vb for the linear approximation are

  • V = 0.5 -> Vb = 1024 * 0.5/5 = 102
  • V = 4.5 -> Vb = 1024 * 4.5/5 = 922

Checking those limits against the actual formula for R

  • Vb = 102 -> log R = 4.3 (instead of 4.43)
  • Vb = 922 -> log R = 2.7 (instead of 2.52)

That’s about what you’d expect from the graph: the line is lower than the dots on the dim end (left) and higher on the bright end (right). On the other paw, getting log R without the floating-point math package makes up for that.

Now, given that you’re going to use a table lookup anyway, you don’t need any arithmetic on Vb at all. Shove all the stuff surrounding Vb to the other side of the equation

(log R – 4.5) * (-1024 / 2) = Vb

(4.5 – log R) * 512 = Vb

Precompute the left side for useful values of R, fill in the corresponding bit pattern to get the desired brightness, then index into the table with the measured Vb: shazam, log R -> brightness bits in one step!

If you’re exceedingly lucky, the brightness bits will be more-or-less in a binary sequence, in which case you can just right-shift Vb to get that number of bits and send ’em directly to the LED drivers. No table needed: one shift and you’re done!

So, for example, suppose you want eight brightness levels controlled by three Brightness Bits BB

BB = Vb >> 7

What’s not to like?

Maybe you already knew that and were wondering why it took me so long to get there…