Archive for August, 2015

Stereo Zoom Microscope: USB Camera Mount

My stereo zoom microscope neatly filled the entrance pupil of the late, lamented Casio EX-Z850, so that a simple adapter holding it on the eyepiece produced credible images:

Thinwall open boxes - side detail - 4.98 4.85 measured

Thinwall open boxes – side detail – 4.98 4.85 measured

Alas, the shutter failed after that image, leaving me with pictures untaken and naught to take them with.

The least-awful alternative seems to be gimmicking up an adapter for a small USB camera from the usual eBay source:

Fashion USB video - case vs camera

Fashion USB video – case vs camera

The camera’s 640×480 VGA resolution is marginally Good Enough for the purpose, as I can zoom the microscope to completely fill all those pixels. The optics aren’t up to the standard set by the microscope, but we can cope with that for a while.

A bit of doodling & OpenSCAD tinkering produced a suitable adapter:

USB Camera Microscope Mount - solid model

USB Camera Microscope Mount – solid model

To which Slic3r applied the usual finishing touches:

USB Camera Microscope Mount - Slic3r preview

USB Camera Microscope Mount – Slic3r preview

A bit of silicone tape holds the sloppy focusing thread in place:

USB Camera Microscope Mount - cap with camera

USB Camera Microscope Mount – cap with camera

Those are 2-56 screws that will hold the cap onto the tube. I drilled out the clearance holes in the cap and tapped the holes in the eyepiece adapter by hand, grabbing the bits with a pin vise.

Focus the lens at infinity, which in this case meant an old DDJ cover poster on the far wall of the Basement Laboratory, and then it’ll be just as happy with the image coming out of the eyepiece as a human eyeball would be.

I put a few snippets of black electrical tape atop the PCB locating tabs before screwing the tube in place. The tube ID is 1 mm smaller than the PCB OD, in order to hold the PCB perpendicular to the optical axis and clamp it firmly in place. Come to find out that the optical axis of the lens isn’t perfectly perpendicular to the PCB, but it’s close enough for my simple needs.

And then it fits just like you’d expect:

USB Camera Microscope Mount - on eyepiece

USB Camera Microscope Mount – on eyepiece

Actually, that’s the second version. The distance from the camera lens (equivalently: the PCB below the optical block, which I used as the datum plane) to the eyepiece is a critical dimension that determines whether the image fills the entrance pupil. I guesstimated the first version by hand-holding the camera and measuring with a caliper, tried it out, then iteratively whacked 2 mm off the tube until the image lit up properly:

USB Camera Microscope Mount - adjusting tube length

USB Camera Microscope Mount – adjusting tube length

Minus 4 mm made it slightly too short, but then I could measure the correct position, tweak that dimension in the code, and get another adapter, just like the first one (plus a few other minor changes), except that it worked:

USB Camera Microscope Mount - first light

USB Camera Microscope Mount – first light

That’s a screen capture from VLC, which plays from /dev/video0 perfectly. Some manual exposure & color balance adjustment may be in order, but it’s pretty good for First Light.

It turns out that removing the eyepiece and holding the bare sensor over the opening also works fine. The real image from the objective fills much more area than the camera’s tiny sensor: the video image covers about one digit in that picture, but gimmicking up a bare-sensor adapter might be useful.

The OpenSCAD source code:

// USB Camera mount for Microscope Eyepiece
// Ed Nisley KE4ZNU - August 2015

Layout = "Build";                    // Show Build Mount Cap

//- Extrusion parameters must match reality!
//  Print with 2 shells

ThreadThick = 0.25;
ThreadWidth = 0.40;

HoleWindage = 0.2;

Protrusion = 0.1;           // make holes end cleanly

function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);

inch = 25.4;

Tap2_56 = 0.070 * inch;
Clear2_56 = 0.082 * inch;
Head2_56 = 0.156 * inch;
Head2_56Thick = 0.055 * inch;
Nut2_56Dia = 0.204 * inch;
Nut2_56Thick = 0.065 * inch;
Washer2_56OD = 0.200 * inch;
Washer2_56ID = 0.095 * inch;

BuildGap = 5.0;

// Dimensions

//-- Camera

PCBThick = 1.1;
PCBDia = 24.5;
PCBClampDia = 23.0;

KeySize = [IntegerMultiple(27.6,ThreadWidth),IntegerMultiple(9.5,ThreadWidth),IntegerMultiple(PCBThick,ThreadThick)];
KeyOffset = [0.0,1.5,0];

CameraOffset = 22.3;                    // distance from eyepiece to camera PCB

WallThick = 4.0;

EyePieceOD = 30.0;
EyePieceLen = 30.0;

BodyOD = EyePieceOD + 2*WallThick;
BodyLen = CameraOffset + EyePieceLen - 5.0;

echo(str("Body length: ",BodyLen));

CapSocket = 10;
CapLen = CapSocket + WallThick;
CableOD = 3.7;

echo(str("Cap length: ",CapLen));

echo(str("Total length: ",BodyLen + CapLen));

NumScrews = 4;
ScrewAngle = 45;

NumSides = 6*4;


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,

// Components

module LensMount() {
    difference() {
            PolyCyl(PCBClampDia,(BodyLen + 2*Protrusion),NumSides);
        for (i=[0:NumScrews-1])
            rotate(ScrewAngle + i*360/NumScrews)
                translate([(BodyOD/2 - 1.5*Head2_56/2),0,-Protrusion])

module CamCap() {
    difference() {
        translate(KeyOffset + [0,0,(CapLen - KeySize[2]/2 + Protrusion/2)])
            cube((KeySize + [0,0,Protrusion]),center=true);
        if (false)
            translate([0,BodyOD/2,(CapLen - CableOD/2 + Protrusion/2)])
                    cube([CableOD,(CableOD + Protrusion),BodyOD],center=true);
        for (i=[0:NumScrews-1])
            rotate(ScrewAngle + i*360/NumScrews)
                translate([(BodyOD/2 - 1.5*Head2_56/2),0,-Protrusion])
                        PolyCyl(Clear2_56,(CapLen + 2*Protrusion),4);

// Build it!

if (Layout == "Mount")

if (Layout == "Cap")

if (Layout == "Show") {
    translate([0,0,CapLen + 5])
if (Layout == "Build") {
    translate([-(BodyOD/2 + BuildGap),0,0])
        translate([(BodyOD/2 + BuildGap),0,0])

, , ,


Casio EX-Z850 Shutter Failure

After nine years, the shutter on my muchrepaired Casio EX-Z850 camera has failed, producing images with horizontal white lines:

Casio EX-Z850 Shutter Failure

Casio EX-Z850 Shutter Failure

That can also come from a sensor failure, but it takes perfectly good movies. That’s the differential diagnosis for shutter failure, because movies don’t use the shutter.

The shutter still functions, in that peering into the lens shows the shutter closing as it takes a picture, so I suspect it’s gotten a bit sticky and slow over the years. None of the various shutter-priority speeds have any effect, which means that the shutter isn’t responding properly.

A quick read of the service manual shows the Field Replaceable Unit for this situation is the entire lens assembly. Back in the day, a new lens assembly came with its own calibration constants on a floppy disk that you’d install with Casio’s service program (the latest version ran with Windows 98!) using a special USB communication mode triggered by a Vulcan Nerve Pinch on the camera. At this late date, none of that stuff remains available.

While I could take the camera apart and crack the lens capsule open, I doubt that would make it better and, in this case, ending up with a crappy camera doesn’t count for much. Extracting the lens assembly requires dismantling the entire thing, which, frankly, doesn’t seem worth the effort…

That image is number 7915: so it’s taken a bit over two images per day for the last nine years. I can’t swear the counter has never been reset, but that seems about right.

Sic transit gloria mundi, etc.


Kenmore 362.75581890 Stove: Weak Oven Igniter

The burner in our oven failed in December 2006, probably because the charred remains of an insect produced a hotspot:

Burned Oven Tube Overview

Burned Oven Tube Overview

That replacement burner came with its own igniter that failed after 8.5 years, with symptoms of slow oven ignition and the occasional smell of propane.

In normal operation, the igniter element glows yellow-hot for a minute or so before the valve opens, gas flows over the igniter, there’s a muffled whoomf, and the oven begins heating. The igniter remains powered as long as the oven is on, emitting a baleful yellow glare through the slots in the oven’s lower cover.

It consists of a ceramic base holding a stout resistance heater that apparently suffers from increasing resistance as it ages, reducing the current to the point where it won’t activate the gas valve.

I didn’t know that, either, but Google sees all, knows all, and tells most.

The gas valve label says it requires 3.3 to 3.6 A from the heater to turn on the gas:

Kenmore range oven gas valve - data plate

Kenmore range oven gas valve – data plate

But the old heater was good for barely 2.6 A (there’s a bit of parallax in this view):

Kenmore range oven gas valve - weak igniter current

Kenmore range oven gas valve – weak igniter current

Igniters range from $18 to upwards of $60 on Amazon, so I picked the cheapest one, waited two days, installed it, and measured 3.5 A at First Light, down to a bit over 3.0 A at running temperature. That’s on the low side of the valve’s spec, but it seems happier with an extra half amp.

We’ll see how long this igniter lasts; maybe next time I’ll double my spend…


Road Conditions: BPAC Presentation

The PDF of my presentation to the Dutchess County Bicycle and Pedestrian Advisory Committee on what happens after a bicyclist reports a hazardous road condition:

BPAC Presentation – 2015-08-27

It doesn’t have my patter, but you’ve already seen most of the pictures and stories here, tagged Tax Dollars Asleep and can probably fill in the blanks.

To quote from the PDCTC Master Plan linked above:

The Plan establishes the following vision: In Dutchess County, walking and bicycling will be part of daily life, providing safe and convenient transportation and recreation.


Rt 376 SB 2015-08-25 - North of Maloney - 2

Rt 376 SB 2015-08-25 – North of Maloney – 2

Spring Rd 2015-08-01 - EB - grate rear view

Spring Rd 2015-08-01 – EB – grate rear view

Mary says it was one of my more impassioned presentations…


HP 7475A Plotter: SuperFormula Demo Madness!

A gallery of SuperFormula plots, resized / contrast stretched / ruthlessly compressed (clicky for more dots):

The gray one at the middle-bottom suffered from that specular reflection; the automagic contrast stretch couldn’t boost the paper with those burned pixels in the way.

Those sheets all have similar plots on the back, some plots used refilled pens that occasionally bled through the paper, others have obviously bad / dry pens, and you’ll spot abrupt color changes where I swapped out a defunct pen on the fly, but they should give you an idea of the variations.

The more recent plots have a legend in the right bottom corner with coefficients and timestamps:

SuperFormula Plot - legend detail

SuperFormula Plot – legend detail

Limiting the pen speed to 10 cm/s (down from the default 38.1 cm/s = 15.00 inch/s) affects only the outermost segments of the spikes; down near the dense center, the 9600 b/s serial data rate limits the plotting speed. Plotting slowly helps old pens with low flow rates draw reasonably dense lines.

Each plot takes an hour, which should suffice for most dog-and-pony events.

I fill a trio of Python lists with useful coefficient values, then choose random elements for each plot: a single value of m determines the number of points for all six traces, then six pairs of values set n1 and n2=n3. The lists are heavily weighted to produce spiky traces, rather than smooth ovals, so the “random” list selections aren’t uniformly distributed across the full numeric range of the values.

Because the coefficient lists contain fixed values, the program can produce only a finite number of different plots, but I’m not expecting to see any duplicates. You can work out the possibilities by yourself.

The modified Chiplotle demo code bears little resemblance to the original:

from chiplotle import *
from math import *
from datetime import *
import random

def superformula_polar(a, b, m, n1, n2, n3, phi):
   ''' Computes the position of the point on a
   superformula curve.
   Superformula has first been proposed by Johan Gielis
   and is a generalization of superellipse.
   Tweaked to return polar coordinates

   t1 = cos(m * phi / 4.0) / a
   t1 = abs(t1)
   t1 = pow(t1, n2)

   t2 = sin(m * phi / 4.0) / b
   t2 = abs(t2)
   t2 = pow(t2, n3)

   t3 = -1 / float(n1)
   r = pow(t1 + t2, t3)
   if abs(r) == 0:
      return (0,0)
 #     return (r * cos(phi), r * sin(phi))
     return (r,phi)

def supershape(width, height, m, n1, n2, n3, 
   point_count=10*1000, percentage=1.0, a=1.0, b=1.0, travel=None):
   '''Supershape, generated using the superformula first proposed 
   by Johan Gielis.

   - `points_count` is the total number of points to compute.
   - `travel` is the length of the outline drawn in radians. 
      3.1416 * 2 is a complete cycle.
   travel = travel or (10*2*pi)

   ## compute points...
   phis = [i * travel / point_count 
      for i in range(1 + int(point_count * percentage))]
   points = [superformula_polar(a, b, m, n1, n2, n3, x) for x in phis]

   ## scale and transpose...
   path = [ ]
   for r, a in points:
      x = width * r * cos(a)
      y = height * r * sin(a)
      path.append(Coordinate(x, y))

   return Path(path)


if __name__ == '__main__':
   paperx = 8000
   papery = 5000
   tscale = 0.45
   numpens = 6
   m_list = [n/10.0 for n in [11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59]];   # prime/10 = number of spikes
   n1_list = [n/100.0 for n in range(15,75,1) + range(80,120,5) + range(120,200,10)]  # ring-ness 0.1 to 2.0, higher is larger diameter
   n2_list = [n/100.0 for n in range(10,50,1) + range(55,100,5) + range(110,200,10)]  # spike-ness 0.1 to 2.0, lower means spiky points
   paramlist = [[n1,n2] for n1 in random.sample(n1_list,numpens) for n2 in random.sample(n2_list,numpens)]
   if  not False:
#     plt.write(chr(27) + '.H200:')   # set hardware handshake block size
     plt.write(hpgl.SI(tscale*0.285,tscale*0.375))    # scale based on B size characters
     plt.write(hpgl.VS(10))                           # slow speed for those abrupt spikes
     pen = 1
     plt.write(hpgl.PA([(paperx - 3000,-(papery - 600))]))
     plt.write(hpgl.LB("Started " + str(
     m = random.choice(m_list)
     for n1, n2 in zip(random.sample(n1_list,numpens),random.sample(n2_list,numpens)):
        n3 = n2
        print "m: ", m, " n1: ", n1, " n2=n3: ", n2
        plt.write(hpgl.PA([(paperx - 3000,-(papery - 500 + 100*(pen - 1)))]))
        plt.write(hpgl.LB("Pen " + str(pen) + ": m=" + str(m) + " n1=" + str(n1) + " n2=n3=" + str(n2)))
        e = supershape(paperx, papery, m, n1, n2, n3)
        if pen < numpens: 
            pen += 1
            pen = 1
     pen = 1
     plt.write(hpgl.PA([(paperx - 3000,-(papery - 500 + 100*numpens))]))
     plt.write(hpgl.LB("Ended   " + str(
     e = supershape(paperx, papery, 1.9, 0.8, 3, 3)

Leave a comment

HP 7475A Plotter: Ceramic Tip Pen Autopsy

It turns out that the ceramic-tip plotter pens don’t come apart at the top of the flange as I expected. Instead, there’s a snug-fitting plug with a tapered top and an invisible joint at the end of the body tube:

HP7475A Plotter - ceramic pen - disassembled

HP7475A Plotter – ceramic pen – disassembled

Refilling a pair of defunct black ceramic pens didn’t bring them back to life: an ample supply of fresh black ink never made it from the fluff to the nib. Soaking the nibs + fiber shafts in 10% ethanol for a day created an unappetizing black vodka shot that did nothing to get the ink where it needed to be.

The right time to refill those pens would have been, oh, probably a decade or two ago…

Some stuff, you just gotta throw out!


DC Motor Mounting Plate

The Squidwrench Power Wheels Racer needed a mounting bracket for its DC motor, so Matt handed me a precut steel slab and some drawings. I did a manual layout to get a feel for the sizes:

Motor Mount - dye layout

Motor Mount – dye layout

Yes, it’s slightly rhomboid & irregular on the sides; it’ll be welded to a U-channel. The front edge is the straightest and I scribed a perpendicular datum line over on the right, from which to measure the motor center point.

But then, realizing I’d have to mill the central hole anyway, I did what I should have done from the beginning and lined it up on the Sherline:

Motor Mount - Sherline laser centering

Motor Mount – Sherline laser centering

With the part zeroed at the center, everything has polar coordinates. The bolt holes are #10 on a 50 mm BCD, which is G0 @25^[45+90*i]. Rather than writing & debugging a program, I did it all by feeding manual instructions into the interpreter; the i gets typed as 0, 1, 2, and 3 by clicking on a previous command, backspacing, and retyping, which is both faster and easier than it sounds. The holes are drill cycles: G81 Z-7 R1 F30

This being steel on a Sherline, the rule of thumb that says you can drill at 100x the drill diameter (in inch/min or mm/min, as appropriate) at 3000 RPM gets derated by at least factor of 10. I settled on 30 mm/min for a #10 drill (0.194 inch = 4.9 mm → 500 mm/min = hogwash) after trying the first hole at 50 mm/min:

Motor Mount - bolt holes

Motor Mount – bolt holes

The least horrible way to cut out the hole for the motor mounting boss involved chain drilling to excavate the most steel with the least effort. These center drill points are at G0 @14 ^[15*i] with i in [0..23]:

Motor Mount - chain center drilling

Motor Mount – chain center drilling

I drilled every even hole #27, then every odd hole #28, both at 50 mm/min, to get a thin web:

Motor Mount - chain drilled

Motor Mount – chain drilled

Then helix-mill downward with a 1/8 inch end mill at 1 mm per pass:

Motor Mount - helix milling

Motor Mount – helix milling

That started at 14 mm from the origin to match the hole circle: G3 I-14 F100 Z-1

Then I switched to a 3/8 inch = 9.5 mm end mill to bring the hole up to size, ending with G3 I-12.75 F300

Motor Mount - center hole milled

Motor Mount – center hole milled

A trial fit showed the hole was slightly off-round, probably due to a few mils of backlash in both axes, and slightly too small, because that’s how I wanted it. Flipped back-to-front, reclamped, recentered, ran the cutter around at 12.75 mm to clear the ovalness, then crept out to 12.8 mm, and it was all good:

Motor Mount - test fit

Motor Mount – test fit

That’s an easy fit with maybe 0.1 mm = 4 mil radial play around the boss. Better than that, I cannot do.

Lacquer thinner stripped the layout dye and it’s ready for welding:

Motor Mount - with motor

Motor Mount – with motor

Reminders for next time…

The drill feed on a rigid machine with plenty of spindle power is 100 x (drill dia) @ 3000 RPM. On the Sherline, in steel, 10 x dia is optimistic. Aluminum feeds run higher, but don’t get stupid.

Re-centering to the accuracy required for this job is a matter of noting the coordinates where the cutter kisses the perimeter across a diameter along each axis, adding the coordinates, dividing by two, moving to that position, and zeroing the origin. Do that in X, Y, X, and Y and it’s good enough. You could automate that with a touch probe, of course. Hand-turning the spindle with the cutter in place to feel it kiss the workpiece is fine, but use the same cutting edge on both sides of the diameter.

Figure the chain drill diameter thusly:

  • Pick a reasonable drill diameter; #10 is about as large as you want on a Sherline
  • Drill circle dia = final milled hole diameter – drill dia – 2 mm, round down to lower integer
  • # holes = π x DCD / drill dia, rounded down to lower integer
  • Hole angle = 360 / # holes
  • Hole radius = DCD / 2

Wisely is it written that a man with a CNC milling machine has many friends.