Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.
Because nearly all of my printed circuit boards are for one-off homebrew projects, I tend to not obsess over getting the last air wire down on the copper. Instead, I route those pesky all-the-way-across-the-board stragglers on Layer 15 with big fat vias on each end, then solder a jumper wire across the board.
In effect, my Layer 15 is outside the board.
The screen shot shows a chunk of a board with some Layer 15 wires. I make ’em fat and use swooping semicircular arcs on the ends: they’re easily visible.
I don’t worry about actually routing the traces; they’re just straight lines and arcs. This generates all manner of overlaps with the rest of the components & wiring, but after I go down through the DRC list and approve ’em all one time, that’s the end of that hassle.
Two key advantages:
All the remaining air wires are genuine unrouted connections
I can print out Layer 15 separately to get a hand wiring map
I make the vias fairly large (here, 100 mils) and a unique shape (octagonal) so that I know each one should get a wire.
I usually wind up doing the power connections the same way; those vias are square. Conversely, ground vias stitching the top & bottom planes together are round; they get a short Z-wire through the board.
This probably won’t work if you’re having the boards built by an actual PCB vendor, as they’ll try to make a three-layer board or kick the board out on layout rule violations… but, on the other hand, if you can afford a four-layer board, then most likely you won’t have any trouble routing the wires.
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
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)
and
(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…
A friend wants a digital clock that’s dim when the house lights are low; standard clocks aren’t nearly dim enough. I know how she feels, having added a primary-red filter in front of a blue vacuum-fluorescent clock display to get rid of those garish digits in the middle of the night.
This job calls for a photosensor of some kind, as you can’t figure it out by time alone.
After a brief struggle, the parts heap disgorged a 1-inch diameter CdS (cadmium sulfide) photoresistor with a dark resistance over 500 kΩ and a full-sunlight resistance around 50 Ω. Ordinary room light, at least around here, is in the 1-10 kΩ range, more or less, kinda sorta. Down the Basement Laboratory, it’s tens of kΩ.
Photoresistor circuit and equation
The canonical sensing circuit is a simple voltage divider, with the photoresistor either on the top or the bottom. Putting it on the top means the voltage increases as the light gets brighter, which has a lot to recommend it.
The resistance varies more or less linearly with the light intensity, to the extent that you can make a very nice linearly variable isolated resistor by bottling up an LED (which has linear intensity with current) with a CdS photoresistor in a light-tight enclosure.
Anyhow, although the resistance R varies linearly, having the R in the denominator means that the voltage V varies inversely. Worse, because the value of R spans about four decades, there’s a serious range & resolution problem.
This graph of V against log R shows the situation.
Output voltage vs log resistance
The dots between R=10 Ω and R=1 kΩ are what the circuit spits out, with the “decade resistor” DR = 100 Ω and R values chosen for nice dot spacing. The long tail beyond 1 kΩ shows that for R greater than 1 kΩ, V doesn’t change by very much at all. Ditto for R less than 20 Ω or so, which is beyond the limit for this photocell.
The straight line through those points is an eyeballometric curve fit to the range from about 20 to 700 and (most crucially) passing through (10 Ω,5 V) and (1000 Ω,0 V). The equation for that line is the usual y = mx + b, albeit with (log R) where you’d expect x. The equation in the lower-left corner is pretty close to what you want, with D = log DR
V = (5/2) * ((1+D) – log R)
Running the photosensor circuit with DR = 1 kΩ would produce an identical series of dots snuggled up along the second line as R varies from 100 Ω to 10 kΩ. Each factor-of-10 change in DR handles another chunk of R’s range, with identical output voltages: when the voltage gets above about 4.5 or below 0.5, just switch to the next value of DR and continue merrily along.
So that’s a low-budget (if you have cheap relays, like MOSFET analog switches), high-dynamic-range (if you have a good buffer amplifier) light sensor.
More on how to turn this into a brightness control tomorrow…
Memo to Self: Can we correlate a digital camera’s exposure at a known ISO with the cell resistance, so I can get some remote light level values?
Problem: I needed a shielding plane under a sensitive gizmo that was separated from the ground plane covering the rest of the board. EAGLE poured the ground plane atop the smaller shield polygon, despite the two signals having different names.
Solution: set the ground plane’s Rank to 2, which means it’s less “important” than the smaller plane. Thus, the smaller plane gets drawn first and the ground plane surrounds it.
[Update: Remember, that struct is bass-ackwards. You want the most-significant fields on the bottom, rather than the top, so the bits fill them in properly. This is how I found out that’s true…]
Coerce the bits into an unnatural union with an unsigned long int and create a variable:
union WWVB_code_ {
uint32_t WWVB_ul;
struct WWVB_bits_ WWVB_bits;
};
union WWVB_code_ ReceivedBits;
Then set a few bits to find out how the compiler arranges things:
Soooo, rats, the DST bits are on the left and the Minute bits are on the right. That doesn’t tell you how they’re actually laid out in memory, but if you’re doing the interrupt handler in C, then you just stuff the incoming MSB-first bits from the radio directly into the right side of the int and let them slide leftward.
If you count ’em up, you’ll find that the commented-out bits allow the remainder to fit into an unsigned long int, which is all of 32 bits on the Arduino. You can actually use an unsigned long long int to get 64 bits, but it seems Arduino bit fields can’t extend beyond 32 bits.
There are ways around that, particularly seeing as how I’m using a simpleminded interpreter to parse the incoming bits, but I’ll doodle about that later.
Insert the usual caveats about portability and interoperability and maintainability and … well, you know why bits fields are such a bad idea.
It goes without saying: the positive receiver output (TCO: Time Code Output) on the CMMR-6P-60 WWVB receiver board tracks the received RF amplitude. In fact, it does go without saying: nowhere does the (rather skimpy) board doc mention that fact.
Receiver Delay
Here’s the output, driven from my WWVB simulator in the Basement Lab. Note that reduced RF corresponds to the active part of the bit, so the output goes low when the bit starts. Conversely, the inverted output (TCON: Time Code Output Negative) goes high, which may be more useful for some purposes.
Of interest: there’s maybe 12 ms of delay on the leading edge and 5 ms on the trailing edge. The received pulse duration is therefore different than the transmitted pulse by some amount. The doc says less than ±35 ms, so it can be longer than what you expect.
I can’t measure the actual RF amplitude at the receiver, but it’s barely above the rather high ambient noise level near all the test equipment. The receiver gives up a few feet away from the simulator’s bar antenna, which means I’m (probably) not corrupting WWVB receivers for miles around.
The Official TI TLC5916 Datasheet is far more confusing on the subject of calculating the external current-setting resistor than it really needs to be.
The basic relations appear on page 17, but the detailed discussion on page 22 will make your head explode. Here’s how I see it …
VRext, the voltage across Rext, the reference resistor, is controlled by a 1.26 V band gap reference multiplied by VG, the voltage gain of a 7-bit DAC.
VRext = 1.26 V * VG
The DAC produces VG from the HC (High Current) bit and the six CC0..CC5 (Configuration Code register) bits. HC selects a range and CC0..CC5 are a binary multiplier.
VG = ((1 + HC) * (1 + CC/64) ) / 4
The minimum VG value for HC = 0 is 1/4 and the max is 1/4 + 63/256 = 127/256:
VG = (1 + CC/64)/4 = 1/4 + CC/256
The minimum VG value for HC = 1 is 1/2 and the max is 1/2 + 63/128 = 127/128:
VG = (1 + CC/64)/2 = 1/2 + CC/128
The absolute minimum VG = 0.25 happens when HG and CC0..5 are all zero. Conversely, absolute maximum VG = 0.992 happens when they’re all one.
That means VRext varies from
VRext (min) = 1.26 * 0.25 = 0.315 V
to
VRext (max) = 1.26 * 0.992 = 1.25 V
There’s a kink in the middle of that range, as shown in the graph on page 23, where HC shifts gears to the high range.
Rext then converts that voltage into Iref, the LED reference current:
Iref = VRext / Rext
The CG (Current Gain) multiplies Iref to set the actual LED current. The CM bit controls CG. You can either use the weird exponential formula in the datasheet or just remember:
CM = 0 selects CG = 5
CM = 1 selects CG = 15
Sooo, finally, Iout (LED current) is just:
Iout = CG * Iref
Knowing all that, you can choose Rext so that the absolute maximum current doesn’t exceed the LED rating:
Iout (max) = 20 mA = 15 * (1.25 V / Rext)
Which tells you that Rext must be no less than
Rext = (15 * 1.25 V) / (0.02 A) = 938 Ω
Then you can find the minimum current through the LEDs:
Iout (min) = 5 * (0.315 V / 938 Ω) = 1.68 mA
Now, given that the chip’s specs seem to indicate that the minimum regulated current is 5 mA, I suspect that all bets are off as regards linearity and matching and all that. Given that my friend wants a really dim clock, I think this will work out just fine…
Memo to Self: For whatever reason (and it must be a real stinker), TI numbers the CC bits backwards. CC0 is the Most Significant Bit, CC5 is the Least Significant Bit.
And because CC5 is the first bit shifted into the TLC5916’s shift register, you must run the Arduino hardware-assisted SPI in LSB-first mode.