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

  • Filament Drive Gear Calculations

    Some equations relevant to indentations produced by a filament drive gear:

    Filament Drive Gear Indentations
    Filament Drive Gear Indentations

    For reference, the smaller indentations are 0.25 mm deep and 1.3 mm across the bottom.

    Variables:

    • d = filament (a.k.a. circle) diameter
    • r = filament radius
    • m = chord depth (inward from circle)
    • c = chord length
    • Θ = angle across chord from circle center, degrees
    • A = chord area (a.k.a. indentation face area)

    The length of the chord at the bottom of the indentation, perpendicular to the filament axis:

    c = 2 sqrt(2mr - m2)

    The chord angle:

    Θ = 2 arcsin(c/2r)

    The chord area, which would be the indentation face if it were perpendicular, which it isn’t:

    A = (r2 / 2) x ((πΘ / 180) - sin(Θ))

    If you measured Θ in radians, the π/180 factor would Go Away.

    Some doodles showing that reducing the indentation from 0.25 to 0.15 reduces the chord area by a factor of two:

    Filament Drive Gear - indentation doodles
    Filament Drive Gear – indentation doodles

    The implication being that you must maintain fairly constant force on the drive bearing against the filament to prevent stripping the indentations.

  • Square Chain Mail Armor: Back From The Abyss

    After a Slic3r commit fixed the bridging regression, I ran off chain mail patches to celebrate:

    Square Chain Mail Armor - 3.3 3.5 4.0 thread bars
    Square Chain Mail Armor – 3.3 3.5 4.0 thread bars

    Two more Scli3r improvements calculate thin-wall and gap infill based on the available space, then vary the extrusion width to make the answers come out right for a given nozzle diameter. As a result, infill between close-set perimeter walls works much better than before; some of my long-held assumptions became invalid.

    The only differences between the sheets: tweaking the BarWidth and SheetSize parameters. The links recalculate themselves around those values.

    The OpenSCAD source code as a GitHub gist:

    // Chain Mail Armor Buttons
    // Ed Nisley KE4ZNU – December 2014
    Layout = "Build"; // Link Button LB Joiner Joiners Build PillarMod
    //——-
    //- Extrusion parameters must match reality!
    // Print with 1 shell and 2+2 solid layers
    ThreadThick = 0.25;
    ThreadWidth = 0.40;
    HoleWindage = 0.2;
    Protrusion = 0.1; // make holes end cleanly
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    //——-
    // Dimensions
    //- Set maximum sheet size
    SheetSizeX = 125; // 170 for full sheet on M2
    SheetSizeY = 125; // 230 …
    //- Diamond or rectangular sheet?
    Diamond = false; // true = rotate 45 degrees, false = 0 degrees for square
    BendAround = "X"; // X or Y = maximum flexibility *around* designated axis
    Cap = true; // true = build bridge layers over links
    CapThick = 4 * ThreadThick; // flat cap on link: >= 3 layers for solid bridging
    Armor = true && Cap; // true = build armor button atop (required) cap
    ArmorThick = IntegerMultiple(2.0,ThreadThick); // height above cap surface
    ArmorSides = 4;
    ArmorAngle = true ? 180/ArmorSides : 0; // true -> rotate half a side for best alignment
    //- Link bar sizes
    BarThick = 3 * ThreadThick;
    BarWidth = 3.3 * ThreadWidth;
    BarClearance = 3 * ThreadThick; // vertical clearance above & below bars
    VertexHack = false; // true to slightly reduce openings to avoid coincident vertices
    //- Compute link sizes from those values
    //- Absolute minimum base link: bar width + corner angle + build clearance around bars
    // rounded up to multiple of thread width to ensure clean filling
    BaseSide = IntegerMultiple((4*BarWidth + 2*BarWidth/sqrt(2) + 3*(2*ThreadWidth)),ThreadWidth);
    BaseHeight = 2*BarThick + BarClearance; // both bars + clearance
    echo(str("BaseSide: ",BaseSide," BaseHeight: ",BaseHeight));
    //echo(str(" Base elements: ",4*BarWidth,", ",2*BarWidth/sqrt(2),", ",3*(2*ThreadWidth)));
    //echo(str(" total: ",(4*BarWidth + 2*BarWidth/sqrt(2) + 3*(2*ThreadWidth))));
    BaseOutDiagonal = BaseSide*sqrt(2) – BarWidth;
    BaseInDiagonal = BaseSide*sqrt(2) – 2*(BarWidth/2 + BarWidth*sqrt(2));
    echo(str("Outside diagonal: ",BaseOutDiagonal));
    //- On-center distance measured along coordinate axis
    // the links are interlaced, so this is half of what you think it should be…
    LinkOC = BaseSide/2 + ThreadWidth;
    LinkSpacing = Diamond ? (sqrt(2)*LinkOC) : LinkOC;
    echo(str("Base spacing: ",LinkSpacing));
    //- Compute how many links fit in sheet
    MinLinksX = ceil((SheetSizeX – (Diamond ? BaseOutDiagonal : BaseSide)) / LinkSpacing);
    MinLinksY = ceil((SheetSizeY – (Diamond ? BaseOutDiagonal : BaseSide)) / LinkSpacing);
    echo(str("MinLinks X: ",MinLinksX," Y: ",MinLinksY));
    NumLinksX = ((0 == (MinLinksX % 2)) && !Diamond) ? MinLinksX + 1 : MinLinksX;
    NumLinksY = ((0 == (MinLinksY % 2) && !Diamond)) ? MinLinksY + 1 : MinLinksY;
    echo(str("Links X: ",NumLinksX," Y: ",NumLinksY));
    //- Armor button base
    ButtonHeight = BaseHeight + BarClearance + CapThick;
    echo(str("ButtonHeight: ",ButtonHeight));
    //- Armor ornament size & shape
    // Fine-tune OD & ID to suit the number of sides…
    TotalHeight = ButtonHeight + ArmorThick;
    echo(str("Overall Armor Height: ",TotalHeight));
    ArmorOD = 1.0 * BaseSide; // tune for best base fit
    ArmorID = 10 * ThreadWidth; // make the tip blunt & strong
    //——-
    module ShowPegGrid(Space = 10.0,Size = 1.0) {
    RangeX = floor(95 / Space);
    RangeY = floor(125 / Space);
    for (x=[-RangeX:RangeX])
    for (y=[-RangeY:RangeY])
    translate([x*Space,y*Space,Size/2])
    %cube(Size,center=true);
    }
    //——-
    // Create link with armor button as needed
    module Link(Topping = false) {
    LinkHeight = (Topping && Cap) ? ButtonHeight : BaseHeight;
    render(convexity=3)
    rotate((BendAround == "X") ? 90 : 0)
    rotate(Diamond ? 45 : 0)
    union() {
    difference() {
    translate([0,0,LinkHeight/2]) // outside shape
    intersection() {
    cube([BaseSide,BaseSide,LinkHeight],center=true);
    rotate(45)
    cube([BaseOutDiagonal,BaseOutDiagonal,(LinkHeight + 2*Protrusion)],center=true);
    }
    translate([0,0,(BaseHeight + BarClearance + 0*ThreadThick – Protrusion)/2])
    intersection() { // inside shape
    cube([(BaseSide – 2*BarWidth),
    (BaseSide – 2*BarWidth),
    (BaseHeight + BarClearance + 0*ThreadThick + (VertexHack ? Protrusion/2 : 0))],
    center=true);
    rotate(45)
    cube([BaseInDiagonal,
    BaseInDiagonal,
    (BaseHeight + BarClearance + 0*ThreadThick + (VertexHack ? Protrusion/2 : 0))],
    center=true);
    }
    translate([0,0,((BarThick + 2*BarClearance)/2 + BarThick)]) // openings for bars
    cube([(BaseSide – 2*BarWidth – 2*BarWidth/sqrt(2) – (VertexHack ? Protrusion/2 : 0)),
    (2*BaseSide),
    BarThick + 2*BarClearance – Protrusion],
    center=true);
    translate([0,0,(BaseHeight/2 – BarThick)])
    cube([(2*BaseSide),
    (BaseSide – 2*BarWidth – 2*BarWidth/sqrt(2) – (VertexHack ? Protrusion/2 : 0)),
    BaseHeight],
    center=true);
    }
    if (Topping && Armor)
    translate([0,0,(ButtonHeight – Protrusion)]) // sink slightly into the cap
    rotate(ArmorAngle)
    cylinder(d1=ArmorOD,d2=ArmorID,h=(ArmorThick + Protrusion), $fn=ArmorSides);
    }
    }
    //——-
    // Create split buttons to join sheets
    module Joiner() {
    translate([-LinkSpacing,0,0])
    difference() {
    Link(false);
    translate([0,0,BarThick + BarClearance + TotalHeight/2 – Protrusion])
    cube([2*LinkSpacing,2*LinkSpacing,TotalHeight],center=true);
    }
    translate([LinkSpacing,0,0])
    intersection() {
    translate([0,0,-(BarThick + BarClearance)])
    Link(true);
    translate([0,0,TotalHeight/2])
    cube([2*LinkSpacing,2*LinkSpacing,TotalHeight],center=true);
    }
    }
    //——-
    // Build it!
    //ShowPegGrid();
    if (Layout == "Link") {
    Link(false);
    }
    if (Layout == "Button") {
    Link(true);
    }
    if (Layout == "LB") {
    color("Brown") Link(true);
    translate([LinkSpacing,LinkSpacing,0])
    color("Orange") Link(false);
    }
    if (Layout == "Build")
    for (ix = [0:(NumLinksX – 1)],
    iy = [0:(NumLinksY – 1)]) {
    x = (ix – (NumLinksX – 1)/2)*LinkSpacing;
    y = (iy – (NumLinksY – 1)/2)*LinkSpacing;
    translate([x,y,0])
    color([(ix/(NumLinksX – 1)),(iy/(NumLinksY – 1)),1.0])
    if (Diamond)
    Link((ix + iy) % 2); // armor at odd,odd & even,even points
    else
    if ((iy % 2) && (ix % 2)) // armor at odd,odd points
    Link(true);
    else if (!(iy % 2) && !(ix % 2)) // connectors at even,even points
    Link(false);
    }
    if (Layout == "Joiner")
    Joiner();
    if (Layout == "Joiners") {
    NumJoiners = max(MinLinksX,MinLinksY)/2;
    for (iy = [0:(NumJoiners – 1)]) {
    y = (iy – (NumJoiners – 1)/2)*2*LinkSpacing + LinkSpacing/2;
    translate([0,y,0])
    color([0.5,(iy/(NumJoiners – 1)),1.0])
    Joiner();
    }
    }
    if (Layout == "PillarMod") // Slic3r modification volume to eliminate pillar infill
    translate([0,0,(BaseHeight + BarClearance)/2])
    cube([1.5*SheetSizeX,1.5*SheetSizeY,BaseHeight + BarClearance],center=true);
  • Raspberry Pi Power Heartbeat LED

    While looking for something else, I found a reference to the /boot/overlays/README file, wherein it is written:

            act_led_trigger         Choose which activity the LED tracks.
                                    Use "heartbeat" for a nice load indicator.
                                    (default "mmc")
    
            act_led_activelow       Set to "on" to invert the sense of the LED
                                    (default "off")
    
            act_led_gpio            Set which GPIO to use for the activity LED
                                    (in case you want to connect it to an external
                                    device)
                                    (default "16" on a non-Plus board, "47" on a
                                    Plus or Pi 2)
    
    ... snippage ...
    
            pwr_led_trigger
            pwr_led_activelow
            pwr_led_gpio
                                    As for act_led_*, but using the PWR LED.
                                    Not available on Model A/B boards.
    

    Although the power LED isn’t (easily) visible through the Canakit cases I’m using (it’s under the barely visible hole in front of the small hole near the hacked RUN connector), turning it into a heartbeat pulse distinguishes the CPU’s “running” and “halted” states; whether it will also distinguish “crashed” is up for grabs.

    It’s not at all clear what other choices you have.

    To enable heartbeating, add this to /boot/config.txt:

    # turn power LED into heartbeat
    dtparam=pwr_led_trigger=heartbeat
    #
    

    I expected a simple 50% duty cycle heartbeat, but it’s an annoying double blink: long off / on / off / on / long off. Fortunately, it still isn’t (easily) visible …

    While you have that file open, reduce the GPU memory to the absolute minimum for headless operation:

    # minimal GPU memory for headless operation
    gpu_mem=16
    #
    

    Some further ideas, including a way to turn off the HDMI interface.

  • LED Bulbs: Train Station Chandelier

    The three big chandeliers in the Poughkeepsie Train Station now sport LED bulbs:

    Poughkeepsie Train Station - LED bulbs
    Poughkeepsie Train Station – LED bulbs

    All three had 36 working bulbs and, with a bit of good QC, should continue that way for a long, long time.

    LED bulbs don’t have the intense point-source brilliance of clear tungsten bulbs and even the warm-white ones tend toward the cool end of the spectrum, but they’re Good Enough …

  • Streaming Player: NFS Program Distribution

    With three identical Raspberry Pi streaming players tootling around the house, it finally dawned on me that they should fetch their Python program directly from The Definitive Source, rather than a local copy.

    Tweak the auto-startup in /etc/rc.local:

    mount -o ro mollusk:/mnt/bulkdata/Project\ Files/Streaming\ Media\ Player/Firmware/ /mnt/part
    sudo -u pi python /mnt/part/Streamer.py &
    

    There’s probably a way to redirect all of the stdout and stderr results to a file for debugging, but the obvious method doesn’t work:

    sudo -u pi sh -c "python /mnt/part/Streamer.py 2>&1 > /tmp/st.log" &
    

    That redirects stdout from the subprocess call to set up the mixer, but doesn’t catch Python’s print output.

    Using the Python logging library would get most of the way to the goal, although stdout from things like the mixer would still vanish.

    Continuing with the network theme, one could netboot the RPi players, but that requires more sysadmin hackery than I’m willing to do, what with the good being the enemy of the best.

  • Panasonic Eneloop Pro NiMH Cells: First Charge

    The Sony DSC-H5 expects a much higher voltage from its pair of NiMH AA cells than the tired Sanyo Eneloops from 2010 can provide these days.

    Eneloop - 8-cell pack - 2010 - 2016 - merged
    Eneloop – 8-cell pack – 2010 – 2016 – merged

    The two upper curves show the first two charges for those eight cells back in 2010.

    The lower curve(s) started out with the wrong endpoint voltage (purple part of the middle curve), so I restarted the test (green curve) and edited the graph image to splice the two curves together into the purple/red curve.

    Although the capacity measured in mA·h isn’t much lower, the voltage depression reduces the available energy and trips the “low battery” alarm much earlier. In round numbers, the old cells were good for a few pictures, even hot off the charger, and didn’t have much energy left without being recharged before use.

    A quartet of Panasonic Eneloop Pro cells just arrived from BatterySpace, a nominally reputable supplier, all sporting a 14-05 date code suggesting they’re just shy of two years old. The packaging claims 85% charge retention after a year, so they should have a bit more than half of their rated 2.45 A·h “minimum” (or 2.55 mA·h “typical”, depending on whether you trust the label on the cell or the big print on the package) capacity remaining (although we don’t know the original state of charge, done from “solar power”). The lower curves say they arrived with 1 A·h remaining:

    Panasonic Eneloop - First Charge
    Panasonic Eneloop – First Charge

    However, the terminal voltage on those bottom curves would have any reasonable device reporting them as dead flat almost instantly, so you really can’t store Eneloops for two years: no surprise there.

    One pass through the 400 mA Sony charger produced the upper curves, with the dotted red curve from Cell A lagging in the middle. After that test, another pass through the charger brought Cell A back (upper solid red line) with the others, so I’ll assume it took a while to wake up.

    A pair of these in the camera will produce 2.2 V through 2.2 A·h, far better than the aged-out Sanyo Eneloops.

    Charging them at 400 mA = C/6 certainly counts as a slow charge. I’ve been charging the Sanyo cells in slow chargers in the hope that they’ll remain happier over the long term.

    The Panasonic Eneloops perform much better than some other cells you’ve seen around here, which may be due to the fact that I paid $5 each for them…

     

  • Sony NP-FS11 Lithium Battery Rebuilds: 2016

    It seems that two years is about as long as the NP-FS11 batteries last, as shown by the two lower curves from the ones I rebuilt in December 2013 with cells from 2011:

    Sony NP-FS11 2011-2016 Packs
    Sony NP-FS11 2011-2016 Packs

    The two middle curves with those same colors show the “back then” performance of those batteries: they’re shot in both total capacity and terminal voltage.

    I bought enough cells back in 2011 to leave two cells unused until now, which I built into a pack and charged. The green curve in the middle shows the result: those cells haven’t lost anything over the last five (!) years, as their performance still matches the other two batteries when they were new.

    The red curves come from a pair of batteries made with fresh new cells from batteryspace.com. They’re nominally 650 mA·h cells, so the NP-FS11 configuration (two parallel cells) should produce 1300 mA·h; surprisingly, they show 1500 mA·h with a nice voltage curve.

    So, although the 2011 cells work as well as their (now defunct) siblings, that pack can’t deliver the same capacity as the new cells. I expect I’ll rebuild it with 2016 cells in about a year.

    For whatever it’s worth, rebuilding these batteries goes much faster when I don’t have to saw them open. The Kapton tape wrapped around the case halves secures them well enough; there’s no need for fancy gluing.

    NP-FS11 Battery Rebuilds - 2016-03
    NP-FS11 Battery Rebuilds – 2016-03

    Yeah, I should make better labels. It’s hard to form a deep emotional attachment to the poor things, though.

    Here’s a case where something performs better than expected; I don’t always buy cheap junk from the usual eBay vendor…