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: Electronics Workbench

Electrical & Electronic gadgets

  • WWVB Receiver Shield Enclosure

    Kapton tape over traces
    Kapton tape over traces

    The little C-Max CMMR-6P-60 WWVB receiver board is somewhat sensitive to its surroundings: putting it too close to fast-switching digital signals is a Bad Idea. Of course, when there’s an antenna connected to the thing, it’s hard to separate the effects, but I’ve been testing reception with the antenna at the end of a two-foot twisted pair: far enough away to eliminate most problems.

    Just to see what happens, I built a little shield enclosure around the receiver board. The clock board has a pair of solid planes isolated from everything else, with a header matching the receiver’s pinout, for this very purpose. The receiver has a fairly solid ground plane on the bottom, but it’s quite sensitive being snuggled up against other objects; the header holds it about 5 mm above the circuit board.

    The dark amber square is Kapton tape across the traces. If I ever do this again, I’ll put the traces on the bottom so the board is entirely shielded and the tape isn’t needed.

    Shield soldered to base
    Shield soldered to base

    Some 1-inch copper tape with adhesive on one side serves as the shield enclosure, with a layer of Kapton tape covering all but about 2 mm of the adhesive near the bottom to insulate the copper from the receiver. Bent those 2 mm strips outward, with the adhesive on the bottom, stuck it to the shield plane, and soldered it in place at the corners.

    The antenna leads poke out through one side; it’s not very elegant, but I think it’s about as good as is needed for this sort of thing.

    I cut the tape at the corners and folded it down to make a little box, stuck a square of copper tape over the top flaps, soldered the corners, and it’s cute. Admittedly, it doesn’t have perfect conduction around the joints; the next time it’s on the bench I’ll add a few solder dots at the midpoints.

    Completed shield enclosure
    Completed shield enclosure

    The immediate effect was to raise the receiver’s Glitchiness score by a factor of about four. However, that’s not entirely a bad thing; it turns out that the reciever is much less Glitchy when it’s subject to high noise levels: the receiver AGC cranks the gain down so low that only heroic pulses get through and the number of glitches drops dramatically.

    As nearly as I can tell, when there’s no WWVB signal, as during the day, a low Glitchiness count means there’s extremely high noise. Thus, a higher count means less noise and better sensitivity.

    More data collection is in order, but the receiver’s LED showing data pulses now tracks the Alpha Geek Clock‘s display almost perfectly.

  • WWVB Antenna: Location, Location, Location

    Given that the wavelength of WWVB’s 60 kHz carrier is 5 kilometers, you’d think that the position of a receiver’s ferrite bar antenna isn’t all that critical. I’ve been running a receiver hitched to a laptop atop a file cabinet, with the bar antenna a few feet away atop an adjoining bookcase.

    Putting the antenna 12 cm over the top shelf surface (in a kludged cardboard holder to keep it off the wood), located on the left side of a mechanical mantel clock, produces these samples:

    
    Glitchiness:  276 Histogram: N!qdNMKE8B8A133111..12.....1.......................
    Glitchiness:  256 Histogram: O!iSMLLCC9A52215111121...1.........................
    Glitchiness:  243 Histogram: V!RfKOF88D977213212...2.3..2...1...................
    Glitchiness:  227 Histogram: NrfaQG6595AI66.222411.1..2.....11..................
    Glitchiness:  249 Histogram: S!eXNIFB76E5432.122.1.11111........................
    Glitchiness:  258 Histogram: SulWLKIIBB68433212113.1......1.....................
    Glitchiness:  119 Histogram: DWJG78753AEB52.711.....23.1.....1.....11.1.........
    Glitchiness:  159 Histogram: McULIE7835H72..1........332............23..........
    
    

    Moving it 40 cm to the other side of the clock, the next few consecutive samples look like this:

    
    Glitchiness:    4 Histogram: .21......3RD11..........332............223.........
    Glitchiness:    4 Histogram: 11..1....3SB3.........1.63.............51..........
    Glitchiness:    3 Histogram: .2.......CJC11..........251............34..........
    Glitchiness:    5 Histogram: 121......9KC3...........18.........1...15..........
    Glitchiness:    5 Histogram: .22......8KD3...........36.........1...33..........
    Glitchiness:   11 Histogram: 131....1.3W71...........451....1.......411........4
    Glitchiness:   21 Histogram: 351......6X231..1.....1.233....2....1..23.........6
    Glitchiness:    6 Histogram: .21..1...7OD1......1....23211..........33..........
    
    

    The first position is roughly equidistant from the apex of the corner, so the antenna is on the diagonal of a corner reflector made from the metalized aluminum foil of the exterior insulation.

    I’m not sure what to make of this, other than that location is everything.

    More on the histogram format there.

    Memo to self: maybe display the Glitchiness on the clock before the first sync to help find a good position?

  • Arduino Pro: Ceramic Resonator Frequency Compensation

    The Arduino Pro gets its 16-MHz CPU clock from a ceramic resonator, rather than a quartz crystal, which means the frequency accuracy is ±0.5% rather than pretty much spot on. I’m building one into a WWVB-based clock, so it knows the exact elapsed time between synch events.

    My clock uses a 20-ms timebase: 16 MHz prescaled by 8, then divided by (nominally) 40000 using Timer1.

    Knowing the exact time between WWVB updates, the firmware compares that with the local time interval to find the offset, finds the fractional error, and then tweaks the Timer1 period to make the answer come out right the next time.

    Here’s what three days in the life of that algorithm look like:

    Drift: TS   5268489 UTC 10006.040959 Elapsed 13920 Offset 0 Corr +0 ICR1 39840
    Drift: TS   5268805 UTC 10006.092559 Elapsed 18960 Offset 1 Corr +2 ICR1 39842
    Drift: TS   5269711 UTC 10007.003159 Elapsed 54360 Offset 0 Corr +0 ICR1 39842
    Drift: TS   5269966 UTC 10007.044659 Elapsed 15300 Offset 0 Corr +0 ICR1 39842
    Drift: TS   5270079 UTC 10007.063959 Elapsed  4920 Offset -1 Corr -8 ICR1 39834
    Drift: TS   5271157 UTC 10008.003759 Elapsed 61440 Offset 12 Corr +7 ICR1 39841
    Drift: TS   5271833 UTC 10008.115359 Elapsed 39780 Offset 1 Corr +1 ICR1 39842
    

    The UTC field is YYDDD.HHMMSS. The TS value is a simple monotonic timestamp: UTC brutally converted to minutes assuming a year is 365.25 days.

    I set ICR1 to 39840 when the program starts, having already determined the actual oscillator frequency for this particular Arduino Pro. That’s not necessary, because the firmware will adjust it automatically, but it does eliminate the first big step that would compensate the resonator’s -0.4% initial frequency error.

    As nearly as I can tell, the corrections are tracking room temperature changes, as it’s been really cold around here lately and the clock is atop a bookcase in an outside corner of the room.

    After the first +2 change, it ran for 19 hours with less than one second of error: 14 ppm. The -8 change was probably an overcorrection, as the synch interval was just over an hour, but so it goes. That caused 195 ppm error over the next 17 hours, then it’s back on track.

    There’s an obvious conflict between getting quick updates as conditions change and minimizing long-term free-run drift. The firmware currently insists on a minimum of 60 minutes between synchs, but (given an initial preset) I think I can dramatically increase that without losing anything.

    This code does the Timer1 setup:

    #define TIMER1COUNTS            39841l
    
    TCCR1B    = B00011000;            // Timer1: CTC mode = 12 high bits, TOP=ICR1, stopped with no clock source
    TCNT1 = 0;            // force count to start from scratch, CTC mode low bits
    TCCR1A = 0;            // no compare outputs to OC1A OC1B, WGM1 1:0 = 00
    TCCR1C = 0;            // no forced compares
    TIMSK1 = 1 << ICIE1;            // allow interrupt on capture event (TCNT == ICF)
    SetICR1(TIMER1COUNTS - 1);            // total counts - 1, start running
    

    The SetICR1 function makes sure the new ICR1 isn’t below the current TCNT1 value, which would cause a horrible timekeeping blip. As it is, there’s a microsecond (more or less) glitch during the update.

    
    void SetICR1(word NewICR1) {
    TCCR1B &= ~B00000111;     // turn off Timer1 by removing the clock source
    ICR1 = NewICR1;
     if (TCNT1 > NewICR1) {     // force counter below new TOP value
     TCNT1 = NewICR1 - 1;
     }
    TCCR1B |= B00000010;     // turn on clock with prescaler
    }
    

    When the firmware does a WWVB synch, it then checks to see if enough time has passed since the last synch and, if so, tweaks ICR1. The variables hold what you’d expect and are all long ints to hold the expected values…

    if ((UTCRightNow.SyncAge != SYNC_UNSYNC) && (UTCRightNow.SyncAge > SYNC_MINDRIFT)) {
     WWVB_Elapsed = 60l * (WWVBToMinutes(&WWVB_Time_Predicted) - WWVBToMinutes(&WWVB_Time_Sync));
     TimeOffset = (60l * (long int)(UTCRightNow.SyncAge - 1)) + (long int)UTCRightNow.Second - WWVB_Elapsed;
     DriftTicks = (int)((FetchICR1() * TimeOffset) / WWVB_Elapsed);
     if (DriftTicks) {
      SetICR1(FetchICR1() + DriftTicks);
     }
    }
    

    The FetchICR1 function reads ICR1 without disabling interrupts, doing it twice to be sure nothing’s whacked the magic hardware that allows atomic two-byte register reads.

    One failure mode: if something goes badly wrong, ICR1 can become so far off the correct value that the clock will never synch again. I must add a bit of defensive code to SetICR1 that ensures the new value is never more than, say, 1% off the nominal value.

    All in all, this works a whole lot better than I expected…

    The catch is that most Arduino applications don’t know the exact time interval and, without that, there’s no way to tweak the oscillator on an ongoing basis. However, for any particular Arduino Pro, I think you could very accurately compensate the initial frequency error by measuring the actual oscillator frequency and then hardcoding the adjustment value.

  • Hobo Data Logger Battery Life

    Another data point…

    I just replaced an Energizer lithium cell that I installed on 19 March 2008. The logger runs full-time, taking data points every few minutes.

    That’s nigh onto two years of life!

    I must conclude the battery life problems mentioned there (admittedly, in a different logger) were due to craptastic Renata cells, rather than the Hobo logger itself.

    Lesson learned…

  • Thinkpad 560Z Configuration

    Turns out the ancient $20 Thinkpad 560Z I’d been using to capture WWVB receiver data didn’t have the IBM configuration utility on it, which made it tough to tweak the LCD timeout. The key parameter for this laptop is that it runs at about 8 W with the LCD turned off, which is just what you want for long-term data collection.

    The thing runs from a 2 GB CompactFlash card stuck in a CF-to-IDE adapter, so it has a (rather slow) solid-state hard drive. The nice part is being able to just jam the CF into the card reader on my desktop box, make appropriate changes, and pop it back in the 560Z.

    Xubuntu automagically mounts all the partitions, so that part is easy. It has a FreeDOS partition that runs the DOS-only config program, a swap partition (not heavily used), and an Ubuntu 8.04 command-line-only installation.

    The IBM config stuff is in a directory on a hard-drive image (saved from the picture frame project), so mount that and copy it over:

    sudo mount -o loop,uid=ed,ro,offset=$((63*512)) develop.hd /mnt/loop
    cp -a /mnt/loop/ThinkPad/ /media/FreeDOS/
    

    While figuring out what to change, it occurred to me that I should just make a batch file with all the proper settings. Here’s a cheat sheet for the available settings:

    Refer to ps2.msg for raw help file
     some commands/options do not apply to 560Z
    
    1. Power Management
    DEFAULT     suspend time, screen off, HD off, standby time, proc speed
    DISK        power-down timeout
    SAfe        safe suspend
    S2H         suspend to hibernate time
    PMode       power mode
    ON          auto-on date/time from suspend
    RI          resume on Ring Detect from serial port
    HSWITCH     hibernate on power off
    SErial      serial port enable
    HFile       create hibernation file
    HTimer      time to hibernate
    CPUPower    stop clock when idle
    POwer       time to suspend
    Cover       suspend with closed cover
    TImer       Power command = suspend or hibernate
    PCIBUSPower PCI power saving
    LCd         time to display power off
    DOCK        suspend when docked
    LBattery    suspend / hibernate with low battery
    SPeed       CPU speed selection
    
    2. Display Device
    SCreen      select LCD / CRT
    HVEXPansion expand 640x480 to 800x600
    F8          F8 selects LCD expansion
    
    3. Alarm Related
    BEEP        beep settings
    
    4. Thinkpad Setup
    IRQ         interrupt assignments
    JStick      joystick config
    PCIIRQ      PCI IRQ assignment
    DMA         DMA channel assignment
    PARallel    parallel port config
    IR          IR port config
    KRate       keyboard repeat rate
    SERA        serial port config
    AUdio       sound system config
    FNSticky    sticky Fn key
    STARTup     display startup screen
    MIDIport    MIDI config
    TPOint      Trackpoint enable / disable
    PRESENtation  disable screen, standby, suspend
    AUDIOCTRL   audio control port config
    
    5. Others
    SUSpend     suspend NOW
    FDD         diskette drive int / ext
    OFF         turn off NOW
    HIBernation hibernate NOW
    TURN        turn off NOW
    BRightness  LCD brightness on battery
    

    And then the batch file:

    ps2 default
    ps2 disk 0 ac
    ps2 pmode custom ac
    ps2 serial off
    ps2 hfile c
    ps2 htimer 0 ac
    ps2 power 0 ac
    ps2 cover disable
    ps2 lcd 5 ac
    ps2 speed auto medium ac
    ps2 hvexpansion off
    ps2 jstick disable
    ps2 parallel disable
    ps2 ir disable
    ps2 krate fast
    ps2 sera disable
    ps2 audio disable
    ps2 midi disable
    ps2 audioctrl disable
    

    The time-of-day clock drifts with breathtaking speed, which may have something to do with the CPUPower option that shuts the processor clock off when there’s nothing useful going on.

  • Character-string Histogram

    I’m in the midst of counting glitches on a WWVB receiver, a glitch being any pulse with a duration that isn’t close to 200, 500, or 800 ms. It’s useful to know the pulse width distribution, which is what a histogram does for a living.

    Rather than a graphic histogram, though, I’ve been using a character array (a string!) with one character for each possible pulse duration, measured in units of 20 ms. The value of each character indicates the number of pulses having that duration during the previous minute.

    The counting sequence goes like this:

    • No counts: . (a period)
    • 1 to 9 counts: 1 .. 9
    • 10 to 35 counts: A .. Z
    • 36 to 61 counts: a .. z
    • More than 61 counts: !

    So a minute with no glitches (and a valid WWVB time code frame) looks like this:

    .........4S9............462............322.........
    

    The three clusters show the three valid pulse widths. The pulse widths from the receiver have an inherent jitter, plus the usual ±1 jitter you get for free when you digitize something, plus (this being RF from half a continent away) whatever the Lords of Cosmic Jest do to the signal. So each width usually occupies two or three cells.

    • The 200 ms binary zero pulses form the cluster on the left: that “S” is equivalent to 10 + 18 = 28 counts. Add in the 4 and 9 on either side and you get 41 binary zero pulses.
    • The middle cluster has the 500 ms binary 1 pulses: 4 + 6 + 2 = 12.
    • Each WWVB time code frame has exactly seven 800 ms frame markers, which form the cluster on the right end: 3+2+2 = 7.

    Add them up: 41 + 12 + 7 = 60. That’s exactly the right number of pulses in a minute. What a pleasant surprise!

    A minute with three out-of-spec glitches looks like this:

    1...1....6NC2............82.......1.....51.........
    

    And a minute with very high noise that pretty much obliterates the WWVB signal:

    f!!jHC6AB746312.2121..2.1..........................
    

    Here’s how all that works…

    The histogram counters form a character array that’s also a string. There are 50 20-ms Jiffies in each second (given by the obvious constant), so the histogram has 52 entries: 50 valid counts (0-49), 1 for “more than that”, and 1 for the null byte at the end of the string.

    char    Durations[JIFFIES_PER_SECOND + 2];
    

    Initialize each counter (character) with the starting value and jam a binary zero at the end:

    memset(Durations,'.',sizeof(Durations)-1);
    Durations[sizeof(Durations) - 1] = 0;
    

    And then tick the appropriate counter as each pulse arrives:

    Index = min(PWM_Width,sizeof(Durations)-2);
    
    switch (Durations[Index]) {
     case '.' :
     Durations[Index] = '1';
     break;
     case '9' :
     Durations[Index] = 'A';
     break;
     case 'Z' :
     Durations[Index] = 'a';
     break;
     case 'z' :
     Durations[Index] = '!';
     break;
     case '!' :
     break;
     default :
     Durations[Index]++;
    }
    

    The switch statement maneuvers the counting sequence through the digits, uppercase and lowercase alphabet, then enforces a stall at the maximum count value of “!”. You can’t just increment each element without some checking, because you do not want unprintable control characters in the string.

    Then you print the histogram as you would any ordinary string. If you’re using an Arduino, as I am, this will suffice:

    Serial.println(Durations);
    

    All this depends on the ASCII character set’s numerical sequence. Ponder the charts there and all should become clear.

    Here are the histograms from an hour of WWVB reception during the late afternoon of New Year’s Day: watch the noise floor rise up and eat the WWVB signal…

    
    .........7OD............36............133..........
     .........BID1...........37.............6.1.........
     .1.......9O91..........262.............43..........
     ........27V2............272............421.........
     .........4Y41...........46............124.........2
     .........AP71...........2811..........123.........2
     .........AN9............731...........1.6..........
     12.111..28IB11.........1623.......2..1.42.....2....
     1412...125Q911..........461.....1......41..........
     1........8Q81..........137...1.........15..........
     .12....119N912.........116..........1.132..........
     1521..1.17O931...1......27...........1..5..........
     .12....12BKB2...........332....1......132.......2..
     25211.1.3AHB5......1...1143........1...14.........3
     45412...4BDA4..12.......25111..1.....1..22.........
     6A44322.26CI53.2.1...1..343...1.......1.11.........
     5D75531113FG511...12..212313.1........1.2..1.......
     1432.1.119GB41..........262.1...11.....14..........
     .5211..115MB4...........441............14.2........
     .4.1....25JB6...........442.........1..231.........
     6B443...27I772..........2322.....1.....1221......2.
     4555411.27HA321........3232......1....114..........
     3232.1.129GI1...1.......4321...1.1.1...111.........
     ...1..1.19O8..1....11...262............24..........
     11......2AN62...........622...........124..........
     111.....2BJ551..1......1522........3..141........21
     1421....29N611.......1.136............1231........2
     1.12.....AIB3........1..38...1........312..........
     7F42..1.28FE341.1.1.....351........1...12...1.....2
     .23......8GG2....11....2423............33..........
     112..13.1AK931.....2....142....2.11.1..3.1........2
     423111..44JD4.2..1.....1433........11111...........
     1413..1.3CL812..11......242..1....11...21.........2
     474112.34AI84.2...3.1...16.2.....1...1.11..........
     8LHC734657D857411..11...131......1..1.1...........2
     JuSFDCCDDCD662412...1..............................
     VmG376788AC8671.21.1..3.......1....................
     IlCB4576CFE532131.11.1..1.1........................
     KxN798EB98A7422...2.1...1..........................
     AYL96853CF7742121311.13..21....1...................
     8TDEB6649A7952..32.41..124.....1.....1............2
     DeO78638C9GA6142.15111..........1........2.....2...
     AcPC83426BD4823.122..11132.1.......................
     7SF31121AHFB73..2.111...321..1.1...................
     7F3221414GG5411.111121.133...1.1.1.................
     6RD9669647F7361.31.2..1212...1.....................
     6L71.2444ALB21...221.212321..2...1...1.............
     4LHFD7638C9B12.31341.22113...2..........1..........
     264321112EFA5.112.22.2.14111..1.....1...1.........2
     235511445BEC3.12..1131.131..1.1........11..........
     7UC741635AAC442.1..112.1..11311.......1.1.........2
     EVLCAB3598E752252133.111.1.1..........1...........2
     JnTE9913A59A452331.13..1222.............1..........
     f!!jHC6AB746312.2121..2.1..........................
     !!!iMB772.4532..1.1.1.1..1.........................
    
  • Arduino Serial Optical Isolator

    Optical Isolator - oops
    Optical Isolator – oops

    It turns out that attaching some, but not all, of the PCs around here to the Arduino Pro board controlling the Totally Featureless Clock cause the WWVB receiver to drown in a sea of noise. In fact, just touching the USB cable’s shield to the FTDI Basic USB-to-serial adapter would bring the noise.

    So this is a quick-and-dirty circuit to see if optical isolation will reduce the problem enough to be bearable.

    The schematic is pretty simple: two bits in, two bits out.

    Optical Isolate Schematic
    Optical Isolate Schematic

    The layout puts the DIP isolators on the top and the SMD resistors on the bottom. I used fancy screw-machine IC socket pins, just because I had some, but you could solder the isolators directly to the board. The FTDI Basic connects through header pins and the Arduino connects through female header sockets, both soldered sideways to the top of the board. I’ll eventually reinforce them with some epoxy, never fear.

    Double-size PCB layout:

    PCB Layout
    PCB Layout

    Actual-size copper images. Remember that the top copper is flipped left-to-right here so it comes out properly after toner-transfer imaging.

    Copper
    Copper

    And the placement info showing where the parts wind up. This is sort of the silkscreen for the top and bottom, both together: the backwards stuff goes on the bottom side.

    Top and Bottom Silkscreen
    Top and Bottom Silkscreen

    The alert reader will note that the photo doesn’t match the rest of the images. Nay, verily, eagle-eyed readers will have picked out a few resistors on the top and two embarrassing little red-wire Xes at the connectors. Somehow, I managed to swap the RxD and TxD pins, even with an FTDI board on the desk next to me. I hate it when that happens… so I fixed the schematic & layout for the next time around.

    The resistors push a lot of current through the LEDs and phototransistors, which is what you need to get decent 19200 b/s serial data pulses. Here’s what the data stream out of the TxD isolator looks like:

    Optoisolator - TXD
    Optoisolator – TXD

    I have the Eagle files and the CNC drill file for my Sherline mill if you must have them, but you can go from those images above directly to the hardware. It’s an evening’s work, more or less.

    You might want to kludge a jumper into the Reset line so it’s impossible to accidentally reset the Arduino. Sometimes you don’t want a reset, like after a few days of data collection…

    Now, does it actually do what I expected? The early reports are good, but I’m at the mercy of the atmosphere and must collect a few days (actually, nights) worth of data to find just how far down the noise went.