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

  • WWVB Reception Quality

    Here on the East Coast of the US, WWVB reception is iffy during the day, due to low signal strength and high ambient noise. Actual data seems hard to come by, so here’s a small contribution.

    This is a plot of the number of glitches per minute, where a glitch is any pulse that’s not within ±60 ms of the expected pulse durations (200, 500, and 800 ms), for a 24-hour period starting at UTC 0257 on 25 Dec 2009 (9:57 EST Christmas Eve 2009). There are 1448 data points, each representing the glitches during the previous minute; each minute starts within 2 seconds of the WWVB on-time frame marker.

    Here’s the raw data, log-scaled on the Y axis to cover the dynamic range. Log scaling can’t handle 0-valued points, so I forced counts of 0 to 0.1 to make them visible.

    Glitchiness - raw - 24 hrs - 2009-12-24
    Glitchiness – raw – 24 hrs – 2009-12-24

    Here’s the same data, Bezier smoothed to make the trends more obvious;  all the points below 1.0 are approximations of a trend toward counts of 0.

    Glitchiness - smoothed - 24 hrs - 2009-12-24
    Glitchiness – smoothed – 24 hrs – 2009-12-24

    Even better, splines show the glitch-free minutes without forcing the data points.

    Glitchiness - splines - 24 hrs - 2009-12-24
    Glitchiness – splines – 24 hrs – 2009-12-24

    My firmware requires four successive glitch-free minutes of reception (plus some additional verification) before synching its local time to WWVB, so it’s exceedingly fussy. Despite that, it still synched 17 times during those 24 hours. The longest free-running time between synchs was 6.8 hours.

    Note that there are 17 downward peaks below 1.0 in that last graph.

    Winter is, of course, the time of best ground-wave propagation from WWVB, so this is about as good as it’s ever going to get.

    Memo to Self: useful Bash and Gnuplot commands…

    
    grep Glitch WWVB_2009-12-24a.log | cut -d H -f 1 > Glitches.txt
    
    set logscale y
    
    set samples 250
    
    plot 'Glitches.txt' using ($2<1?0.1:$2) with points lt 3 pt 2
    
    plot 'Glitches.txt' using 2 smooth csplines with linespoints lt 3 pt 0
    
    
    log-scaled on the Y axis to cover the dynamic range. Log scaling can’t handle 0-valued points, so I forced them to 0.1;
  • HP54602 Oscilloscope Trace Conversion Tweakage

    The script (writeups there and there) I use to convert the HPGL screen dumps from my HP54602 into PNG images produced a transparent background. I put the files into an OpenOffice mockup of my Circuit Cellar columns and the background turns white, so I figured it worked OK.

    Turns out that the workflow at Circuit Cellar Galactic HQ turns the background black. A bit of digging showed that the ImageMagick convert program produced an alpha channel that selected only the traces and left everything else unselected. Why that produces white here and black there is a mystery, but there’s no point in putting up with such nonsense.

    Another wrestling match produced this revision (the two changed lines are highlighted), which has no alpha channel and a white background. That ought to simplify things: an image shouldn’t depend on where it’s dropped to look right.

    #!/usr/bin/kermit +
    # Fetches screen shot from HP54602B oscilloscope
    # 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
    
    # Wait for PRINT button to send the plot
    echo Set HP54602B for HP Plotter, FACTORS ON, 19200, DTR
    echo Press PRINT SCREEN button on HP54602B...
    
    log session "\%1.hgl"
    
    # Factors On
    input 480 \x03
    
    close session
    close
    
    echo Converting HPGL in
    echo --\%1.hgl
    echo to PNG in
    echo --\%1.png
    
    # Factors Off
    #run hp2xx -q -m png -a 1.762 -h 91 -c 14 "\%1.hgl"
    #run mogrify -density 300 -resize 200% "\%1.png"
    
    # Factors On
    run sed '/lb/!d' "\%1.hgl" > "\%1-1.hgl"
    run hp2xx -q -m eps -r 270 -a 0.447 -c 14 -f "\%1.eps" "\%1-1.hgl"
    run rm "\%1-1.hgl"
    run convert "\%1.eps" -alpha off -resize 675x452 "\%1.png"
    
    echo Finished!
    
    exit 0
    
  • TLC5916 Configuration Code Setting

    TLC5916 Writing Config Code
    TLC5916 Writing Config Code

    The TLC5916 data sheet clearly shows that you write the configuration code (which controls the LED current) by shifting seven bits in, then raising LE during the 8th SCK pulse while simultaneously shifting the 8th bit.

    That makes no sense whatsoever: you couldn’t use standard SPI hardware in a chained configuration, because you’d have to blip LE while shifting.

    In fact, the chip doesn’t work that way. You set the config code in Special Mode just like you set the LED driver bits in Normal Mode: shift ’em all in, then blip LE to latch ’em into the parallel holding register.

    Here’s the code to make it happen…

    DisableSPI();                               // manual SPI control
    
     digitalWrite(PIN_DISABLE_DISPLAY,HIGH);    // initial condition
     digitalWrite(PIN_LATCH_DO,LOW);
    
     PulsePin(PIN_SCK);                         // 1
     digitalWrite(PIN_DISABLE_DISPLAY,LOW);
     PulsePin(PIN_SCK);                         // 2
     digitalWrite(PIN_DISABLE_DISPLAY,HIGH);
     PulsePin(PIN_SCK);                         // 3
     digitalWrite(PIN_LATCH_DO,HIGH);           //   sets Special Mode
     PulsePin(PIN_SCK);                         // 4
     digitalWrite(PIN_LATCH_DO,LOW);
     PulsePin(PIN_SCK);                         // 5
    
    //-- Send brightness level
    
     EnableSPI();                               // turn on SPI hardware
    
     SendRecSPI(Brightness);
     SendRecSPI(Brightness);
     SendRecSPI(Brightness);
     SendRecSPI(Brightness);
     SendRecSPI(Brightness);
    
     PulsePin(PIN_LATCH_DO);                    // latch new shift reg contents into drivers
    
    //-- put LED drivers back in Normal Mode
    
     DisableSPI();
    
     digitalWrite(PIN_DISABLE_DISPLAY,HIGH);     // initial condition
     digitalWrite(PIN_LATCH_DO,LOW);
    
     PulsePin(PIN_SCK);                          // 1
     digitalWrite(PIN_DISABLE_DISPLAY,LOW);
     PulsePin(PIN_SCK);                          // 2
     digitalWrite(PIN_DISABLE_DISPLAY,HIGH);
     PulsePin(PIN_SCK);                          // 3
     digitalWrite(PIN_LATCH_DO,LOW);             //   sets Normal Mode
     PulsePin(PIN_SCK);                          // 4
     digitalWrite(PIN_LATCH_DO,LOW);
     PulsePin(PIN_SCK);                          // 5
    
     digitalWrite(PIN_DISABLE_DISPLAY,LOW);      // turn the LEDs on again
    

    The SendRecSPI() function does exactly what you’d expect:

    byte SendRecSPI(byte Dbyte) {                // send one byte, get another in exchange
    
     SPDR = Dbyte;                      // assume it's OK to send a new byte
    
     while (! (SPSR & (1 << SPIF))) {   // wait for shift to finish
      continue;
     }
    
     return SPDR;                       // SPIF will be cleared
    }
    

    I don’t know. Maybe the chip also works the way they show in the datasheet, but I doubt it’s worth finding out.

  • TLC5916 LED Driver Current Monotonicity Hack

    TLC5916 Current Gain vs Config Code
    TLC5916 Current Gain vs Config Code

    I’m using Texas Instruments TLC5916 constant-current LED driver chips for my my friend’s Totally Featureless Clock. An 8-bit configuration value sets the output current, with the external resistor defining the maximum value as described there.

    The problem is that the current-versus-config-value curve has a non-monotonic discontinuity in the middle, where the Current Multiplier bit switches from 0 to 1. I don’t know in which alternate universe this design decision made sense, but right here and now…

    It. Does. Not.

    Why it’s a problem: the LED brightness tracks room illumination as seen by a CdS photoresistor, averaged over maybe half a minute. The brightness changes very slowly, so the jump when it goes from 0x7f to 0x80 is really eyecatching. At least to me, anyway.

    Eyeballometric measurement of the curve shows the current at 0x80 pretty much matches the current at 0x60, soooo let’s just shove the second quarter of the curve (between 0x40 and 0x7f) downward until it meets the high-current value at 0x80.

    This code does the trick:

    if ((Brightness >= 0x40) && (Brightness <= 0x7f)) {
     Brightness = 0x40 + ((Brightness & 0x3f) >> 1);
    }
    

    Basically, that maps 0x7f to 0x5f. The output current for 0x5f is pretty close to the current for 0x80, making the step pretty much a non-issue.

    You could, if you were fussy enough, work out the actual current mapping values from the data sheet equations and make the ends match up perfectly.

  • Arduino: Slave Select Pin MUST Be An Output in Master Mode

    I’m using hardware-assisted SPI for a project, copied in my own boilerplate code, assigned the bits, and… it didn’t work.

    Jammed hard with mysterious symptoms. Looked like a stack crash, looked like the hardware was broken, looked like a lot of things.

    The final hint, found by stuffing Serial.print() statements in all the usual spots, was that the SPCR register mysteriously changed from the desired 0x71 to 0x61, without any of my code doing the writing.

    Turns out that the Fine Manual has this to say:

    Bit 4 – MSTR: Master/Slave Select

    [snippage] If -SS is configured as an input and is driven low while MSTR is set, MSTR will be cleared, and SPIF in SPSR will become set. The user will then have to set MSTR to re-enable SPI Master mode.

    I planned to use Arduino Pin 10 (PWM10) as the signal to latch the output shift registers, but because I’m developing the code on an Arduino Pro before making the circuit board, I hadn’t gotten around to initializing that pin… which, as you might expect, is also the -SS pin.

    With the pin not set up as an output, it defaults to an input. My cut-n-paste code blipped the pin high and left it low to simulate latching the ‘595 shift registers… but, for input pins, writing a value simulates what would happen when an external signal drives the pin.

    Soooo, I had inadvertently set -SS low, which turned off Master mode, which meant the hardware wasn’t going to send the next byte, which means SPIF wasn’t going to automatically go high when I dropped a byte in SPDR. The code, of course, waited until SPIF was clear before loading SPDR, then hung waiting for it to go high again.

    As always, stupid errors are easy to fix after figuring things out, but ouch did it take a while…

    Moral of the story: always initialize all the I/O pins! (But you knew that, right?)

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