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: Software

General-purpose computers doing something specific

  • Laser Cutter: Improved Tube Support Pads

    Laser Cutter: Improved Tube Support Pads

    A recent mirror alignment check led to complete failure at the laser head aperture just upstream of Mirror 3:

    Beam alignment - M3 fail
    Beam alignment – M3 fail

    Those five spots come from the center of the platform and the four corners; they will overlay into a single spot in a properly aligned machine.

    Pondering my options reminded me that I intended to build new laser tube support pads, because the ones shipped inside the machine seemed crudely made:

    CO2 Laser supports - OEM hardware
    CO2 Laser supports – OEM hardware

    It’s partly disassembled in preparation for the next step.

    The chipboard shims underneath the stack are mine, but the OEM pile was unstable even with the screws tightened. The reason became obvious when I took the stack apart:

    CO2 laser supports - OEM molded parts
    CO2 laser supports – OEM molded parts

    The bump in the middle of the upper block surrounds the post of the laser tube cradle. It looks like this from the side:

    CO2 Laser supports - OEM tube cradle - side view
    CO2 Laser supports – OEM tube cradle – side view

    All of the blocks were crudely molded and could not be stacked into a stable pile. The tech who assembled and aligned the machine tightened the screws so firmly that the washers crushed into saddles:

    CO2 Laser supports - OEM crushed washers
    CO2 Laser supports – OEM crushed washers

    I can do better than that, if only because I’m not on the clock.

    The tube support on the right end (toward the beam outlet) screwed into a nice set of threaded inserts brazed onto the floor of the laser compartment.

    As far as I can tell, the laser cabinet was intended for a real 60 W tube measuring 1200 mm that would stick out into a box on the side of the cabinet, but would allow the left tube support base (shown above) to screw into a similar quartet of threaded inserts. Instead, it has an overdriven 50 W tube measuring 1050 mm with the left support screwed into four crudely hand-drilled and -tapped holes so far off the centerline as to jam the screws against the front end of their slots in order to get the tube barely into alignment, with the screws on the output side jammed against the rear end of their slots.

    To answer a question you may have: the commercial tube supports one might buy from a reputable supplier (or, for that matter, Amazon) are either exactly as wide as the compartment (thus eliminating one degree of freedom) or obviously unsteady, and would surely require drilling more holes in awkward locations.

    So, we begin.

    The general idea is to make a larger set of blocks fitting another quartet of holes with threaded inserts on the right side of the compartment floor:

    CO2 Laser supports - installed right
    CO2 Laser supports – installed right

    On the right, I stuck the bottom block to the shelf with double-sided tape:

    CO2 Laser supports - installed left
    CO2 Laser supports – installed left

    Because I was unwilling to:

    • Drill and tap holes with the tube in place or
    • Remove the tube to get safer access

    The alert reader will note the four tapped holes immediately to the right of the new blocks. Those were evidently intended for a center tube support for the longer tube, because the crudely hand-drilled holes hide just out of view to the left of the new blocks.

    At the far left of that picture, beyond the two holes probably intended for coolant tubes, you can see one of the four holes with tapped inserts that would match longer tubes, where the 50 W tube has its anode and coolant connections.

    The larger blocks I made have a hole accommodating the bulge in the tube cradle to let it slide back and forth as needed:

    CO2 Laser supports - gluing top layers
    CO2 Laser supports – gluing top layers

    That seemed easier and less exciting than attempting to flycut the bottom of the OEM plastic tube cradle.

    The chipboard layer serves as a guide to keep the tube cradle lined up, with its now much shorter screws into the brass inserts epoxied into the plywood layer.

    I glued the top layers together to get a rigid assembly, with the lower layers being replaceable shims adding up to the right height, whatever that might be. The LightBurn layout has an assortment of useful pieces, some of which I didn’t need:

    Laser tube support blocks - LightBurn layout
    Laser tube support blocks – LightBurn layout

    If this were a greenfield project, the leftmost Base MDF pad would come in handy, as its slots are large enough to clear the flat side of the 4 mm rivnuts I’d install in the compartment floor.

    Thin shims come from paperboard boxes & chipboard:

    CO2 Laser supports - thin shims
    CO2 Laser supports – thin shims

    Thicker spacers come from (scrap) plywood and MDF:

    CO2 Laser supports - thIck shims
    CO2 Laser supports – thIck shims

    Skipping ahead a few days, the tube & mirror realignment came out much better:

    Alignment at Mirror 3 - four corners - 2023-09-02
    Alignment at Mirror 3 – four corners – 2023-09-02

    That’s only the four corners of the platform, but it’s OK by me.

    If you’re fussy, the scorches are all low by a bit under 2 mm. Fixing that requires raising the tube by 2 mm, which I can certainly do, but I’m going to let this whole affair mellow out for a while.

    The LightBurn SVG layout as a GitHub Gist:

    Loading
    Sorry, something went wrong. Reload?
    Sorry, we cannot display this file.
    Sorry, this file is invalid so it cannot be displayed.

  • Laser Cutter: Mirror Pin Wrench

    Laser Cutter: Mirror Pin Wrench

    After struggling with pin pliers again, I finally made a pin wrench for the laser cutter’s mirror retaining rings:

    Laser Mirror Pin Wrench - in use
    Laser Mirror Pin Wrench – in use

    The odd grayish tint toward the flat end of the knob comes from residual black filament in the hot end after switching to retina-burn orange PETG.

    The solid model looks about like you’d expect:

    Mirror Pin Wrench - Solid Model
    Mirror Pin Wrench – Solid Model

    The pins are snippets of 3/32 inch = 2.4 mm steel rod with ground-round ends to fit the 2.5 mm pin sockets in the retaining ring.

    They’re rammed into place with a drill press to keep them aligned with the holes:

    Laser Mirror Pin Wrench - pin insertion
    Laser Mirror Pin Wrench – pin insertion

    Pressed flush with the central boss that aligns the wrench with the ring:

    Laser Mirror Pin Wrench - pin leveling
    Laser Mirror Pin Wrench – pin leveling

    Then put the ring on the bench, set the wrench atop the ring with the pins in the sockets, and press firmly to seat the pins to the proper depth. The end results should look like this:

    Laser Mirror Pin Wrench - mirror ring test
    Laser Mirror Pin Wrench – mirror ring test

    The next time I clean the mirrors, there will be less muttering.

    The OpenSCAD source code as a GitHub Gist:

    // OMTech laser cutter mirror pin wrench
    // Ed Nisley – KE4ZNU – August 2023
    // From https://www.thingiverse.com/thing:4146258
    use <knurledFinishLib_v2_1.scad>
    /* [Hidden] */
    ThreadThick = 0.20;
    ThreadWidth = 0.40;
    HoleWindage = 0.2; // extra clearance
    Protrusion = 0.1; // make holes end cleanly
    inch = 25.4;
    //———————-
    // Dimensions
    /* [Knob] */
    PinDia = 2.4; // pin diameter
    PinOC = 20.5; // … on-center spacing
    PinDepth = 10.0; // … hole depth
    LocDia = 14.5; // central stud
    LocLength = 3.0;
    ShaftDia = 26.0; // un-knurled section diameter
    ShaftLength = 15.0; // … length
    KnurlDia = 30.0; // diameter at midline of knurl diamonds
    KnurlLen = 20.0; // … length of knurled section
    /* [Hidden] */
    KnurlDPNom = 32; // Nominal diametral pitch = (# diamonds) / (OD inches)
    DiamondDepth = 0.5; // … depth of diamonds
    DiamondAspect = 2; // length to width ratio
    KnurlID = KnurlDia – DiamondDepth; // dia at bottom of knurl
    NumDiamonds = ceil(KnurlDPNom * KnurlID / inch);
    echo(str("Num diamonds: ",NumDiamonds));
    NumSides = 4*NumDiamonds; // 4 facets per diamond
    KnurlDP = NumDiamonds / (KnurlID / inch); // actual DP
    echo(str("DP Nom: ",KnurlDPNom," actual: ",KnurlDP));
    DiamondWidth = (KnurlID * PI) / NumDiamonds;
    DiamondLenNom = DiamondAspect * DiamondWidth; // nominal diamond length
    DiamondLength = KnurlLen / round(KnurlLen/DiamondLenNom); // … actual
    TaperLength = 0.75*DiamondLength;
    KnobOAL = ShaftLength + KnurlLen + 2*TaperLength;
    //———————-
    // 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);
    }
    //- Build it
    difference() {
    union() {
    render(convexity=10)
    translate([0,0,TaperLength])
    knurl(k_cyl_hg=KnurlLen,
    k_cyl_od=KnurlDia,
    knurl_wd=DiamondWidth,
    knurl_hg=DiamondLength,
    knurl_dp=DiamondDepth,
    e_smooth=DiamondLength/2);
    color("Orange")
    cylinder(r1=ShaftDia/2,
    r2=(KnurlDia – DiamondDepth)/2,
    h=(TaperLength + Protrusion),
    $fn=NumSides);
    color("Orange")
    translate([0,0,(TaperLength + KnurlLen – Protrusion)])
    cylinder(r2=ShaftDia/2,
    r1=(KnurlDia – DiamondDepth)/2,
    h=(TaperLength + Protrusion),
    $fn=NumSides);
    color("Moccasin")
    translate([0,0,(2*TaperLength + KnurlLen – Protrusion)])
    cylinder(r=ShaftDia/2,h=(ShaftLength + Protrusion),$fn=NumSides);
    color("Brown")
    translate([0,0,KnobOAL – Protrusion])
    cylinder(r=LocDia/2,h=(LocLength + Protrusion),$fn=NumSides);
    }
    for (i=[-1,1])
    translate([i*PinOC/2,0,KnobOAL – PinDepth])
    rotate(180/6)
    PolyCyl(PinDia,PinDepth + Protrusion,6);
    }

    It descends from a long line of similar things dating back to the OG Sherline Speed Wrenches.

  • Popsicle Mixing Sticks

    Popsicle Mixing Sticks

    Perhaps popsicle stick mixers?

    Popsicle stick mixer - in action
    Popsicle stick mixer – in action

    I made a batch to see if they’d simplify mixing my usual tiny batches of epoxy … and they do! Now I need not worry about forgetting to wipe off the screwdriver or cross-contaminating the resin / hardener tubes.

    Reshaping the tip so the laser beam enters at right angles to the stick produced a cleaner cut and a slightly narrower blade:

    Popsicle stick mixer - cutting
    Popsicle stick mixer – cutting

    The fixture and LightBurn template I made for the engraved markers came in handy. Aligning the template to the fixture proceeds as with the larger craft stick garden markers.

    A small holder keeps finished sticks ready for use:

    Popsicle stick mixer - presentation box
    Popsicle stick mixer – presentation box

    I don’t know how long the box originally holding 1000 sticks has been sitting on the shop shelf, but it’s at least half full despite my continuing efforts. Maybe I can get ahead on my holiday gift prep?

    The LightBurn SVG template layout as a GitHub Gist:

    Loading
    Sorry, something went wrong. Reload?
    Sorry, we cannot display this file.
    Sorry, this file is invalid so it cannot be displayed.

  • Tour Easy Running Lights: Firmware

    Tour Easy Running Lights: Firmware

    The optoisolator carrying the Bafang controller’s LIGHT signal pulls Pin 2 down to turn the LED on constantly for night riding:

        if (!Morser.continueSending())
            if (digitalRead(PIN_LIGHTMODE) == HIGH)
                Morser.startSending();
            else
                digitalWrite(PIN_OUTPUT,HIGH);      // constantly turn on in headlight mode
    

    That’s the entirety of the program’s loop() function, so there’s not much to the firmware.

    Imagine that: a whole computer devoted to sampling an input bit a zillion times a second and persistently setting an output bit:

    Tour Easy Running Light - Arduino view
    Tour Easy Running Light – Arduino view

    The Morse output to the rear is now “s” rather than “i” for more blinkiness, but I doubt anybody will ever notice.

    The next time I raise the hood on this thing, I’ll add a digital input to select FRONT or REAR mode to get me out of having to remember which hardware goes where.

    The Arduino source code as a GitHub Gist:

    // Tour Easy Running Light
    // Ed Nisley – KE4ZNU
    // September 2021
    // 2023-03 preprocessorize for front/rear lights
    // https://github.com/markfickett/arduinomorse
    #include <morse.h>
    // Bafang headlight output pulls pin low
    #define PIN_LIGHTMODE 2
    #define PIN_OUTPUT 13
    #define FRONT
    #if defined(FRONT)
    #define BLINKS "b e "
    #define POLARITY false
    #elif defined(REAR)
    #define BLINKS "s "
    #define POLARITY true
    #else
    #error "Needs FRONT or REAR"
    #endif
    // second param: true = active low output
    LEDMorseSender Morser(PIN_OUTPUT,POLARITY,(float)10.0);
    void setup()
    {
    pinMode(PIN_LIGHTMODE,INPUT_PULLUP);
    Morser.setup();
    Morser.setMessage(String("qst de ke4znu "));
    Morser.sendBlocking();
    Morser.setSpeed(75);
    Morser.setMessage(String(BLINKS));
    }
    void loop()
    {
    if (!Morser.continueSending())
    if (digitalRead(PIN_LIGHTMODE) == HIGH)
    Morser.startSending();
    else
    digitalWrite(PIN_OUTPUT,HIGH); // constantly turn on in headlight mode
    }

  • SJCAM M20 Camera: NP-BX1 Battery and Charger Holder

    SJCAM M20 Camera: NP-BX1 Battery and Charger Holder

    A little tweakage to the NP-BX1 battery holder for the astable multivibrator blinkies produced a simple version with the wire exit holes on the bottom:

    NP-BX1 Simple Holder - solid model
    NP-BX1 Simple Holder – solid model

    The four corner holes hold locating pins in the layered acrylic base:

    SJCAM M20 Battery Replacement - case layers
    SJCAM M20 Battery Replacement – case layers

    Those pins got cut slightly shorter to fit in the battery holder; in this photo they’re serving to align the layers and adhesive sheets while I stacked them up.

    The geometry is straightforward, with the outer perimeter matching the 3D printed battery holder:

    SJCAM M20 Car-Mode Battery Hack - battery case
    SJCAM M20 Car-Mode Battery Hack – battery case

    Cut one base and two wall layers from 3 mm (or a bit less) transparent acrylic, plus three adhesive sheets. I stuck adhesive on both sides of one wall layer, using the pins to align the adhesive, stuck the layer to the base, then topped it with the second wall layer, again using the alignment pins.

    The motivation for transparent layered acrylic is being able to see the charge controller’s red and green status LEDs glowing inside the box. This probably isn’t required, but seemed like a Good Idea™ for the initial version.

    With all that in hand, wire it up:

    SJCAM M20 Battery Replacement - charger wiring
    SJCAM M20 Battery Replacement – charger wiring

    The USB charger PCB sits atop a layer of double-sided foam tape. After verifying that the circuitry worked, I globbed the wires in place with hot-melt glue to make it less rickety than the picture suggests.

    The alert reader will have noticed the holes in the 3D printed NP-BX1 holder were drilled, not printed. In the unlikely event I need another case, the holes will automagically appear in the right place.

    I haven’t yet peeled the protective paper off that top adhesive sheet to make a permanent assembly:

    SJCAM M20 Battery Replacement - trial install
    SJCAM M20 Battery Replacement – trial install

    We use the car so infrequently that it’ll take a while to build up enough confidence to stick it together and stick it to the dashboard.

    On the whole, it’s ugly but sufficient to the task.

    A doodle with key dimensions, plus some ideas not surviving contact with reality:

    SJCAM M20 Car-Mode Battery Hack - case doodle
    SJCAM M20 Car-Mode Battery Hack – case doodle

    I truly hope this entire effort is a waste of time.

  • SJCAM M20 Camera: Car Mode Battery Hack

    SJCAM M20 Camera: Car Mode Battery Hack

    The last lithium cell (a.k.a. battery) for the longsuffering SJCAM M20 transformed itself into a spicy pillow:

    SJCAM M20 - spicy pillow lithium battery
    SJCAM M20 – spicy pillow lithium battery

    SJCAM no longer sells those batteries and nobody else does, either, surely because the +4.35V marking shows they’re a special-formula high-voltage lithium mix that doesn’t work with ordinary chargers. Worse, you can’t substitute an ordinary (i.e. cheap) battery, because applying a high-voltage charger to a 4.2 V cell makes Bad Things™ happen.

    Putting the M20 camera in Car Mode makes it begin recording when it sees 5 V on its USB input and shut down a few seconds after the USB input drops to 0 V. Without the internal battery, the camera’s clock doesn’t survive when the external power vanishes, which seems critical for a camera sitting on a dashboard.

    Mashing all that together, I wondered if I could use one of the many leftover low-voltage NP-BX1 batteries from the Sony AS30V helmet camera without starting a dashboard fire, by preventing the camera from charging the battery, while still using it when the USB input is inactive (which, for our car, is pretty nearly all the time).

    The circuitry, such as it is, uses a cheap 1S USB charge controller and a Schottky diode:

    SJCAM M20 Car-Mode Battery Hack - circuit doodle
    SJCAM M20 Car-Mode Battery Hack – circuit doodle

    Power comes in on the left from a USB converter plugged into the Accessory Power Outlet in the center console and goes out to the camera’s USB jack, using a butchered cable soldered to the charge controller’s pads in the middle. The controller manages the NP-BX1 battery as usual, but a diode prevents the camera from trying to send charge current into the controller.

    This should just barely work, as the diode reduces the battery voltage by a few hundred millivolts, so the camera will see the fully charged low-voltage battery as a mostly discharged high-voltage battery.

    Suiting action to words:

    SJCAM M20 Battery Replacement - circuitry
    SJCAM M20 Battery Replacement – circuitry

    It’s built inside the gutted remains of an M20 battery case. The 100µF tantalum cap provides local buffering to prevent the camera from browning out during bursts of file activity while recording. The wire emerges through holes gnawed in the battery case and the camera housing:

    SJCAM M20 Battery Replacement - camera cable exit
    SJCAM M20 Battery Replacement – camera cable exit

    The charge controller on the other end of the wire lives in a layered laser-cut acrylic case attached to a modified version of the venerable 3D printed NP-BX1 battery holder:

    SJCAM M20 Battery Replacement - charger wiring
    SJCAM M20 Battery Replacement – charger wiring

    More on the cases tomorrow.

    Putting it all together, the lashup goes a little something like this:

    SJCAM M20 Battery Replacement - trial install
    SJCAM M20 Battery Replacement – trial install

    The battery pack will eventually get stuck to the dashboard underneath the overhang, out of direct sunlight. Things get hot in there, but with a bit of luck the battery will survive.

    The rakish tilt puts the hood along the bottom of the image, although raising the camera would reduce tilt and cut down on the skyline view:

    SJCAM M20 Car-Mode Battery Hack - test ride
    SJCAM M20 Car-Mode Battery Hack – test ride

    The battery icon instantly switches from “charging” to “desperately low” when the USB power drops, which is about what I expected, but the camera continues to record for about ten seconds before shutting down normally.

    The NP-BX1 battery in the holder comes from the batch of craptastic BatMax batteries with a depressed starting voltage. An actual new cell with a slightly higher voltage would keep the camera slightly happier during those last ten seconds, but … so far, so good.

    Another possibility would be a trio of 1.5 V bucked lithium AA cells, with the diode to prevent charging and minus the charger.

  • Mini-Lathe Chuck Stops: CNC Pocketing

    Mini-Lathe Chuck Stops: CNC Pocketing

    With the fixture aligned and the chuck stop blank clamped down, all that’s left is to make three little pockets:

    Lathe Chuck Stop - Pocketing - LinuxCNC backplot
    Lathe Chuck Stop – Pocketing – LinuxCNC backplot

    Although Javascript may be the gom jabbar of programming, the blinding syntactic noise of raw G-Code puts you in a similar world of hurt:

    #<chuckrad>=20.000                  (radius to center of magnet)
    #<chuckjaws>=3                      (number of jaws)
    #<chuckang>=[360.0/#<chuckjaws>]    (angle between jaws)
    
    #<bitrad>=[2.900/2]                 (cutter radius)
    
    #<pocketrad>=[4.100/2]              (magnet pocket radius)
    #<pocketdeep>=2.200                 ( … depth)
    #<xoffs>=[#<pocketrad>-#<bitrad>]   (pocket center to cutter center)
    
    #<safez>=20.0                   (above all the clamps & gadgets)
    
    G21 G54 G80 G90 G94             (metric!)
    
    F600                            (full speed for the Sherline)
    
    G0 Z#<safez>
    

    Obviously, those magic numbers must match the laser-cut blanks, the magnets, the cutting bit in the spindle, the clamps on the table, the speed of the machine, and everything else you overlooked.

    So. Much. Pain.

    Knowing the angle to the current pocket, polar coordinate notation gets to the center point, with a jaunt in relative motion to the starting point for the helix into the pocket:

    #<ang>=[#<chuckang>/2]          (set starting angle)
    O100 REPEAT [#<chuckjaws>]
    
    G0 @#<chuckrad> ^#<ang>         (to hole center)
    G91                             (relative motion …)
    G0 X#<xoffs>                    ( … to helix start …)
    G90                             ( … and done)
    
    G0 Z0                           ( to surface)
    

    Each pocket consists of a helix cut to the bottom, two clearing passes, and another helix back to the surface:

    G2 I[-#<xoffs>] Z[-#<pocketdeep>] P[1+FUP[#<pocketdeep>]]   (into hole)
    G2 I[-#<xoffs>] P2                                          (clean bottom)
    G3 I[-#<xoffs>] Z0 P[1+FUP[#<pocketdeep>]]                  (shave sides)
    

    That dance produced rounder pockets with cleaner bottoms than just a single helix down and a straight pull upward.

    Then set up for the next hole and clean up after the last one:

    G0 @#<chuckrad> ^#<ang>         (back to center)
    G0 Z#<safez>
    
    #<ang>=[#<ang>+#<chuckang>]     (set up next hole)
    O100 ENDREPEAT
    
    G0 Z[2*#<safez>]
    G0 X0 Y0
    
    M2
    

    I ran the Sherline XY axes at their 600 mm/min top speed, the spindle at 10 kRPM with a shiny new 3 mm (nominal!) cutter, ramped into the helix at ≅10° (on a 1 mm circle!), and it sliced the acrylic into nice chips without getting all melty.

    Unlike with Javascript, when you get something wrong in G-Code, you can hear the crash.

    The LinuxCNC pocketing code as a GitHub Gist:

    (Magnet pockets for laser-cut lathe chuck stops)
    (2023-07 Ed Nisley)
    #<chuckrad>=20.000 (radius to center of magnet)
    #<chuckjaws>=3 (number of jaws)
    #<chuckang>=[360.0/#<chuckjaws>] (angle between jaws)
    #<bitrad>=[2.900/2] (cutter radius)
    #<pocketrad>=[4.100/2] (magnet pocket radius)
    #<pocketdeep>=2.200 ( … depth)
    #<xoffs>=[#<pocketrad>-#<bitrad>] (pocket center to cutter center)
    #<safez>=20.0 (above all the clamps & gadgets)
    G21 G54 G80 G90 G94 (metric!)
    F600 (full speed for the Sherline)
    G0 Z#<safez>
    #<ang>=[#<chuckang>/2] (set starting angle)
    O100 REPEAT [#<chuckjaws>]
    G0 @#<chuckrad> ^#<ang> (to hole center)
    G91 (relative motion …)
    G0 X#<xoffs> ( … to helix start …)
    G90 ( … and done)
    G0 Z0 ( to surface)
    G2 I[-#<xoffs>] Z[-#<pocketdeep>] P[1+FUP[#<pocketdeep>]] (into hole)
    G2 I[-#<xoffs>] P2 (clean bottom)
    G3 I[-#<xoffs>] Z0 P[1+FUP[#<pocketdeep>]] (shave sides)
    G0 @#<chuckrad> ^#<ang> (back to center)
    G0 Z#<safez>
    #<ang>=[#<ang>+#<chuckang>] (set up next hole)
    O100 ENDREPEAT
    G0 Z[2*#<safez>]
    G0 X0 Y0
    M2