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

  • Cleaning 3D Printed Recesses

    Having used screwdrivers and other improvised tools to clean out various 3D printed recesses, it finally penetrated my thick consciousness that a boring bar is exactly the right hammer for the job:

    Cleaning screw head recesses with a boring bar
    Cleaning screw head recesses with a boring bar

    In normal use, a boring bar’s head cuts mainly on its end surface, with the side cleaning up the hole’s periphery. Those edges remove droopy threads and Reversal zittage around a hole’s interior; an end mill works better to make the recess uniformly deeper.

    I have a few sets of these things, with larger & smaller cutting ends and longer & shorter shanks, that I occasionally use for lathe boring and rarely for mill boring (in the manual mill, not the Sherline!). The smallest head in the collection is maybe 4 mm across, so there’s a definite lower limit on the size of the hole they’ll clear.

    Hand-held while cutting plastic? They’ll last forever!

    I should probably print up some handles…

  • Planet Bike Superflash: Tour Easy Mount

    Having not yet gotten around to building better taillights for our bikes, we picked up some Planet Bike Superflash lights on sale. I don’t like single-LED lights, because the optics produce a concentrated beam (which is how they get such high lumen ratings) that’s essentially invisible anywhere off-axis; a taillight that requires careful alignment for maximum effect is a Bad Thing. But, eh, they were on sale…

    The graceful OEM seatpost mount, done in engineering plastic with smooth curves and something of a reputation for fragility, doesn’t work on a recumbent, so I build a butt-ugly mount that should last forever. It clamps firmly around a length of grippy silicone tape on the top seat frame rail:

    Superflash on Tour Easy
    Superflash on Tour Easy

    The reviews also complain that normal road vibrations transmitted through the somewhat whippy OEM mount pop the case apart, depositing the lens and electronics on the road behind you. Hence the black tape across the case joint.

    Here’s the whole affair on the bench:

    Superflash on mount
    Superflash on mount

    The weird color line comes from white plastic left in the extruder that covers the bottom layer or two of each part. I’m not fussy about the first pass of any new gadget, because I know I’ll build at least one more to get everything right.

    This is the first build arrangement; note the huge white teardrop blob at the start of the Skirt outline on the left. Obviously I didn’t have the initial retraction under control:

    Superflash mount on build platform
    Superflash mount on build platform

    The screw recesses built over the plate and got cute little support spiders to keep their interiors from sagging:

    Superflash mount - bolt support
    Superflash mount – bolt support

    After doing it that way, I flipped the top piece over so it builds with the screw head recesses upward to get a better finish on those nice curves. That means the arch needs support, which almost worked, although some of the fins fell over:

    Superflash mount - failed arch support
    Superflash mount – failed arch support

    The solid model now adds a two-layer-thick flat plate joining the fins that should hold them firmly to the build plate.

    Clamp Support - Solid Model
    Clamp Support – Solid Model

    I also added an option to build the flash mounting shoe separately:

    Superflash mount - solid model
    Superflash mount – solid model

    That gives better control over the flange thickness, which turns out to be critical parameter requiring a bit of adjustment with a file in the first version. Of course, the shoe needs an alignment pin and another assembly step to glue it in place:

    Superflash mount - gluing shoe
    Superflash mount – gluing shoe

    A 4-40 setscrew jams into the latch recess in the Superflash case, thus preventing it from walking off the shoe. You don’t need any particular pressure here, just enough protrusion to engage the case:

    Superflash mount - setscrew
    Superflash mount – setscrew

    The first pass at hex nut recesses were exactly cos(30) too large, as I forgot my Useful Sizes file has the across-the-points diameter, so I added a dab of epoxy to each recess before gluing the halves together with solvent:

    Superflash mount - glue clamping
    Superflash mount – glue clamping

    And then it’s all good.

    The OpenSCAD source code:

    // Planet Bike Superflash mount for Tour Easy seatback
    // Ed Nisley KE4ZNU - Dec 2011
    
    Layout = "Show";            // Assembly: Show
                                // Parts: Clamp Base Shoe Mount
                                // Build Plate: Build
    
    SeparateShoe = true;        // true = print mounting shoe separately
                                // false = join shoe to Mount block
    
    Support = true;             // true = include support
    
    Gap = 8;                    // between "Show" objects
    
    include </home/ed/Thing-O-Matic/lib/MCAD/units.scad>
    include </home/ed/Thing-O-Matic/Useful Sizes.scad>
    include </home/ed/Thing-O-Matic/lib/visibone_colors.scad>
    
    //-------
    //- Extrusion parameters must match reality!
    //  Print with +1 shells, 3 solid layers, 0.2 infill
    
    ThreadThick = 0.25;
    ThreadWidth = 2.0 * ThreadThick;
    
    HoleFinagle = 0.1;
    HoleFudge = 1.00;
    
    function HoleAdjust(Diameter) = HoleFudge*Diameter + HoleFinagle;
    
    Protrusion = 0.1;           // make holes end cleanly
    
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    function IntegerMultipleMin(Size,Unit) = Unit * floor(Size / Unit);
    
    //-------
    // Dimensions
    
    BarDia = (5/8) * inch;              // seat back rail diameter
    BarRad = BarDia/2;
    
    TapeThick = 0.3;                    // grippy tape around bar
    
    HoleDia = BarDia + 2*TapeThick;     // total hole dia
    HoleRad = HoleDia/2;
    HoleSides = 4*5;
    
    echo("Bar hole dia: ",HoleDia);
    
    TightSpace = 1.0;                   // space for tightening screws
    
    PlateWidth = 20.0;                  // mounting plate across flanges
    PlateLength = 20.0;                 //  ... parallel to flanges
    PlateThick = IntegerMultipleMin(1.96,ThreadThick);          //  ... thickness
    FlangeThick = IntegerMultiple(1.40,ThreadThick);            // lamp flange thickness
    FlangeWidth = 2.0;                  //  ... width
    
    ShoeThick = PlateThick + FlangeThick;    // dingus protruding from main block
    ShoeOffset = 1.0;                   // offset due to end wall
    
    echo("Shoe thickness: ",ShoeThick," = ",PlateThick," + ",FlangeThick);
    
    LockOffset = -5.0;                  // offset of locking setscrew
    
    TopRoundRad = 1.5*Head10_32/2;      // tidy rounding on top edge of clamp
    echo("Top rounding radius: ",TopRoundRad);
    
    NutDia = Nut10_32Dia*cos(30);       // adjust from across-points to across-flats dia
    NutPart = IntegerMultiple(0.5*Nut10_32Thick,ThreadThick);  // part of nut in each half
    
    BoltOffset = HoleRad + max(Head10_32,NutDia);
    BoltClear = Clear10_32;
    BoltHeadDia = Head10_32;
    BoltHeadThick = Head10_32Thick;
    
    MountWidth = PlateLength + ShoeOffset;         // side-to-side
    MountLength = HoleDia + 3.5*max(BoltHeadDia,NutDia);
    
    ClampHeight = TopRoundRad + HoleRad;            // includes gap/2 for simplicity
    BaseHeight = NutPart + HoleRad;                 //  ... likewise
    MountHeight = PlateWidth;
    
    echo("Mount width: ",MountWidth," length: ",MountLength);
    echo("Height of clamp: ",ClampHeight," base: ",BaseHeight," mount: ",MountHeight);
    echo(" total: ",ClampHeight+BaseHeight+MountHeight);
    
    AlignPegDia = 2.9;                  // shoe alignment peg
    AlignPegLength = ShoeThick;
    
    echo("Alignment peg length: ",AlignPegLength);
    
    //-------
    
    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=HoleAdjust(FixDia)/2,h=Height,$fn=Sides);
    }
    
    module ShowPegGrid(Space = 10.0,Size = 1.0) {
    
      Range = floor(50 / Space);
    
        for (x=[-Range:Range])
          for (y=[-Range:Range])
            translate([x*Space,y*Space,Size/2])
              %cube(Size,center=true);
    }
    
    //-------
    // Upper clamp half
    
    module Clamp() {
    
      difference() {
        translate([0,0,-TightSpace/2]) {
          difference() {
            translate([0,0,ClampHeight/2]) {
              intersection() {
                translate([0,0,-TopRoundRad])
                  minkowski() {
                    cube([(MountLength - 2*TopRoundRad),
                          (MountWidth - 2*Protrusion),
                          ClampHeight],center=true);
                    rotate([90,0,0])
                      cylinder(r=TopRoundRad,h=Protrusion,$fn=4*8);
    
                  }
                cube([MountLength,MountWidth,ClampHeight],center=true);
              }
            }
            translate([0,(MountWidth/2 + Protrusion)])
              rotate([90,0,0])
                PolyCyl(HoleDia,(MountWidth + 2*Protrusion),HoleSides);
            for (Index=[-1,1])
              translate([(Index*BoltOffset),0,0]) {
                translate([0,0,-Protrusion])
                  PolyCyl(BoltClear,(ClampHeight + Protrusion));
                translate([0,0,(ClampHeight - BoltHeadThick)])
                  PolyCyl(BoltHeadDia,(BoltHeadThick + Protrusion));
              }
          }
    
        }
        translate([0,0,-TightSpace/2])
          cube([(MountLength + 2*Protrusion),
               (MountWidth + 2*Protrusion),
               TightSpace],center=true);
      }
    
        if (Support)                    // choose support to suit printing orientation
          union() {
            translate([0,0,1.5*ThreadThick])
              cube([0.75*HoleDia,(MountWidth + 2*ThreadWidth),3*ThreadThick],center=true);
            intersection() {
              for (Index=[-3:3])
                translate([0,Index*(MountWidth/6),-TightSpace/2])
                  rotate([90,0,0])
                    cylinder(r=(HoleRad - 0.25*ThreadThick),
                            h=2*ThreadWidth,center=true,$fn=HoleSides);
              translate([-HoleRad,-MountWidth,0])
                cube([HoleDia,2*MountWidth,HoleRad]);
            }
          }
    }
    
    //-------
    // Lower clamp half = base
    
    module Base() {
    
      difference() {
        translate([0,0,-TightSpace/2])
          difference() {
            translate([0,0,BaseHeight/2])
              cube([MountLength,MountWidth,BaseHeight],center=true);
            translate([0,(MountWidth/2 + Protrusion)])
              rotate([90,0,0])
                PolyCyl(HoleDia,(MountWidth + 2*Protrusion),HoleSides);
            for (Index=[-1,1])
              translate([(Index*BoltOffset),0,0]) {
                translate([0,0,-Protrusion])
                  PolyCyl(BoltClear,(BaseHeight + Protrusion));
                translate([0,0,(BaseHeight - NutPart)])
                  rotate(30)
                    PolyCyl(NutDia,(NutPart + Protrusion),6);
    //              cylinder(r=NutDia/2,h=(NutPart + Protrusion),$fn=6);
              }
          }
        translate([0,0,-TightSpace/2])
          cube([(MountLength + 2*Protrusion),
               (MountWidth + 2*Protrusion),
               TightSpace],center=true);
    
      }
    
      if (Support)
        for (Index=[-1,1])                  // support inside nut openings
          translate([(Index*BoltOffset),
                    0,
                    (BaseHeight - (NutPart - ThreadThick) - TightSpace/2)]) {
            translate([0,0,0])
              for (Seg=[0:5]) {
                rotate(30 + 360*Seg/6)
                  cube([NutDia/2,2*ThreadWidth,NutPart - ThreadThick],center=false);
              }
          }
    
    }
    
    //-------
    // Superflash mounting shoe
    // Offset by -ShoeOffset/2 in Y to align on Mount (half of total offset on each side)
    
    module Shoe() {
    
        difference() {
          translate([-ShoeThick/2,-ShoeOffset/2,PlateWidth/2])
            if (SeparateShoe)
              cube([ShoeThick,PlateLength,PlateWidth],center=true);
            else
              cube([(ShoeThick + Protrusion),PlateLength,PlateWidth],center=true);
    
          translate([-(FlangeThick - Protrusion),
                    -(PlateLength/2 + ShoeOffset/2 + Protrusion),
                    (MountHeight - FlangeWidth)])
            cube([FlangeThick,(PlateLength + 2*Protrusion),(FlangeWidth + Protrusion)]);
    
          translate([-(FlangeThick - Protrusion),
                    -(PlateLength/2 + ShoeOffset/2 + Protrusion),
                    -Protrusion])
            cube([FlangeThick,(PlateLength + 2*Protrusion),(FlangeWidth + Protrusion)]);
    
          translate([-(ShoeThick + Protrusion),LockOffset,MountHeight/2])
            rotate([0,90,0])
              rotate(0)                 // align to match Mount hole orientation
                PolyCyl(Tap4_40,(ShoeThick + 2*Protrusion));
    
          if (SeparateShoe)
            translate([-(ShoeThick - AlignPegLength/2),0,MountHeight/2])
              rotate([0,90,0])
                PolyCyl(AlignPegDia,AlignPegLength);
        }
    }
    
    //-------
    // Bottom block for Superflash mount
    
    module Mount() {
    
      translate([0,0,MountHeight/2])
        union() {
          difference() {
            union() {
              translate([-MountLength/4,0,0])
                cube([MountLength/2,MountWidth,MountHeight],center=true);
              translate([((MountLength/2 - MountHeight)/2 + Protrusion),0,0])
                cube([(MountLength/2 - MountHeight + 2*Protrusion),
                     MountWidth,
                     MountHeight],center=true);
              translate([(MountLength/2 - MountHeight),0,0])
                intersection() {
                  translate([MountLength/4,0,0])
                    cube([MountLength/2,MountWidth,MountHeight],center=true);
                  translate([0,0,MountHeight/2])
                    rotate([90,0,0])
                      cylinder(r=MountHeight,h=MountWidth,center=true,$fn=4*16);
                }
            }
    
            translate([-(MountLength/2 + Protrusion),LockOffset,0])
              rotate([0,90,0])
                rotate(0)       // align through hole sides with point upward
                  PolyCyl(Clear4_40,(MountLength + 2*Protrusion));
    
            for (Index=[-1,1])
              translate([(Index*BoltOffset),0,0]) {
                translate([0,0,BaseHeight/2])
                  PolyCyl(BoltClear,(BaseHeight/2 + Protrusion));
                translate([0,0,(BaseHeight - NutPart)])
                  rotate(30)
                    PolyCyl(NutDia,(NutPart + Protrusion),6);
              }
    
            if (SeparateShoe)
              translate([-(MountLength/2 + AlignPegLength/2),0,0])
                rotate([0,90,0])
                  PolyCyl(AlignPegDia,AlignPegLength);
          }
    
          if (Support)
            for (Index=[-1,1])            // support inside nut openings
              translate([(Index*BoltOffset),0,(MountHeight/2 - (NutPart - ThreadThick))]) {
                translate([0,0,0])
                  for (Seg=[0:5]) {
                    rotate(30 + 360*Seg/6)
                      cube([NutDia/2,
                          2*ThreadWidth,
                          (NutPart - ThreadThick)],center=false);
                  }
              }
    
          if (!SeparateShoe)
            translate([-MountLength/2,0,-MountHeight/2])
              Shoe();
        }
    }
    
    //-------
    
    ShowPegGrid();
    
    if (Layout == "Clamp")
      Clamp();
    
    if (Layout == "Base")
      Base();
    
    if (Layout == "Shoe")
      Shoe();
    
    if (Layout == "Mount")
      Mount();
    
    if (Layout == "Show") {
      translate([0,0,(BaseHeight + MountHeight + Gap)]) {
        translate([0,0,TightSpace/2 + Gap])
          color(MFG) Clamp();
        translate([0,0,-TightSpace/2])
          rotate([180,0,0])
            color(DHC) Base();
      }
      translate([0,0,0])
        color(LDM) render(convexity=3) Mount();
    
      if (SeparateShoe)
        translate([-(MountLength/2 + Gap),0,0])
          color(DDM) Shoe();
    }
    
    if (Layout == "Build") {
      translate([-15,30,(BaseHeight - TightSpace/2)]) rotate([180,0,0])
        Base();
      translate([-15,00,0]) rotate([0,0,0])
        Clamp();
      if (SeparateShoe)
        translate([20,30,ShoeThick]) rotate([0,-90,180])
          Shoe();
      if (SeparateShoe)
        translate([-15,-30,MountHeight]) rotate([180,0,180])
          Mount();
      else
        translate([-15,-40,MountWidth/2]) rotate([90,0,180])
          Mount();
    
    }
    

    The original doodles, done on a retina-burning yellow scratchpad:

    Superflash Mount Doodles
    Superflash Mount Doodles
  • Thing-O-Matic: Revised start.gcode and end.gcode

    SJFW supports the Dimension plugin with the E extruder axis and doesn’t implement the older M101/M103/M108/M113 direct extruder commands. So I rewrote start.gcode to take advantage of that, compensate for Z-axis backlash, and clean up a few other nits; an older version (for MBI firmware) is there. The new files are at the bottom of this post.

    The M351 disables dry-run mode, which I often engage while manually tweaking things: running the whole start.gcode file without heating helps get all the coordinates just right.

    Coarse homing sets the axis positions to zero, rather than their real values, to simplify the fine homing process. I could switch into G91 relative motion to back off the switches, but this is easier. The fine homing now runs dead slow through the 2 mm backoff distance and produces very consistent results.

    The first wipe gets rid of any residual snot before touching off on the Z-minimum platform switch, although I generally pick it off with a tweezer as the extruder heats.

    Unlike the MBI firmware, SJFW’s G92 can set each axis independently, which means the Z-min touchoff need not simultaneously reset the XY position. Note that the G92 distance gets smaller to raise the Z=0.0 position: a thinner switch means a smaller distance to the platform, so when you make the value smaller (for a constant switch height) the nozzle doesn’t travel as far downward to Z=0.0.

    I’d hoped to use SJFW’s M220/M221 commands to auto-set the home positions (including Z-min!), but they don’t quite work for this “in development” version. At least, feeding the appropriate commands directly through pronsole produces error messages indicating that the home positions aren’t set after a single-axis move, so I’ll continue using G92 for a while.

    I ram 10 mm of filament into the extruder with the nozzle positioned in front of the wiper blade, which generally does not poot out a giant tangle of thread because the previous print ended with a retraction that leaves the extruder depressurized. The point here is to squirt out at least some thread to get the extruder pressure close to normal, then do a retraction and wipe to prevent drooling on the way to the start of the Skirt outline.

    The Z axis has about 0.2 mm of backlash, which I compensate in two places. The mechanical Z=0.0 position works out to be 0.2 mm above the platform with the Z axis descending, so I made the G92 value slightly too small. The next two Z motions after the G92 descend to Z=0.0 in the left rear corner, then ascend to Z=0.2 to take up the mechanical backlash without causing any mechanical motion. After that, the nozzle is exactly (kinda-sorta) 0.2 mm above the platform and that matches the firmware’s notion of where it is. All successive Z motions (including to the first layer at Z=0.25) will be upward, so the backlash stays compensated and every layer comes out just about exactly correct.

    The last G92 presets the E axis position so that the first anti-reversal at the start of the Skirt thread doesn’t produce a giant blob. This is totally empirical; I think my manual reversal after priming doesn’t result in exactly the same extruder pressure as Skeinforge’s reversals.

    SJFW doesn’t implement the G0 rapid-motion command at all (which, for me, is better than implementing it wrong), so all the motion commands use G1 with an appropriate F feedrate parameter.

    However, extruder speed control using a G1 F before the G1 E doesn’t produce consistent results. I think the extruder normally runs in inverse-time mode, where the speed makes the E distance come out right for the prevailing XY speed. All I know for sure is that changing the F parameter doesn’t work the way it should: it does not set the subsequent filament speed in any predictable way.

    The new start.gcode file:

    ;---- start.gcode begins ----
    ; TOM 286 - Al plates + Geared extruder + Zmin platform sense
    ; Ed Nisley - KE4ZNU - Dec 2011
    ; SJFW 1.11 without G0 and G28/G16x homing
    ; Not yet -> Requires M220/M221 endstop positions
    ; See TOM286-sjfw.gcode for EEPROM setup
    ;
    ; Set initial conditions
    G21                 ; set units to mm
    G90                 ; set positioning to absolute
    ;----------
    ; Dry run?
    M351 P0             ; P0 = normal P1 = no heat
    ;----------
    ; Begin heating
    M104 S200           ; extruder head
    M140 S110           ; HBP
    ;----------
    ; Coarse home axes
    ; Set zero at limits for convenience
    G1 Z999 F1500       ; home Z to get nozzle out of danger zone
    G92 Z0
    G1 Y-999 F4000      ; retract Y to get X out of front opening
    G92 Y0
    G1 X-999 F4000      ; now safe to home X
    G92 X0
    ;----------
    ; Fine home axes
    ; Set actual offsets!
    G1 X2 Y2 Z-2 F5000  ; back off switches
    G1 Z999 F50
    G92 Z116.3
    G1 Y-999 F50
    G92 Y-58.5
    G1 X-999 F50
    G92 X-53.5
    ;----------
    ; Initial nozzle wipe to clear snot for Z touchoff
    G1 X0 Y0 Z3.0 F1500     ; pause at center to build confidence
    G4 P1000
    G1 Z10                  ; ensure clearance
    G1 X39 Y-58.0 F10000    ; move to front, avoid wiper blade
    G1 X55                  ; to wipe station
    G1 Z6.0                 ; to wipe level
    M116                    ; wait for temperature settling
    G1 Y-45 F500            ; slowly wipe nozzle
    ;-----------------------------------------------
    ; Z platform height touchoff
    ; Make sure the XY position is actually over the switch!
    ; Home Z downward to platform switch
    ; Compensate for 0.05 mm backlash in G92: make it 0.05 too low
    G1 X55.5 Y8.2 Z3.0 F6000     ; get over build platform switch
    G1 Z0 F50                    ; home downward very slowly
    G92 Z1.35                    ; set Z-min switch height
    G1 Z6.0 F1000                ; back off switch to wipe level
    ;-----------------------------------------------
    ; Prime extruder to stabilize initial pressure
    G1 X55 Y-45 F6000   ; set up for wipe from rear
    G1 Y-58.0 F500      ; wipe to front
    G91                 ; use incremental motion for extrusion
    G1 F4               ; set slow rate
    G1 E10              ; extrude enough to get good pressure
    G1 F4000            ; set for fast retract
    G1 E-2.0            ; retract
    G90                 ; back to absolute motion
    G1 Y-45 F1000       ; wipe nozzle to rear
    ;----------
    ; Set up for Skirt start in left rear corner
    ; Compensate for Z backlash: move upward from zero point
    G1 X-50 Y55 Z0.0 F10000     ; left rear corner -- kiss platform
    G1 Z0.2 F1500       ; take up Z backlash to less than thread height
    G92 E1.5            ; preset to avoid huge un-Reversal blob
    ;G1 X0 Y0
    ;---- start.gcode ends ----
    

    The matching end.gcode on the other end of the file simply retracts a bit more filament, then positions the stage front-and-center for easy build plate removal:

    ;---- end.gcode starts ----
    ; TOM 286 - Al plates + Geared extruder
    ; Ed Nisley - KE4ZNU - Dec 2011
    ; SJFW 1.11 without G0 and G28/G16x homing
    ; Not yet -> Requires M220/M221 endstop positions
    ; See TOM286-sjfw.gcode for EEPROM setup
    ;- inhale filament blob
    G91
    G1 E-5 F900
    G90
    ;- turn off heaters
    M104 S0         ; extruder head
    M140 S0         ; HBP
    ;- move to eject position
    G1 Z999 F1000   ; home Z to get nozzle away from object
    G92 Z117.2      ; reset Z
    G1 X0 F6000     ; center X axis
    G1 Y35          ; move Y stage forward
    ;---- end.gcode ends ----
    

    The replace.csv file that squashes commands that SJFW doesn’t implement:

    M101	;-- M101 no Extruder Forward
    M103	;-- M103 no All Extruders Off
    M108	;-- M108 no Extruder Speed
    M113	;-- M113 no Extruder PWM
    
  • Blog Summary: 2011

    For the last year, it seems:

    • This is the go-to place for butterfly pictures
    • There’s an obvious need for Arduino PWM and SPI information
    • Houses are trouble
    Blog Summary - 2011 YTD
    Blog Summary – 2011 YTD

    Despite having no publicity, the site gets about 850 views per day: half from search engines, a third from RSS, and the rest from folks rattling around inside. Not much by commercial standards, but I’m pleased you’re interested enough to stop by…

  • Monthly Aphorism: On Branding

    • Never get a tattoo where the judge can see it

    I suppose if you don’t understand the wisdom in that, there’s no point in trying to explain it. Perhaps this will suffice: put the slip of paper you were taking to the tattoo artist in a safe place, fast-forward two decades, then look at it again before flipping the read-only bit.

    And “If you can read this, roll me over” tattooed above your rump is never appropriate…

  • SJFW: Initial EEPROM Configuration File

    Starting with acceleration and speed values based on those initial estimates, I hand-fed G-Code moves directly into the printer via pronsole while bouncing among these choices:

    • Increase acceleration until motor stalls
    • Decrease acceleration until motor starts
    • Increase velocity until motor stalls
    • Decrease velocity until motor runs

    The X axis runs fine at 333 mm/s = 20 m/min with 20 m/s2 acceleration; that little motor isn’t an impediment at all. As expected, the Y axis can’t accelerate that hard; it eventually started at 10 m/s2 to run at 250 mm/s = 15 m/min. I set X = 15 m/s2 and Y = 5 m/s2, with different maximum speeds.

    With those values in place, the printer can run the Smooth Axis Test at 250 mm/s, which is breathtaking and surprisingly noise-free: acceleration control eliminates the jarring start-stop motion. I modified Jackson’s G-Code to remove position testing, which uses codes that SJFW doesn’t implement.

    It’s worth mentioning I haven’t adjusted the motor currents at all.

    The Z axis can run at 2000 mm/min = 33 mm/s with acceleration around 1500 mm/s2. I backed that off to 1500 mm/min = 25 mm/s with 1000 mm/s2 acceleration. It’s noticeably faster than before, but that really doesn’t make much difference; there’s no point in replacing the stock MBI high-resistance / high-inductance motor.

    The E axis seems to require setting its speed with a separate G1 Exxx command (which is how Skeinforge does it) to get consistent results, although I confess to not taking good notes. I disconnected the filament drive to run the motor / gears without a load, got a workable speed & acceleration combination, reconnected the drive, fired up the hot end, and squirted filament all over the place to get actual numbers. It turns out that the little stepper on the extruder can actually ram a few millimeters of filament into the hot end at 4000 mm/min = 66 mm/s, but with acceleration down at 250 mm/s2. That’s dramatically peppier than the previous pace, which should reduce the Reversal Zittage problem.

    The resulting SJFW config file, with many unchanged default entries, will not work on a stock Thing-O-Matic:

    ; TOM286 with Z-min switch
    M402                            ; Write this config to EEPROM
    M309 P1 S0                      ; Endstops
    M300 X28 Y25 Z22 E15            ; STEP pins
    M301 X27 Y24 Z17 E14            ; DIR pins
    M302 X26 Y23 Z16 E3             ; ENABLE pins
    M304 X12 Y10 Z8                 ; MIN pins
    M305 Z7                         ; MAX pins
    M307 X1 Y1 Z0 E1                ; Axis Inversion
    M308 X0 Y0 Z0 E0                ; Disable After Move
    M200 X47.06985 Y47.06985 Z200  E48.30 ; Steps per MM
    M201 X1200     Y1200     Z1000 E60    ; Start Speed mm/min
    M202 X20000    Y15000    Z1500 E4000  ; Max speed mm/min
    M203 X1800     Y1800     Z1500 E90    ; Avg speed mm/min
    M206 X15000    Y5000     Z1000 E250   ; Acceleration mm/s^2
    ;M220 X-52.5    Y-58.5    Z1.50        ; Home - min
    ;M221                     Z117.2       ; Home - max
    ; LCD setup as per sjfw page on reprap wiki.
    M250 P47     ;set LCD RS pin
    M251 P43     ;set LCD RW pin
    M252 P41     ;set LCD E pin
    M253 S4 P39  ;set LCD Data 4 pin
    M253 S5 P37  ;set LCD Data 5 pin
    M253 S6 P35  ;set LCD Data 6 pin
    M253 S7 P33  ;set LCD Data 7 pin - always set last of all LCD pins OR ELSE!
    M255 S2 P48  ;set Keypad Col 3 pin
    M255 S1 P46  ;set Keypad Col 2 pin
    M254 S3 P42  ;set Keypad Row 4 pin
    M254 S2 P40  ;set Keypad Row 3 pin
    M254 S1 P38  ;set Keypad Row 2 pin
    M254 S0 P36  ;set Keypad Row 1 pin
    M255 S3 P34  ;set Keypad Col 4 pin
    M255 S0 P44  ;set Keypad Col 1 pin - always set last of all Keypad pins OR ELSE!
    M104 S0                          ; Turn off hotend heat (Important with EC!)
    M140 S0                          ; Turn off platform heat (Important with EC!)
    M350 P1                          ; Enable lookahead
    ;M211 P5000                       ; Report temperatures every 5 seconds
    M84                              ; Disable all Motors
    M400                             ; End EEPROM write
    

    I haven’t connected an LCD or keyboard to the thing; for me, printing is fire-and-forget.

  • Rant: Software Testing

    I admit to having expectations, perhaps unreasonable expectations, about what should transpire after filing a bug report with a software project large enough to have a bug tracking system.

    • Acknowledge the report right away, lest it appear nobody cares.
    • Figure out what else you need to know; I give good bug report, but if you need more, ask now.
    • Triage, set, and meet a due date, lest your development process appear shambolic.
    • When we outsiders become the software testers, keep us in the loop.

    My experience shows that the larger and better-funded the organization, the less productive will be any given report: individual problems get lost in the noise. Firefox, Ubuntu, and the late OpenOffice serve as cautionary tales; their forums may help, but submitting a problem report doesn’t increase the likelihood of getting a timely fix.

    I cut one-horse open-source operations considerable slack, although they rarely need any. For example, a recent OpenSCAD problem produced turnaround time measured in hours, including a completely new source file cooked up by a bystander and the lead developer polishing it off a sleep cycle later. That was on Christmas Eve, from on vacation, in a low-bandwidth zone, evidently through an iDingus.

    Perhaps I’m more sensitive to software quality assurance than most folks, but for good reason.

    Quite some years ago, my esteemed wife earned an IBM Outstanding Technical Achievement Award for testing a major component of a major OS. They don’t hand those tchotchkes out lightly and rarely for anything other than development. She had skip-leveled(*) to her umpteenth-level manager: “IBM must not ship this product. It does not work. It is an embarrassment. I will not concur with any decision to ship.

    The thing eventually shipped, half a year behind schedule, after the developers produced code that passed her test suite and she signed off on the results. Word has it that blood ran ankle-deep in the corridors toward the end.

    If you should ever encounter someone in need of a software-testing team leader who doesn’t take crap from anybody, let me know. She’ll definitely require considerable inducement to drag her back into the field, away from her gardens and quilting…

    So, anyway, we know a bit about software testing and verification and QC in these parts.

    Selah.

    (*) Old-school IBM jargon for walking around several levels of obstructive management to meet with a Big Shot in the corner office. Might not happen in the New IBM, alas.