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

  • Automated Scan-and-Enhance: ImageMagick to the Rescue

    Mary’s folks enjoy the daily crossword, but they wanted a slightly larger edition… and, after a bit of procrastination, I conjured up an automated way to make it happen, so her father need not do this manually with The GIMP and Xsane.

    The scanner, an old HP Scanjet 3970, dropped off the Windows driver list after Vista, so it now runs only with Linux.

    Doing the scan is straightforward, as it’s the default scanner:

    scanimage --mode Gray --opt_emulategray=yes --resolution 300 -x 115 -y 210 --format=pnm & scan.pnm
    

    The X and Y coordinates set the scan dimensions in millimeters, which should be as small as possible consistent with scanning the whole crossword.

    The driver produces output image files in PNM format, which isn’t particularly common these days, or TIFFImageMagick knows what to do with both of them; I picked PNM.

    Unfortunately, for some unknown reason, the SANE driver produces a severely low-contrast image:

    HP3900 Grayscale Scan
    HP3900 Grayscale Scan

    ImageMagick can produce a histogram:

    convert scan.pnm histogram:hist.png
    

    Which shows the problem:

    HP3900 Grayscale Histogram
    HP3900 Grayscale Histogram

    That’s using the grayscale emulation mode: the driver does a Color scan and converts to Gray mode for the output image. It seems having the driver do the conversion produces better results than scanning directly in Color and then applying ImageMagick, but it’s not my scanner and I don’t have a lot of experience with it.

    Given the PNM image:

    • Blow out the contrast
    • Resize the scan to fill the page
    • Crisp up the edges a bit
    convert scan.pnm -level 45%,60% -resize 2400x3000 +repage -unsharp 0 trim.png
    

    Which looks like this:

    Crossword - contrasty resize
    Crossword – contrasty resize

    This being Linux, the best way to print something is with either Postscript or PDF. I used PDF, because then we can look at the results with Reader, a more familiar program than, say, Evince:

    convert -density 300 -size 2550x3300 canvas:white trim.png -gravity center -composite page.pdf
    

    Which centers the crossword on the page over a white background with enough margin to keep the printer happy:

    Crossword - full page
    Crossword – full page

    That PDF goes to the default printer queue, where it’s turned into Postscript and comes out exactly like it should:

    lp page.pdf
    

    I gimmicked the default printer instance to use only black ink by creating a separate CUPS printer with the appropriate defaults. Other programs pay no attention to that setting and the printer uses colored inks. There is no explanation I can find for any of this; Linux / CUPS printing is basically a black box operation.

    In theory, you could print the composited image file as a PNG or some such, but I cannot make it come out the right size in the right place.

    You could do all of that in one line, with one huge ImageMagick invocation kicking off the scan and firing the result to the printer, but leaving some intermediate results lying along the trail isn’t necessarily a Bad Thing. I should probably use random temporary file names, though, in the interest of not polluting the namespace.

    All this happened remotely, with me signed on through SSH: hooray for the command line. Had to use SCP a few times to fetch those intermediate files to puzzle over the results, too.

    The complete Bash script:

    #!/bin/bash
    scanimage --mode Gray --opt_emulategray=yes --resolution 300 -x 115 -y 210 --format=pnm > /tmp/scan.pnm
    convert /tmp/scan.pnm -level 45%,60% -resize 2400x3000 +repage -unsharp 0 /tmp/trim.png
    convert -density 300 -size 2550x3300 canvas:white /tmp/trim.png -gravity center -composite /tmp/page.pdf
    lp /tmp/page.pdf
    

    A slightly closer scan crop with left and top margins may also work, at the cost of more precise positioning on the scanner:

    #!/bin/bash
    scanimage --mode Gray --opt_emulategray=yes --resolution 300 -l 5 -t 6 -x 105 -y 190 --format=pnm > /tmp/scan.pnm
    
    
  • Portable AC Vent Plug

    This plug for a portable air conditioner’s window vent may be un-buildable with my current state of 3D printer-fu. The top view shows the recess for a disk of insulating foam:

    Portable AC Vent Plug - solid model - top
    Portable AC Vent Plug – solid model – top

    The side view shows the thread profile and the groove for the O-ring seal:

    Portable AC Vent Plug - solid model - side
    Portable AC Vent Plug – solid model – side

    The bottom view shows the hemispheric finger grip recess:

    Portable AC Vent Plug - solid model - bottom
    Portable AC Vent Plug – solid model – bottom

    Basically, that design requires extensive support material no matter how it’s laid out. I tried the obvious way without any support, but that huge flat surface popped off the glass:

    AC Vent Plug - flat build
    AC Vent Plug – flat build

    The thread and groove overhangs in that orientation would require support and then extensive cleanout. Slic3r doesn’t do a good job of supporting internal layers, so the bottom of the recesses tend to flop into the hexagonal infill. I’m not sure building internal support all the way up the inside of the threads would be a Good Thing, though.

    Rotating by 90 degrees and cutting it in half failed because the automagic support structure popped off the platform:

    AC Vent Plug - split build
    AC Vent Plug – split build

    Admittedly, that was before I started using hairspray (on the platform!), but both orientations require far too much support. In fact, the rotated version might weigh half a kilo…

    I’m also generating the thread elements incorrectly; the joints don’t meet smoothly at the junctions. I think tapering each element so the smaller end nests inside the larger end will work better. Perhaps using a scaled hexagonal element would be better / faster than the current extruded 2D shape?

    I think the correct way to proceed will be a 3D print of the finger grip and flange section, oriented so the hemisphere points upward, with the threaded section made from a length of PVC pipe with lathe-turned threads and O-ring groove, butted against the flange around the grip section.

    Problem: that’s an 8 mm pitch thread and my inch-size lathe doesn’t do metric:

    • Need: 8 mm = 0.315 in → 3.175 TPI
    • Closest: 3-1/4 TPI → 0.308 in = 7.82 mm
    • Worse: 3 TPI → 0.333 in = 8.47 mm

    The plug needs about three turns, which means the 3-1/4 TPI = 7.82 mm pitch thread would be off by 0.54 mm, roughly a third of the thread form’s crest. That might actually work, as the “thread” on the inside of the pipe this thing fits into is actually a thin ridge, rather than an actual thread shape, and the plug is supposed to jam against the flange anyway.

    Maybe a four-axis setup in the Sherline, with the rotary table holding the PVC pipe (or whatever) aligned with the X axis? It would just barely fit under the spindle with the end mill in a collet.

    The pipe rack doesn’t hold any suitable plastic pipe.

    The OpenSCAD source code:

    // Portable AC Vent Plug
    // Ed Nisley KE4ZNU - June 2013
    
    Layout = "Show";			// Show Build Grip Helix HelixUnit Ring Plug Stiffener
    //-------
    //- Extrusion parameters must match reality!
    //  Print with +0 shells and 3 solid layers
    
    ThreadThick = 0.25;
    ThreadWidth = 0.40;
    
    HoleWindage = 0.3;
    
    Protrusion = 0.1;			// make holes end cleanly
    
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    
    //-------
    // Plug Dimensions
    
    PlugLength = 53.0;
    PlugOD = 128.0;
    
    FlangeThick = 5.0;
    FlangeWidth = 4.0;
    FlangeOD = 2*FlangeWidth + PlugOD;
    
    HelixPitch = 8.0;
    HelixOffset = 29.0 - HelixPitch/2;		// add half turn to ensure snug fit against flange
    HelixLength = PlugLength - HelixOffset;
    HelixDepth = 3.0;
    HelixBase = 3.5;
    HelixTip = 1.8;
    
    ORingDia = 3.4;
    ORingDepth = 0.75 * ORingDia;
    ORingOffset = ORingDia/2;
    
    GripOD = 90.0;
    GripDepth = 20.0;
    GripBar = 15.0;
    
    AlignDia = 3.0;
    AlignDepth = 5.0;
    
    FoamOD = PlugOD - 4*HelixDepth;
    FoamDepth = PlugLength - GripDepth;
    
    NumSides = 12*4;
    $fn = NumSides;
    
    echo("Flange OD: ",FlangeOD);
    echo("Overall length:",(FlangeThick + PlugLength));
    
    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);
    }
    
    module ShowPegGrid(Space = 10.0,Size = 1.0) {
    
      RangeX = floor(100 / Space);
      RangeY = floor(125 / Space);
    
    	for (x=[-RangeX:RangeX])
    	  for (y=[-RangeY:RangeY])
    		translate([x*Space,y*Space,Size/2])
    		  %cube(Size,center=true);
    
    }
    
    //-------
    // Recessed finger grip
    // Includes Protrusion below Z=0 for clean removal from plug
    
    module GripShape(Length,Width,Depth) {
    
    Radius = (pow(Depth,2) + pow(Length,2)/4) / (2*Depth);
    
    	difference(convexity=2) {
    		intersection() {
    			translate([0,0,(-Radius + Depth)])
    				sphere(r=Radius);
    			translate([0,0,(Depth/2 - Protrusion)])
    				cube([2*Radius,2*Radius,(Depth + Protrusion)],center=true);
    		}
    		translate([0,0,Depth/2])
    			cube([2*Radius,Width,(Depth + Protrusion)],center=true);
    	}
    }
    
    //-------
    // One section of the screw thread helix
    
    module HelixUnit() {
    
    UnitAdjust = 1.25;
    
    	linear_extrude(height=UnitAdjust*PI*PlugOD/NumSides,center=true)
    		polygon(points=[
    				[0,0],
    				[-HelixDepth,(HelixBase - HelixTip)/2],
    				[-HelixDepth,HelixBase - (HelixBase - HelixTip)/2],
    				[0,HelixBase],
    				[Protrusion,HelixBase],
    				[Protrusion,0],
    				[0,0]
    		]);
    }
    
    //-------
    // Helix Element Shape
    
    module HelixShape() {
    
        Turns = HelixLength/HelixPitch;
        Slices = Turns*NumSides;
        RotIncr = 1/NumSides;
        PitchRad = PlugOD/2;
        ZIncr = HelixLength/Slices;
        helixangle = atan(HelixPitch/(3.14159265358979*PlugOD));
    
        for (i = [0:Slices-1]) {
    		translate([PitchRad*cos(360*i/NumSides),PitchRad*sin(360*i/NumSides),i*ZIncr])
    			rotate([90+helixangle,0,360*i/NumSides])
    				HelixUnit();
    	}
    
    }
    
    //-------
    // O Ring Shape
    
    module ORingShape(MajorDia,MinorDia,Recess) {
    	rotate_extrude()
    		translate([MajorDia/2,0,0])
    			polygon(points=[[0,0],
    							[-Recess,0.25*MinorDia],
    							[-Recess,0.75*MinorDia],
    							[0,MinorDia],
    							[Protrusion,MinorDia],
    							[Protrusion,0],
    							[0,0]]);
    }
    
    //-------
    // Overall plug shape
    
    module PlugShape() {
    
    	difference(convexity=5) {
    		union() {
    			cylinder(r=FlangeOD/2,h=FlangeThick);
    			cylinder(r=PlugOD/2,h=(PlugLength + FlangeThick));
    		}
    		translate([0,0,(PlugLength + FlangeThick - FoamDepth)])
    			cylinder(r=FoamOD/2,h=(FoamDepth + Protrusion));
    		translate([0,0,(ORingOffset + FlangeThick - ORingDia/2)])
    			ORingShape(PlugOD,ORingDia,ORingDepth);
    		GripShape(GripOD,GripBar,GripDepth);
    /*		for (x=[-1,1])
    			translate([(x*PlugOD/4),AlignDepth,(FlangeThick + PlugLength/2)])
    				rotate([90,0,0])
    					PolyCyl(AlignDia,2*AlignDepth,6);
    */
    	}
    }
    
    //-------
    // Plug assembly
    
    module PlugAssembly() {
    
    	difference(convexity=5) {
    		render(convexity=5) PlugShape();
    		translate([0,0,(FlangeThick + HelixOffset)])
    			HelixShape();
    	}
    }
    
    //-------
    // Build it!
    
    ShowPegGrid();
    
    if (Layout == "Plug") {
    	PlugShape();
    }
    
    if (Layout == "Show") {
    	PlugAssembly();
    }
    
    if (Layout == "Build") {
    	difference() {
    		intersection(convexity=10) {
    			translate([0,0,FlangeOD/2])
    				cube([2*FlangeOD,3*PlugLength,FlangeOD],center=true);
    			union() {
    				translate([0,10,0])
    					rotate([90,0,180])
    						PlugAssembly();
    				translate([0,-10,0])
    					rotate([-90,0,180])
    						PlugAssembly();
    			}
    		}
    	}
    }
    
    if (Layout == "Grip")
    	GripShape(GripOD,GripBar,GripDepth);
    
    if (Layout == "Ring")
    	ORingShape(PlugOD,ORingDia,ORingDepth);
    
    if (Layout == "HelixUnit")
    	HelixUnit();
    
    if (Layout == "Helix")
    	HelixShape();
    
  • Magnetics Overview

    I wound up doing an impromptu magnetics (as it applies to transformers) review during a recent SqWr meeting. This summarizes, re-orders, and maybe expands on some quadrille paper scribbling, so that if I ever do it again I’ll have a better starting point. Searching on the obvious terms will produce a surfeit of links; Wikipedia may be helpful for diagrams.

    Corrections and further points-to-ponder will be gratefully received…

    Magnetizing force H comes from amp-turns (amps I, turns N) around the core, which produces flux phi = Φ = NI. Edit: that’s not quite right. Thanks to Martin Bosch for catching the units mismatch!

    Magnetomotive force ℱ comes from ampere-turns (amps I, turns N) around the core: mmf = ℱ = NI.

    The magnetizing force H is the mmf per unit length of the solenoid or core: H = ℱ / L.

    Flux density B comes from permeability (mu = μ) times H, which is a DC relationship that doesn’t care about frequency in the least: B = μH. For an air-core inductor or transformer, μ is the mu-sub-zero = μ0 of free space, but if there’s a core involved, then you use the permeability of the material near the conductor, which will be the material’s dimensionless relative permeability μR times μ0.

    Total flux Φ is the integral of flux density B over all the little areas covering the surface you’re interested in, oriented in a consistent manner using the right-hand rule.

    If you have an iron(-like) core inside the coil, then essentially all the flux is in the core, so the integral reduces to B times the area (call it a) of the core at right angles to the flux: Φ = Ba = μaH. In this case, μ is the relative permeability of the core times μ0 of free space.

    You can plot BH curves (B for various H values) using a straightforward circuit and an oscilloscope. The X axis voltage is proportional to the winding current I and the Y axis voltage is proportional to Φ. The trick is the integrator on the secondary that converts EMF = dΦ / dt into a voltage directly proportional to Φ. The same trick works on inductors if you add a few turns to act as a secondary.

    All that’s true for DC as well as AC, but transformers only work on AC, as summarized by Lenz.

    The induced EMF is proportional to flux change through the secondary windings, which number n turns: EMF = – n dΦ / dt. That’s obviously proportional to frequency: higher frequency = higher EMF. Flux is all in the transformer core, so it’s still μaH. Note that these are secondary turns, so it’s n rather than N. Air-core transformers exist, but coupling the flux poses a problem; looking up variometer or variocoupler may be instructive.

    The negative sign says the induced EMF creates a current that creates a magnetic field that points the other way, so as to oppose the original field change. In effect, the induced EMF works to cancel out the field you’re creating.

    Knowing how much EMF you need in the secondary for the purposes of your circuit, you know the product of five things:

    • n – secondary turns
    • μ – (relative) core permeability
    • a – area
    • f – frequency
    • H – from primary

    Now you get to pick what’s important, but they all have gotchas:

    The ratio n:N seems easy to control, but it tops out at a few hundred. If you care about the voltage ratio, then that fixes the turns ratio.

    Choose different core material to increase μ, but then you hit core saturation in B as H increases. Practical core materials may give you permeability over two or three orders of magnitude, but with significant side effects.

    Reduce B for a given Φ by using a larger core area a, which obviously requires a bigger core that may not fit the application.

    Increase frequency f to get more EMF and thus H, but it may be limited by your application and other losses and effects. Higher frequency = more traverses of that BH curve with hysteresis = more core losses = can’t use lossy metals.

    Increase primary H, but again you hit core saturation in B.

    The circuit driving the primary must be able to handle the total load, which means it must be able to drive the impedance presented by the transformer + secondary load. That determines the primary inductance (to get the reactance high enough that the transformer presents the secondary load to the primary circuit), which determines the core + turns at the operating frequency.

    The core must support the flux required to drive the load without saturation, which constrains the material and the area. For heavy loads (i.e., “power” transformers), output power also constrains the secondary turns and wire size, which constrains the minimum core opening and thus overall size.

  • TC4Server: Eagle HAL Device

    Dan Newman’s TC4Server turns the TC4 thermocouple board into a USB HID input device that’s compatible with HAL’s hal_input module:

    TC4 on ProtoScrewShield on Leonardo
    TC4 on ProtoScrewShield on Leonardo

    For simplicity (i.e., to avoid writing a special driver), TC4Server misrepresents itself as a nine-axis joystick-like device suited for RC airplane control:

    halrun
    halcmd: loadusr -W hal_input +A Leonardo
    halcmd: show
    ... snippage ...
    Component Pins:
    Owner   Type  Dir         Value  Name
         5  s32   OUT          2941  input.0.abs-rudder-counts
         5  s32   IN           4095  input.0.abs-rudder-flat
         5  s32   IN            255  input.0.abs-rudder-fuzz
         5  bit   OUT          TRUE  input.0.abs-rudder-is-neg
         5  bit   OUT         FALSE  input.0.abs-rudder-is-pos
         5  float IN        32767.5  input.0.abs-rudder-offset
         5  float OUT    -0.9102464  input.0.abs-rudder-position
         5  float IN        32767.5  input.0.abs-rudder-scale
         5  s32   OUT          2947  input.0.abs-rx-counts
         5  s32   IN           4095  input.0.abs-rx-flat
         5  s32   IN            255  input.0.abs-rx-fuzz
         5  bit   OUT          TRUE  input.0.abs-rx-is-neg
         5  bit   OUT         FALSE  input.0.abs-rx-is-pos
         5  float IN        32767.5  input.0.abs-rx-offset
         5  float OUT    -0.9100633  input.0.abs-rx-position
         5  float IN        32767.5  input.0.abs-rx-scale
         5  s32   OUT         65535  input.0.abs-ry-counts
         5  s32   IN           4095  input.0.abs-ry-flat
         5  s32   IN            255  input.0.abs-ry-fuzz
         5  bit   OUT         FALSE  input.0.abs-ry-is-neg
         5  bit   OUT          TRUE  input.0.abs-ry-is-pos
         5  float IN        32767.5  input.0.abs-ry-offset
         5  float OUT             1  input.0.abs-ry-position
         5  float IN        32767.5  input.0.abs-ry-scale
         5  s32   OUT         65535  input.0.abs-rz-counts
         5  s32   IN           4095  input.0.abs-rz-flat
         5  s32   IN            255  input.0.abs-rz-fuzz
         5  bit   OUT         FALSE  input.0.abs-rz-is-neg
         5  bit   OUT          TRUE  input.0.abs-rz-is-pos
         5  float IN        32767.5  input.0.abs-rz-offset
         5  float OUT             1  input.0.abs-rz-position
         5  float IN        32767.5  input.0.abs-rz-scale
         5  s32   OUT         65535  input.0.abs-throttle-counts
         5  s32   IN           4095  input.0.abs-throttle-flat
         5  s32   IN            255  input.0.abs-throttle-fuzz
         5  bit   OUT         FALSE  input.0.abs-throttle-is-neg
         5  bit   OUT          TRUE  input.0.abs-throttle-is-pos
         5  float IN        32767.5  input.0.abs-throttle-offset
         5  float OUT             1  input.0.abs-throttle-position
         5  float IN        32767.5  input.0.abs-throttle-scale
         5  s32   OUT          2957  input.0.abs-wheel-counts
         5  s32   IN           4095  input.0.abs-wheel-flat
         5  s32   IN            255  input.0.abs-wheel-fuzz
         5  bit   OUT          TRUE  input.0.abs-wheel-is-neg
         5  bit   OUT         FALSE  input.0.abs-wheel-is-pos
         5  float IN        32767.5  input.0.abs-wheel-offset
         5  float OUT    -0.9097581  input.0.abs-wheel-position
         5  float IN        32767.5  input.0.abs-wheel-scale
         5  s32   OUT          2942  input.0.abs-x-counts
         5  s32   IN           4095  input.0.abs-x-flat
         5  s32   IN            255  input.0.abs-x-fuzz
         5  bit   OUT          TRUE  input.0.abs-x-is-neg
         5  bit   OUT         FALSE  input.0.abs-x-is-pos
         5  float IN        32767.5  input.0.abs-x-offset
         5  float OUT    -0.9102159  input.0.abs-x-position
         5  float IN        32767.5  input.0.abs-x-scale
         5  s32   OUT          2942  input.0.abs-y-counts
         5  s32   IN           4095  input.0.abs-y-flat
         5  s32   IN            255  input.0.abs-y-fuzz
         5  bit   OUT          TRUE  input.0.abs-y-is-neg
         5  bit   OUT         FALSE  input.0.abs-y-is-pos
         5  float IN        32767.5  input.0.abs-y-offset
         5  float OUT    -0.9102159  input.0.abs-y-position
         5  float IN        32767.5  input.0.abs-y-scale
         5  s32   OUT          2940  input.0.abs-z-counts
         5  s32   IN           4095  input.0.abs-z-flat
         5  s32   IN            255  input.0.abs-z-fuzz
         5  bit   OUT          TRUE  input.0.abs-z-is-neg
         5  bit   OUT         FALSE  input.0.abs-z-is-pos
         5  float IN        32767.5  input.0.abs-z-offset
         5  float OUT     -0.910277  input.0.abs-z-position
         5  float IN        32767.5  input.0.abs-z-scale
         5  s32   OUT          2941  input.1.abs-rudder-counts
         5  s32   IN           4095  input.1.abs-rudder-flat
         5  s32   IN            255  input.1.abs-rudder-fuzz
         5  bit   OUT          TRUE  input.1.abs-rudder-is-neg
         5  bit   OUT         FALSE  input.1.abs-rudder-is-pos
         5  float IN        32767.5  input.1.abs-rudder-offset
         5  float OUT    -0.9102464  input.1.abs-rudder-position
         5  float IN        32767.5  input.1.abs-rudder-scale
         5  s32   OUT          2947  input.1.abs-rx-counts
         5  s32   IN           4095  input.1.abs-rx-flat
         5  s32   IN            255  input.1.abs-rx-fuzz
         5  bit   OUT          TRUE  input.1.abs-rx-is-neg
         5  bit   OUT         FALSE  input.1.abs-rx-is-pos
         5  float IN        32767.5  input.1.abs-rx-offset
         5  float OUT    -0.9100633  input.1.abs-rx-position
         5  float IN        32767.5  input.1.abs-rx-scale
         5  s32   OUT         65535  input.1.abs-ry-counts
         5  s32   IN           4095  input.1.abs-ry-flat
         5  s32   IN            255  input.1.abs-ry-fuzz
         5  bit   OUT         FALSE  input.1.abs-ry-is-neg
         5  bit   OUT          TRUE  input.1.abs-ry-is-pos
         5  float IN        32767.5  input.1.abs-ry-offset
         5  float OUT             1  input.1.abs-ry-position
         5  float IN        32767.5  input.1.abs-ry-scale
         5  s32   OUT         65535  input.1.abs-rz-counts
         5  s32   IN           4095  input.1.abs-rz-flat
         5  s32   IN            255  input.1.abs-rz-fuzz
         5  bit   OUT         FALSE  input.1.abs-rz-is-neg
         5  bit   OUT          TRUE  input.1.abs-rz-is-pos
         5  float IN        32767.5  input.1.abs-rz-offset
         5  float OUT             1  input.1.abs-rz-position
         5  float IN        32767.5  input.1.abs-rz-scale
         5  s32   OUT         65535  input.1.abs-throttle-counts
         5  s32   IN           4095  input.1.abs-throttle-flat
         5  s32   IN            255  input.1.abs-throttle-fuzz
         5  bit   OUT         FALSE  input.1.abs-throttle-is-neg
         5  bit   OUT          TRUE  input.1.abs-throttle-is-pos
         5  float IN        32767.5  input.1.abs-throttle-offset
         5  float OUT             1  input.1.abs-throttle-position
         5  float IN        32767.5  input.1.abs-throttle-scale
         5  s32   OUT          2957  input.1.abs-wheel-counts
         5  s32   IN           4095  input.1.abs-wheel-flat
         5  s32   IN            255  input.1.abs-wheel-fuzz
         5  bit   OUT          TRUE  input.1.abs-wheel-is-neg
         5  bit   OUT         FALSE  input.1.abs-wheel-is-pos
         5  float IN        32767.5  input.1.abs-wheel-offset
         5  float OUT    -0.9097581  input.1.abs-wheel-position
         5  float IN        32767.5  input.1.abs-wheel-scale
         5  s32   OUT          2942  input.1.abs-x-counts
         5  s32   IN           4095  input.1.abs-x-flat
         5  s32   IN            255  input.1.abs-x-fuzz
         5  bit   OUT          TRUE  input.1.abs-x-is-neg
         5  bit   OUT         FALSE  input.1.abs-x-is-pos
         5  float IN        32767.5  input.1.abs-x-offset
         5  float OUT    -0.9102159  input.1.abs-x-position
         5  float IN        32767.5  input.1.abs-x-scale
         5  s32   OUT          2942  input.1.abs-y-counts
         5  s32   IN           4095  input.1.abs-y-flat
         5  s32   IN            255  input.1.abs-y-fuzz
         5  bit   OUT          TRUE  input.1.abs-y-is-neg
         5  bit   OUT         FALSE  input.1.abs-y-is-pos
         5  float IN        32767.5  input.1.abs-y-offset
         5  float OUT    -0.9102159  input.1.abs-y-position
         5  float IN        32767.5  input.1.abs-y-scale
         5  s32   OUT          2940  input.1.abs-z-counts
         5  s32   IN           4095  input.1.abs-z-flat
         5  s32   IN            255  input.1.abs-z-fuzz
         5  bit   OUT          TRUE  input.1.abs-z-is-neg
         5  bit   OUT         FALSE  input.1.abs-z-is-pos
         5  float IN        32767.5  input.1.abs-z-offset
         5  float OUT     -0.910277  input.1.abs-z-position
         5  float IN        32767.5  input.1.abs-z-scale
    
    ... snippage ...
    Parameters:
    Owner   Type  Dir         Value  Name
         5  s32   RO          65535  input.0.abs-rudder-max
         5  s32   RO              0  input.0.abs-rudder-min
         5  s32   RO          65535  input.0.abs-rx-max
         5  s32   RO              0  input.0.abs-rx-min
         5  s32   RO          65535  input.0.abs-ry-max
         5  s32   RO              0  input.0.abs-ry-min
         5  s32   RO          65535  input.0.abs-rz-max
         5  s32   RO              0  input.0.abs-rz-min
         5  s32   RO          65535  input.0.abs-throttle-max
         5  s32   RO              0  input.0.abs-throttle-min
         5  s32   RO          65535  input.0.abs-wheel-max
         5  s32   RO              0  input.0.abs-wheel-min
         5  s32   RO          65535  input.0.abs-x-max
         5  s32   RO              0  input.0.abs-x-min
         5  s32   RO          65535  input.0.abs-y-max
         5  s32   RO              0  input.0.abs-y-min
         5  s32   RO          65535  input.0.abs-z-max
         5  s32   RO              0  input.0.abs-z-min
         5  s32   RO          65535  input.1.abs-rudder-max
         5  s32   RO              0  input.1.abs-rudder-min
         5  s32   RO          65535  input.1.abs-rx-max
         5  s32   RO              0  input.1.abs-rx-min
         5  s32   RO          65535  input.1.abs-ry-max
         5  s32   RO              0  input.1.abs-ry-min
         5  s32   RO          65535  input.1.abs-rz-max
         5  s32   RO              0  input.1.abs-rz-min
         5  s32   RO          65535  input.1.abs-throttle-max
         5  s32   RO              0  input.1.abs-throttle-min
         5  s32   RO          65535  input.1.abs-wheel-max
         5  s32   RO              0  input.1.abs-wheel-min
         5  s32   RO          65535  input.1.abs-x-max
         5  s32   RO              0  input.1.abs-x-min
         5  s32   RO          65535  input.1.abs-y-max
         5  s32   RO              0  input.1.abs-y-min
         5  s32   RO          65535  input.1.abs-z-max
         5  s32   RO              0  input.1.abs-z-min
    ... snippage ...
    

    Dan’s program assigns the outputs thusly:

    • Wheel – ambient temperature as measured on TC4 board
    • X Y Z Rudder – thermocouples – channels 1 through 4
    • RX RY RZ  Throttle – thermistors – channels 5 through 8

    I created a huge Eagle device that encapsulates the whole thing. A simple demo schematic includes the constants that make the temperatures come out in °C:

    TC4Server - Eagle Schematic
    TC4Server – Eagle Schematic

    That picture produces this HAL file:

    # HAL config file automatically generated by Eagle-CAD ULP:
    # [/mnt/bulkdata/Project Files/eagle/ulp/hal-write-2.5.ulp]
    # (C) Martin Schoeneck.de 2008
    # Charalampos Alexopoulos 2011
    # Mods Ed Nisley KE4ZNU 2010 2013
    # Path        [/mnt/bulkdata/Project Files/eagle/projects/LinuxCNC for M2/]
    # ProjectName [LinuxCNC M2 - TC4Server Test]
    # File name   [/mnt/bulkdata/Project Files/eagle/projects/LinuxCNC for M2/TC4Server.hal]
    # Created     [20:03:16 03-Jun-2013]
    
    ####################################################
    # Load realtime and userspace modules
    loadusr -W hal_input -A +Leonardo
    loadrt threads name1=servo-thread period1=1000000
    loadrt constant        count=4
    loadrt conv_float_s32        count=2
    
    ####################################################
    # Hook functions into threads
    addf constant.0        servo-thread
    addf constant.1        servo-thread
    addf constant.2        servo-thread
    addf constant.3        servo-thread
    addf conv-float-s32.0        servo-thread
    addf conv-float-s32.1        servo-thread
    
    ####################################################
    # Set parameters
    
    ####################################################
    # Set constants
    setp constant.0.value    10
    setp constant.1.value    2732
    setp constant.2.value    0
    setp constant.3.value    0
    
    ####################################################
    # Connect Modules with nets
    net n_2 constant.2.out conv-float-s32.1.in
    net n_3 constant.3.out conv-float-s32.0.in
    net tc4-ambient input.0.abs-wheel-position
    net tc4-flat input.0.abs-wheel-flat input.0.abs-x-flat input.0.abs-y-flat input.0.abs-z-flat input.0.abs-rudder-flat input.0.abs-rx-flat input.0.abs-ry-flat input.0.abs-rz-flat input.0.abs-throttle-flat conv-float-s32.1.out
    net tc4-fuzz input.0.abs-throttle-fuzz input.0.abs-rz-fuzz input.0.abs-ry-fuzz input.0.abs-rx-fuzz input.0.abs-rudder-fuzz input.0.abs-z-fuzz input.0.abs-y-fuzz input.0.abs-x-fuzz input.0.abs-wheel-fuzz conv-float-s32.0.out
    net tc4-offset input.0.abs-wheel-offset input.0.abs-x-offset input.0.abs-y-offset input.0.abs-z-offset input.0.abs-rudder-offset input.0.abs-rx-offset input.0.abs-ry-offset input.0.abs-rz-offset input.0.abs-throttle-offset constant.1.out
    net tc4-scale input.0.abs-wheel-scale input.0.abs-x-scale input.0.abs-y-scale input.0.abs-z-scale input.0.abs-rudder-scale input.0.abs-rx-scale input.0.abs-ry-scale input.0.abs-rz-scale input.0.abs-throttle-scale constant.0.out
    net tcouple-1 input.0.abs-x-position
    net tcouple-2 input.0.abs-y-position
    net tcouple-3 input.0.abs-z-position
    net tcouple-4 input.0.abs-rudder-position
    net tmistor-5 input.0.abs-rx-position
    net tmistor-6 input.0.abs-ry-position
    net tmistor-7 input.0.abs-rz-position
    net tmistor-8 input.0.abs-throttle-position
    

    Fire it up with halrun to see the temperatures (alphabetically by the pin name):

    halrun -I -f TC4Server.hal
    halcmd: start
    halcmd: show pin *position
    Component Pins:
    Owner   Type  Dir         Value  Name
         5  float OUT          20.9  input.0.abs-rudder-position ==> tcouple-4
         5  float OUT          21.5  input.0.abs-rx-position ==> tmistor-5
         5  float OUT        6280.3  input.0.abs-ry-position ==> tmistor-6
         5  float OUT        6280.3  input.0.abs-rz-position ==> tmistor-7
         5  float OUT        6280.3  input.0.abs-throttle-position ==> tmistor-8
         5  float OUT          22.5  input.0.abs-wheel-position ==> tc4-ambient
         5  float OUT            21  input.0.abs-x-position ==> tcouple-1
         5  float OUT            21  input.0.abs-y-position ==> tcouple-2
         5  float OUT          20.8  input.0.abs-z-position ==> tcouple-3
    

    The sensors do not correspond to the picture at the top: only the first thermocouple and first thermistor are connected ; the ADC returns bogus data for disconnected inputs, which means you must be careful about tightening the wires and checking the result. Dan’s firmware has the ability to disable unused sensors, in which case you get a huge value; when used for heater control, a sensor failing high means the heater will turn off, but, should you use this gadget in a freezer, you might want them to fail low (so modify the code for your own use).

    The ambient temperature reported for the board runs 1 or 2 °C higher than the actual ambient air temperature, probably because of all those components doing useful things up close to the sensor chip. That particular ambient temperature serves as the cold junction reference for the thermocouples; the other temperatures don’t change very much as the board warms up, so it’s all good.

    Remember to issue the start command in halrun, because otherwise nothing changes.

    Also remember that you must configure TC4Server with the thermistor characteristics before you use it as a hal_input device.

  • DIY Vanilla Extract: Vintage 2013

    It seems we go through maybe 1.5 l of vanilla extract in a bit over a year , so a month ago I picked up a pound of Grade B Madagascar Vanilla Beans from the usual eBay supplier, two bottles of low-end vodka (Popov and Crystal Palace), and combined them in a pair of apple juice bottles. Fast forward through a month of daily shaking and we have a still life with chopped vanilla beans:

    DIY Vanilla Extract 2013
    DIY Vanilla Extract 2013

    Having established that running the extracted and drained bean chunks through a blender doesn’t produce anything useful, I dumped the chunks into a pair of plastic tubs with loose-fitting lids and put them in the basement. That ought to improve the ambiance down there something fine!

    The net yield looks to be a bit over 1.5 liter of extract from each 1.9 liter apple juice jar filled with vodka + chopped beans. The rule of thumb is a quart (= 1 liter) of hooch for each 1/4 pound of beans, but I figure this isn’t an exact science and those jars came from the stockpile.

    To my untrained nose, the Crystal Palace vanilla extract smells better than the Popov brew, but I’d be surprised if that makes any difference in the pancakes.

    Verily: nothing exceeds like excess!

  • Stapler Latch Spring Replacement

    It’s garden fence stapling season, which means it’s time for the annual stapler annoyance. There’s supposed to be a plastic tab angling under the right side of the latch, pressing against the bottom of the staple channel and forcing the triangular tab on the top out through a small opening. All that’s left is the stub:

    Stapler latch - broken spring tab
    Stapler latch – broken spring tab

    It looks like it ought to be a great 3D printing project, but I came to my senses and snipped off a length of phosphor bronze spring stock, rolled it up, and positioned it inside the opening:

    Stapler latch - metal spring fitting
    Stapler latch – metal spring fitting

    The latch slides into the staple channel until the pin on the side you can see above engages a hole in the channel. The spring looked like this on the way in:

    Stapler latch - before closing
    Stapler latch – before closing

    And now it’s ready for action:

    Stapler latch - in place
    Stapler latch – in place

    Works like a champ and took maybe 15 minutes, tops, to accomplish. Shame it took me a few decades to get around to fixing it, but I finally dropped the clipped-and-filed nail I’d been jamming between the latch and the channel one too many times…

  • Search Engine Optimization: Which One Is Not Like The Other Ones?

    Now that Google encrypts your search terms (so they can sell the results to their customers), it’s harder to determine where folks come from. WordPress does report whatever search terms it can, though, and a recent search for plastic kitchen sink strainer caught my eye.

    Here’s what you get (or, at least, what I got on that day) by feeding those words into Google Image Search:

    Plastic Kitchen Sink Strainer - Image search results
    Plastic Kitchen Sink Strainer – Image search results

    Search engine optimization like that is to die for, eh?

    The related post described a cleanup operation that didn’t really achieve very much in the long run:

    Skimming the strainer
    Skimming the strainer

    Some years ago I machined a pair of smoke gray acrylic sink strainers (using LinuxCNC / EMC2 loops and trig functions) on the Sherline and wrote it up for my Digital Machinist column. They came out quite nicely:

    CNC Sink Strainer
    CNC Sink Strainer

    Then I did a 3D printed version on the Thing-O-Matic:

    Strainer plate fill
    Strainer plate fill

    Which produced a note about small features and another Digital Machinist column, of course.

    Subtractive machining is definitely the right hammer for some jobs…