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

  • Crystal Parameter Measurement Musings

    In order to probe a crystal’s response with decent resolution, I need a gadget to step a decent-quality sine wave by 0.01 Hz across the 10-to-100 kHz range and a logarithmic front end with a decent dynamic range. That’s prompted by looking at crystal responses through the SA’s 30 Hz resolution bandwidth:

    Quartz Resonator 32.765 kHz - 34.6 pF
    Quartz Resonator 32.765 kHz – 34.6 pF

    Mashing a cheap AD9850/AD9851 DDS board against an Arduino Pro Mini, adding a knob, and topping with a small display might be useful. A Raspberry Pi could dump the response data directly into a file via WiFi, which may be more complication that seems warranted.

    The DDS boards come with absurdly high-speed clock generators of dubious stability; a slower clock might be better. A local 10 MHz oscillator, calibrated against the 10 MHz output of the HP 3801 GPS stabilized receiver would be useful. If the local oscillator is stable enough, a calibration adjustment might suffice: dial for 10 MHz out, then zero-beat with the GPS reference, so that the indicated frequency would be dead on to a fraction of 1 Hz.

    The HP 8591 spectrum analyzer has a better-quality RF front end than I can possibly build (or imagine!), but, at these low frequencies, a simple RF peak detector and log amp based on the ADL5303 or ADL5306 should get close enough. One can get AD8302 / AD8310 chips on boards from the usual low-budget suppliers; a fully connectorized AD8310 board may be a good starting point, as it’s not much more than the single-connector version.

    With frequencies from 10 kHz to 100 kHz coming from a local oscillator, one might argue for a synchronous detector, formerly known as a lock-in amplifier. A Tayloe Detector might be a quick-and-dirty way to sweep a tracking-filter-and-detector over the frequency range. Because it’s a tracking generator, the filter bandwidth need not be very tight.

    At some point, of course, you just digitize the incoming signal and apply DSP, but the whole point of this is to poke around in the analog domain. This must not turn into an elaborate software project, too.

     

  • Quartz Resonator Test Fixture: 32 kHz Quartz Tuning Fork

    Soldering a 32.768 kHz quartz tuning fork resonator into the test fixture:

    Quartz crystal resonance test fixture
    Quartz crystal resonance test fixture

    The HP 8591 tracking generator doesn’t go below 100 kHz, so I used the FG085 DDS function generator as a source. I trust the 8591’s calibration more than the FG805’s, but right now I’m more interested in the differences between successive frequencies and the DDS can step in 1 Hz increments.

    The output appears on the 8591, with a big hump comes from the analyzer’s 30 Hz IF filter response sweeping across what’s essentially a single-frequency input. The hump is not the crystal’s response spectrum!

    With the jumper installed to short the 33 pF cap, the output peaks at 32.764:

    Removing the jumper to put the cap in the circuit, the response peaks at 32.765 kHz:

    The marker delta shows the difference between the two peaks, ignoring their 1 Hz difference:

    Quartz Resonator 32.764-5 no-34.6 pF delta
    Quartz Resonator 32.764-5 no-34.6 pF delta

    So I’d say the cap really does change the resonator series resonance by just about exactly 1 Hz.

    With the jumper installed to remove the cap from the circuit, setting the reference marker at the 32.764 kHz peak, and measuring the relative response at 32.765 kHz :

    Quartz Resonator 32.764-5 no cap delta
    Quartz Resonator 32.764-5 no cap delta

    So the response peak is much much narrower than 1 Hz: being off-peak by 1 Hz knocks 13-ish dB from the response.

    What’s painfully obvious: my instrumentation is totally inadequate for crystal measurements at these frequencies!

  • Quartz Resonator Test Fixture: 3.58 MHz Crystal Test

    Just to see if the resonator test fixture produced meaningful results, I plugged a 3.57954 MHz color burst crystal into the socket:

    Quartz test fixture - 3.57954 MHz crystal
    Quartz test fixture – 3.57954 MHz crystal

    This is a staged recreation based on actual events; pay no attention to the Colpitts oscillators growing in the background.

    Attaching goesinta and goesouta cables to the HP 8591 spectrum analyzer & tracking generator showed it worked just fine:

    Quartz 3.57954 MHz - no cap
    Quartz 3.57954 MHz – no cap

    The reference level is -40 dBm, not the usual 0 dBm, due to the loss in those resistive pads. Unsurprisingly, the parallel resonance valley looks pretty ragged at -120 dBm = 1 nW = 7 µV.

    Remove the jumper to put the capacitor in series:

    Quartz 3.57954 MHz - 36.4pF
    Quartz 3.57954 MHz – 36.4pF

    The marker delta resolution surely isn’t 1 Hz, but 750 Hz should get us in the right ballpark.

    Substituting a 72 Ω resistor, found by binary search rather than twiddling a pot:

    Quartz 3.57954 MHz - 72ohm
    Quartz 3.57954 MHz – 72ohm

    Which gives us all the measurements:

    • Fs = 3.57824 MHz
    • Fc = Fs + 750 Hz = 3.57899 MHz
    • Rm = 72 Ω
    • C0 = 3.83 pF
    • Cpar = 3.70 pF

    Turn the crank and the crystal motional parameters pop out:

    • Lm = 117 mH
    • Cm = 17 fF
    • Rm = 72 Ω
    • Q = 36 k

    Looks like a pretty good crystal to me!

  • Quartz Resonator Test Fixture

    A recent QEX article (Jan/Feb 2017 2016; sorry ’bout that), Crystal Measurement Parameters Simplified, Chuck Adams K7QO) suggested a simplified version of the K8IQY crystal parameter test fixture would work just as well for low-frequency quartz resonators:

    Quartz crystal resonance test fixture - schematic
    Quartz crystal resonance test fixture – schematic

    The resistive pads eliminate the fussy toroids and their frequency dependence.

    Tossing a handful of parts on a small proto board:

    Quartz crystal resonance test fixture
    Quartz crystal resonance test fixture

    I found two absurdly long hunks of RG-174 coax with BNC connectors, so that’s how it connects to the outside world; sacrificing a short SMA jumper would reduce the clutter, but that’s in the nature of fine tuning. At the frequencies this fixture will see, coax properties don’t matter.

    I can’t think of a better way to mount those AT26 cans than by soldering the wire leads directly to a pin header; pushing them under spring clips seems fraught with peril, not to mention excessive stray capacitance.

    Measure the actual in-circuit capacitance for the 33 pF cap (shown as 39 pF in the schematic, it’s not critical), which worked out to 34.6 pF.  That’s the external series capacitance Cx.

    The overall procedure, slightly modified from the original:

    • Measure C0 with resonator in capacitance fixture
    • Solder resonator to pins
    • Remove jumper to put capacitor Cx in series
    • Find series-resonant peak = Fc
    • Install jumper to short Cx
    • Find series-resonant peak = Fs < Fc
    • Remember the peak amplitude
    • Unsolder crystal
    • Install suitable trimpot = Rm in socket
    • Adjust trimpot to produce same output amplitude

    Crunch the numbers to get the crystal’s motional parameters:

    Rm = trimpot resistance
    Lm = 1 / [4 π2 (Fs + Fc) (Fs - Fc) (C0 + Cx)]
    Cm = 1 / [(2 π Fs)2 Lm]
    Q = [2 π Fs Lm] / Rm

    Then you’re done!

  • AADE LC Meter: AT26 Crystal Capacitance Fixture

    Crystals (or resonators) in AT26 packages have vanishingly small capacitances, so I conjured a little fixture for my AADE L/C Meter IIB (*) that holds them securely under little fingers snipped from an EMI shield:

    AT26 crystal capacitance fixture - Cpar detail
    AT26 crystal capacitance fixture – Cpar detail

    The finger on the right sits atop a snippet of rectangular brass tube so it need not bend so far.

    The base is a snippet of double-sided PCB with copper tape soldered around the edges. I drilled the holes slightly oversize and soldered copper tape there, giving the top foil a direct connection to the terminals. The raggedy slot looks like it came from a hacksaw; no false advertising there.

    The meter reports 6.5 pF of stray capacitance and nulls it to zero as usual. Without the fixture, it shows 2.5 pF.

    With the crystal in that position, the meter measures Cpar, the parasitic capacitance from both terminals to the can, which should be (roughly) twice the capacitance from either terminal to the can.

    Two more clips measure C0, the plate-to-plate capacitance:

    AT26 crystal capacitance fixture - C0 detail
    AT26 crystal capacitance fixture – C0 detail

    The meter drive is about 200 mV at 700 kHz, far away from resonance. Assuming the resonator’s effective series resistance is 25 kΩ (tuning forks aren’t crystals!), it’s dissipating 1.5 µW (and less as the ESR goes up). That may be slightly hot for some resonators, but it’s surely survivable.

    Some preliminary data on five 32.768 kHz crystals shows Cpar = 0.4 pF and C0 = 0.9 pF. I don’t trust those numbers very much, but they’re reproducible within 0.1-ish pF.

    (*) Almost All Digital Electronics and its website vanished after the owner died; the meter continues to work fine. The cheap knockoffs flooding eBay and Amazon may get you close to the goal.

  • Quartz Tuning Fork Resonator Teardown

    Thinking of a 60 kHz crystal filter front end for the WWVB receiver brought a little bag of 32.768 kHz crystals to the surface; I figured I could use them as crash test dummies while a bag of 60 kHz crystals travels around the planet. Come to find out they don’t behave quite like crystals and a bit of investigation shows the little cans contain tuning fork resonators, not crystal slabs.

    I had to see that, so I grabbed the base of one in a pin vise:

    Quartz resonator - pin vise
    Quartz resonator – pin vise

    I don’t know the part number for those resonators, but it’s something like AT26, where the “26” means a cylindrical can 2 mm OD and 6 mm long, more or less.

    Notching the can at the chuck with a triangular file, then wiggling the can with needle-nose pliers, eventually broke it off:

    Quartz resonator - A side
    Quartz resonator – A side

    The other side:

    Quartz resonator - B side
    Quartz resonator – B side

    A look through the microscope show they’re transparent, with laser trim scars on the ends:

    Quartz resonator - detail
    Quartz resonator – detail

    The “holes” are unplated quartz areas, clear as the finest glass.

    Not what I was expecting to see, at all!

  • Vacuum Tube Lights: Duodecar Rebuild

    You’ll recall the LED atop the 21HB5A tube failed, shortly after replacing the bottom LED and rewiring the ersatz plate lead, which led me to rebuild the whole thing with SK6812 RGBW LEDs. So I printed all the plastic parts again, because the duodecar tube socket’s pin circle can fit into a hard drive platter’s unmodified 25 mm hole, then drilled another platter to suit:

    Duodecar disk drilling
    Duodecar disk drilling

    The hole under the drill fits the 3.5 mm stereo socket for the ersatz plate lead, so it’s bigger than before.

    I’ve switched from Arduino Pro Minis with a separate USB converter to Arduino Nanos with an on-board CH340 USB chip, because the fake FTDI chips on the converters are a continuing aggravation:

    21HB5A base - interior
    21HB5A base – interior

    Adding those wire slots to the sockets definitely helps tidy things up; the wires no longer need a crude cable tie anchoring them to the socket mounting screws.

    I wanted to drive the LEDs from the A7 pin, rather than the A3 pin I’d been using on the Pro Minis, to keep the wires closer together, but it turns out that A6 and A7 can’t become digital output pins. So I used A5, although I may come to regret the backward incompatibility.

    In any event, the 21HB5A tube looks spiffy with its new LEDs in full effect:

    21HB5A with RBGBW LEDs - cyan violet phase
    21HB5A with RBGBW LEDs – cyan violet phase

    I dialed the white LED PWM down to 32, making the colors somewhat pastel, rather than washed-out.

    The Arduino source code as a GitHub Gist:

    // Neopixel mood lighting for vacuum tubes
    // Ed Nisley – KE4ANU – June 2016
    // September 2016 – Add Morse library and blinkiness
    // October 2016 – Set random colors at cycle end
    // March 2017 – RGBW SK6812 LEDs
    #include <Adafruit_NeoPixel.h>
    #include <morse.h>
    #include <Entropy.h>
    //———-
    // Pin assignments
    const byte PIN_NEO = A5; // DO – data out to first Neopixel
    const byte PIN_HEARTBEAT = 13; // DO – Arduino LED
    #define PIN_MORSE 12
    //———-
    // Constants
    // number of pixels
    #define PIXELS 2
    // index of the Morse output pixel and how fast it sends
    boolean Send_Morse = false;
    #define PIXEL_MORSE (PIXELS – 1)
    #define MORSE_WPM 10
    // lag between adjacent pixel, degrees of slowest period
    #define PIXELPHASE 45
    // update LEDs only this many ms apart (minus loop() overhead)
    #define UPDATEINTERVAL 50ul
    #define UPDATEMS (UPDATEINTERVAL – 1ul)
    // number of steps per cycle, before applying prime factors
    #define RESOLUTION 500
    //———-
    // Globals
    // instantiate the Neopixel buffer array
    Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXELS, PIN_NEO, NEO_GRBW + NEO_KHZ800);
    uint32_t FullWhite = strip.Color(255,255,255,255);
    uint32_t FullOff = strip.Color(0,0,0,0);
    uint32_t MorseColor;
    struct pixcolor_t {
    unsigned int Prime;
    unsigned int NumSteps;
    unsigned int Step;
    float StepSize;
    float Phase;
    byte MaxPWM;
    };
    unsigned int PlatterSteps;
    byte PrimeList[] = {3,5,7,13,19,29};
    // colors in each LED
    enum pixcolors {RED, GREEN, BLUE, WHITE, PIXELSIZE};
    struct pixcolor_t Pixels[PIXELSIZE]; // all the data for each pixel color intensity
    uint32_t UniColor;
    unsigned long MillisNow;
    unsigned long MillisThen;
    // Morse code
    char * MorseText = " cq cq cq de ke4znu";
    LEDMorseSender Morse(PIN_MORSE, (float)MORSE_WPM);
    uint8_t PrevMorse, ThisMorse;
    //– Figure PWM based on current state
    byte StepColor(byte Color, float Phi) {
    byte Value;
    Value = (Pixels[Color].MaxPWM / 2.0) * (1.0 + sin(Pixels[Color].Step * Pixels[Color].StepSize + Phi));
    // Value = (Value) ? Value : Pixels[Color].MaxPWM; // flash at dimmest points for debug
    return Value;
    }
    //– Select three unique primes for the color generator function
    // Then compute all the step parameters based on those values
    void SetColorGenerators(void) {
    Pixels[RED].Prime = PrimeList[random(sizeof(PrimeList))];
    do {
    Pixels[GREEN].Prime = PrimeList[random(sizeof(PrimeList))];
    } while (Pixels[RED].Prime == Pixels[GREEN].Prime);
    do {
    Pixels[BLUE].Prime = PrimeList[random(sizeof(PrimeList))];
    } while (Pixels[BLUE].Prime == Pixels[RED].Prime ||
    Pixels[BLUE].Prime == Pixels[GREEN].Prime);
    do {
    Pixels[WHITE].Prime = PrimeList[random(sizeof(PrimeList))];
    } while (Pixels[WHITE].Prime == Pixels[RED].Prime ||
    Pixels[WHITE].Prime == Pixels[GREEN].Prime ||
    Pixels[WHITE].Prime == Pixels[BLUE].Prime);
    printf("Primes: %d %d %d %d\r\n",Pixels[RED].Prime,Pixels[GREEN].Prime,Pixels[BLUE].Prime,Pixels[WHITE].Prime);
    Pixels[RED].MaxPWM = 255;
    Pixels[GREEN].MaxPWM = 255;
    Pixels[BLUE].MaxPWM = 255;
    Pixels[WHITE].MaxPWM = 32;
    unsigned int PhaseSteps = (unsigned int) ((PIXELPHASE / 360.0) *
    RESOLUTION * (unsigned int) max(max(max(Pixels[RED].Prime,Pixels[GREEN].Prime),Pixels[BLUE].Prime),Pixels[WHITE].Prime));
    printf("Pixel phase offset: %d deg = %d steps\r\n",(int)PIXELPHASE,PhaseSteps);
    for (byte c=0; c < PIXELSIZE; c++) {
    Pixels[c].NumSteps = RESOLUTION * Pixels[c].Prime; // steps per cycle
    Pixels[c].StepSize = TWO_PI / Pixels[c].NumSteps; // radians per step
    Pixels[c].Step = random(Pixels[c].NumSteps); // current step
    Pixels[c].Phase = PhaseSteps * Pixels[c].StepSize;; // phase in radians for this color
    printf(" c: %d Steps: %d Init: %d Phase: %d deg",c,Pixels[c].NumSteps,Pixels[c].Step,(int)(Pixels[c].Phase * 360.0 / TWO_PI));
    printf(" PWM: %d\r\n",Pixels[c].MaxPWM);
    }
    }
    //– Helper routine for printf()
    int s_putc(char c, FILE *t) {
    Serial.write(c);
    }
    //——————
    // Set the mood
    void setup() {
    pinMode(PIN_HEARTBEAT,OUTPUT);
    digitalWrite(PIN_HEARTBEAT,LOW); // show we arrived
    Serial.begin(57600);
    fdevopen(&s_putc,0); // set up serial output for printf()
    printf("Vacuum Tube Mood Light – RGBW\r\nEd Nisley – KE4ZNU – March 2017\r\n");
    Entropy.initialize(); // start up entropy collector
    // set up pixels
    strip.begin();
    strip.show();
    // lamp test: a brilliant white flash
    printf("Lamp test: flash white\r\n");
    for (byte i=0; i<5 ; i++) {
    for (int j=0; j < strip.numPixels(); j++) { // fill LEDs with white
    strip.setPixelColor(j,FullWhite);
    }
    strip.show();
    delay(500);
    for (int j=0; j < strip.numPixels(); j++) { // fill LEDs with black
    strip.setPixelColor(j,FullOff);
    }
    strip.show();
    delay(500);
    }
    // get an actual random number
    uint32_t rn = Entropy.random();
    printf("Random seed: %08lx\r\n",rn);
    randomSeed(rn);
    // set up the color generators
    SetColorGenerators();
    // set up Morse generator
    Morse.setup();
    Morse.setMessage(String(MorseText));
    MorseColor = strip.Color(255,random(32,64),random(16),0);
    PrevMorse = ThisMorse = digitalRead(PIN_MORSE);
    printf("Morse enabled: %d at %d wpm color: %08lx\n [%s]\r\n",Send_Morse,MORSE_WPM,MorseColor,MorseText);
    MillisNow = MillisThen = millis();
    }
    //——————
    // Run the mood
    void loop() {
    if (!Morse.continueSending()) {
    printf("Restarting Morse message\r\n");
    Morse.startSending();
    }
    ThisMorse = digitalRead(PIN_MORSE);
    MillisNow = millis();
    if (((MillisNow – MillisThen) >= UPDATEMS) || // time for color change?
    (PrevMorse != ThisMorse)) { // Morse output bit changed?
    digitalWrite(PIN_HEARTBEAT,HIGH);
    if (Send_Morse && ThisMorse) { // if Morse output high, overlay flash
    strip.setPixelColor(PIXEL_MORSE,MorseColor);
    }
    PrevMorse = ThisMorse;
    strip.show(); // send out precomputed colors
    boolean CycleRun = false; // check to see if all cycles have ended
    for (byte c=0; c < PIXELSIZE; c++) { // compute next increment for each color
    if (++Pixels[c].Step >= Pixels[c].NumSteps) {
    Pixels[c].Step = 0;
    printf("Cycle %d steps %d at %8ld delta %ld ms\r\n",c,Pixels[c].NumSteps,MillisNow,(MillisNow – MillisThen));
    }
    else {
    CycleRun = true; // this color is still cycling
    }
    }
    // If all cycles have completed, reset the color generators
    if (!CycleRun) {
    printf("All cycles ended: setting new color generator values\r\n");
    SetColorGenerators();
    }
    for (int i=0; i < strip.numPixels(); i++) { // for each pixel
    byte Value[PIXELSIZE];
    for (byte c=0; c < PIXELSIZE; c++) { // … for each color
    Value[c] = (Pixels[c].MaxPWM / 2.0) * (1.0 + sin(Pixels[c].Step * Pixels[c].StepSize – i*Pixels[c].Phase));
    }
    UniColor = strip.Color(Value[RED],Value[GREEN],Value[BLUE],Value[WHITE]);
    strip.setPixelColor(i,UniColor);
    }
    MillisThen = MillisNow;
    digitalWrite(PIN_HEARTBEAT,LOW);
    }
    }
    view raw TubeMorse.ino hosted with ❤ by GitHub