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.

Author: Ed

  • Mailing Tube End Caps: Screw-in Version

    The mailing tube arrived with contents intact, although the USPS inlet scanning didn’t work and the tube pretty much teleported across several states without leaving any tracking data behind. The recipient suggested several modifications to the caps:

    Review of user experience of tube end:
    The ribs on the endcap are very good at holding the cap on, so much so that I had to use a prying implement to remove it, which cracked the flange.
    Would consider less depth on the cap, and possibly another layer on the flange.

    Some continuous process improvement (a.k.a OpenSCAD hackage) produced a swoopy threaded cap with thumb-and-finger grips:

    Mailing Tube Screw Cap - top - Slic3r
    Mailing Tube Screw Cap – top – Slic3r

    The finger grips are what’s left after stepping a sphere out of the cap while rotating it around the middle:

    Mailing Tube Cap - finger grip construction
    Mailing Tube Cap – finger grip construction

    That worked out surprisingly well, with the deep end providing enough of a vertical-ish surface to push against.

    The two hex holes fit a pin wrench, because the grips twist only one way: outward. The wrench eliminates the need for a flange, as you can now adjust the cap insertion before slathering packing tape over the ends. Man, I loves me some good late binding action!

    A three-start thread seemed like overkill, but was quick & easy. The “thread form” consists of square rods sunk into the cap perimeter, with one edge sticking out:

    Mailing Tube Cap - thread detail
    Mailing Tube Cap – thread detail

    They’re 1.05 times longer than the cap perimeter facets to make their ends overlap, although they’re not tapered like the ones in the broom handle dingus, because it didn’t (seem to) make any difference to the model’s manifoldhood.

    Not needing any endcaps right now, I built one for show-n-tell:

    Threaded mailing tube end cap - installed
    Threaded mailing tube end cap – installed

    The OpenSCAD source code as a GitHub Gist:

    // Mailing tube end cap
    // Ed Nisley KE4ZNU – June 2017
    Layout = "Build";
    Model = "Screw";
    //- Extrusion parameters – must match reality!
    ThreadThick = 0.25;
    ThreadWidth = 0.40;
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    Protrusion = 0.1;
    HoleWindage = 0.2;
    //- Screw sizes
    inch = 25.4;
    TubeID = 2 * inch;
    TubeWall = 0.1 * inch;
    CapInsert = 15.0;
    CapRim = 6*ThreadThick;
    CapWall = 3*ThreadWidth;
    NumFlanges = 3;
    FlangeHeight = 3*ThreadThick;
    FlangeWidth = ThreadWidth/2;
    FlangeSpace = CapInsert / (NumFlanges + 1);
    ThumbHoleOD = 20.0;
    ThumbHoleAngle = 100;
    ThumbHoleSteps = 10;
    SpannerPinOD = 5.0;
    HelixOD = 4*ThreadThick;
    HelixHeight = 0.75*CapInsert;
    HelixAngle = atan(HelixHeight/(PI*TubeID));
    HelixStarts = 3;
    OAHeight = CapInsert + CapRim;
    NumRibs = 3*4;
    NumSides = 3*NumRibs;
    //- Adjust hole diameter to make the size come out right
    module PolyCyl(Dia,Height,ForceSides=0) { // based on nophead's polyholes
    Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);
    FixDia = Dia / cos(180/Sides);
    cylinder(r=(FixDia + HoleWindage)/2,h=Height,$fn=Sides);
    }
    module ScrewCap() {
    union() {
    difference() {
    cylinder(d=TubeID,h=OAHeight,$fn=NumSides);
    for (a=[0,180])
    for (i=[0:ThumbHoleSteps-1])
    rotate(a + i*ThumbHoleAngle/ThumbHoleSteps)
    translate([TubeID/4,0,-i*ThumbHoleOD/(2*ThumbHoleSteps)])
    sphere(d=ThumbHoleOD);
    for (a=[0,180])
    rotate(a – 60)
    translate([0.75*TubeID/2,0,-Protrusion])
    rotate(0*180/6)
    PolyCyl(SpannerPinOD,0.75*CapInsert,6);
    }
    for (s=[0:HelixStarts-1])
    for (i=[0:NumSides-1])
    rotate(i*360/NumSides + 180/NumSides + s*360/HelixStarts)
    translate([TubeID/2 – 0.25*HelixOD,0,i*HelixHeight/NumSides + HelixOD])
    rotate([90 + HelixAngle,0,0])
    cylinder(d=HelixOD,h=1.05*PI*TubeID/NumSides,center=true,$fn=4);
    }
    }
    module PushCap() {
    difference() {
    cylinder(d=TubeID,h=OAHeight,$fn=NumSides);
    translate([0,0,CapWall])
    cylinder(d=TubeID – 2*CapWall,h=OAHeight,$fn=NumSides);
    }
    for (i=[1:NumFlanges])
    translate([0,0,i*FlangeSpace])
    difference() {
    cylinder(d=TubeID + 2*FlangeWidth,h=FlangeHeight,$fn=NumSides);
    translate([0,0,-Protrusion])
    cylinder(d=TubeID – 2*CapWall,h=FlangeHeight + 2*Protrusion,$fn=NumSides);
    }
    for (i=[0:NumRibs-1])
    rotate(i*360/NumRibs)
    translate([0,-ThreadWidth,CapWall + ThreadThick])
    cube([TubeID/2 – CapWall/2,2*ThreadWidth,CapInsert + CapRim – CapWall – ThreadThick],center=false);
    translate([0,0,CapInsert]) {
    difference() {
    cylinder(d=TubeID + 2*TubeWall,h=CapRim,$fn=NumSides);
    translate([0,0,-Protrusion])
    cylinder(d=TubeID – 3*2*CapWall,h=2*CapRim,$fn=NumSides);
    }
    }
    }
    //- Build things
    if (Model == "Push")
    if (Layout == "Show")
    PushCap();
    else if (Layout == "Build")
    translate([0,0,OAHeight])
    rotate([180,0,0])
    PushCap();
    if (Model == "Screw")
    if (Layout == "Show")
    ScrewCap();
    else if (Layout == "Build")
    translate([0,0,OAHeight])
    rotate([180,0,0])
    ScrewCap();
  • Handbag Strap Rivet Repair

    One of the leather strap anchors on Mary’s giant haul-everything-to-a-concert(*) handbag pulled its rivet through the canvas fabric:

    Handbag - pulled-through rivet
    Handbag – pulled-through rivet

    We knotted the strap around the zippered opening and completed the mission.

    Of course, it wouldn’t have pulled through if they’d splurged on washers, but noooo too expensive:

    Handbag - intact rivet - inside
    Handbag – intact rivet – inside

    Some rummaging produced a pan-head M3 screw of suitable length:

    Handbag - repaired - outside
    Handbag – repaired – outside

    A slightly battered acorn nut was a special treat for the inside, with another washer to keep me happy:

    Handbag - repaired - inside
    Handbag – repaired – inside

    That was easy!

    (*) At Tanglewood, where they don’t strip-search you on the way in, tow-behind coolers seemed de rigueur, and a good time was had by all.

  • Why Electricity Won

    Spotted this impressive array at an apartment building:

    Gas meter array
    Gas meter array

    That’s just for one wing; the other end of the building has a similar installation. Each apartment has an electric stove and gas heat / AC.

    The plumbing!

  • LF Crystal Tester: Bring the Noise!

    The OLED display refresh contributes 100 Hz noise pulses to the low-level sine wave from the crystal test fixture:

    OLED Enabled - 100 Hz display refresh
    OLED Enabled – 100 Hz display refresh

    Disabling the display by activating its powersave option reveals 60 Hz pulses from the USB port on the Arduino Nano:

    OLED Powersave - 60 Hz USB Ground Loop
    OLED Powersave – 60 Hz USB Ground Loop

    Unplugging the USB cable, leaving just the +5 VDC power supply and coax cable to the oscilloscope, solves most of the problem:

    OLED Powersave - USB unplugged
    OLED Powersave – USB unplugged

    A closer look shows some (relatively) low frequency noise remains in full effect:

    OLED Powersave - USB unplugged - detail
    OLED Powersave – USB unplugged – detail

    Disabling the display while measuring the crystal seems sensible, although, to avoid surprises, a pushbutton should start the process. Unplugging the USB port puts a real crimp in the data collection, although that’s probably survivable with a USB isolator, one of which is on the way around the planet.

    The remaining low-level chop requires more thought. Somewhat to my surprise, holding the Arduino Reset button down doesn’t change much of anything, so it’s not a firmware thing.

    Those 10 µF coupling caps gotta go.

    With the OLED dark and the USB carrying data:

    Spectrum - OLED Powersave - USB in
    Spectrum – OLED Powersave – USB in

    Compare that to the first pass:

    Spectrum-60
    Spectrum-60

    Tamping down the noise seems to reduce the overall amplitude variation, but it also makes the capacitor-in and capacitor-out curves more consistent. There may be other things going on that I haven’t accounted for.

    The peak frequencies differ by 0.2 Hz, which is probably due to a few degrees of temperature difference. Obviously, it’s badly in need of a temperature calibration & correction.

  • LF Crystal Tester: LM75 Temperature Sensor

    A strip of NXP (née Philips plus Freescale, including the part of Motorola that didn’t become ON) LM75A I²C temperature sensors arrived from beyond the horizon. To see if they worked, I soldered thin wires directly to the SO-8 pins, entombed it in Kapton tape to prevent spitzensparken, and jammed it under the foam insulation atop the AD9850 DDS module:

    AD9850 DDS module with LM75A Temperature Sensor
    AD9850 DDS module with LM75A Temperature Sensor

    This turns out to be easier than screwing around with thermistors, because the chip reports the temperature directly in Celcius with ⅛ °C resolution. Classic LM75 chips from National (now absorbed by TI) had ½ °C resolution, but the datasheet shows the bits have an easily extensible format:

    LM75A Temperature Data Format
    LM75A Temperature Data Format

    Huh. Fixed-point data, split neatly on a byte boundary. Who’d’a thunk it?

    There’s a standard Arduino library using, naturally enough, floating point numbers, but I already have big fixed point numbers lying around and, with the I²C hardware up & running from the X axis DAC and OLED display, this was straightforward:

    Wire.requestFrom(LM75_ADDR,2);
    Temp.fx_32.high = Wire.read();
    Temp.fx_32.low = (uint32_t)Wire.read() << 24;
    PrintFixedPtRounded(Buffer,Temp,3);
    u8x8.drawString(0,ln,"DDS C          ");
    u8x8.drawString(16-strlen(Buffer),ln,Buffer);
    printf(",%s",Buffer);
    ln += 1;
    

    The next-to-last line squirts the temperature through the serial port to make those nice plots.

    Casually ignoring all I²C bus error conditions will eventually lead to heartache and confusion. In particular, the Basement Laboratory temperature must never fall below 0 °C, because I just plunk the two’s-complement temperature data into an unsigned fixed point number.

    Which produces the next-to-bottom line:

    DDS OLED with LM75 temperature
    DDS OLED with LM75 temperature

    Alas, the u8x8 font doesn’t include a degree symbol.

    Given sufficient motivation, I can now calibrate the DDS output against the GPS-locked 10 MHz standard to get a (most likely) linear equation for the oscillator frequency offset as a function of temperature. The DDS module includes a comparator to square up its sine wave, so an XOR phase detector or something based on filtering the output of an analog switch might be feasible.

  • LF Crystal Tester: Fixture Output Amp

    The crystal test fixture and amp huddle in front of the OLED display:

    Crystal Tester - First Light
    Crystal Tester – First Light

    The schematic:

    Test fixture - Relay - MAX4255 amp - schematic
    Test fixture – Relay – MAX4255 amp – schematic

    The 22 pF cap now sits across the relay’s NO contacts, so as to simplify measuring the total in-circuit capacitance. The LED turns on when the relay shorts out the capacitor, which has a 50% probability of making more sense.

    The quartz tuning fork resonators have an ESR around 20 or 30 kΩ, so the off-resonance output should be down something like -60 dB = 20 log (24 / 24×10³) from the 150 mV input: 200 µV (-ish). It’s actually around 1 mV, suggesting plenty of blowby through the baling-wire connections hidden under that neat top surface. I think that’s why the whole setup shows only about 8 dB of dynamic range; more attention to detail may be in order, although the peaks probably won’t move all that much.

    Anyhow, even though the AD8310 log amp module should be able to handle such a tiny signal, the MAX4255 amp provides 40 dB of gain (OK, just 39.8 dB) and rolls off the high end at 220 kHz as a side benefit of its 22 MHz GBW.

    There’s way too much low frequency rumble at the amp output:

    100 Hz noise at MA4255 output
    100 Hz noise at MA4255 output

    What look like grass is actually the 60 kHz resonator output: those big lumps & bumps are noise from this-and-that. The repetitive peaks and dents exactly 10 ms apart (the cursors span four of ’em) felt a lot like OLED refresh cycles and, indeed, went away when I yanked the display out. Pulling the USB connection eliminates another tremendous heap o’ noise, so there’s likely a ground loop (-ish) thing going on, too. This may call for a USB optical isolator, its commercial equivalent, or more eBay offerings. Getting rid of that junk may improve the dynamic range enough to keep me from doing anything drastic.

    The AD8310 log amp input now has decent coupling caps, so it’s not seeing the VCC/2 bias, and I removed that kludged-in 50 Ω terminating resistor to present its full 1.1 kΩ input resistance to the op amp.

  • LF Crystal Tester: DDS Buffer Amp

    The Big Ideas: the DDS output, being more-or-less constant, needs a variable-gain amp to set the crystal drive level. The amp also fixes the impedance mismatch between the DDS output and the crystal, which may not be much of a problem for the (very) high ESR quartz tuning fork resonators in play.

    The AD9850 DDS output feeds a 70 MHz (-ish) elliptical reconstruction filter chopping off image frequencies descending from the 125 MHz sampling clock, with a 100 Ω (-ish) output impedance that’s just about purely resistive at 60 kHz. An on-board 3.9 kΩ resistor (labeled with 392 on their schematic) sets the full-scale output current to 10 mA for a peak voltage of 1 V. The module uses only the + output of the differential pair, which means the sine wave runs from 0 V to 1 V: 1 Vpp = 500 mVpeak = 353 mVrms (ignoring the 500 mV offset).

    Pin header J3 normally sports a jumper to connect the 3.9 kΩ RSET resistor, but you can insert an external resistor to increase the resistance and decrease the output current:

    IOUT = 32 × 1.248 V / RSET

    A little hot-melt glue action produced a suitable lashup from a 5 kΩ trimpot:

    AD9850 DDS Module - 5 k external RSET trimpot
    AD9850 DDS Module – 5 k external RSET trimpot

    The pillars of green wire insulation forestall screwdriver shorts to the bare pin headers, although that’s less of risk with the upper insulating foam sheet in place:

    Crystal Tester - First Light
    Crystal Tester – First Light

    A 5 kΩ trimpot can vary the output voltage downward by a factor of 2 = -6 dB, more or less.

    All the quartz tuning fork resonator specs I’ve found, none of which may apply to the units on hand, seem to require no more than 1 µW drive. Given a resonator’s equivalent series resistance of around 20 kΩ (for real!), the drive voltage will be 150 mV (-ish):

    1 µW = V² / 20 kΩ, so V = sqrt(20×10³) = 141 mV

    The nominal version of the crystal tester had a 50 Ω input impedance, so I picked a MAX4165 op amp with mojo sufficient for anything over 25 Ω; in retrospect, a lighter load than 48 Ω would be fine.

    In any event, the amp looks like this:

    MAX4165 Buffer Amp
    MAX4165 Buffer Amp

    What looks like a DIP switch is really the 3×2 jumper header just to the right of the foam insulation, in front of the SOT23 space transformer PCB carrying the MAX4165. No jumper = 0 dB gain, then 6 dB steps upward from there. The -6 dB trimpot range gives more-or-less continuous output tweakage across 24 dB, -6 dB to +18 dB, which is certainly excessive. The 24 Ω terminating resistors provide 6 dB loss into the crystal, so the effective range is -12 to +12 dB, with 0 dB = 350 mVrms and -6 dB = 150 mVrms (-ish) at the crystal.

    It’s a non-inverting amplifier, which (also in retrospect) probably isn’t a win:

    • Yet Another Bypass Cap on the cold end of the gain-setting resistors
    • Overly elaborate VCC/2 biasing to maintain sufficiently high input impedance

    I’m reasonably sure all those big caps contribute to some low-level motorboating, but haven’t tracked it down.