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.

Tag: Improvements

Making the world a better place, one piece at a time

  • Sony HDR-AS30V Helmet Camera: MicroSD Card Spacer

    Sony tried, they really tried, to make their proprietary Memory Stick flash memory cards catch on, but the slot in their HDR-AS30V Action / Helmet camera accepts both Memory Stick Micro and MicroSD cards. The two cards have slightly different sizes, the AS30V’s dual-purpose slot allows MicroSD cards to sit misaligned with the contacts, and the camera frequently kvetches about having no card.

    The only solution seemed to be starting the camera while watching the display to ensure the card worked, but it would sometimes joggle out of position during a ride.

    I cut out a tiny polypropylene rectangle(-ish) spacer to fill the Memory Stick side of the slot, sized to fit between the spring fingers holding the MicroSD card against its contacts:

    Sony HDR-AS30V Camera - MicroSD card and spacer
    Sony HDR-AS30V Camera – MicroSD card and spacer

    Not the best cutting job I’ve ever done, but it was an iterative process and that’s where I stopped. If this works and I have need for another / better spacer, I promise to do better.

    The spacer’s somewhat mottled appearance comes from tapeless sticky (an adhesive layer on a peel-off backing: inverse tape!) applied to the top side, which will affix it to the slot. I’d rather glue the spacer to the MicroSD card, but then the card wouldn’t fit in the USB 3.0 adapter I use to transfer the files.

    The chips along the left edge of silkscreen come from my fingernail, because pressing exactly there seems to be the best way to force the damn thing into the proper alignment.

    So the slot + spacer looks like this:

    Sony HDR-AS30V Camera - dual-card slot with spacer
    Sony HDR-AS30V Camera – dual-card slot with spacer

    The MicroSD card fits in the far side of the slot, facing toward you with contacts downward, thusly:

    Sony HDR-AS30V Camera - MicroSD card with spacer
    Sony HDR-AS30V Camera – MicroSD card with spacer

    And then It Just Works™, at least on the very few rides we’ve gotten in during December and early January.

    Incidentally, the blue and exceedingly thin latch finger holding the battery in place will snap, should you drop the camera on its non-lens end from any height. Conversely, should you drop it on the lens end, you can kiss the optics goodbye. Your choice.

  • Tensilizing Copper Wire

    The “bus bars” on the battery holders are 14 AWG copper wire:

    Astable - NP-BX1 base - detail
    Astable – NP-BX1 base – detail

    Slightly stretching the wire straightens and work-hardens it, which I’d been doing by clamping one end in the bench vise, grabbing the other in a Vise-Grip, and whacking the Vise-Grip with a hammer. The results tended to be, mmm, hit-or-miss, with the wires often acquiring a slight bend due to an errant whack.

    I finally fished out the slide hammer Mary made when we took a BOCES adult-ed machine shop class many many years ago:

    Slide Hammer
    Slide Hammer

    The snout captured the head of a sheet metal screw you’d previously driven into a dented automobile fender. For my simple purposes, jamming the wire into the snout and tightening it firmly provides a Good Enough™ grip:

    Slide Hammer Snout
    Slide Hammer Snout

    Clamp the other end of the wire into the bench vise, pull gently on the hammer to take the slack out of the wire, and slap the weight until one end of the wire breaks.

    With a bit of attention to detail, the wires come out perfectly straight and ready to become Art:

    Straightened 14 AWG Copper Wires
    Straightened 14 AWG Copper Wires

    The wires start out at 1.60 mm diameter (14 AWG should be 1.628, but you know how this stuff goes) and break around 1.55 mm. In principle, when the diameter drops 3%, the area will decrease by 6% and the length should increase by 6%, but in reality the 150 mm length stretches by only 1 mm = 1%, not 3 mm. My measurement-fu seems weak.

    Highly recommended, particularly when your Favorite Wife made the tool.

    The Harbor Freight version comes with a bunch of snouts suitable for car repair and is utterly unromantic.

  • Astable Multivibrator: Monochrome Pirhana LED

    The LED parts box disgorged some single-color Pirhana-style LEDs:

    Astable - 2N7000 - Mono Pirhana LED
    Astable – 2N7000 – Mono Pirhana LED

    Didn’t quite catch the blink, but the Ping-Pong ball radome lights up just as you’d expect.

    The radome sits on a stripped-down RGB LED spider:

    Astable Multivibrator Battery Holder - mono LED Spider - fit view
    Astable Multivibrator Battery Holder – mono LED Spider – fit view

    The circuitry is the same as the First Light version, with a 1 MΩ resistor stabilizing the LED ballast resistor:

    Astable - 2N7000 - Mono Pirhana LED - detail
    Astable – 2N7000 – Mono Pirhana LED – detail

    Those are 1 µF ceramic caps in the astable section, so I’m no longer abusing electrolytics, and a stylin’ 100 nF film cap metering out the LED pulse up above.

    Just for pretty, I’ve been using yellow / black wires for the battery connections and matching the LED color with its cathode lead.

    The OpenSCAD source code as a GitHub Gist:

    // Holder for Li-Ion battery packs
    // Ed Nisley KE4ZNU January 2013
    // 2018-11-15 Adapted for 1.5 mm pogo pins, battery data table
    // 2018-12 RGB LED spider, general cleanups
    /* [Layout options] */
    BatteryName = "NP-BX1"; // [NP-BX1,NB-5L,NB-6L]
    RGBCircuit = false; // false = 1 strut pair, true = 2 pairs
    Layout = "Spider"; // [Build,Show,Fit,Case,Lid,Pins,RGBSpider,Spider]
    /* [Extrusion parameters] – must match reality! */
    // Print with +2 shells and 3 solid layers
    ThreadThick = 0.25;
    ThreadWidth = 0.40;
    HoleWindage = 0.2;
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    function IntegerLessMultiple(Size,Unit) = Unit * floor(Size / Unit);
    Protrusion = 0.1; // make holes end cleanly
    /* [Hidden] */
    inch = 25.4;
    BuildOffset = 3.0; // clearance for build layout
    Gap = 2.0; // separation for Fit parts
    //- Basic dimensions
    WallThick = 4*ThreadWidth; // holder sidewalls
    BaseThick = 6*ThreadThick; // bottom of holder to bottom of battery
    TopThick = 6*ThreadThick; // top of battery to top of holder
    //- Battery dimensions – rationalized from several samples
    // Coordinate origin at battery corner with contacts, key openings downward
    T_NAME = 0; // Name must fit recess, so don't get loquacious
    T_SIZE = 1;
    T_CONTACTS = 2;
    T_KEYS = 3;
    BatteryData = [
    ["NP-BX1",[43.0,30.0,9.5],[[-0.75,6.0,6.2,"+"],[-0.75,16.0,6.2,"-"]],[[1.70,3.70,2.90],[1.70,3.60,2.90]]],
    ["NB-5L", [45.0,32.0,8.0],[[-0.82,4.5,3.5,"-"],[-0.82,11.0,3.5,"+"]],[[2.2,0.75,2.0],[2.2,2.8,2.0]]],
    ["NB-6L",[42.5,35.5,7.0],[[-0.85,5.50,3.05,"-"],[-0.85,11.90,3.05,"+"]],[[2.0,0.70,2.8],[2.0,2.00,2.8]]],
    ];
    echo(str("Battery: ",BatteryName));
    BatteryIndex = search([BatteryName],BatteryData,1,0)[0];
    echo(str(" Index: ",BatteryIndex));
    BatterySize = BatteryData[BatteryIndex][T_SIZE]; // X = length, Y = width, Z = thickness
    echo(str(" Size: ",BatterySize));
    Contacts = BatteryData[BatteryIndex][T_CONTACTS]; // relative to battery edge, front, and bottom
    echo(str(" Contacts: ",Contacts));
    ContactOC = Contacts[1].y – Contacts[0].y; // + and – terminals for pogo pin contacts
    ContactCenter = Contacts[0].y + ContactOC/2;
    KeyBlocks = BatteryData[BatteryIndex][T_KEYS]; // recesses in battery face set X position
    echo(str(" Keys: ",KeyBlocks));
    //- Pin dimensions
    ID = 0;
    OD = 1;
    LENGTH = 2;
    PinShank = [1.5,2.0,6.5]; // shank, flange, compressed length
    PinFlange = [1.5,2.0,0.5]; // flange, length included in PinShank
    PinTip = [0.9,0.9,2.5]; // extended spring-loaded tip
    WireOD = 1.7; // wiring from pins to circuitry
    PinChannel = WireOD; // cut behind flange for solder overflow
    PinRecess = 3.0; // recess behind pin flange end for epoxy fill
    echo(str("Contact tip dia: ",PinTip[OD]));
    echo(str(" .. shank dia: ",PinShank[ID]));
    OverTravel = 0.5; // space beyond battery face at X origin
    //- Holder dimensions
    GuideRadius = ThreadWidth; // friction fit ridges
    GuideOffset = 7; // from compartment corners
    LidOverhang = 2.0; // atop of battery for retention
    LidClearance = LidOverhang * (BatterySize.z/BatterySize.x); // … clearance above battery for tilting
    echo(str("Lid clearance: ",LidClearance));
    CaseSize = [BatterySize.x + PinShank[LENGTH] + OverTravel + PinRecess + GuideRadius + WallThick,
    BatterySize.y + 2*WallThick + 2*GuideRadius,
    BatterySize.z + BaseThick + TopThick + LidClearance];
    echo(str("Case size: ",CaseSize));
    CaseOffset = [-(PinShank[LENGTH] + OverTravel + PinRecess),-(WallThick + GuideRadius),0]; // position around battery
    ThumbRadius = 10.0; // thumb opening at end of battery
    CornerRadius = 3*ThreadThick; // nice corner rounding
    LidSize = [-CaseOffset.x + LidOverhang,CaseSize.y,TopThick];
    LidOffset = [0.0,CaseOffset.y,0];
    //- Wire struts
    StrutDia = 1.6; // AWG 14 = 1.6 mm
    StrutSides = 3*4;
    StrutBase = [StrutDia,StrutDia + 4*WallThick,CaseSize.z – TopThick]; // ID = wire, OD = buildable
    //StrutOC = [IntegerLessMultiple(BatterySize.x – StrutBase[OD],5.0), // set easy OC wire spacing
    // IntegerMultiple(CaseSize.y + StrutBase[OD],5.0)];
    StrutOC = [IntegerLessMultiple(CaseSize.x – 2*CornerRadius -2*StrutBase[OD],5.0),
    IntegerMultiple(CaseSize.y + StrutBase[OD],5.0)];
    StrutOffset = [CaseSize.x/2 + CaseOffset.x,BatterySize.y/2]; // from case centerlines
    StrutAngle = atan(StrutOC.y/StrutOC.x);
    echo(str("Strut OC: ",StrutOC));
    //- RGB / Pirhana / Neopixel-ish LEDs
    RGBBody = [8.0,8.0,5.0]; // Z = body height
    PixelPCB = [4.0,10.0,3.0]; // Neopixel-ish PCBs, ID = chip window
    RGBPin = 5.0; // pin length
    RGBPinsOC = [5.0,5.0]; // pin layout
    RGBRecess = RGBBody.z + RGBPin/2; // maximum LED recess depth
    BallOD = 40.0; // radome sphere
    BallSides = 4*StrutSides; // nice number of sides
    BallPillar = [norm([RGBBody.x,RGBBody.y]),
    norm([RGBBody.x,RGBBody.y]) + 4*WallThick,
    StrutBase[OD] + RGBBody.z];
    BallChordM = BallOD/2 – sqrt(pow(BallOD/2,2) – (pow(BallPillar[OD],2))/4);
    echo(str("Ball chord depth: ",BallChordM));
    //———————-
    // Useful routines
    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);
    }
    //——————-
    //– Guides for tighter friction fit
    module Guides() {
    translate([GuideOffset,-GuideRadius,0])
    PolyCyl(2*GuideRadius,(BatterySize.z – Protrusion),4);
    translate([GuideOffset,(BatterySize.y + GuideRadius),0])
    PolyCyl(2*GuideRadius,(BatterySize.z – Protrusion),4);
    translate([(BatterySize.x – GuideOffset),-GuideRadius,0])
    PolyCyl(2*GuideRadius,(BatterySize.z – Protrusion),4);
    translate([(BatterySize.x – GuideOffset),(BatterySize.y + GuideRadius),0])
    PolyCyl(2*GuideRadius,(BatterySize.z – Protrusion),4);
    translate([(BatterySize.x + GuideRadius),GuideOffset/2,0])
    PolyCyl(2*GuideRadius,(BatterySize.z – Protrusion),4);
    translate([(BatterySize.x + GuideRadius),(BatterySize.y – GuideOffset/2),0])
    PolyCyl(2*GuideRadius,(BatterySize.z – Protrusion),4);
    }
    //– Contact pins
    // Rotated to put them in their natural oriention
    // Aligned to put tip base / end of shank at Overtravel limit
    module PinShape() {
    translate([-(PinShank[LENGTH] + OverTravel),0,0])
    rotate([0,90,0])
    rotate(180/6)
    union() {
    PolyCyl(PinTip[OD],PinShank[LENGTH] + PinTip[LENGTH],6);
    PolyCyl(PinShank[ID],PinShank[LENGTH] + Protrusion,6); // slight extension for clean cuts
    PolyCyl(PinFlange[OD],PinFlange[LENGTH],6);
    }
    }
    // Position pins to put end of shank at battery face
    // Does not include recess access into case
    module PinAssembly() {
    union() {
    for (p = Contacts)
    translate([0,p.y,p.z])
    PinShape();
    translate([-(PinShank[LENGTH] + OverTravel) + PinChannel/2, // solder space
    ContactCenter,
    Contacts[0].z])
    cube([PinChannel,
    (Contacts[1].y – Contacts[0].y + PinFlange[OD]),
    PinFlange[OD]],center=true);
    for (j=[-1,1]) // wire channels
    translate([-(PinShank[LENGTH] + OverTravel – PinChannel/2),
    j*ContactOC/4 + ContactCenter,
    Contacts[0].z – PinFlange[OD]/2])
    rotate(180/6)
    PolyCyl(WireOD,CaseSize.z,6);
    }
    }
    //– Case with origin at battery corner
    module Case() {
    difference() {
    union() {
    difference() {
    union() {
    translate([(CaseSize.x/2 + CaseOffset.x), // basic case shape
    (CaseSize.y/2 + CaseOffset.y),
    (CaseSize.z/2 – BaseThick)])
    hull()
    for (i=[-1,1], j=[-1,1], k=[-1,1])
    translate([i*(CaseSize.x/2 – CornerRadius),
    j*(CaseSize.y/2 – CornerRadius),
    k*(CaseSize.z/2 – CornerRadius)])
    sphere(r=CornerRadius/cos(180/8),$fn=8); // cos() fixes undersize spheres!
    for (i= RGBCircuit ? [-1,1] : -1) { // strut bases
    hull()
    for (j=[-1,1])
    translate([i*StrutOC.x/2 + StrutOffset.x,j*StrutOC.y/2 + StrutOffset.y,-BaseThick])
    rotate(180/StrutSides)
    cylinder(d=StrutBase[OD],h=StrutBase[LENGTH],$fn=StrutSides);
    translate([i*StrutOC.x/2 + StrutOffset.x,StrutOffset.y,StrutBase[LENGTH]/2 – BaseThick])
    cube([2*StrutBase[OD],StrutOC.y,StrutBase[LENGTH]],center=true); // blocks for fairing
    for (j=[-1,1]) // hemisphere caps
    translate([i*StrutOC.x/2 + StrutOffset.x,
    j*StrutOC.y/2 + StrutOffset.y,
    StrutBase[LENGTH] – BaseThick])
    rotate(180/StrutSides)
    sphere(d=StrutBase[OD]/cos(180/StrutSides),$fn=StrutSides);
    }
    }
    translate([-OverTravel,-GuideRadius,0])
    cube([(BatterySize.x + GuideRadius + OverTravel),
    (BatterySize.y + 2*GuideRadius),
    (BatterySize.z + LidClearance + Protrusion)]); // battery space
    translate([BatterySize.x/2,BatterySize.y/2,0]) // recess around battery name
    cube([0.8*BatterySize.x,8,2*ThreadThick],center=true);
    translate([CaseOffset.x + CaseSize.x/2,BatterySize.y/2,-BaseThick + ThreadThick – Protrusion]) // recess around battery name
    cube([0.75*CaseSize.x,8,2*ThreadThick],center=true);
    }
    Guides(); // improve friction fit
    translate([-OverTravel,-GuideRadius,0]) // battery keying blocks
    cube(KeyBlocks[0] + [OverTravel,GuideRadius,0],center=false);
    translate([-OverTravel,(BatterySize.y – KeyBlocks[1].y),0])
    cube(KeyBlocks[1] + [OverTravel,GuideRadius,0],center=false);
    translate([BatterySize.x/2,BatterySize.y/2,-ThreadThick])
    linear_extrude(height=2*ThreadThick,convexity=10)
    text(text=BatteryName,size=5,spacing=1.20,font="Arial:style:Bold",halign="center",valign="center");
    translate([CaseOffset.x + CaseSize.x/2,BatterySize.y/2,-BaseThick])
    linear_extrude(height=2*ThreadThick + Protrusion,convexity=10)
    mirror([0,1,0])
    text(text="KE4ZNU",size=6,spacing=1.20,font="Arial:style:Bold",halign="center",valign="center");
    }
    translate([2*CaseOffset.x, // battery top access
    (CaseOffset.y – Protrusion),
    BatterySize.z + LidClearance])
    cube([2*CaseSize.x,(CaseSize.y + 2*Protrusion),2*TopThick]);
    for (i2 = RGBCircuit ? [-1,1] : -1) { // strut wire holes and fairing
    for (j=[-1,1])
    translate([i2*StrutOC.x/2 + StrutOffset.x,j*StrutOC.y/2 + StrutOffset.y,0])
    rotate(180/StrutSides)
    PolyCyl(StrutBase[ID],2*StrutBase[LENGTH],StrutSides);
    for (i=[-1,1], j=[-1,1])
    translate([i*StrutBase[OD] + (i2*StrutOC.x/2 + StrutOffset.x),
    j*StrutOC.y/2 + StrutOffset.y,
    -(BaseThick + Protrusion)])
    rotate(180/StrutSides)
    PolyCyl(StrutBase[OD],StrutBase[LENGTH] + 2*Protrusion,StrutSides);
    }
    translate([(BatterySize.x – Protrusion), // remove thumb notch
    (CaseSize.y/2 + CaseOffset.y),
    (ThumbRadius)])
    rotate([90,0,0])
    rotate([0,90,0])
    cylinder(r=ThumbRadius,
    h=(WallThick + GuideRadius + 2*Protrusion),
    $fn=22);
    PinAssembly(); // pins and wiring
    translate([CaseOffset.x + PinRecess + Protrusion,(Contacts[1].y + Contacts[0].y)/2,Contacts[0].z])
    translate([-PinRecess,0,0])
    cube([2*PinRecess,
    (Contacts[1].y – Contacts[0].y + PinFlange[OD]/cos(180/6) + 2*HoleWindage),
    2*PinFlange[OD]],center=true); // pin insertion hole
    }
    }
    // Lid position offset to match case
    // The polarity indicator recesses are pure bodges
    module Lid() {
    union() {
    difference() {
    translate([-LidSize.x/2 + LidOffset.x + LidOverhang,LidSize.y/2 + LidOffset.y,0])
    difference() {
    hull()
    for (i=[-1,1], j=[-1,1], k=[-1,1])
    translate([i*(LidSize.x/2 – CornerRadius),
    j*(LidSize.y/2 – CornerRadius),
    k*(LidSize.z – CornerRadius)]) // double thickness for flat bottom
    sphere(r=CornerRadius,$fn=8);
    translate([0,0,-LidSize.z/2]) // remove bottom
    cube([(LidSize.x + 2*Protrusion),(LidSize.y + 2*Protrusion),LidSize.z],center=true);
    translate([LidSize.x/8,0,0])
    cube([LidSize.x/4,0.75*LidSize.y,4*ThreadThick],center=true); // epoxy recess
    }
    translate([0,0,-(Contacts[0].z + PinFlange[OD])]) // punch wire holes
    PinAssembly();
    for (n=[0,1]) // polarity recesses
    translate([-LidOverhang/2 – 0.40,Contacts[n].y,LidSize.z – ThreadThick/2])
    cube([4,4.5,ThreadThick + Protrusion],center=true);
    }
    for (n=[0,1]) // polarity indicators
    translate([-LidOverhang/2,Contacts[n].y,LidSize.z – 1*ThreadThick]) // … proud of surface
    rotate(90)
    linear_extrude(height=2*ThreadThick,convexity=10)
    text(text=Contacts[n][3],size=5,font="Arial:style:Bold",halign="center",valign="center");
    }
    }
    // Spider for RGB LED + radome atop vertical struts
    module RGBSpider() {
    difference() {
    union() {
    for (i=[-1,1], j=[-1,1]) {
    translate([i*StrutOC.x/2,j*StrutOC.y/2,StrutBase[OD]/2])
    rotate(180/StrutSides) // doesn't quite match crosspieces; close enough
    sphere(d=StrutBase[OD]/cos(180/StrutSides),$fn=StrutSides);
    translate([i*StrutOC.x/2,j*StrutOC.y/2,0])
    rotate(180/StrutSides)
    cylinder(d=StrutBase[OD],h=StrutBase[OD]/2,$fn=StrutSides);
    }
    for (m=[-1,1]) // connecting bars
    rotate(m*StrutAngle)
    translate([0,0,StrutBase[OD]/4])
    cube([norm(StrutOC),StrutBase[OD],StrutBase[OD]/2],center=true);
    translate([0,0,0]) // pillar for RGB LED and ball
    cylinder(d=BallPillar[OD],h=BallPillar[LENGTH],$fn=BallSides);
    }
    for (i=[-1,1], j=[-1,1]) // strut wires
    translate([i*StrutOC.x/2,j*StrutOC.y/2,-Protrusion])
    rotate(0)
    PolyCyl(StrutBase[ID],StrutBase[OD]/2,6);
    for (m=[-1,1], n=[0,1]) // RGBA wires through bars
    rotate(m*StrutAngle + n*180)
    translate([StrutOC.x/3,0,-Protrusion])
    PolyCyl(StrutBase[ID],StrutBase[OD],6);
    translate([0,0,BallOD/2 + BallPillar[LENGTH] – BallChordM]) // ball inset
    sphere(d=BallOD);
    translate([0,0,2*RGBBody.z + (BallPillar[LENGTH] – BallChordM) – RGBRecess]) // LED inset
    cube(RGBBody + [HoleWindage,HoleWindage,3*RGBBody.z],center=true); // XY clearance + huge height for E-Z cut
    translate([0,0,StrutBase[OD]/2]) // Neopixel recess
    PolyCyl(PixelPCB[OD],3*RGBBody.z,BallSides/2);
    for (m=[-1,1]) // RGBA wires through pillar
    rotate(m*StrutAngle)
    translate([0,0,StrutBase[OD]/2 + WireOD/2 + 0*Protrusion])
    cube([norm(StrutOC)/2,WireOD,WireOD],center=true);
    }
    }
    // Spider for single LED atop struts, with the ball
    // Aligned to struts at terminal end of battery on Y axis
    module Spider() {
    difference() {
    union() {
    for (j=[-1,1]) {
    translate([-StrutOC.x/2,j*StrutOC.y/2,StrutBase[OD]/2])
    rotate(180/StrutSides)
    sphere(d=StrutBase[OD]/cos(180/StrutSides),$fn=StrutSides);
    translate([-StrutOC.x/2,j*StrutOC.y/2,0])
    rotate(180/StrutSides)
    cylinder(d=StrutBase[OD],h=StrutBase[OD]/2,$fn=StrutSides);
    }
    translate([-StrutOC.x/2,0,StrutBase[OD]/4]) // connecting bars
    cube([StrutBase[OD]*cos(180/StrutSides),StrutOC.y,StrutBase[OD]/2],center=true);
    translate([-StrutOC.x/2,0,0]) // pillar for RGB LED and ball
    cylinder(d=BallPillar[OD],h=BallPillar[LENGTH],$fn=BallSides);
    }
    for (j=[-1,1]) // strut wires
    translate([-StrutOC.x/2,j*StrutOC.y/2,-Protrusion])
    rotate(0)
    PolyCyl(StrutBase[ID],StrutBase[OD]/2,6);
    translate([-StrutOC.x/2,0,0]) // wires through bars
    for (n=[-1,1])
    rotate(n*90)
    translate([StrutOC.x/3,0,-Protrusion])
    PolyCyl(StrutBase[ID],StrutBase[OD],6);
    translate([-StrutOC.x/2,0,-Protrusion]) // center hole for Neopixel
    rotate(180/6)
    PolyCyl(StrutBase[ID],StrutBase[OD],6);
    translate([-StrutOC.x/2,0,BallOD/2 + BallPillar[LENGTH] – BallChordM]) // ball inset
    sphere(d=BallOD);
    translate([-StrutOC.x/2,0,2*RGBBody.z + (BallPillar[LENGTH] – BallChordM) – RGBRecess]) // LED inset
    cube(RGBBody + [HoleWindage,HoleWindage,3*RGBBody.z],center=true); // XY clearance + huge height for E-Z cut
    translate([-StrutOC.x/2,0,StrutBase[OD]/2]) // Neopixel recess
    PolyCyl(PixelPCB[OD],3*RGBBody.z,BallSides/2);
    translate([-StrutOC.x/2,0,StrutBase[OD]/2 + WireOD/2 + 0*Protrusion]) // wire channels
    cube([WireOD,StrutOC.y/2,WireOD],center=true);
    }
    }
    //——————-
    // Build it!
    if (Layout == "Case")
    Case();
    if (Layout == "Lid")
    Lid();
    if (Layout == "RGBSpider") {
    RGBSpider();
    }
    if (Layout == "Spider") {
    Spider();
    }
    if (Layout == "Pins") {
    color("Silver",0.5)
    PinShape();
    PinAssembly();
    }
    if (Layout == "Fit") { // reveal pin assembly
    difference() {
    Case();
    translate([(CaseOffset.x – Protrusion),
    Contacts[1].y,
    Contacts[1].z])
    cube([(-CaseOffset.x + Protrusion),CaseSize.y,CaseSize.z]);
    translate([(CaseOffset.x – Protrusion),
    (CaseOffset.y – Protrusion),
    0])
    cube([(-CaseOffset.x + Protrusion),
    Contacts[0].y + Protrusion – CaseOffset.y,
    CaseSize.z]);
    }
    translate([0,0,BatterySize.z + Gap])
    Lid();
    color("Silver",0.15)
    PinAssembly();
    if (RGBCircuit) {
    translate([StrutOC.x/2,BatterySize.y/2,2*BatterySize.z])
    difference() {
    RGBSpider();
    rotate(180-StrutAngle)
    translate([0,0,-Protrusion])
    cube([norm(StrutOC),StrutBase[OD],2*BallPillar.z],center=false);
    }
    color("Green",0.35)
    translate([StrutOC.x/2,BatterySize.y/2,2*BatterySize.z + BallOD/2 + BallPillar[LENGTH] – BallChordM])
    sphere(d=BallOD);
    }
    else {
    difference() {
    translate([StrutOC.x/2,BatterySize.y/2,2*BatterySize.z])
    Spider();
    translate([-BallPillar[OD],BatterySize.y/2,2*BatterySize.z – Protrusion])
    cube([BallPillar[OD],StrutOC.y,2*BallPillar.z],center=false);
    }
    color("Green",0.35)
    translate([0,BatterySize.y/2,2*BatterySize.z + BallOD/2 + BallPillar[LENGTH] – BallChordM])
    sphere(d=BallOD);
    }
    }
    if (Layout == "Build") {
    rotate(90) {
    translate([-BatterySize.x/2,-BatterySize.y/2,BaseThick])
    Case();
    translate([-CaseSize.x + LidSize.x,-(LidSize.y/2 + LidOffset.y),0])
    Lid();
    if (RGBCircuit)
    translate([StrutOC.x + BatterySize.x/2,0,0])
    RGBSpider();
    else
    translate([StrutOC.x + BatterySize.x/2,0,0])
    Spider();
    }
    }
    if (Layout == "Show") {
    Case();
    translate([0,0,(BatterySize.z + Gap)])
    Lid();
    color("Silver",0.25)
    PinAssembly();
    if (RGBCircuit) {
    translate([StrutOC.x/2,BatterySize.y/2,2*BatterySize.z])
    RGBSpider();
    color("Green",0.35)
    translate([StrutOC.x/2,BatterySize.y/2,2*BatterySize.z + BallOD/2 + BallPillar[LENGTH] – BallChordM])
    sphere(d=BallOD);
    }
    else {
    translate([StrutOC.x/2,BatterySize.y/2,2*BatterySize.z])
    Spider();
    color("Green",0.35)
    translate([0,BatterySize.y/2,2*BatterySize.z + BallOD/2 + BallPillar[LENGTH] – BallChordM])
    sphere(d=BallOD);
    }
    }
  • Minilathe MT3 Spindle Collet Fitting

    I’ve used the LMS set of inch-size MT3 spindle collets on occasion, but releasing them required an unseemly amount of drawbar battering. It recently occurred to me to check their fit in the spindle taper:

    Minilathe - MT3 collet - taper test
    Minilathe – MT3 collet – taper test

    Huh.

    The only place they touch the spindle is right around the base, so it’s no wonder they clamp poorly and release grudgingly. I tried several others with the same result.

    Cross-checking shows a much closer fit along the entire length of the dead center, so it’s not the spindle’s fault:

    Minilathe - Dead Center - MT3 taper check
    Minilathe – Dead Center – MT3 taper check

    Stipulated: we’re not talking toolroom precision here

    I set the collets on centers:

    Minilathe - MT3 collet - drive setup
    Minilathe – MT3 collet – drive setup

    And proceeded to file away the offending section to move the clamping force closer to the business end of the collet:

    Minilathe - MT3 collet - filed result
    Minilathe – MT3 collet – filed result

    I did the small collets, the ones I’m most likely to need, and left the big ones for another rainy day.

    They don’t have much clamping range and seem good only for exact-inch-size rods.

    I should lay in a stock of ER16 and maybe ER32 collets for small stuff.

     

  • Debossed Printed Legends

    [Update: It seems I interchanged “em” and “de” throughout this post.  ]

    Up to this point, I’ve been labeling printed parts with emdebossed legends that look OK on the solid model:

    Astable Multivibrator Battery Holder
    Astable Multivibrator Battery Holder

    Alas, the recessed letters become lost in their perimeter threads:

    3D Printed Legend - Embossed
    3D Printed Legend – Embossed

    Raising the legend above the surface (“deembossing”) works reasonably well, but raised letters would interfere with sliding the battery into the holder and tend to get lost amid the surface infill pattern.

    The blindingly obvious solution, after far too long, raises the letters above a frame embossed into the surface:

    Astable Multivibrator Battery Holder - Legend Debossed
    Astable Multivibrator Battery Holder – Legend Debossed

    Which looks OK in the real world, too:

    3D Printed Legend - Debossed
    3D Printed Legend – Debossed

    The frame is one thread deep and the legend is one thread tall, putting the letters flush with the surrounding surface and allowing the battery to slide smoothly.

    The legend on the bottom surface shows even more improvement:

    NP-BX1 battery holder - Raised vs Recessed Legend
    NP-BX1 battery holder – Raised vs Recessed Legend

    An OpenSCAD program can’t get the size of a rendered text string, so the fixed-size frame must surround the largest possible text, which isn’t much of a problem for my simple needs.

  • Astable Multivibrator: 2N7000 MOSFET First Light

    Taping a cardboard support under the soldering fixture helped hold all the parts in place:

    Astable - 2N7000 soldering
    Astable – 2N7000 soldering

    The struts fit neatly into an NP-BX1 battery holder and the circuit began blinking merrily:

    Astable - 2N7000 assembled
    Astable – 2N7000 assembled

    My photography hand is weak …

    The circuit schematic / layout resembles this:

    LED Schematic - MOSFET transistors
    LED Schematic – MOSFET transistors

    The missing 1 MΩ resistor at the LED would serve as a physical support to tether the loose end of the 100 (-ish) Ω resistor, which desperately needed some stabilization under the LED spider.

    The simulation says it should blink about every 4s:

    Astable - 2N7000 buffered - true model
    Astable – 2N7000 buffered – true model

    The 2N7000 MOSFETs use a SPICE model from the Motorola ON Semi downloads, although they behaved about the same way using the LTSpice 2N7002 model.

    It really does blink every 4s:

    Astable - 2N7000 overview
    Astable – 2N7000 overview

    The LED pulse width should be about 50 ms:

    Astable - 2N7000 buffered - LED current - true model
    Astable – 2N7000 buffered – LED current – true model

    The voltage at the bottom of the ballast resistor is directly proportional to the LED current:

    Astable - 2N7000 pulse detail
    Astable – 2N7000 pulse detail

    So the pulse is actually 80-ish ms, which is Close Enough™ for my purposes.

    The key advantage here is making both the astable’s period and the blink’s duration (roughly) proportional to the component values, so I can tweak them with some confidence the results will come out more-or-less right.

    I love it when a plan comes together!

     

     

  • Astable Multivibrator: 2N7000 MOSFETs

    Some poking around revealed an astable multivibrator using now-obsolescent ZVNL110A MOSFET transistors. The key idea seems to be large gate resistors putting the DC operating point exactly at the voltage required to hold each transistor in the linear region, pretty much guaranteeing the astable will eventually start up.

    A bit of simulation suggests this variation ought to work:

    Astable - 2N7000 buffered
    Astable – 2N7000 buffered

    Well, after the kickstarter in the lower left shorts the transistor for millisecond to enforce some asymmetry, whereoupon the simulation ticks along just fine.

    The yellow trace shows the voltage across C2 ramping back and forth between ±1.3 V, with a period just over 4 s and almost exactly a 50% duty cycle: much better than the bipolar version, with sensible component values. As before, the cap sees both polarities, so an electrolytic cap isn’t appropriate.

    The red trace is the drain voltage at M2 (presumably, “M for MOSFET”, rather than a plebeian “Q” or “T”), which is firmly at 0 V when it’s ON and ramps upward as R4 pulls C1 higher to turn it even more firmly OFF.

    The green trace shows the LED current pulse when M2 turns ON at the end of each cycle. Rather than contort the astable into a very low duty cycle, I generate the pulse by dumping current through a smallish cap into the gate of M4. A few tens of milliseconds makes a perfectly serviceable blink and keeps the average current drain down around a milliamp or so.

    In between, M3 buffers the astable’s output to deliver enough current to C4. Without the buffer, the cap draws enough current to mess with the oscillations; that’s how I got backed into this corner.

    Figuring the LED at 20 mA for 50 ms, the astable at 10 µA, and the buffer at half of 40 µA, the average current of 1 mA comes entirely from the LED, so even a weak lithium camera battery should last a good long while.

    If the low average drain ekes 1000 mA·h from the battery, the LED should blink for a month or two before the battery shuts down.