Advertisements

Archive for November, 2011

Presta Valve: Proper Pump Attachment Thereto

All our bikes have Presta valves, which seem better suited for bike rims than the larger and more common automotive Schraeder valves:

Presta valve stem

Presta valve stem

For all these years, I’d been attaching the pump head so the obvious sealing ring near the nozzle opening lined up with the flat section adjacent to the valve core stem. The pump head never seemed stable on the stem, often leaked, and generally had a precarious hold:

Incorrect Presta pump head attachment

Incorrect Presta pump head attachment

Come to find out, more by accident than intention, that the correct way to attach the pump head involves ramming it all the way down onto the stem so that it can seal along the entire length of the threads. That’s nice and secure, doesn’t leak, and even looks like it should work perfectly:

Correct Presta pump head attachment

Correct Presta pump head attachment

I’d feel even more like a doof if I hadn’t learned to do it wrong by watching somebody else back in the day or if I haven’t observed many other people making exactly the same mistake. I think the fact that the short nozzles on the old-school ZĂ©fal pumps I swore by back in my wedgie-bike days never got a good grip on Presta stems got me off to a bad start, but … dang do I feel stupid.

FWIW, the little tab sticking out under the latch handle makes up for a bit of slop in the valve head. When I got the pump, the Schraeder nozzle didn’t seal very well, either, and taking up a few mils of slack helped immeasurably. We don’t need that nozzle very often, but our bicycle touring guests frequently do; they know that they can top off a Schraeder-valved tube at any gas station or with any pump anywhere around the world.

[Update: I hate it when I misspell a word in the title…]

Advertisements

10 Comments

Basement Safe Humidity

The discussion about drying my silica gel stash prompted me to toss a Hobo datalogger into the safe along with the desiccant bag. We now have enough data to spot a trend:

Basement Safe Humidity - Oct-Nov 2011

Basement Safe Humidity - Oct-Nov 2011

Verily, one measurement trumps a thousand opinions: I was totally wrong about the door seal. Either that or the safe’s contents started out a lot wetter than I thought.

The basement humidity runs about 55% RH, pumped down by a dehumidifier in the summer and ambient air in the winter, which (I think) sets the upper limit. Modulo having hygroscopic stuff like paper in the safe, I suppose.

I’ll toss a fresh bag in there, tape over the door crack, and see what happens during the next month.

FWIW, the Onset HOBOware program doesn’t run under Wine and Wine doesn’t support USB hardware anyway, which is one of the few reasons I have a Token Windows Laptop. I’ve set it up to automagically export the data into CSV files, from which this went into OpenOffice 3.2 for a quick look. Surprisingly, HOBOware is a Java program, but evidently written specifically to avoid portability; they have Windows and Mac versions and that’s all. Worse, there’s no way to extract data from the loggers without using that program, because Onset doesn’t document the interface protocol. Enough said.

Memo to Self: measure it!

3 Comments

EMC2 HAL Configuration by Eagle Schematics: The Code

As part of that renaming adventure with the Logitech Gamepad configuration, I realized I hadn’t put my version of Martin Shoeneck’s Eagle-to-HAL conversion script anywhere useful.

Herewith, the script that you’ll apply to schematics built with parts from the hal-config-2.4.lbr.odt library (which you must rename to get ride of the ODT extension):

/******************************************************************************
 * HAL-Configurator
 *
 * Author: Martin Schoeneck 2008
 * Additional gates and tweaks: Ed Nisley KE4ZNU 2010
 *****************************************************************************/
#usage "<h1>HAL-Configurator</h1>Start from a Schematic where symbols from hal-config.lbr are used!";

string output_path =    "./";
string dev_loadrt =     "LOADRT";
string dev_loadusr =    "LOADUSR";
string dev_thread =     "THREAD";
string dev_parameter =  "PARAMETER";

string dev_names[] = {
"CONSTANT",								// must be first entry to make set_constants() work
"ABS",				// 2.4
"AND2",
"BLEND",			// 2.4
"CHARGE-PUMP",		// 2.4
"COMP",
"CONV_S32_FLOAT",	// 2.4
"DDT",				// 2.4
"DEADZONE",			// 2.4
"DEBOUNCE",			// 2.4
"EDGE",
"ENCODER",			// 2.4
"ENCODER-RATIO",	// 2.4
"ESTOP-LATCH",
"FLIPFLOP",
"FREQGEN",			// 2.4
"LOWPASS",
"MULT2",			// 2.4
"MUX2",
"MUX4",				// 2.4
"MUX8",				// 2.4
"NEAR",				// 2.4
"NOT",
"ONESHOT",
"OR2",
"SAMPLER",			// 2.4
"SCALE",			// 2.4
"SELECT8",			// 2.4
"SUM2",
"TIMEDELAY",		// 2.4
"TOGGLE",			// 2.4
"WCOMP",			// 2.4
"XOR2",				// 2.4
""					// end flag
};

/*******************************************************************************
 * Global Stuff
 ******************************************************************************/

string FileName;
string ProjectPath;
string ProjectName;

void Info(string Message) {
	dlgMessageBox(";<b>Info</b><p>\n" + Message);
}

void Warn(string Message) {
	dlgMessageBox("!<b>Warning</b><p>\n" + Message + "<p>see usage");
}

void Error(string Message) {
	dlgMessageBox(":<hr><b>Error</b><p>\n" + Message + "<p>see usage");
	exit(1);
}

string replace(string str, char a, char b) {
	// in string str replace a with b
	int pos = -1;
	do {
		// find that character
		pos = strchr(str, a);
		// replace if found
		if(pos >= 0) {
			str[pos] = b;
		}
	} while(pos >= 0);

	return str;
}

// the part name contains an index and is written in capital letters
string get_module_name(UL_PART P) {
	// check module name, syntax: INDEX:NAME
	string mod_name = strlwr(P.name);
	// split string at the : if exists
	string a[];
	int c = strsplit(a, mod_name, ':');
	mod_name = a[c-1];
	// if name starts with '[' we need uppercase letters
	if(mod_name[0] == '[') {
		mod_name = strupr(mod_name);
	}

	return mod_name;
}

string comment(string mess) {
	string str = "\n\n####################################################\n";
	if(mess != "") {
		str += "# " + mess + "\n";
	}

	return str;
}

// if this is a device for loading a module, load it (usr/rt)
string load_module(UL_PART P) {
	string str = "";

	// it's a module if the device's name starts with LOADRT/LOADUSR
	if((strstr(P.device.name, dev_loadrt) == 0) ||
	   (strstr(P.device.name, dev_loadusr) == 0)) {

		// now add the string to our script
		str += P.value + "\n";
	}

	return str;
}

// count used digital gates (and, or, etc) and load module if neccessary
string load_blocks() {
	string str = "";

	int index;

	int dev_counters[];
	string dname[];

	// count the gates that are used
	schematic(S) { S.parts(P) {
		strsplit(dname,P.device.name,'.');		// extract first part of name
		if ("" != lookup(dev_names,dname[0],0)) {
			for (index = 0;  (dname[0] != dev_names[index]) ; index++) {
				continue;
			}
			dev_counters[index]++;
		}
	} }

// force lowercase module names...

	for (index = 0; ("" != dev_names[index]) ; index++) {
		if (dev_counters[index]) {
			sprintf(str,"%sloadrt %s\t\tcount=%d\n",str,strlwr(dev_names[index]),dev_counters[index]);
		}
	}

	return str;
}

string hook_function(UL_NET N) {
	string str = "";

	// is this net connected to a thread (work as functions here)?
	int    noclkpins       = 0;
	string thread_name     = "";  // this net should be connected to a thread
	string thread_position = "";
	N.pinrefs(PR) {
		// this net is connected to a clk-pin
		if(PR.pin.function == PIN_FUNCTION_FLAG_CLK) {
			// check the part: is it a thread-device?
			if(strstr(PR.part.device.name, dev_thread) == 0) {
				// we need the name of the thread
				thread_name = strlwr(PR.part.name);
				// and we need the position (position _ is ignored)
				thread_position = strlwr(PR.pin.name);
				thread_position = replace(thread_position, '_', ' ');
			}
		} else {
			// no clk-pin, this is no function-net
			noclkpins++;
			break;
		}
	}

	// found a thread?
	if(noclkpins == 0 && thread_name != "") {
		// all the other pins are interesting now
		N.pinrefs(PR) {
			// this pin does not belong to the thread
			if(strstr(PR.part.device.name, dev_thread) != 0) {
				// name of the pin is name of the function
				//string function_name = strlwr(PR.pin.name);
				string function_name = strlwr(PR.instance.gate.name);
				// if functionname starts with a '.', it will be appended to the modulename
				if(function_name[0] == '.') {
					// if the name is only a point, it will be ignored
					if(strlen(function_name) == 1) {
						function_name = "";
					}
					function_name = get_module_name(PR.part) + function_name;
				}
				str += "addf " + function_name + "\t\t" + thread_name + "\t" + thread_position + "\n";
			}
		}
	}

	return str;
}

string set_parameter(UL_NET N) {
	string str = "";

	// is this net connected to a parameter-device?
	int    nodotpins       = 0;
	string parameter_value = "";
	N.pinrefs(PR) {
		// this net is connected to a dot-pin
		if(PR.pin.function == PIN_FUNCTION_FLAG_DOT) {
			// check the part: is it a parameter-device?
//			str += "** dev name [" + PR.part.device.name + "] [" + dev_parameter + "]\n";
			if(strstr(PR.part.device.name, dev_parameter) == 0) {
				// we need the value of that parameter
				parameter_value = PR.part.value;
//				str += "**  value [" + PR.part.value +"]\n";
			}
		} else {
			// no clk-pin, this is no function-net
			nodotpins++;
			break;
		}
	}

	// found a parameter?
	if(nodotpins == 0 && parameter_value != "") {
		// all the other pins are interesting now
		N.pinrefs(PR) {
//			str += "** dev name [" + PR.part.device.name + "] [" + dev_parameter + "]\n";
			// this pin does not belong to the parameter-device
			if(strstr(PR.part.device.name, dev_parameter) != 0) {
				// name of the pin is name of the function
				//string parameter_name = strlwr(PR.pin.name);
				string parameter_name = strlwr(PR.instance.gate.name);
				// if functionname starts with a '.', it will be appended to the modulename
//				str += "** param (gate) name [" + parameter_name + "]\n";
				if(parameter_name[0] == '.') {
					// if the name is only a point, it will be ignored
					if(strlen(parameter_name) == 1) {
						parameter_name = "";
					}
					parameter_name = get_module_name(PR.part) + parameter_name;
//					str += "** param (part) name [" + parameter_name + "]\n";
				}
				str += "setp " + parameter_name + "\t" + parameter_value + "\n";
			}
		}
	}

	return str;
}

// if this is a 'constant'-device, set its value
// NOTE: this is hardcoded to use the first entry in the dev_names[] array!
string set_constants(UL_PART P) {
	string str = "";

	// 'constant'-device?
	if(strstr(P.device.name, dev_names[0]) == 0) {
		str += "setp " + get_module_name(P) + ".value\t" + P.value + "\n";
	}

	return str;
}

string connect_net(UL_NET N) {
	string str = "";

	// find all neccessary net-members
	string pins = "";
	N.pinrefs(P) {
		// only non-functional pins are connected
		if(P.pin.function == PIN_FUNCTION_FLAG_NONE) {
			string pin_name =  strlwr(P.pin.name);
			string part_name = strlwr(P.part.name);
			pin_name =  replace(pin_name,  '$', '_');
			part_name = replace(part_name, '$', '_');
			pins += part_name + "." + pin_name + " ";
		}
	}

	if(pins != "") {
		string net_name = strlwr(N.name);
		net_name = replace(net_name, '$', '_');
		str += "net " + net_name + " " + pins + "\n";
	}

	return str;
}

/*******************************************************************************
 * Main program.
 ******************************************************************************/
// is the schematic editor running?
if (!schematic) {
	Error("No Schematic!<br>This program will only work in the schematic editor.");
}

schematic(S) {
	ProjectPath = filedir(S.name);
	ProjectName = filesetext(filename(S.name), "");
}

// build configuration

string cs = "# HAL config file automatically generated by Eagle-CAD ULP:\n";
cs += "# [" + argv[0] + "]\n";
cs += "# (C) Martin Schoeneck.de 2008\n";
cs += "# Mods Ed Nisley 2010\n";

FileName = ProjectPath + ProjectName + ".hal";

cs += "# Path        [" + ProjectPath + "]\n";
cs += "# ProjectName [" + ProjectName + "]\n";

//cs += "# File name: [" + FileName + "]\n\n";

// ask for a filename: where should we write the configuration?

FileName = dlgFileSave("Save Configuration", FileName, "*.hal");

if(!FileName) {
	exit(0);
}

cs += "# File name   [" + FileName + "]\n";
cs += "# Created     [" + t2string(time(),"hh:mm:ss dd-MMM-yyyy") + "]\n\n";

schematic(S) {
	// load modules
	cs += comment("Load realtime and userspace modules");
	S.parts(P) {
		cs += load_module(P);
	}

	// load blocks
	cs += load_blocks();

	// add functions
	cs += comment("Hook functions into threads");
	S.nets(N) {
		cs += hook_function(N);
	}

	// set parameters
	cs += comment("Set parameters");
	S.nets(N) {
		cs += set_parameter(N);
	}

	// set constant values
	cs += comment("Set constants");
	S.parts(P) {
		cs += set_constants(P);
	}

	// build nets and connect them
	cs += comment("Connect Modules with nets");
	S.nets(N) {
		cs += connect_net(N);
	}
}

// open/overwrite the target file to save the configuration
output(FileName, "wt") {
	printf(cs);
}

,

Leave a comment

Northern Saw-Whet Owls at Vassar

We recently attended an evening presentation at the Vassar College Ecological Preserve about their Northern Saw-Whet Owl (aka NSWO) research program. You can read more about both that and the owls elsewhere on the Intertubes, but I was impressed by the owl handling process.

NSWOs arrive from the mist net (the location of which the researchers do not describe in any detail, for obvious reasons) in a bulk carrier made of small tin cans strapped together with duct tape:

Owl carrier

Owl carrier

Another container holds the Owl Under Test while being weighed:

Saw-whet owl in can

Saw-whet owl in can

They express their obvious displeasure at this treatment by clacking their beaks (“KLOK! KLOK!”) and, if given the slightest opportunity, latching onto a finger:

Saw-whet owl vs researcher

Saw-whet owl vs researcher

Their claws will give you a nasty puncture wound or eight in a heartbeat; note how their feet remain carefully captured at all times. Despite that, the researchers sported many hand scars. FWIW, the owls are murder on mice and other critters, but evidently look a lot like lunch to larger owls and hawks.

NSWOs obey the general rule that anything with ears enjoys being scratched behind them. It may be reflex, rather than true bliss, but it works:

Saw-whet owl - calmed

Saw-whet owl - calmed

After weighing, measuring, blood-sampling, and stroking, the handler takes each owl outdoors, gives it a minute to reset its eyes for night flight, and releases it.

2 Comments

SX230HS Adapter: Main Tube and Assembly

The main tube connects the camera mounting plate and the snout on the front, so it’s a structural element of a sort. The ID fits over the non-moving lens turret base on the camera and the inner length is a few millimeters longer than the maximum lens turret extension:

Camera mount tube - interior

Camera mount tube - interior

As you might expect by now, the front bulkhead has four alignment peg holes for the snout:

Camera mount tube

Camera mount tube

The OpenSCAD code sets the wall thickness to 3 thread widths, but Skeinforge prints two adjacent threads with no fill at all. I think the polygon corners eliminate the one-thread-width fill and the perimeter threads wind up near enough to merge properly.

I assembled snouts to main tubes first, because it was easier to clamp bare cylinders to the bench:

Microscope eyepiece adapter - snout clamping

Microscope eyepiece adapter - snout clamping

Then glue the tube to the mounting plate using a couple of clamps:

Microscope eyepiece adapter - baseplate clamping

Microscope eyepiece adapter - baseplate clamping

The alignment is pretty close to being right, but if when I do this again I’ll add alignment pegs along the trench in the mounting plate to make sure the tube doesn’t ease slightly to one side, thusly:

SX230HS Macro Lens mount - solid model - exploded with pegs

SX230HS Macro Lens mount - solid model - exploded with pegs

You can see the entrance pupil isn’t quite filled in the last picture there, so a bit more attention to detail is in order. A bigger doublet lens would help, too.

The current version of the OpenSCAD source code with those pegs:

// Close-up lens mount & Microscope adapter for Canon SX230HS camera
// Ed Nisley KE4ZNU - Nov 2011

Mount = "LEDRing";			// End result: LEDRing Eyepiece

Layout = "Show";			// Assembly: Show
							// Parts: Plate Tube LEDRing Camera Eyepiece
							// Build Plates: Build1..4

Gap = 10;					// 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.33;
ThreadWidth = 2.0 * ThreadThick;

HoleFinagle = 0.2;
HoleFudge = 1.00;

function HoleAdjust(Diameter) = HoleFudge*Diameter + HoleFinagle;

Protrusion = 0.1;			// make holes end cleanly

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

//-------
// Dimensions

// doublet lens

LensDia = 25.0;
LensRad = LensDia/2;
LensClearance = 0.2;

LensEdge = 6.7;
LensThick = 8.6;
LensRimThick = IntegerMultiple((2.0 + LensThick),ThreadThick);

// LED ring light

LEDRingOD = 50.0;
LEDRingID = 36.0;
LEDBoardThick = 1.5;
LEDThick = 4.0;
LEDRingClearance = 0.5;
LEDWireHoleDia = 3.0;

// microscope eyepiece

EyepieceOD = 30.0;
EyepieceID = 24.0;
EyepieceLength = 25.0;

// camera
// Origin at base of [0] ring, Z+ along lens axis, X+ toward bottom, Y+ toward left

CameraBodyWidth = 2*10.6;							// 2 x center-to-curve edge
CameraBaseWidth = 15.5;								// flat part of bottom front to back
CameraBaseRadius = (CameraBodyWidth - CameraBaseWidth)/2;	// edge rounding
CameraBaseLength = 60.0;							// centered on lens axis
CameraBaseHeight = 55.0;							// main body height
CameraBaseThick = 0.9;								// downward from lens ring

echo(str("Camera base radius: ",CameraBaseRadius));

TripodHoleOffset = -19.0;							// mount screw wrt lens centerline
TripodHoleDia = Clear025_20;						// clearance hole

TripodScrewHeadDia = 14.5;							// recess for screw mounting camera
TripodScrewHeadRad = TripodScrewHeadDia/2;
TripodScrewHeadThick = 3.0;

// main lens tube

TubeDia = 		[53.0,	44.0,	40.0,	37.6];		// lens rings, [0] is fixed to body
TubeLength = 	[8.1,	20.6,	17.6,	12.7];

TubeEndClearance = 2.0;								// camera lens end to tube end
TubeEndThickness = IntegerMultiple(1.5,ThreadThick);
TubeInnerClearance = 0.5;

TubeInnerLength = TubeLength[0] + TubeLength[1] + TubeLength[2] + TubeLength[3] +
				  TubeEndClearance;
TubeOuterLength = TubeInnerLength + TubeEndThickness;

TubeID = TubeDia[0] + TubeInnerClearance;
TubeOD = TubeID + 6*ThreadWidth;
TubeWall = (TubeOD - TubeID)/2;
TubeSides = 48;

echo(str("Main tube outer length: ",TubeOuterLength));
echo(str("          ID: ",TubeID," OD: ",TubeOD," wall: ",TubeWall));

// camera mounting base

BaseWidth = IntegerMultiple((CameraBaseWidth + 2*CameraBaseRadius),ThreadThick);
BaseLength = 60.0;
BaseThick = IntegerMultiple((1.0 + Nut025_20Thick + CameraBaseThick),ThreadThick);

// LED ring mount

LEDBaseThick = IntegerMultiple(2.0,ThreadThick);	// base under lens + LED ring
LEDBaseRimWidth = IntegerMultiple(6.0,ThreadWidth);
LEDBaseRimThick = IntegerMultiple(LensThick,ThreadThick);

LEDBaseOD = max((LEDRingOD + LEDRingClearance + LEDBaseRimWidth),TubeOD);

echo(str("LED Ring OD: ",LEDBaseOD));

// alignment pins between tube and LED ring / microscope eyepiece

AlignPinOD = 2.9;

SnoutPins = 4;
SnoutPinCircleDia = TubeOD - 2*TubeWall - 2*AlignPinOD;		// 2*PinOD -> more clearance

// alignment pins between tube and base plate

BasePins = 2;
BasePinOffset = 10.0;
BasePinSpacing = BaseLength/3;

//-------

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);

}

//-------

//- Camera body segment
//	Including lens base and peg for tripod hole access
//	Z=0 at edge of lens base ring, X=0 along lens axis

module CameraBody() {

  translate([0,0,-CameraBaseThick])
	rotate(90)
	  union() {
		translate([0,0,(CameraBaseHeight/2 + CameraBaseRadius)])
		  minkowski() {
			cube([CameraBaseWidth,
				  (CameraBaseLength + 2*Protrusion),
				  CameraBaseHeight],center=true);
			rotate([90,0,0])
			  cylinder(r=CameraBaseRadius,h=Protrusion,$fn=8);
		  }

		translate([0,0,(TubeDia[0]/2 + CameraBaseThick)])
		  rotate([0,90,0])
			rotate(180/TubeSides)
			  cylinder(r=(TubeDia[0]/2 + CameraBaseThick),
					  h=(CameraBodyWidth/2 + Protrusion),
					  $fn=TubeSides);

		translate([CameraBodyWidth/2,0,(TubeDia[0]/2 + CameraBaseThick)])
		  rotate([0,90,0])
			cylinder(r=TubeDia[0]/2,h=TubeLength[0]);

		translate([(TubeLength[0] + CameraBodyWidth/2),
				  0,(TubeDia[0]/2 + CameraBaseThick)])
		  rotate([0,90,0])
			cylinder(r=TubeDia[1]/2,h=TubeLength[1]);

		translate([(TubeLength[0] + TubeLength[1] + CameraBodyWidth/2),
				  0,(TubeDia[0]/2 + CameraBaseThick)])
		  rotate([0,90,0])
			cylinder(r=TubeDia[2]/2,h=TubeLength[2]);

		translate([(TubeLength[0] + TubeLength[1] + TubeLength[2] + CameraBodyWidth/2),
				  0,(TubeDia[0]/2 + CameraBaseThick)])
		  rotate([0,90,0])
			cylinder(r=TubeDia[3]/2,h=TubeLength[3]);

		translate([0,TripodHoleOffset,-BaseThick])
		  PolyCyl(TripodHoleDia,(BaseThick + 2*Protrusion));

	  }
}

//- Main tube

module Tube() {

  difference() {
	cylinder(r=TubeOD/2,h=TubeOuterLength,$fn=TubeSides);

	translate([0,0,TubeEndThickness])
	  PolyCyl(TubeID,(TubeInnerLength + Protrusion),TubeSides);

	translate([0,0,-Protrusion]) {
	  if (Mount == "LEDRing")
		cylinder(r=LensRad,h=(TubeEndThickness + 2*Protrusion));
	  if (Mount == "Eyepiece")
		cylinder(r=EyepieceID/2,h=(TubeEndThickness + 2*Protrusion));
	}

	for (Index = [0:SnoutPins-1])
	  rotate(Index*90)
		translate([(SnoutPinCircleDia/2),0,-ThreadThick])
		  rotate(180)			// flat sides outward
			PolyCyl(AlignPinOD,TubeEndThickness);

	for (Index = [0:BasePins-1])
		translate([0,-(TubeOD/2 + Protrusion),
				  (TubeOuterLength - BasePinOffset - Index*BasePinSpacing)])
		  rotate([-90,90,0])					// y = flat toward camera
			PolyCyl(AlignPinOD,(TubeWall + 2*Protrusion));
  }

}

//- Base plate

module BasePlate() {

  union() {
	difference() {
		linear_extrude(height=BaseThick)
		  hull() {
			translate([-(BaseLength/2 - BaseWidth/2),0,0])
			  circle(BaseWidth/2);
			translate([ (BaseLength/2 - BaseWidth/2),0,0])
			  circle(BaseWidth/2);
			translate([0,(0.75*BaseLength),0])
			  circle(BaseWidth/2);
		  }

		translate([0,0,BaseThick])
		  CameraBody();

		translate([0,(TubeOuterLength + CameraBodyWidth/2),
				  (BaseThick + TubeDia[0]/2)])
		  rotate([90,0,0])
			PolyCyl(TubeOD,TubeOuterLength,$fn=TubeSides);

		for (Index = [0:BasePins-1])
			translate([0,(CameraBodyWidth/2 + BasePinOffset + Index*BasePinSpacing),
					  3*ThreadThick])
			  rotate(90)										// flat toward camera
				PolyCyl(AlignPinOD,BaseThick);

		translate([0,0,3*ThreadThick])
		  PolyCyl((Nut025_20Dia*sqrt(3)/2),2*Nut025_20Thick,6);	// dia across hex flats

		translate([0,0,-Protrusion])
		  PolyCyl(Clear025_20,(BaseThick + 2*Protrusion));

		translate([TripodHoleOffset,0,3*ThreadThick])
		  PolyCyl((Nut025_20Dia*sqrt(3)/2),2*Nut025_20Thick,6);	// dia across hex flats

		translate([TripodHoleOffset,0,-Protrusion])
		  PolyCyl(Clear025_20,(BaseThick + 2*Protrusion));

		translate([-TripodHoleOffset,0,-Protrusion])
		  PolyCyl(TripodScrewHeadDia,(TripodScrewHeadThick + Protrusion));
	}

	translate([-TripodHoleOffset,0,0]) {				// support for tripod screw hole
	  for (Index=[0:3])
		rotate(Index*45)
		  translate([-ThreadWidth,-TripodScrewHeadRad,0])
			cube([2*ThreadWidth,TripodScrewHeadDia,TripodScrewHeadThick]);

	  cylinder(r=0.4*TripodScrewHeadRad,h=(BaseThick - CameraBaseThick),$fn=9);
	}
  }
}

//- LED mounting ring

module LEDRing() {

  difference() {
	cylinder(r=LEDBaseOD/2,h=LensRimThick,$fn=48);

	translate([0,0,-Protrusion])
	  PolyCyl((LensDia + LensClearance),
			  (LensRimThick + 2*Protrusion));

	translate([0,0,LEDBaseRimThick])
	  difference() {
		PolyCyl(LEDBaseOD,LensThick);
		PolyCyl(LEDRingID,LensThick);
	  }

	translate([0,0,LEDBaseThick])
	  difference() {
		PolyCyl((LEDRingOD + LEDRingClearance),LensThick);
		cylinder(r1=HoleAdjust(LEDRingID - LEDRingClearance)/2,
				 r2=HoleAdjust(LensDia + LensClearance)/2 + 2*ThreadWidth,
				 h=LensThick);
	  }

	for (Index = [0:SnoutPins-1])
	  rotate(Index*90)
		translate([(SnoutPinCircleDia/2),0,-ThreadThick])
		  rotate(180)			// flat sides outward
			PolyCyl(AlignPinOD,LEDBaseThick);

	rotate(45)
	  translate([0,LEDRingID/2,(LEDBaseThick + 1.2*LEDWireHoleDia/2)])
		rotate([0,-90,0])			// flat side down
		  rotate([-90,0,0])
			PolyCyl(LEDWireHoleDia,2*LEDBaseRimWidth);
  }

}

//- Microscope eyepiece adapter

module EyepieceMount() {

  difference() {
	cylinder(r1=TubeOD/2,
			 r2=(EyepieceOD + 8*ThreadWidth)/2,
			 h=EyepieceLength,
			 $fn=TubeSides);

	translate([0,0,-Protrusion])
	  PolyCyl(EyepieceOD,(EyepieceLength + 2*Protrusion));

	for (Index = [0:SnoutPins-1])
	  rotate(Index*90)
		translate([(SnoutPinCircleDia/2),0,-ThreadThick])
		  rotate(180)			// flat sides outward
			PolyCyl(AlignPinOD,6*ThreadThick);
  }

}

//-------
// Build it!

if (Layout != "Show")
  ShowPegGrid();

if (Layout == "Tube")
  Tube();

if (Layout == "LEDRing")
  LEDRing();

if (Layout == "Plate")
  BasePlate();

if (Layout == "Camera")
  CameraBody();

if (Layout == "Eyepiece")
  EyepieceMount();

if (Layout == "Build1")
  translate([0,-BaseLength/3,0])
	BasePlate();

if (Layout == "Build2")
  Tube();

if (Layout == "Build3")
  LEDRing();

if (Layout == "Build4")
  EyepieceMount();

if (Layout == "Show") {
  translate([0,TubeOuterLength,TubeDia[0]/2]) {
	rotate([90,0,0])
	  color(LTC) Tube();
	translate([0,(Gap/2 - TubeEndThickness - Protrusion),0])
	  rotate([-90,0,0])
		for (Index = [0:SnoutPins-1])
		  rotate(Index*90)
			translate([(SnoutPinCircleDia/2),0,0])
			  rotate(180)			// flat sides outward
				PolyCyl(AlignPinOD,(TubeEndThickness + LEDBaseThick));

	translate([0,Gap,0])
	  rotate([-90,0,0]) {
		if (Mount == "LEDRing")
		  color(OOR) LEDRing();
		if (Mount == "Eyepiece")
		  color(OOR) EyepieceMount();
	  }
  }

  translate([0,-CameraBodyWidth/2,0])
	color(PG) CameraBody();

  color(PDA)
	render()
	translate([0,-CameraBodyWidth/2,-(BaseThick + Gap)])
	  BasePlate();

  for (Index = [0:BasePins-1])
	  translate([0,(BasePinOffset + Index*BasePinSpacing),
				-Gap/2])
		rotate([180,0,90])										// flat toward camera
		  PolyCyl(AlignPinOD,BaseThick/2);

}

Leave a comment

SX230HS Adapter: Microscope Snout

The microscope eyepiece adapter was easy enough: a cone with a cylinder punched out of it:

Microscope eyepiece adapter - front view

Microscope eyepiece adapter - front view

The thin part of the tip is four threads wide around the eyepiece OD, which makes for good fill.

The bottom has the usual four alignment pin holes:

Microscope eyepiece adapter - bottom view

Microscope eyepiece adapter - bottom view

The main tube opening ID is equal to the diameter of the flat rim around the microscope eyepiece, which provides a positive stop with plenty of surface area.

It looks about like you’d expect on the microscope, with the LED ring light from Planet Barbie around the objective lenses:

SX230HS on microscope

SX230HS on microscope

The lowest magnification is much higher than I expected; it’s about 2.5 mm across. This is a 1206 SMD resistor:

Microscope adapter - minimum magnification

Microscope adapter - minimum magnification

The highest magnification shows details of a laser etched numeral:

Microscope adapter - maximum magnification

Microscope adapter - maximum magnification

The alignment obviously isn’t up to par and I think the exposure needs some work. All in all, though, it’s usable as-is.

1 Comment

SX230HS Adapter: Macro Lens Snout

The macro lens adapter took shape around a nice 25 mm doublet lens from the Box o’ Lenses. The Canon SX230HS has a lens opening that’s just about 25 mm in diameter and a larger lens would be better, but at maximum zoom the image pretty much fills the camera’s entrance pupil. I ordered a pair of 50 mm LED ring lights from halfway around the planet and built the snout to hold the ring around the lens:

Macro lens snout with LED ring light

Macro lens snout with LED ring light

That’s the first pass to get the sizes right and work out some details. In particular, that small white ring inside the aperture below the lens didn’t work at all, so I made the main tube opening a bit smaller.

The solid model shows the details a bit better:

Macro adapter snout - solid model - front view

Macro adapter snout - solid model - front view

The inner cone shields the lens edges from (most of the) scattered LED light. I considered angling the side walls to concentrate the ring light, but wasn’t convinced it’d be worth the effort because the LEDs have such a broad beamwidth anyway. The little hole is for the LED power cable, which goes to a 12 V switching wall wart. The 5 strings of 3 LEDs draw about 70 mA, which suggests I should hack the ballast resistors down a bit to boost the current up to 20 mA per string. FWIW, the resistors give 25 mA per string at 13.8 V, so I could probably goose the current a lot higher.

The bottom has four shallow holes for the alignment pegs cut from ABS filament:

Macro adapter snout - solid model - bottom view

Macro adapter snout - solid model - bottom view

The hole in the front end of the main tube is marginally smaller than the lens diameter, as I used the OpenSCAD cylinder primitive instead of the PolyCyl module that slightly enlarges holes to make the answer come out right. The difference is just enough to form a solid stop that aligns the lens and prevents it from sliding into the body before the glue cures.

The whole affair looks pretty scary from the victim’s point of view:

SX230HS macro adapter LED ring light - front view

SX230HS macro adapter LED ring light - front view

But the camera’s view seems OK, albeit with some vignetting:

Canon NB-5L battery through macro lens adapter

Canon NB-5L battery through macro lens adapter

Limited by vignetting & entrance pupil filling, zooming controls the horizontal subject size from 25 mm to about 10 mm. Depth of field is a few mm, at best; the printing on the far end of that battery is fuzzier than it seems.

Best results so far come from:

  • Manual aperture at f/8
  • Manual focus at infinity (move the subject to focus)
  • Shutter delay = 2 seconds to let the camera stop shaking

2 Comments