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.

Month: October 2017

  • Streaming Radio Player: OLED SPI Speed Fix

    The OLED displays on the streaming radio players have SH1106 controllers supported by the Luma library, which works just fine. Digging into the source shows the default SH1106 setup (see the class spi() at the bottom) uses an 8 MHz clock:

        def __init__(self, spi=None, gpio=None, port=0, device=0,
                     bus_speed_hz=8000000, transfer_size=4096,
                     gpio_DC=24, gpio_RST=25):
    assert(bus_speed_hz in [mhz * 1000000 for mhz in [0.5, 1, 2, 4, 8, 16, 32]])
    

    Alas, the SH1106 doc suggests a maximum SPI clock of 2 to 4 MHz, the latter only with fair skies, a tailwind, and a stiff power supply:

    SH1106 OLED Controller - SPI timing
    SH1106 OLED Controller – SPI timing

    The display doesn’t get updated all that often, so there’s no point in rushing things:

    serial = spi(device=0,port=0,bus_speed_hz=1000000)
    device = sh1106(serial)
    

    They’ve been ticking along without mysterious blanking or mirroring for a bit over two weeks, so I’ll call it a fix.

  • Google Pixel vs. USB Mounting

    For reasons undoubtedly making sense at the time, the Google Pixel (and, most likely, current Android devices) don’t support the USB Mass Storage protocol. A bit of poking around suggests the jmtpfs utility supplies the other end of the Pixel’s Media Transfer Protocol and the process goes a little something like this:

    • Once upon a time, create a mountpoint: mkdir /mnt/pixel
    • Unlock the phone
    • Plug in the USB cable
    • Pull down the top menu, tap USB charging this device
    • Select Transfer Files
    • sudo jmtpfs /mnt/pixel -o allow_other,fsname="Pixel"

    The allow_other parameter sets the directory / file permissions so ordinary users can access the files. The fsname is just for pretty.

    The Pixel’s storage then appears as the awkwardly named /mnt/pixel/Internal\ shared\ storage/ directory.

    Despite being somewhat Linuxy under the hood, the mapped storage doesn’t support the usual filesystem attributes, so don’t try to transfer them with, say, rsync -a:

    rsync -rhu --progress /mnt/music/Music\ for\ Programming /mnt/pixel/Internal\ shared\ storage/Music/
    

    When you’re done: sudo umount /mnt/pixel

    This may not be a win over bankshotting off Dropbox or Google Drive, except for sensitive bits like private keys and suchlike.

    Thunar apparently knows how to detect and mount mtp devices automagically and I suppose GUI-fied drag-n-drop works as you’d expect.

  • Hooker Avenue at Raymond: Left Turn on Red

    T=0: You can’t tell, but the signals for Hooker Avenue have been yellow for several seconds and are about to turn red:

    Raymond - Left on Red - 2017-10-11 - 1
    Raymond – Left on Red – 2017-10-11 – 1

    T+3: The opposing signals have been red for a while, but nobody much cares about that:

    Raymond - Left on Red - 2017-10-11 - 2
    Raymond – Left on Red – 2017-10-11 – 2

    T+11: Right-turning traffic (with a green arrow) blocks his path, so he just drops to a dead stop in the middle of the intersection:

    Raymond - Left on Red - 2017-10-11 - 3
    Raymond – Left on Red – 2017-10-11 – 3

    T+14: Finally! All clear for a left on red:

    Raymond - Left on Red - 2017-10-11 - 4
    Raymond – Left on Red – 2017-10-11 – 4

    When a cyclist delays a driver for two, maybe three, seconds, even while riding legally, outrage occurs.

    And, yeah, I’ve made mistakes, too. Happens to everybody. Cyclists seem to arouse disproportionate outrage, so I try very hard to ride within the rules and the lines.

  • Honeybee for Supper

    We often have supper on the patio, with a fly swatter at the ready, but honeybees get special treatment:

    Honeybee on cooked squash
    Honeybee on cooked squash

    She surveyed both our plates, landed on my cooked squash, and probed into the crevices as she would to extract nectar from a flower. The weather has been dry for the last few days and we think she was looking for anything providing a bit of moisture.

    I splashed some water on the table and plopped that part of the squash nearby, in the hopes she’d find what she needs. We’ll never know the end of the story.

  • Shortening a 2MT-to-1MT Morse Taper Sleeve

    The hulking 1/2 inch Jacobs chuck is grossly oversized for most of the holes I poke in things spinning in the lathe. I already have several smaller Jacobs chucks for the Sherline’s 1 MT spindle, so I got some Morse Taper Sleeve Adapters for the mini-lathe’s 2MT tailstock. They’re longer than the “short” 2MT dead center:

    1MT to 2MT adapter - vs 2MT dead center
    1MT to 2MT adapter – vs 2MT dead center

    Because they’re longer, the tailstock ram loses nearly an inch of travel it can’t afford.

    So I hacksawed the taper just beyond the opening at the tang and faced off the ragged end:

    1MT to 2MT adapter - facing
    1MT to 2MT adapter – facing

    The steady rest jaws don’t match the Morse taper angle, but they’re way better than assuming the nose of the Jacob chuck can hold such an ungainly affair.

    The short 1MT taper on the drill chuck doesn’t extend to the opening: when it’s firmly pushed into the socket, there’s no simple way to eject it. So, drill a small hole for a pin punch to pop it out:

    1MT to 2MT adapter - center drilling
    1MT to 2MT adapter – center drilling

    I hate hammering on tooling, which means I must eventually enlarge the hole to clear a 5 mm bolt, make a square-ish nut to fit inside the slot, and gimmick up a plug for the 1/4-20 socket in the 1MT taper (used by the Sherline mill drawbar). More work than I want to take on right now, but it’ll provide some Quality Shop Time.

    If the truth be known, I also got a 3/8-16 thread to 2MT adapter for the mid-size Jacobs chuck seen in the pictures, thus eliminating the thread-to-1MT adapter and plugging the chuck directly into the tailstock. The 1MT adapter will come in handy for the least Jacobs chuck; although LMS has a 0JT-to-2MT adapter, the less I monkey with that tiny taper, the better off we’ll both be.

  • Plotter Grit Wheel Bushings

    As part of recommissioning the lathe tailstock, I made some bushings to adapt Dremel sanding drums bands to an 8 mm shaft (in imitation of the grit drive wheels on the HP plotter):

    Plotter Y bushing - samples
    Plotter Y bushing – samples

    They’re not all the same because the lad who’s building the plotter got to turn out his own bushings. We think the knurled version, with a setscrew to lock it on the shaft, will work better than adhesive-bonding the drum to the bushing.

    The overall process starts with a rough 1/2 inch aluminum rod. Skim-cut to get a concentric surface and face the end smooth:

    Plotter Y bushing - facing
    Plotter Y bushing – facing

    Then knurl it:

    Plotter Y bushing - knurling
    Plotter Y bushing – knurling

    The skim cut makes the aluminum rod a loose fit inside the sanding band, but the knurling enlarges the diameter enough to make it a firm press fit and I think it’ll have enough traction to stay in place.

    FWIW, the wheels in the LittleMachineShop knurling tool seem pretty bad: the central holes aren’t quite concentric with the cutting edge, the bores are a loose fit on the mounting screws, the wheels are much narrower than the slots they ride in, so they wobble uncontrollably. It’s not a fatal flaw, but they definitely produce a sub-par knurl.

    Face off the front, cut the knurling down at each end, then part it off:

    Plotter Y bushing - cutoff
    Plotter Y bushing – cutoff

    Clamp it in the Sherline mill, laser-spot the edges, set the origin in the middle, and center drill:

    Plotter Y bushing - center drill
    Plotter Y bushing – center drill

    Drill and tap for a teeny M3 setscrew:

    Plotter Y bushing - tapping
    Plotter Y bushing – tapping

    Clean out the chips, debur the hole, install the setscrew, and you’re half-done: do it again to get the second drive roller!

     

  • NEMA17 Motor and Bearing Mounts

    As part of coaching a student (and his father!) on their incredibly ambitious build-a-plotter-from-scratch project, I suggested stealing using HP’s grit-wheel paper drive, rather than fiddling with guide rods to move either the pen carrier or the entire paper platform. Dremel sanding drums seem about the right size and they had an 8 mm shaft harvested from a defunct printer, so a pair of mounts moves the project along:

    NEMA17 and Bearing Mounts - Slic3r preview
    NEMA17 and Bearing Mounts – Slic3r preview

    The motor mount code is a hack job from my old NEMA17 mount and the code has a lot not to like. The bearing mount puts the bearing on the proper centerline using brute force copypasta and depends on friction to hold it in place. The two models should be integrated into the same file, the shaft centerline shouldn’t involve the printed thread width, and blah blah blah:

    NEMA17 motor and bearing mounts
    NEMA17 motor and bearing mounts

    I had him turn the shaft adapter from an aluminum rod in the mini-lathe: he’s hooked.

    The OpenSCAD source code as a GitHub Gist:

    // Ball bearing mount
    // Ed Nisley KE4ZNU 2017-10-09
    //– Extrusion parameters
    ThreadThick = 0.20;
    ThreadWidth = 0.40;
    HoleWindage = 0.2; // enlarge hole dia by this amount
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    Protrusion = 0.1; // make holes look good and joints intersect properly
    //– Useful sizes
    inch = 25.4;
    Tap10_32 = 0.159 * inch;
    Clear10_32 = 0.190 * inch;
    Head10_32 = 0.373 * inch;
    Head10_32Thick = 0.110 * inch;
    Nut10_32Dia = 0.433 * inch;
    Nut10_32Thick = 0.130 * inch;
    // Bearing sizes
    ID = 0;
    OD = 1;
    LENGTH = 2;
    Bearing = [8.0,22.0,7.0];
    ShaftHeight = IntegerMultiple(25.2,ThreadWidth); // arbitrary or copied from motor mount
    WallThick = 3.0;
    //– Mount Sizes
    MountSize = Bearing[OD] + 2*WallThick;
    BaseThick = max(WallThick,ShaftHeight – MountSize/2); // baseplate
    StandBoltHead = IntegerMultiple(Head10_32,2); // bolt head rounded up
    StandBoltClear = 1.25 * StandBoltHead;
    StandBoltOC = IntegerMultiple(MountSize + StandBoltClear,2);
    StandLength = StandBoltOC + StandBoltClear;
    StandThick = StandBoltClear + WallThick;
    StandHeight = MountSize + BaseThick;
    Cutout = (StandLength – MountSize)/2;
    echo(str("Stand Base: ",StandLength," x ",StandThick," x ",BaseThick));
    echo(str("Stand Bolt OC: ",StandBoltOC));
    echo(str("Shaft Height:",ShaftHeight));
    //———————-
    // 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);
    }
    //———————-
    // Put it together
    module BearingMount() {
    difference() {
    translate([BaseThick/2,0,StandThick/2])
    cube([StandHeight,StandLength,StandThick],center=true);
    translate([0,0,-Protrusion])
    PolyCyl(Bearing[OD],(StandThick + 2*Protrusion));
    for (j=[-1,1]) // cutouts over bolts
    translate([-(StandHeight/2 – ShaftHeight + WallThick),
    j*(StandLength/2 – Cutout/2 + Protrusion/2),
    (WallThick + StandThick/2)])
    cube([StandHeight,
    Cutout + Protrusion,
    StandThick],center=true);
    for (j=[-1,1]) // stand bolt holes – base
    translate([(MountSize/2 – Protrusion),
    j*StandBoltOC/2,
    WallThick + StandBoltClear/2])
    rotate([0,90,0])
    rotate(180/6)
    PolyCyl(Clear10_32,BaseThick + 2*Protrusion,6);
    for (j=[-1,1]) // stand bolt holes – back
    translate([0,j*StandBoltOC/2,-Protrusion])
    rotate(180/6)
    PolyCyl(Clear10_32,StandThick + 2*Protrusion,6);
    translate([0,-(MountSize/2 – ThreadWidth/2),(StandThick – WallThick)/2 + WallThick])
    rotate([90,180,0])
    linear_extrude(ThreadWidth,convexity=10)
    text(text=str(ShaftHeight),size=6,spacing=1.20,font="Arial",halign="center",valign="center");
    }
    }
    //———————-
    // Build it
    BearingMount();
    // NEMA 17 stepper mount
    // Ed Nisley KE4ZNU August 2011
    // Tweaked & thinned 2017-10-09
    //– Extrusion parameters
    ThreadThick = 0.25;
    ThreadWidth = 0.4;
    HoleWindage = 0.3; // enlarge hole dia by this amount
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    Protrusion = 0.1; // make holes look good and joints intersect properly
    //– Useful sizes
    inch = 25.4;
    Tap10_32 = 0.159 * inch;
    Clear10_32 = 0.190 * inch;
    Head10_32 = 0.373 * inch;
    Head10_32Thick = 0.110 * inch;
    Nut10_32Dia = 0.433 * inch;
    Nut10_32Thick = 0.130 * inch;
    NEMA17_ShaftDia = 5.0;
    NEMA17_ShaftLength = 24.0;
    NEMA17_PilotDia = 0.866 * inch;
    NEMA17_PilotLength = 0.080 * inch;
    NEMA17_BCD = 1.725 * inch;
    NEMA17_BoltDia = 3.5;
    NEMA17_BoltOC = 1.220 * inch;
    //– Mount Sizes
    MountWidth = IntegerMultiple(NEMA17_BCD,ThreadWidth); // use BCD for motor clearance
    MountThick = IntegerMultiple(4.0,ThreadThick); // for stiffness
    MountBoltDia = 3.0;
    StandThick = 3.0; // baseplate
    StrutThick = IntegerMultiple(3.0,ThreadWidth); // sides holding motor mount
    UprightLength = MountWidth + 2*StrutThick;
    StandBoltHead = IntegerMultiple(Head10_32,5); // bolt head rounded up
    StandBoltOC = IntegerMultiple(UprightLength + 2*StandBoltHead,5);
    StandLength = StandBoltOC + 2*StandBoltHead;
    StandWidth = 2*StandBoltHead;
    StandBoltClear = (StandLength – UprightLength)/2; // flat around bolt head
    MotorRecess = StandWidth – MountThick;
    ShaftHeight = IntegerMultiple(StandThick + MountWidth/2,ThreadWidth);
    echo(str("Stand Base: ",StandLength," x ",StandWidth," x ",StandThick));
    echo(str("Stand Bolt OC: ",StandBoltOC));
    echo(str("Shaft Height:",ShaftHeight));
    echo(str("Strut Thick: ",StrutThick));
    //———————-
    // 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);
    }
    //———————-
    // Model
    module MotorMount() {
    difference() {
    translate([StandThick/2,0,StandWidth/2])
    cube([(MountWidth + StandThick),StandLength,StandWidth],center=true);
    translate([-Protrusion/2,0,StandWidth – (MotorRecess – Protrusion)/2])
    cube([(MountWidth + Protrusion),MountWidth,(MotorRecess + Protrusion)],center=true);
    translate([0,0,-Protrusion]) // pilot hole
    PolyCyl(NEMA17_PilotDia,(MountThick + 2*Protrusion));
    for (i=[-1,1]) // motor bolt holes
    for (j=[-1,1])
    translate([i*NEMA17_BoltOC/2,j*NEMA17_BoltOC/2,-Protrusion])
    PolyCyl(MountBoltDia,(MountThick + 2*Protrusion),6);
    for (j=[-1,1]) // cutouts over bolts
    translate([-Protrusion/2,
    j*((StandLength – StandBoltClear)/2 + Protrusion/2),
    StandWidth/2])
    cube([(MountWidth + Protrusion),
    (StandBoltClear + Protrusion),
    (StandWidth + 2*Protrusion)],center=true);
    for (j=[-1,1]) // stand bolt holes
    translate([(MountWidth/2 – Protrusion),j*StandBoltOC/2,StandWidth/2])
    rotate([0,90,0])
    rotate(180/6)
    PolyCyl(Clear10_32,StandThick + 2*Protrusion,6);
    translate([0,-(UprightLength/2 – ThreadWidth/2),StandWidth/2])
    rotate([90,180,0])
    linear_extrude(ThreadWidth,convexity=10)
    text(text=str(ShaftHeight),size=6,spacing=1.20,font="Arial",halign="center",valign="center");
    }
    }
    //———————-
    // Build it
    MotorMount();