The Smell of Molten Projects in the Morning

Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.

Category: Software

General-purpose computers doing something specific

  • 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)

    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…

  • Arduino: Bit Fields Start Low

    The first bit in an Arduino bit field is the lowest-order bit.

    Set up a bit field to hold, oh, say, the data bits from the WWVB radio time code:

    struct WWVB_bits_ {
     unsigned char Minute_10:3;
     unsigned char Minute_1:4;
     unsigned char Hour_10:2;
     unsigned char Hour_1:4;
     unsigned char DOY_100:2;
     unsigned char DOY_10:4;
     unsigned char DOY_1:4;
    // unsigned char UT1_SIGN:3;
    // unsigned char UT1_CORR:4;
    // unsigned char Year_10:4;
    // unsigned char Year_1:4;
     unsigned char LY:1;
     unsigned char LS:1;
     unsigned char DST57:1;
     unsigned char DST58:1;
    };
    

    [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:

     ReceivedBits.WWVB_bits.DST57 = 1;
     ReceivedBits.WWVB_bits.DST58 = 1;
     ReceivedBits.WWVB_bits.Hour_1 = 9;
     ReceivedBits.WWVB_bits.Minute_1 = 4;
    
     sprintf(PrintBuffer,"Bit field: %08lX",ReceivedBits.WWVB_ul);
     Serial.println(PrintBuffer);
    

    Which produces this informative line:

    Bit field: 03001220
    
    

    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.

  • Arduino: Remember the Ampersand with PROGMEM Variables!

    Got burned by this one again…

    The pgm_read_whatever() functions/macros require the address of the variable stored in PROGMEM (the program Flash ROM), not the variable’s value. More on that in the Arduino doc there.

    So, for example, this does not work, no matter how easy it might be to type:

    #include <avr/pgmspace.h>
    #define TZ_INDEX     3     // hardcoded for simplicity
    PROGMEM prog_char TimeZone[8] = {0,-3,-4,-5,-6,-7,-9,-10};
    char TZOffset;
    TZOffset = pgm_read_byte(TimeZone[TZ_INDEX]);
    

    What you really want looks like this:

    TZOffset = pgm_read_byte(&TimeZone[TZ_INDEX]);
    

    See the difference?

    Maybe next time I’ll remember that…

  • Source Code Reformatting

    I just figured out how to use the WordPress “sourcecode” formatting and applied it to my software-related posts. It produces much nicer results than the manual formatting I was using, mostly by preventing long lines from jamming into the right column.

    The catch: WordPress imposes a round trip from my original text to the screen encoding and back, which sometimes randomly mangles special symbols. Angle brackets and double-quotes, in particular, take serious damage.

    If you happen to remember a favorite chunk of code in a previous post, please take a look at it and see if I missed any of the obvious text-replacement errors. Trawling through the Software category should turn up most of the posts.

    As is always the case with program listings, the errors will be really obvious to everyone except me.

    Thanks…

  • SPICE Crystal Model

    Linear Technology’s LTSpice generic capacitor model has all the parts you need to synthesize a crystal, which is pointed out in the help file and various spots around the web. What’s missing is the relation between all the parts and the values you have in hand for an arbitrary crystal.

    SPICE Capacitor Model
    SPICE Capacitor Model

    The crystal capacitor model looks like this…

    Cpar (usually C0) along the right edge is the inter-electrode capacitance, on the order of a few pF.

    Rpar (usually R0) along the left edge is the parasitic resistance across the case, on the order of hundreds of MΩ.

    The RCL string in the middle is the “motional” part of the crystal model, generally found with a subscript “m” in the specs.

    • Rser (Rm or ESR) is on the order of 100 Ω
    • Capacitance (Cm) is the motional capacitance, on the order of fF (that’s femtofarad: 10-15)
    • Lser (Lm) is tens to thousands of mH
    • RLshunt is something I haven’t seen in any other model and, in fact, it doesn’t appear in the properties panel.
    Crystal Properties
    Crystal Properties

    Now, the part I screwed up is that the capacitor’s value (the number appearing on the schematic) is Capacitance (in the angle brackets that royally screw up WordPress HTML), not Cpar. So the crystal capacitor properties panel looks like this…

    That models a 10 MHz crystal, taken directly from a sidebar in Refinements in Crystal Ladder Filter Design by the legendary Wes Hayward W7ZOI, in the June 1995 issue of QEX.

    Guess what? Plug it into a model of his crystal-measuring circuit and it works exactly like he says it should. No surprise there…

    SPICE has a bit of trouble simulating high-Q oscillators; they tend to not start up properly. If nothing seems to be happening, wait for a few tens-to-hundreds of milliseconds before despairing. Try chopping Rser down by a factor of two or four to see if that improves its disposition.

    You could try injecting a few (hundred thousand) cycles of a kickstart signal, but that’s fraught with peril: you’re simulating something even further from reality than usual.

    Memo to Self: You can rename the cap from C2 (or whatever) to X1 (or whatever) and everything still works fine.

  • Why Friends Don’t Let Friends Use Windows For Embedded Systems

    OK, this is shooting the low-hanging fish right off the barrel (or some such mixed metaphor), but why does anybody still use Internet Explorer and Windows for embedded systems?

    cimg4138 - IE Was Unable to Link to Web PageThe proximate cause is a dead Internet link, but somebody obviously didn’t take that problem into account during the design phase. I’m sure there’s a keyboard hidden inside the box, wherever the box might be, but the rest of us are left to snicker at a jammed display.

    The problem resolved itself (or somebody plugged in the cable) by the time we walked past the display again.

  • HP8591E Spectrum Analyzer: Capturing Screen Images Therefrom

    While I’m thinking about instrument screen shots, this is the script for my Hewlett-Packard 8591E Spectrum Analyzer.

    You’ll need C-Kermit, ImageMagick, and hp2xx for this one, too.

    The cable must cross-connect RTS/CTS for hardware flow control.

    Set the spectrum analyzer to

    • print-to-plotter
    • 19200 b/s (limited by USB-to-RS-232 converter)
    • One plot per page

    Turning menus off doesn’t seem to have any effect on screen captures from the serial port, so the script crops off that part of the image.

    Copy-n-paste the following text into a file (gethp8591), make it executable (chmod u+x gethp8591), and run it with a file name (./gethp8591 test).

    Unlike the (well, my) 54602 ‘scope, the 8591 responds to serial commands just like the Fine Manual says. So before you run this script, make sure the screen shows what you want.

    #!/usr/bin/kermit +
    # Fetches screen shot from HP8591E spectrum analyzer
    # Presumes it's set up for plotter output...
    # Converts HPGL to PNG image
    
    set modem none
    set line /dev/ttyUSB0
    set speed 19200
    set flow rts/cts
    set carrier-watch off
    
    # Make sure we have a param
    if not defined \%1 ask \%1 {File name? }
    
    set input echo off
    set input buffer-length 200000
    
    # Tell it what size to plot
    echo Triggering plot output...
    output plot 0,0,60000,40000;
    
    log session "\%1.hgl"
    
    # Wait for end of data stream
    input 400 SP;
    
    echo ... HPGL data captured
    
    close session
    close
    
    echo Converting HPGL in
    echo --\%1.hgl
    echo to PNG in
    echo --\%1.png
    
    run hp2xx -m png -c 143 "\%1.hgl"
    
    echo Cropping and resizing
    run mogrify -crop "515x395+0+0!" "\%1.png"
    run mogrify -density 300 -resize 200% "\%1.png"
    
    echo Finished!
    
    exit 0
    

    Here’s a picture of the FM broadcast band, as seen from the Basement Laboratory. The marker looks a bit off from 104.7 MHz, but that’s a combination of broad span and skinny peaks.

    Spectrum Analyzer Screen Capture
    Spectrum Analyzer Screen Capture