Canon SX230HS Microscope and Close-up Macro Adapters

The deal was, if my Shop Assistant repaired my pocket camera, she could have it. She did, which meant I lost the ability to take pix through the microscope. While I was conjuring up a replacement, it occurred to me that I should also build a gadget to hold a close-up lens in front of the camera for tighter macro shots that don’t quite require a microscope’s magnification.

The solid model of the microscope adapter:

Microscope mount - solid model
Microscope mount - solid model

The close-up macro adapter, with an LED ring light around the snout:

LED Ring mount - solid model
LED Ring mount - solid model

They have a common camera mounting plate, with a hex recess for a 1/4-20 nut that mates with a standard tripod screw and some support material sticking up through the hole for the screw that holds the camera to the plate:

Mounting plate - solid model - top view
Mounting plate - solid model - top view

The main tube glues into the plate’s cutout and is long enough to accommodate the fully extended lens turret, with four shallow holes for filament snippet locating pins to align the snout:

Main tube - solid model - bottom view
Main tube - solid model - bottom view

An exploded view shows how everything fits together, with the stud below the camera representing its tripod mounting screw:

LED Ring mount - solid model - exploded view
LED Ring mount - solid model - exploded view

More details on the parts will appear over the next few days, but here’s the view through the macro adapter:

Dahlia through macro adapter
Dahlia through macro adapter

Yeah, some slight vignetting, but overall it’s pretty good.

The OpenSCAD source code that builds both adapters:

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

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

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

Gap = 12;					// 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.02;

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

AlignPins = 4;
AlignPinOD = 2.9;
AlignPinCircleDia = TubeOD - 2*TubeWall - 2*AlignPinOD;		// 2*PinOD -> more clearance

//-------

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:AlignPins-1])
	  rotate(Index*90)
		translate([(AlignPinCircleDia/2),0,-ThreadThick])
		  rotate(180)			// flat sides outward
			PolyCyl(AlignPinOD,TubeEndThickness);
  }

}

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

		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:AlignPins-1])
	  rotate(Index*90)
		translate([(AlignPinCircleDia/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:AlignPins-1])
	  rotate(Index*90)
		translate([(AlignPinCircleDia/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,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();
}

The original doodles & dimensions:

Close-up Lens Doodles
Close-up Lens Doodles