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.

Day: May 23, 2013

  • LED + Photodiode Test Fixture

    An upcoming Circuit Cellar column calls for a way to measure LED light output vs. current input, which means I need some way to hold LEDs directly over a photodiode while excluding ambient light. Fortunately, the M2 had black PLA filament already loaded:

    LED Photocell Fixture - parts
    LED Photocell Fixture – parts

    That honkin’ big photodiode is a surplus PIN-10AP that’s been lying in wait for an opportunity just like this. The green filter matches the silicon response to CIE-standard human eye sensitivity, so the output tracks what you’d actually see. That’s irrelevant for testing red LEDs that all have pretty much the same wavelength, but it might come in handy for something.

    The main body of the fixture holds the LED about 1 mm from the front of the photodiode, indexed against the LED flange so they’re all at a consistent location. The cap has three locating pins made of 3 mm orange filament, with black foam rubber to push the LED into position and block ambient light.

    The business end looks like this:

    LED Photocell Fixture - LED view
    LED Photocell Fixture – LED view

    The most convenient way to mount the thing involves a right-angle BNC adapter in my trusty bench vise:

    LED Photocell Fixture - with breadboard
    LED Photocell Fixture – with breadboard

    The circuitry has a voltage-to-current driver for the LED and a zero-bias current-to-voltage converter for the photocell. The zero-bias trick keeps the voltage across the photodiode at zero, so the current varies linearly with illumination.

    The solid model laid out for printing along the X axis:

    LED Fixture for PIN-10AP Photodiode - solid model overview
    LED Fixture for PIN-10AP Photodiode – solid model overview

    It obviously has some improvements over the as-printed one in the pictures, in the unlikely event I need another fixture. The most important: a rear ring covering the back of the photodiode. Turns out that the PIN-10AP filter cap leaks a surprising amount of light around the body; I covered the gap with black tape to make the measurements, but that’s crude.

    I added a few screw holes to hold the parts together, but the cap (with the foam and pegs) must come off easily while swapping LEDs. I’d be tempted to sink studs into the body and use wing nuts to hold the lid in place, but I don’t have any 4-40 wing nuts…

    There’s a tiny bit of support under the central hole to support the LED flange recess and the trench for some foam under the leads:

    LED Fixture for PIN-10AP Photodiode - support
    LED Fixture for PIN-10AP Photodiode – support

    That’s another improvement; the as-printed one has foam on only one side of the leads.

    The OpenSCAD source code:

    // LED test fixture for PIN-10AP photodiode
    // Ed Nisley KE4ZNU May 2013
    
    // Layouts: Adapter AdapterSupport Cap Shield Build Show
    
    Layout = "Build";
    
    Gap = 8;		// between parts in Show
    
    //- Extrusion parameters must match reality!
    //  Print with +1 shells and 3 solid layers
    
    ThreadThick = 0.25;
    ThreadWidth = 0.40;
    
    HoleWindage = 0.2;
    
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    
    Protrusion = 0.1;			// make holes end cleanly
    
    Spacing = 5;				// between parts on build platform
    
    inch = 25.4;
    
    Tap2_56 = 0.070 * inch;
    Clear2_56 = 0.082 * inch;
    Head2_56 = 0.156 * inch;
    Head2_56Thick = 0.055 * inch;
    
    //----------------------
    // Dimensions
    
    PhotoDiodeOD = 31.3;
    PhotoDiodeStemOD = 16.0;
    PhotoDiodeStemLength = 8.0;
    PhotoDiodeWindowDia = 17.7;
    PhotoDiodeHeight = 14.0;
    
    FixtureOD = PhotoDiodeOD + 2*7.0;
    
    LEDDia = 5.0;				// LED body
    LEDFlangeOD = 6.0;			// flange at base of LED
    LEDFlangeThick = IntegerMultiple(1.5,ThreadThick);
    LEDLength = 10.0;			// overall length
    LEDRecess = 4.0;			// tube to fit LED body
    LEDSides = 8;
    
    FixtureLength = PhotoDiodeHeight + LEDLength + IntegerMultiple(1.0,ThreadThick);
    
    CapLength = 15.0;			// LED cover
    
    FoamOD = FixtureOD/2;
    FoamThick = IntegerMultiple(2.0,ThreadThick);
    
    TrenchDepth = 2*FoamThick;
    TrenchWidth = LEDDia;
    
    ShieldThick = 5.0;
    ShieldScrewCircle = PhotoDiodeOD + (FixtureOD - PhotoDiodeOD)/2;
    
    PinOD = 3.0;				// alignment pin (filament)
    PinLength = 10.0;			//   ... total length
    PinCircle = FixtureOD/2;
    
    GrubScrewOD = Tap2_56;
    
    $fn=4*6;					// default cylinder sides
    
    //----------------------
    // Useful routines
    
    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(95 / Space);
      RangeY = floor(125 / Space);
    
    	for (x=[-RangeX:RangeX])
    	  for (y=[-RangeY:RangeY])
    		translate([x*Space,y*Space,Size/2])
    		  %cube(Size,center=true);
    
    }
    
    //-----------------------
    // Parts
    
    module Adapter() {
    
    	difference() {
    		cylinder(r=FixtureOD/2,h=FixtureLength);
    		translate([0,0,-Protrusion]) {
    			PolyCyl(LEDDia,2*FixtureLength,LEDSides);
    			PolyCyl(PhotoDiodeWindowDia,(FixtureLength - LEDRecess + Protrusion));
    			PolyCyl(PhotoDiodeOD,(PhotoDiodeHeight + Protrusion));
    		}
    		translate([0,0,(FixtureLength - LEDFlangeThick)])
    			PolyCyl(LEDFlangeOD,2*LEDFlangeThick,LEDSides);
    		translate([FixtureOD/2,0,(FixtureLength + FoamThick/2 - LEDFlangeThick)]) {
    			cube([FixtureOD,TrenchWidth,FoamThick],center=true);
    		}
    		for (angle = [90:90:270])
    			rotate(angle)
    				translate([0.75*PinCircle,0,(FixtureLength - PinLength/2)])
    					PolyCyl(PinOD,PinLength,6);
    		for (angle = [0:120:240])
    			rotate(angle)
    				translate([ShieldScrewCircle/2,0,-Protrusion])
    					rotate(45)
    						PolyCyl(Tap2_56,(ShieldThick - 6*ThreadThick + Protrusion));
    		if (0)
    			translate([0,0,FixtureLength/4])
    				rotate([0,90,0])
    					PolyCyl(GrubScrewOD,FixtureOD);
    	}
    
    }
    
    module AdapterSupport() {
    
    spiderthick = IntegerMultiple(LEDFlangeThick - ThreadThick,ThreadThick);
    
    	color("Yellow")
    		union() {
    			for (leg = [0:LEDSides/2 - 1])
    				rotate(leg*360/LEDSides)
    					translate([0,0,spiderthick/2])
    					cube([(LEDFlangeOD - 0.5*ThreadWidth),
    							2.5*ThreadWidth,
    							spiderthick],
    							center=true);
    			cylinder(r=LEDDia/2,h=spiderthick,$fn=LEDSides);
    			for (bar = [-1:1])
    				translate([LEDDia/3,(bar*3*ThreadWidth - ThreadWidth),0])
    					cube([FixtureOD/2,2*ThreadWidth,IntegerMultiple(LEDFlangeThick - ThreadThick)]);
    		}
    }
    
    module Cap() {
    
    	difference() {
    		cylinder(r=FixtureOD/2,h=CapLength);
    		translate([(FixtureOD/2 - LEDDia/2),0,CapLength]) {
    			cube([FixtureOD,TrenchWidth,2*TrenchDepth],center=true);
    		}
    		translate([0,0,(CapLength - FoamThick)])
    			PolyCyl(FoamOD,(FoamThick + Protrusion));
    		for (angle = [90:90:270])
    			rotate(angle)
    				translate([0.75*PinCircle,0,(CapLength - PinLength/2)])
    					PolyCyl(PinOD,PinLength,6);
    	}
    
    }
    
    module Shield() {
    
    	difference() {
    		cylinder(r=FixtureOD/2,h=ShieldThick);
    		translate([0,0,-Protrusion])
    			PolyCyl(PhotoDiodeStemOD,(ShieldThick + 2*Protrusion));
    		for (angle = [0:120:240])
    			rotate(angle) {
    				translate([ShieldScrewCircle/2,0,-Protrusion])
    					rotate(180/5)
    						PolyCyl(Clear2_56,(ShieldThick + 2*Protrusion));
    				if (0)
    				translate([ShieldScrewCircle/2,0,(ShieldThick - 1.5*Head2_56Thick)])
    					rotate(180/6)
    						PolyCyl(Head2_56,4*Head2_56Thick);
    			}
    	}
    }
    
    //-------------------
    // Build it...
    
    ShowPegGrid();
    
    if (Layout == "Adapter")
    	Adapter();
    
    if (Layout == "Cap")
    	Cap();
    
    if (Layout == "Shield")
    	Shield();
    
    if (Layout == "Show") {
    	translate([0,0,(ShieldThick + Gap)]) {
    		translate([0,0,FixtureLength + CapLength + Gap])
    			rotate([180,0,0])
    				Cap();
    		Adapter();
    
    		color("Orange")
    		for (angle = [90:90:270])
    			rotate(angle)
    				translate([0.75*PinCircle,0,(FixtureLength + Gap - PinLength/2)])
    					PolyCyl(PinOD,PinLength,6);
    	}
    
    	Shield();
    }
    
    	if (Layout == "AdapterSupport") {
    	translate([0,0,FixtureLength])
    		rotate([180,0,0])
    			%Adapter();
    	AdapterSupport();
    }
    
    if (Layout == "Build") {
    	translate([(Spacing + FixtureOD),0,0]) {
    		translate([0,0,FixtureLength])
    			rotate([180,0,0])
    				Adapter();
    		AdapterSupport();
    	}
    	translate([0,0,0])
    		Cap();
    
    	translate([-(Spacing + FixtureOD),0,0])
    		Shield();
    }