Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.
A plot of the temperature and humidity inside the basement safe over the last year-and-a-half:
Basement Safe
The tray of silica gel (or whatever those granules might be) holds the humidity firmly at 14 to 15 %, at least with a simple masking tape seal around the door opening that dates back to early 2012. Less water vapor gets through the door during the winter, due to the lower basement humidity when it’s cold outside, but it looks like four regenerations per year with just under a kilogram of desiccant in the tray.
Ordinarily, it’d be time for those granules to endure another oven session, but I just picked up a bunch of real silica gel beads and must conjure up some porous bags.
The Bash / Gnuplot script that cleaned the CSV files and produced the plot:
#!/bin/sh
#-- overhead
export GDFONTPATH="/usr/share/fonts/truetype/"
base="${1%.*}"
echo Base name: ${base}
ofile=${base}.png
tfile=$(tempfile)
echo Input file: $1
echo Temporary file: ${tfile}
echo Output file: ${ofile}
#-- prepare csv Hobo logger file
sed 's/^\"/#&/' "$1" | sed 's/^.*Logged/#&/' | sed 's/ ,/,/' | sed 's/\/\([0-9][0-9]\) /\/20\1 /' > ${tfile}
#-- do it
gnuplot << EOF
#set term x11
set term png font "arialbd.ttf" 18 size 950,600
set output "${ofile}"
set title "${base}"
set key noautotitles
unset mouse
set bmargin 4
set grid xtics ytics
set timefmt "%m/%d/%Y %H:%M:%S"
set xdata time
set xlabel "Date"
set format x "%y-%m"
#set xrange [1.8:2.2]
set xtics font "arial,12"
#set mxtics 2
#set logscale y
set ytics nomirror autofreq
set ylabel "Temperature - F"
set format y "%3.0f"
set yrange [40:90]
set mytics 2
set y2label "Relative Humidity - %"
set y2tics nomirror autofreq
set format y2 "%3.0f"
set y2range [10:60]
#set y2tics 32
#set rmargin 9
set datafile separator ","
#set label 1 "label text" at 2.100,110 right font "arialbd,18"
#set arrow from 2.100,110 to 2.105,103 lt 1 lw 2 lc 0
plot \
"${tfile}" using 2:3 axes x1y1 with lines lt 3 title "Temperature",\
"${tfile}" using 2:4 axes x1y2 with lines lt 4 title "Humidity"
EOF
The M2’s build platform consists of an 8×10 inch glass slab atop an aluminum spider, all supported by a trio of fairly stiff springs. Back when I was experimenting with excessive acceleration, I inserted some silicone rubber cylinders to boost the spring constant and stabilize the platform
I removed the screws and springs one by one, tucked a cylinder inside the spring, and reinstalled it:
Silicone rubber pads for M2 platform – installed
The trick is to park the nozzle near the edge of the platform where it will rise without the screw holding it down, measure the distance twixt nozzle and platform, lower the platform by a (known!) 50 mm, install the cylinder, raise the platform, then tweak the screw to put the same distance between the nozzle and the platform as you started with.
This probably doesn’t make much difference with the default 3 m/s2 acceleration, but up around 10 m/s2 it seemed wobbly. No suprise: that’s over 1 G of lateral acceleration and the platform weighs a pound or so.
The hose attached to this garden sprayer had failed last season, but the hose fitting had become one with the sprayer. Soaking it with penetrating oil for far longer than seemed necessary didn’t help, so I tried brute force:
Garden sprayer hose fitting
After convincing myself that wasn’t going to work, I cut the fitting off and tried the old standby of collapsing the threaded shell inward with a small punch:
Garden sprayer – rolled-in fitting
That didn’t work, either: the shell really had become one with the sprayer.
As it turned out, the plastic sprayer body had begun to crack in several high-stress locations and would shortly become Yet Another Project. I cut my losses and tossed the hose and the sprayer.
Given that test fixture, the obvious question is whether the PIN-10AP photodiode’s output current varies linearly with light intensity, just like the specs would lead you to believe. I excavated the sheet of 2-stop neutral density filter gel from the Parts Warehouse Wing and cut some 30 mm disks:
LED Photodiode test fixture – ND filter disks
A single filter layer should reduce the light intensity by 2 f/stops = a factor of 4. Each successive layer reduces the intensity by another factor of 4. They’re all at least reasonably clean and free of defects, but they’re definitely not optical lens quality.
Running the LED with a 100 mA pulse at 20% duty cycle and stacking the disks in the fixture, one by one, between the LED and photodiode, produces this data:
Layers
Attenuation
Scale
V
I – uA
Ratio
0
1
1.0000
8.7
87
1
4
0.2500
1.9
19
4.58
2
16
0.0625
0.43
4.3
4.42
3
64
0.0156
0.097
0.97
4.43
4
256
0.0039
0.022
0.22
4.41
The Ratio column divides successive pairs of current values. The first step, from “no filter” to “one filter”, came out a bit larger than the rest, probably because the gel sheet isn’t anti-reflective and some light bounces off the top.
After that, though, it looks just like I’m cheating, doesn’t it?
The ratios should be 4.0, but the actual 4.4 means it’s a 2.1 stop filter. Close enough, methinks.
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
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
The most convenient way to mount the thing involves a right-angle BNC adapter in my trusty bench vise:
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
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
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();
}