Monthly Science: Burnett Signal Timing

The NYS DOT has been improving the pedestrian crossings at the Burnett – Rt 55 intersection. I expect this will be a bullet item in their Complete Streets compliance document, with favorable job reviews for all parties. The situation for bicyclists using the intersection, which provides the only access from Poughkeepsie to the Dutchess Rail Trail, hasn’t changed in the slightest. No signal timing adjustments, no bike-capable sensor loops, no lane markings, no shoulders, no nothing.

Here’s what NYS DOT’s Complete Streets program looks like from our perspective, with the four-digit frame numbers ticking along at 60 frame/sec.

We’re waiting on Overocker Rd for Burnett traffic to clear enough to cross three lanes from a cold start:

Burnett Signal - 2020-09-25 - front 0006
Burnett Signal – 2020-09-25 – front 0006

That building over there across Burnett is the NYS DOT Region 8 Headquarters, so we’re not in the hinterlands where nobody ever goes.

We’re rolling:

Burnett Signal - 2020-09-25 - front 0258
Burnett Signal – 2020-09-25 – front 0258

The Burnett signals just turned green, although the cars haven’t started moving yet, and we’re accelerating out of Overocker:

Burnett Signal - 2020-09-25 - front 0463
Burnett Signal – 2020-09-25 – front 0463

About 1.5 seconds later, the vehicles have started moving and we’re lining up for the left side of the right-hand lane:

Burnett Signal - 2020-09-25 - front 0752
Burnett Signal – 2020-09-25 – front 0752

There’s no traffic behind us, so we can ride a little more to the right than we usually do, in the hopes of triggering the signal’s unmarked sensor loop:

Burnett Signal - 2020-09-25 - front 1178
Burnett Signal – 2020-09-25 – front 1178

We didn’t expect anything different:

Burnett Signal - 2020-09-25 - front 1333
Burnett Signal – 2020-09-25 – front 1333

We’re rolling at about 12 mph and it’s unreasonable to expect us to jam to a stop whenever the signal turns yellow. Oh, did you notice the truck parked in the sidewalk over on the left?

As usual, 4.3 seconds later, the Burnett signals turn red, so we’re now riding in the “intersection clearing” delay:

Burnett Signal - 2020-09-25 - front 1593
Burnett Signal – 2020-09-25 – front 1593

Two seconds later, the Rt 55 signals turn green:

Burnett Signal - 2020-09-25 - front 1711
Burnett Signal – 2020-09-25 – front 1711

Did you notice all three eastbound lanes of Rt 55 (on our right) were occupied? That means a driver can’t come zipping through without stopping at the green light in their direction.

One second later, we’re still proceeding through the intersection, clearing the lethally smooth manhole cover by a few inches, and approaching the far side:

Burnett Signal - 2020-09-25 - front 1771
Burnett Signal – 2020-09-25 – front 1771

Here’s what the intersection looks like behind me:

Burnett Signal - 2020-09-25 - rear 1
Burnett Signal – 2020-09-25 – rear 1

Another second goes by and we’re pretty much into the far right lane , with the westbound traffic beginning to move:

Burnett Signal - 2020-09-25 - front 1831
Burnett Signal – 2020-09-25 – front 1831

The pedestrian crossing ladder has fresh new paint. They milled off the old paint while reconstructing the crossing, so the scarred asphalt will deteriorate into potholes after a few freeze-thaw cycles. Not their problem, it seems.

Although it’s been three seconds since Rt 55 got a green signal, the eastbound drivers remain stunned by our presence:

Burnett Signal - 2020-09-25 - rear 2
Burnett Signal – 2020-09-25 – rear 2

After another second, we’re almost where we need to be:

Burnett Signal - 2020-09-25 - front 1891
Burnett Signal – 2020-09-25 – front 1891

There’s a new concrete sidewalk on the right, with a wheelchair-accessible signal button I can now hit with my elbow when we’re headed in the other direction. It’s worth noting there is no way to reach Overocker by bicycle, other than riding the sidewalk; there’s only one “complete” direction for vehicular cyclists.

One second later puts us as far to the right as we can get, given all the gravel / debris / deteriorated asphalt along the fog line near the curb:

Burnett Signal - 2020-09-25 - front 1957
Burnett Signal – 2020-09-25 – front 1957

Which is good, because four seconds after the green signal for Rt 55, the pack has overtaken us:

Burnett Signal - 2020-09-25 - rear 3
Burnett Signal – 2020-09-25 – rear 3

If you were the driver of the grayish car in the middle lane, directly behind the black one giving us plenty of room, you might be surprised at the abrupt lane change in front of you. Maybe not, because you had a front-row seat while we went through the intersection.

Elapsed time from the green signal on Burnett: 25 seconds. My point is that another few seconds of all-red intersection clearing time wouldn’t materially affect anybody’s day and would go a long way toward improving bicycle safety.

Unlike the pedestrian crossing upgrade, NYS DOT could fix this with zero capital expenditure: one engineer with keys to the control box, a screwdriver or keyboard (depending on the age of the controls), and the ability to do the right thing could fix it before lunch tomorrow.

But it’s just a typical bike ride on NYS DOT’s Complete Streets, where their planners & designers claim to “promote pedestrian and bicycle travel for all persons.” Maybe that’s true somewhere in NYS DOT’s fantasies, but you’ll find far more evidence from our rides, with plenty of numbers, showing that’s not the case around here.

Raspberry Pi HQ Camera Mount

As far as I can tell, Raspberry Pi cases are a solved problem, so 3D printing an intricate widget to stick a Pi on the back of an HQ camera seems unnecessary unless you really, really like solid modeling, which, admittedly, can be a thing. All you really need is a simple adapter between the camera PCB and the case of your choice:

HQ Camera Backplate - OpenSCAD model
HQ Camera Backplate – OpenSCAD model

A quartet of 6 mm M2.5 nylon spacers mount the adapter to the camera PCB:

RPi HQ Camera - nylon standoffs
RPi HQ Camera – nylon standoffs

The plate has recesses to put the screw heads below the surface. I used nylon screws, but it doesn’t really matter.

The case has all the right openings, slots in the bottom for a pair of screws, and costs six bucks. A pair of M3 brass inserts epoxied into the plate capture the screws:

RPi HQ Camera - case adapter plate - screws
RPi HQ Camera – case adapter plate – screws

Thick washers punched from an old credit card go under the screws to compensate for the case’s silicone bump feet. I suppose Doing the Right Thing would involve 3D printed spacers matching the cross-shaped case cutouts.

Not everyone agrees with my choice of retina-burn orange PETG:

RPi HQ Camera - 16 mm lens - case adapter plate
RPi HQ Camera – 16 mm lens – case adapter plate

Yes, that’s a C-mount TV lens lurking in the background, about which more later.

The OpenSCAD source code as a GitHub Gist:

// Raspberry Pi HQ Camera Backplate
// Ed Nisley KE4ZNU 2020-09
//-- Extrusion parameters
/* [Hidden] */
ThreadThick = 0.25;
ThreadWidth = 0.40;
HoleWindage = 0.2;
function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
function IntegerLessMultiple(Size,Unit) = Unit * floor(Size / Unit);
Protrusion = 0.1; // make holes end cleanly
inch = 25.4;
ID = 0;
OD = 1;
LENGTH = 2;
//- Basic dimensions
CamPCB = [39.0,39.0,1.5]; // Overall PCB size, plus a bit
CornerRound = 3.0; // ... has rounded corners
CamScrewOC = [30.0,30.0,0]; // ... mounting screw layout
CamScrew = [2.5,5.0,2.2]; // ... LENGTH = head thickness
Standoff = [2.5,5.5,6.0]; // nylon standoffs
Insert = [3.0,4.0,4.0];
WallThick = IntegerMultiple(2.0,ThreadWidth);
PlateThick = Insert[LENGTH];
CamBox = [CamPCB.x + 2*WallThick,
CamPCB.y + 2*WallThick,
Standoff.z + PlateThick + CamPCB.z + 1.0];
PiPlate = [90.0,60.0,PlateThick];
PiPlateOffset = [0.0,(PiPlate.y - CamBox.y)/2,0];
PiSlotOC = [0.0,40.0];
PiSlotOffset = [3.5,3.5];
NumSides = 2*3*4;
TextDepth = 2*ThreadThick;
//----------------------
// 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);
}
//----------------------
// Build it
difference() {
union() {
hull() // camera enclosure
for (i=[-1,1], j=[-1,1])
translate([i*(CamBox.x/2 - CornerRound),j*(CamBox.y/2 - CornerRound),0])
cylinder(r=CornerRound,h=CamBox.z,$fn=NumSides);
translate(PiPlateOffset)
hull()
for (i=[-1,1], j=[-1,1]) // Pi case plate
translate([i*(PiPlate.x/2 - CornerRound),j*(PiPlate.y/2 - CornerRound),0])
cylinder(r=CornerRound,h=PiPlate.z,$fn=NumSides);
}
hull() // camera PCB space
for (i=[-1,1], j=[-1,1])
translate([i*(CamPCB.x/2 - CornerRound),j*(CamPCB.y/2 - CornerRound),PlateThick])
cylinder(r=CornerRound,h=CamBox.z,$fn=NumSides);
translate([0,-CamBox.y/2,PlateThick + CamBox.z/2])
cube([CamScrewOC.x - Standoff[OD],CamBox.y,CamBox.z],center=true);
for (i=[-1,1], j=[-1,1]) // camera screws with head recesses
translate([i*CamScrewOC.x/2,j*CamScrewOC.y/2,-Protrusion]) {
PolyCyl(CamScrew[ID],2*CamBox.z,6);
PolyCyl(CamScrew[OD],CamScrew[LENGTH] + Protrusion,6);
}
for (j=[-1,1]) // Pi case screw inserts
translate([0,j*PiSlotOC.y/2 + PiSlotOffset.y,-Protrusion] + PiPlateOffset)
PolyCyl(Insert[OD],2*PiPlate.z,6);
translate([-PiPlate.x/2 + (PiPlate.x - CamBox.x)/4,0,PlateThick - TextDepth/2] + PiPlateOffset)
cube([15.0,30.0,TextDepth + Protrusion],center=true);
}
translate([-PiPlate.x/2 + (PiPlate.x - CamBox.x)/4 + 3,0,PlateThick - TextDepth - Protrusion] + PiPlateOffset)
linear_extrude(height=TextDepth + Protrusion,convexity=2)
rotate(-90)
text("Ed Nisley",font="Arial:style=Bold",halign="center",valign="center",size=4,spacing=1.05);
translate([-PiPlate.x/2 + (PiPlate.x - CamBox.x)/4 - 3,0,PlateThick - TextDepth - Protrusion] + PiPlateOffset)
linear_extrude(height=TextDepth + Protrusion,convexity=2)
rotate(-90)
text("KE4ZNU",font="Arial:style=Bold",halign="center",valign="center",size=4,spacing=1.05);

Mary’s Zucchini Bread Recipe

After grating the nutmeg, continue with this:

Mary's Zucchini Bread Recipe
Mary’s Zucchini Bread Recipe

To end up with this:

Zucchini bread - minus QC sample
Zucchini bread – minus QC sample

Mary omits the cloves.

Applesauce is completely optional. Should you prefer a softer & sweeter loaf, give it a try.

Conversely, reduce the sugar by about half if you’ve accustomed yourself to a keto-oid diet; the raisins carry enough sweetness for us. You can use brown sugar if you like.

She derived it from the Garden Way’s Zucchini Cookbook by Ralston & Jordan (© 1977):

Zucchini Bread Recipe - Garden Way Zucchini Cookbook
Zucchini Bread Recipe – Garden Way Zucchini Cookbook

Obviously, cooking is not an exact science; a recipe is just where you start …

Algorithmic pricing / money laundering is a thing:

Garden Way Zucchini Cookbook - Amazon listing
Garden Way Zucchini Cookbook – Amazon listing

Ya can’t make this stuff up …

Bike Helmet Mirror: Ball Mount

Nine years ago, I didn’t know how enough to design a bike helmet mirror with a ball mount, but even an old dog can learn a new trick:

Helmet Mirror Ball Mount - on helmet
Helmet Mirror Ball Mount – on helmet

However, it’s worth noting my original, butt-ugly Az-El mounts lasted for all of those nine years, admittedly with adjustments along the way, which is far more than the commercial mounts making me unhappy enough to scratch my itch.

The mount adapts the split spherical clamp from the daytime running light:

Helmet Mirror Mount - Ball
Helmet Mirror Mount – Ball

Scaling it down for a 10 mm polypropylene ball around the base of the 30 mm inspection mirror’s shaft simplified everything:

Helmet Mirror Ball Mount - drilled ball test
Helmet Mirror Ball Mount – drilled ball test

I’m reasonably sure I couldn’t have bought 100 polypro balls for eight bucks a decade ago, but we’ll never know. Drilling the hole was a complete botch job, about which more later. The shaft came from a spare mirror mount I made up a while ago; a new shaft appears below.

The solid model, like Gaul, is in three parts divided:

Helmet Mirror Ball Mount - Slic3r
Helmet Mirror Ball Mount – Slic3r

The helmet plate (on the right) has a slight indent more-or-less matching the helmet curvature and gets a layer of good double-stick foam tape. The clamp base (on the left) has a pair of brass inserts epoxied into matching recesses below the M3 clearance holes:

Helmet Mirror Ball Mount - inserts
Helmet Mirror Ball Mount – inserts

A layer of epoxy then sticks the helmet plate in place, with the inserts providing positive alignment:

Helmet Mirror Ball Mount - plates
Helmet Mirror Ball Mount – plates

The clamp screws pull the inserts against the plastic in the clamp base, so they can’t pull out or through, and the plates give the epoxy enough bonding surface that (I’m pretty sure) they won’t ever come apart.

I turned down a 2 mm brass insert to fit inside the butt end of the mirror shaft and topped it off with a random screw harvested from a dead hard drive:

Helmet Mirror Ball Mount - assembled - rear view
Helmet Mirror Ball Mount – assembled – rear view

At the start, it wasn’t obvious the shaft would stay stuck in the ball, so I figured making it impossible to pull out would eliminate the need to find it by the side of the road. As things turned out, the clamp exerts enough force to ensure the shaft ain’t goin’ nowhere, so I’ll plug future shafts with epoxy.

The front side of the clamp looks downright sleek:

Helmet Mirror Ball Mount - assembled - front view
Helmet Mirror Ball Mount – assembled – front view

Well, how about “chunky”?

The weird gray-black highlights are optical effects from clear / natural PETG, rather than embedded grunge; it looks better in person. I should have used retina-burn orange or stylin’ black.

This mount is much smaller than the old one and should, in the event of a crash, not cause much injury. Based on how the running light clamp fractures, I expect the clamp will simply tear out of the base on impact. In the last decade, neither of us has crashed, so I don’t know what the old mount would do.

The clamp is 7 mm thick (front-to-back), set by the M3 washer diameter, with 1.5 mm of ball sticking out on each side. The model has a kerf one thread high (0.25 mm) between the pieces to add clamping force and, with the screws tightened down, moving the ball requires a disturbingly large effort. I added a touch of rosin and that ball straight-up won’t move, which probably means the shaft will bend upon droppage; I have several spare mirrors in stock.

On the other paw, the ball turns smoothly in the clamp and it’s easy to position the shaft as needed: much better than the old Az-El mount!

The inspection mirror hangs from a double ball joint which arrives with a crappy screw + nut. I epoxied the old mirror mount nut in place, but this time around I drilled the plates for a 3 mm stainless SHCS, used a wave washer for a bit of flexible force, and topped it off with a nyloc nut:

Helmet Mirror Ball Mount - mirror joint
Helmet Mirror Ball Mount – mirror joint

I’m unhappy with how it looks and don’t like how the washer hangs in free space between those bumps, so I may eventually turn little brass fittings to even things out. It’s either that or more epoxy.

So far, though, it’s working pretty well and both units meet customer requirements.

The OpenSCAD source code as a GitHub Gist:

// Bike helmet mirror mount - ball joint
// Ed Nisley KE4ZNU 2020-09
/* [Layout options] */
Layout = "Build"; // [Build, Show, Plate, Base, Clamp]
//-- Extrusion parameters
// Extrusion parameters
/* [Hidden] */
ThreadThick = 0.25;
ThreadWidth = 0.40;
HoleWindage = 0.2;
function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
function IntegerLessMultiple(Size,Unit) = Unit * floor(Size / Unit);
Protrusion = 0.1; // make holes end cleanly
inch = 25.4;
ID = 0;
OD = 1;
LENGTH = 2;
//- Basic dimensions
MountDia = 30.0; // footprint on helmet
BallDia = 10.0;
BallRad = BallDia / 2;
WallThick = IntegerMultiple(2.0,ThreadWidth);
FloorThick = IntegerMultiple(2.0,ThreadThick);
CornerRound = 2.0;
Insert = [3.0,4.0,4.0]; // threaded brass insert
Screw = [3.0,5.5,25.0]; // clamp screw
Washer = [3.7,7.0,0.7]; // washer
ShowGap = 2.0;
BuildGap = 5.0;
//-- Helmet Interface Plate
ScrewOC = BallDia + 2*WallThick + Screw[ID];
echo(str("Screw OC: ",ScrewOC));
Clamp = [ceil(Washer[OD]), // barely holds washer under screw
ScrewOC + Washer[OD], // minimal clearance for washer
BallDia +2*FloorThick // screw fits through insert
];
Kerf = ThreadThick;
echo(str("Clamp: ",Clamp));
HelmetCX = 60.0; // empirical helmet side curve
HelmetMX = 3.0;
HelmetRX = (pow(HelmetMX,2) + pow(HelmetCX,2)/4)/(2*HelmetMX);
HelmetPlateC = MountDia;
HelmetPlateTheta = atan(HelmetPlateC/HelmetRX);
HelmetPlateM = 2*HelmetRX*pow(sin(HelmetPlateTheta/4),2);
echo(str("Plate indent: ",HelmetPlateM));
HelmetPlateThick = max(FloorThick,0.6*Insert[LENGTH]) + HelmetPlateM;
echo(str("Screw length: ",Clamp.z + Insert[LENGTH]));
MountSides = 2*3*4;
//----------------------
// 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);
}
//----------------------
// Clamp frame around ball
module ClampFrame() {
difference() {
union() {
hull()
for (i=[-1,1], j=[-1,1]) {
translate([i*(Clamp.x/2 - CornerRound),j*(Clamp.y/2 - CornerRound),Clamp.z/2 - CornerRound])
sphere(r=CornerRound,$fn=24);
translate([i*(Clamp.x/2 - CornerRound),j*(Clamp.y/2 - CornerRound),-Clamp.z/2])
cylinder(r=CornerRound,$fn=24);
}
for (j=[-1,1])
translate([0,j*ScrewOC/2,0])
rotate(180/12)
cylinder(d=Washer[OD],h=Clamp.z/2,$fn=12);
}
sphere(d=BallDia + HoleWindage,$fn=48);
cube([2*MountDia,2*MountDia,Kerf],center=true);
for (j=[-1,1])
translate([0,j*ScrewOC/2,-Screw[LENGTH]])
rotate(180/6)
PolyCyl(Screw[ID],2*Screw[LENGTH],6);
}
}
module ClampSelect(Section) {
XlateZ = (Section == "Top") ? Clamp.z/2 :
(Section == "Bottom") ? -Clamp.z/2 :
0;
intersection(convexity=5) {
ClampFrame();
translate([0,0,XlateZ])
cube([2*Clamp.x,2*Clamp.y,Clamp.z + 2*Protrusion],center=true);
}
}
//----------------------
// Concave plate fitting helmet shell
module HelmetPlate() {
render()
difference() {
cylinder(d=MountDia,h=HelmetPlateThick,$fn=MountSides);
translate([0,0,HelmetPlateThick - HelmetPlateM + HelmetRX])
sphere(r=HelmetRX,$fn=128);
for (j=[-1,1])
translate([0,j*ScrewOC/2,-Protrusion]) {
PolyCyl(Insert[OD],0.6*Insert[LENGTH] + Protrusion,6);
PolyCyl(Screw[ID],2*HelmetPlateThick,6);
}
}
}
//----------------------
// Base of clamp ring
module MountBase() {
difference() {
union() {
cylinder(d=MountDia,h=FloorThick,$fn=MountSides);
translate([0,0,FloorThick + Clamp.z/2])
ClampSelect("Bottom");
}
for (j=[-1,1])
translate([0,j*ScrewOC/2,-Protrusion])
rotate(180/6)
PolyCyl(Insert[OD],0.6*Insert[LENGTH] + Protrusion,6);
}
}
//----------------------
// Lash it together
if (Layout == "Plate") {
HelmetPlate();
}
if (Layout == "Base") {
MountBase();
}
if (Layout == "Clamp") {
ClampFrame();
}
if (Layout == "Show") {
rotate([180,0,0])
HelmetPlate();
translate([0,0,ShowGap]) {
MountBase();
color("Ivory",0.3)
translate([0,0,Clamp.z/2 + FloorThick + ShowGap/2])
sphere(d=BallDia);
translate([0,0,Clamp.z/2 + FloorThick + ShowGap])
ClampSelect("Top");
}
}
if (Layout == "Build") {
translate([MountDia/2 + BuildGap,0,0])
HelmetPlate();
translate([-(MountDia/2 + BuildGap),0,0])
MountBase();
translate([0,MountDia/2 + BuildGap,Clamp.z/2])
rotate([0,180,0])
rotate(90)
ClampSelect("Top");
}

The original doodles include a bit of dress-up fairing that didn’t make the cut:

Helmet Mirror Ball Mount - doodles
Helmet Mirror Ball Mount – doodles

Discrete LM3909 LED Flasher: Circuit Variations

The basic discrete LM3909 LED Flasher circuit looks like this:

Discrete LM3909 - basic circuit
Discrete LM3909 – basic circuit

The LM3909 IC boosted a single 1.5 V cell enough to fire a red(-ish) LED, even with the cell well under 1 V. I want to blink a blue(-ish) LED from a pair of AA alkaline cells (with the right size & heft to serve as a base for the hairball circuitry), so the voltage ranges from just over 3 V down to maybe 1.5 V. Although the original circuit works, the LED pulse is long enough to put a reverse bias on the timing capacitor; a 470 µF electrolytic cap (positive terminal on the right at node P2-OUT) produces a pulse every few seconds.

A slightly tweaked version of the circuitry puts -400 mV across C1 (green trace) by the end of the pulse:

Discrete LM3909 - basic circuit - 3.0 V simulation
Discrete LM3909 – basic circuit – 3.0 V simulation

The App Note describes the negative feedback loop from the collector of “power transistor” Q3 through Q4 and Q1, closing through the Q2 current mirror. The base-emitter drops of Q4 and Q1 set the trip point where Q1 starts to conduct and the LED turns on.

Q3 is on when the LED is on, with C1 reverse-charging through R1 and the LED. The voltage at the top of R2 rises from the negative voltage at the start of the pulse, carrying the emitter of Q1 along with it. The LED pulse will end when the rising emitter voltage shuts off Q1 and, thus, the Q2 current mirror driving Q3. Because Q3 holds the bottom of R5 close to 0 V, the base of Q4 is at about half the supply voltage, so Q1 remains on until its emitter rises to about 2 forward drops (handwavingly ignoring the R6 + R7 voltage divider) below the supply.

If the LED pulse is longer than required to completely discharge C1, the poor cap gets reverse-biased and suffers indigestion. Aluminum electrolytics can withstand a little reverse bias, but it’s Bad Practice.

When Q3 and the LED are off, C1 forward-charges through (R4 + R5) + R2, with most of the initial voltage across R2, because C1 should start with a little more than 0 V across it. This holds the current mirror off until C1 charges enough to raise the base of Q4 about two forward drops above Q1’s emitter, shove current through Q4 and Q1, turn on the Q2 current mirror, Q3, and light the LED.

Around and around it goes!

The worst case for reverse charge happens at higher supply (a.k.a. battery) voltages and higher LED currents. Reducing the reverse charge time requires more forward drop through Q4 + Q1 to soak up the higher voltage and lower the trip voltage at Q1’s emitter, which suggests putting another forward-biased junction in series.

Putting a diode in Q1’s base lead doesn’t produce much improvement:

Discrete LM3909 - Q1 B diode - 3.0 V
Discrete LM3909 – Q1 B diode – 3.0 V

Perhaps because the 27 µA current at the trip point is so low the diode doesn’t actually have much forward drop; the simulation says 400 mV.

Putting the diode in the emitter runs the current mirror’s 5 mA through it:

Discrete LM3909 - Q1 E diode - 3.0 V
Discrete LM3909 – Q1 E diode – 3.0 V

The overall period remains about 2 s, but the LED pulse = reverse charge time drops by a factor of two and the cap voltage bottoms out at 0 V, so that’s good.

A Darlington transistor provides far more gain to compensate for the reduced base drive:

Discrete LM3909 - Darl Q1 - 3.0 V
Discrete LM3909 – Darl Q1 – 3.0 V

The LED pulse is slightly shorter and its current goes up a smidge, but the cap voltage remains above zero.

A line in the LM3909 App Note mentions that the Q2 current mirror amplifies Q1’s emitter current by a factor of three: “This current will be amplified by about 3 by Q2 and passed to the base of Q3”. An IC current mirror’s designer can scale its output by varying the collector area, but out here in the discrete world we must splice multiple transistors in parallel:

Discrete LM3909 - Darl Q1 3xQ2- 3.0 V
Discrete LM3909 – Darl Q1 3xQ2- 3.0 V

More base drive in Q3 doesn’t buy much, because it’s already pretty well saturated during the pulse, but the current goes up enough to push C1 slightly into reverse charge territory again. As far as I can tell, the factor-of-three gain was required to make up for the relatively poor performance of IC technology around 1970; things have definitely improved since then.

It’s worth mentioning that the actual circuitry (in particular, the LEDs!) will differ from the simulations, so the pretty plots are more along the lines of serving suggestions than actual predictions. Verily, a simulation can’t prove that a circuit will work, but can sometimes help show why it won’t.

All the LTSpice simulation files tucked into a GitHub Gist:

Version 4
SHEET 1 1816 760
WIRE 64 -16 0 -16
WIRE 112 -16 64 -16
WIRE 272 -16 112 -16
WIRE 544 -16 272 -16
WIRE 1024 -16 544 -16
WIRE 1200 -16 1024 -16
WIRE 112 0 112 -16
WIRE 272 0 272 -16
WIRE 1024 32 1024 -16
WIRE 1200 32 1200 -16
WIRE 0 48 0 -16
WIRE 544 48 544 -16
WIRE 944 80 864 80
WIRE 960 80 944 80
WIRE 1136 80 1104 80
WIRE 272 96 272 80
WIRE 400 96 272 96
WIRE 480 96 400 96
WIRE 400 112 400 96
WIRE 112 128 112 80
WIRE 272 128 272 96
WIRE 688 128 640 128
WIRE 800 128 752 128
WIRE 944 144 944 80
WIRE 1024 144 1024 128
WIRE 1024 144 944 144
WIRE 1104 144 1104 80
WIRE 1104 144 1024 144
WIRE 112 160 112 128
WIRE 864 192 864 176
WIRE 912 192 864 192
WIRE 0 208 0 128
WIRE 400 208 400 192
WIRE 640 208 640 128
WIRE 640 208 400 208
WIRE 400 224 400 208
WIRE 112 272 112 224
WIRE 128 272 112 272
WIRE 224 272 192 272
WIRE 272 272 272 208
WIRE 272 272 224 272
WIRE 912 272 912 192
WIRE 400 336 400 304
WIRE 544 336 544 144
WIRE 544 336 400 336
WIRE 112 368 112 272
WIRE 912 368 912 352
WIRE 912 368 112 368
WIRE 272 400 272 272
WIRE 112 416 112 368
WIRE 1200 448 1200 128
WIRE 1200 448 336 448
WIRE 112 464 112 416
WIRE 400 480 400 336
WIRE 0 496 0 288
WIRE 0 496 -32 496
WIRE -32 528 -32 496
WIRE 0 576 0 496
WIRE 64 576 0 576
WIRE 112 576 112 544
WIRE 112 576 64 576
WIRE 272 576 272 496
WIRE 272 576 112 576
WIRE 400 576 400 560
WIRE 400 576 272 576
FLAG -32 528 0
FLAG 112 128 P6-RLIM
FLAG 112 416 P18-RC
FLAG 224 272 P2-OUT
FLAG 64 -16 P5-V+
FLAG 64 576 P4-V-
SYMBOL res 96 -16 R0
SYMATTR InstName R1
SYMATTR Value 39
SYMBOL LED 96 160 R0
SYMATTR InstName D1
SYMATTR Value LXHL-BW02
SYMBOL res 96 448 R0
SYMATTR InstName R2
SYMATTR Value 9.1k
SYMBOL cap 192 256 R90
WINDOW 0 0 32 VBottom 2
WINDOW 3 32 32 VTop 2
WINDOW 40 52 32 VTop 2
SYMATTR InstName C1
SYMATTR Value 1m
SYMATTR SpiceLine2 ic=0.5
SYMBOL res 256 112 R0
SYMATTR InstName R5
SYMATTR Value 390
SYMBOL res 256 -16 R0
SYMATTR InstName R4
SYMATTR Value 390
SYMBOL npn 336 400 M0
SYMATTR InstName Q3
SYMATTR Value 2N3904
SYMBOL res 384 96 R0
SYMATTR InstName R6
SYMATTR Value 20k
SYMBOL res 384 208 R0
SYMATTR InstName R7
SYMATTR Value 10k
SYMBOL res 384 464 R0
SYMATTR InstName R8
SYMATTR Value 20k
SYMBOL npn 480 48 R0
SYMATTR InstName Q4
SYMATTR Value 2N3904
SYMBOL npn 800 80 R0
SYMATTR InstName Q1
SYMATTR Value 2N3904
SYMBOL pnp 960 128 M180
SYMATTR InstName Q2a
SYMATTR Value 2N3906
SYMBOL voltage 0 192 R0
WINDOW 123 0 0 Left 0
WINDOW 39 24 116 Left 2
SYMATTR InstName V1
SYMATTR Value 1.5
SYMBOL res 896 256 R0
SYMATTR InstName R9
SYMATTR Value 100
SYMBOL pnp 1136 128 M180
SYMATTR InstName Q2b
SYMATTR Value 2N3906
SYMBOL res -16 32 R0
SYMATTR InstName PTC
SYMATTR Value 5
SYMBOL diode 688 144 R270
WINDOW 0 32 32 VTop 2
WINDOW 3 0 32 VBottom 2
SYMATTR InstName D2
SYMATTR Value 1N4148
TEXT -56 192 VRight 2 ;AA Alkaline
TEXT 512 528 Left 2 !.tran 30
TEXT 504 496 Left 2 ;Pins 3 and 7 = no connect
TEXT 760 488 Left 2 ;Sorted by Vd at If=20 mA \n \nPart # Mfg Is (A) N Iave (A) Vf@Iave (V) Vd@If (V)\nQTLP690C Fairchild 1.00E-22 1.500 0.16 1.90 1.82\nPT-121-B Luminous 4.35E-07 8.370 20.00 3.84 2.34\nLUW-W5AP OSRAM 6.57E-08 7.267 2.00 3.26 2.39\nLXHL-BW02 Lumileds 4.50E-20 2.600 0.40 2.95 2.75\nW5AP-LZMZ-5K Lumileds 3.50E-17 3.120 2.00 3.13 2.76\nLXK2-PW14 Lumileds 3.50E-17 3.120 1.60 3.11 2.76\nAOT-2015 AOT 5.96E-10 6.222 0.18 3.16 2.80\nNSSW008CT-P Nichia 2.30E-16 3.430 0.04 2.92 2.86\nNSSWS108T Nichia 1.13E-18 3.020 0.04 2.99 2.94\nNSPW500BS Nichia 2.70E-10 6.790 0.03 3.27 3.20\nNSCW100 Nichia 1.69E-08 9.626 0.03 3.60 3.50
TEXT 168 256 Left 4 ;+
TEXT 656 56 Left 2 ;3 V battery needs more VBE
Version 4
SHEET 1 1816 760
WIRE 64 -16 0 -16
WIRE 112 -16 64 -16
WIRE 272 -16 112 -16
WIRE 544 -16 272 -16
WIRE 1024 -16 544 -16
WIRE 1200 -16 1024 -16
WIRE 112 0 112 -16
WIRE 272 0 272 -16
WIRE 1024 32 1024 -16
WIRE 1200 32 1200 -16
WIRE 0 48 0 -16
WIRE 544 48 544 -16
WIRE 848 80 736 80
WIRE 944 80 848 80
WIRE 960 80 944 80
WIRE 1136 80 1104 80
WIRE 272 96 272 80
WIRE 400 96 272 96
WIRE 480 96 400 96
WIRE 400 112 400 96
WIRE 112 128 112 80
WIRE 272 128 272 96
WIRE 672 128 640 128
WIRE 848 144 848 80
WIRE 944 144 944 80
WIRE 1024 144 1024 128
WIRE 1024 144 944 144
WIRE 1104 144 1104 80
WIRE 1104 144 1024 144
WIRE 112 160 112 128
WIRE 736 192 736 176
WIRE 784 192 736 192
WIRE 0 208 0 128
WIRE 400 208 400 192
WIRE 640 208 640 128
WIRE 640 208 400 208
WIRE 400 224 400 208
WIRE 912 240 848 240
WIRE 112 272 112 224
WIRE 128 272 112 272
WIRE 224 272 192 272
WIRE 272 272 272 208
WIRE 272 272 224 272
WIRE 912 272 912 240
WIRE 400 336 400 304
WIRE 544 336 544 144
WIRE 544 336 400 336
WIRE 112 368 112 272
WIRE 912 368 912 352
WIRE 912 368 112 368
WIRE 272 400 272 272
WIRE 112 416 112 368
WIRE 1200 448 1200 128
WIRE 1200 448 336 448
WIRE 112 464 112 416
WIRE 400 480 400 336
WIRE 0 496 0 288
WIRE 0 496 -32 496
WIRE -32 528 -32 496
WIRE 0 576 0 496
WIRE 64 576 0 576
WIRE 112 576 112 544
WIRE 112 576 64 576
WIRE 272 576 272 496
WIRE 272 576 112 576
WIRE 400 576 400 560
WIRE 400 576 272 576
FLAG -32 528 0
FLAG 112 128 P6-RLIM
FLAG 112 416 P18-RC
FLAG 224 272 P2-OUT
FLAG 64 -16 P5-V+
FLAG 64 576 P4-V-
SYMBOL res 96 -16 R0
SYMATTR InstName R1
SYMATTR Value 39
SYMBOL LED 96 160 R0
SYMATTR InstName D1
SYMATTR Value NSSW008CT-P1
SYMBOL res 96 448 R0
SYMATTR InstName R2
SYMATTR Value 9.1k
SYMBOL cap 192 256 R90
WINDOW 0 0 32 VBottom 2
WINDOW 3 32 32 VTop 2
WINDOW 40 52 32 VTop 2
SYMATTR InstName C1
SYMATTR Value 470
SYMATTR SpiceLine2 ic=0.5
SYMBOL res 256 112 R0
SYMATTR InstName R5
SYMATTR Value 390
SYMBOL res 256 -16 R0
SYMATTR InstName R4
SYMATTR Value 390
SYMBOL npn 336 400 M0
SYMATTR InstName Q3
SYMATTR Value 2N3904
SYMBOL res 384 96 R0
SYMATTR InstName R6
SYMATTR Value 20k
SYMBOL res 384 208 R0
SYMATTR InstName R7
SYMATTR Value 10k
SYMBOL res 384 464 R0
SYMATTR InstName R8
SYMATTR Value 20k
SYMBOL npn 480 48 R0
SYMATTR InstName Q4
SYMATTR Value 2N3904
SYMBOL npn 672 80 R0
SYMATTR InstName Q1a
SYMATTR Value 2N3904
SYMBOL pnp 960 128 M180
SYMATTR InstName Q2a
SYMATTR Value 2N3906
SYMBOL voltage 0 192 R0
WINDOW 123 0 0 Left 0
WINDOW 39 24 116 Left 2
SYMATTR InstName V1
SYMATTR Value 1.5
SYMBOL res 896 256 R0
SYMATTR InstName R9
SYMATTR Value 100
SYMBOL pnp 1136 128 M180
SYMATTR InstName Q2b
SYMATTR Value 2N3906
SYMBOL npn 784 144 R0
SYMATTR InstName Q1b
SYMATTR Value 2N3904
SYMBOL res -16 32 R0
SYMATTR InstName PTC
SYMATTR Value 5
TEXT -56 192 VRight 2 ;AA Alkaline
TEXT 512 528 Left 2 !.tran 30
TEXT 504 496 Left 2 ;Pins 3 and 7 = no connect
TEXT 760 488 Left 2 ;Sorted by Vd at If=20 mA \n \nPart # Mfg Is (A) N Iave (A) Vf@Iave (V) Vd@If (V)\nQTLP690C Fairchild 1.00E-22 1.500 0.16 1.90 1.82\nPT-121-B Luminous 4.35E-07 8.370 20.00 3.84 2.34\nLUW-W5AP OSRAM 6.57E-08 7.267 2.00 3.26 2.39\nLXHL-BW02 Lumileds 4.50E-20 2.600 0.40 2.95 2.75\nW5AP-LZMZ-5K Lumileds 3.50E-17 3.120 2.00 3.13 2.76\nLXK2-PW14 Lumileds 3.50E-17 3.120 1.60 3.11 2.76\nAOT-2015 AOT 5.96E-10 6.222 0.18 3.16 2.80\nNSSW008CT-P Nichia 2.30E-16 3.430 0.04 2.92 2.86\nNSSWS108T Nichia 1.13E-18 3.020 0.04 2.99 2.94\nNSPW500BS Nichia 2.70E-10 6.790 0.03 3.27 3.20\nNSCW100 Nichia 1.69E-08 9.626 0.03 3.60 3.50
TEXT 168 256 Left 4 ;+
TEXT 664 256 Left 2 ;Use MPSA14 Darlington
TEXT 656 56 Left 2 ;3 V battery needs more VBE
Version 4
SHEET 1 1816 760
WIRE 64 -16 0 -16
WIRE 112 -16 64 -16
WIRE 272 -16 112 -16
WIRE 544 -16 272 -16
WIRE 1024 -16 544 -16
WIRE 1200 -16 1024 -16
WIRE 1360 -16 1200 -16
WIRE 1520 -16 1360 -16
WIRE 112 0 112 -16
WIRE 272 0 272 -16
WIRE 1024 32 1024 -16
WIRE 1200 32 1200 -16
WIRE 1360 32 1360 -16
WIRE 1520 32 1520 -16
WIRE 0 48 0 -16
WIRE 544 48 544 -16
WIRE 848 80 736 80
WIRE 944 80 848 80
WIRE 960 80 944 80
WIRE 1136 80 1104 80
WIRE 1296 80 1280 80
WIRE 1456 80 1440 80
WIRE 272 96 272 80
WIRE 400 96 272 96
WIRE 480 96 400 96
WIRE 400 112 400 96
WIRE 112 128 112 80
WIRE 272 128 272 96
WIRE 672 128 640 128
WIRE 848 144 848 80
WIRE 944 144 944 80
WIRE 1024 144 1024 128
WIRE 1024 144 944 144
WIRE 1104 144 1104 80
WIRE 1104 144 1024 144
WIRE 1280 144 1280 80
WIRE 1280 144 1104 144
WIRE 1440 144 1440 80
WIRE 1440 144 1280 144
WIRE 112 160 112 128
WIRE 736 192 736 176
WIRE 784 192 736 192
WIRE 1200 192 1200 128
WIRE 1360 192 1360 128
WIRE 1360 192 1200 192
WIRE 1520 192 1520 128
WIRE 1520 192 1360 192
WIRE 0 208 0 128
WIRE 400 208 400 192
WIRE 640 208 640 128
WIRE 640 208 400 208
WIRE 400 224 400 208
WIRE 912 240 848 240
WIRE 112 272 112 224
WIRE 128 272 112 272
WIRE 224 272 192 272
WIRE 272 272 272 208
WIRE 272 272 224 272
WIRE 912 272 912 240
WIRE 400 336 400 304
WIRE 544 336 544 144
WIRE 544 336 400 336
WIRE 112 368 112 272
WIRE 912 368 912 352
WIRE 912 368 112 368
WIRE 272 400 272 272
WIRE 112 416 112 368
WIRE 1200 448 1200 192
WIRE 1200 448 336 448
WIRE 112 464 112 416
WIRE 400 480 400 336
WIRE 0 496 0 288
WIRE 0 496 -32 496
WIRE -32 528 -32 496
WIRE 0 576 0 496
WIRE 64 576 0 576
WIRE 112 576 112 544
WIRE 112 576 64 576
WIRE 272 576 272 496
WIRE 272 576 112 576
WIRE 400 576 400 560
WIRE 400 576 272 576
FLAG -32 528 0
FLAG 112 128 P6-RLIM
FLAG 112 416 P18-RC
FLAG 224 272 P2-OUT
FLAG 64 -16 P5-V+
FLAG 64 576 P4-V-
SYMBOL res 96 -16 R0
SYMATTR InstName R1
SYMATTR Value 39
SYMBOL LED 96 160 R0
SYMATTR InstName D1
SYMATTR Value NSSW008CT-P1
SYMBOL res 96 448 R0
SYMATTR InstName R2
SYMATTR Value 9.1k
SYMBOL cap 192 256 R90
WINDOW 0 0 32 VBottom 2
WINDOW 3 32 32 VTop 2
WINDOW 40 52 32 VTop 2
SYMATTR InstName C1
SYMATTR Value 470
SYMATTR SpiceLine2 ic=0.5
SYMBOL res 256 112 R0
SYMATTR InstName R5
SYMATTR Value 390
SYMBOL res 256 -16 R0
SYMATTR InstName R4
SYMATTR Value 390
SYMBOL npn 336 400 M0
SYMATTR InstName Q3
SYMATTR Value 2N3904
SYMBOL res 384 96 R0
SYMATTR InstName R6
SYMATTR Value 20k
SYMBOL res 384 208 R0
SYMATTR InstName R7
SYMATTR Value 10k
SYMBOL res 384 464 R0
SYMATTR InstName R8
SYMATTR Value 20k
SYMBOL npn 480 48 R0
SYMATTR InstName Q4
SYMATTR Value 2N3904
SYMBOL npn 672 80 R0
SYMATTR InstName Q1a
SYMATTR Value 2N3904
SYMBOL pnp 960 128 M180
SYMATTR InstName Q2a
SYMATTR Value 2N3906
SYMBOL voltage 0 192 R0
WINDOW 123 0 0 Left 0
WINDOW 39 24 116 Left 2
SYMATTR InstName V1
SYMATTR Value 1.5
SYMBOL res 896 256 R0
SYMATTR InstName R9
SYMATTR Value 100
SYMBOL pnp 1136 128 M180
SYMATTR InstName Q2b
SYMATTR Value 2N3906
SYMBOL pnp 1296 128 M180
SYMATTR InstName Q2c
SYMATTR Value 2N3906
SYMBOL pnp 1456 128 M180
SYMATTR InstName Q2d
SYMATTR Value 2N3906
SYMBOL npn 784 144 R0
SYMATTR InstName Q1b
SYMATTR Value 2N3904
SYMBOL res -16 32 R0
SYMATTR InstName PTC
SYMATTR Value 5
TEXT -56 192 VRight 2 ;AA Alkaline
TEXT 512 528 Left 2 !.tran 15
TEXT 504 496 Left 2 ;Pins 3 and 7 = no connect
TEXT 760 488 Left 2 ;Sorted by Vd at If=20 mA \n \nPart # Mfg Is (A) N Iave (A) Vf@Iave (V) Vd@If (V)\nQTLP690C Fairchild 1.00E-22 1.500 0.16 1.90 1.82\nPT-121-B Luminous 4.35E-07 8.370 20.00 3.84 2.34\nLUW-W5AP OSRAM 6.57E-08 7.267 2.00 3.26 2.39\nLXHL-BW02 Lumileds 4.50E-20 2.600 0.40 2.95 2.75\nW5AP-LZMZ-5K Lumileds 3.50E-17 3.120 2.00 3.13 2.76\nLXK2-PW14 Lumileds 3.50E-17 3.120 1.60 3.11 2.76\nAOT-2015 AOT 5.96E-10 6.222 0.18 3.16 2.80\nNSSW008CT-P Nichia 2.30E-16 3.430 0.04 2.92 2.86\nNSSWS108T Nichia 1.13E-18 3.020 0.04 2.99 2.94\nNSPW500BS Nichia 2.70E-10 6.790 0.03 3.27 3.20\nNSCW100 Nichia 1.69E-08 9.626 0.03 3.60 3.50
TEXT 168 256 Left 4 ;+
TEXT 1224 224 Left 2 ;Current mirror with 3X current gain
TEXT 664 256 Left 2 ;Use MPSA14 Darlington
TEXT 656 56 Left 2 ;3 V battery needs more VBE
Version 4
SHEET 1 1816 760
WIRE 64 -16 0 -16
WIRE 112 -16 64 -16
WIRE 272 -16 112 -16
WIRE 544 -16 272 -16
WIRE 1024 -16 544 -16
WIRE 1200 -16 1024 -16
WIRE 112 0 112 -16
WIRE 272 0 272 -16
WIRE 1024 32 1024 -16
WIRE 1200 32 1200 -16
WIRE 0 48 0 -16
WIRE 544 48 544 -16
WIRE 944 80 768 80
WIRE 960 80 944 80
WIRE 1136 80 1104 80
WIRE 272 96 272 80
WIRE 400 96 272 96
WIRE 480 96 400 96
WIRE 400 112 400 96
WIRE 112 128 112 80
WIRE 272 128 272 96
WIRE 704 128 640 128
WIRE 944 144 944 80
WIRE 1024 144 1024 128
WIRE 1024 144 944 144
WIRE 1104 144 1104 80
WIRE 1104 144 1024 144
WIRE 112 160 112 128
WIRE 768 192 768 176
WIRE 800 192 768 192
WIRE 912 192 864 192
WIRE 0 208 0 128
WIRE 400 208 400 192
WIRE 640 208 640 128
WIRE 640 208 400 208
WIRE 400 224 400 208
WIRE 112 272 112 224
WIRE 128 272 112 272
WIRE 224 272 192 272
WIRE 272 272 272 208
WIRE 272 272 224 272
WIRE 912 272 912 192
WIRE 400 336 400 304
WIRE 544 336 544 144
WIRE 544 336 400 336
WIRE 112 368 112 272
WIRE 912 368 912 352
WIRE 912 368 112 368
WIRE 272 400 272 272
WIRE 112 416 112 368
WIRE 1200 448 1200 128
WIRE 1200 448 336 448
WIRE 112 464 112 416
WIRE 400 480 400 336
WIRE 0 496 0 288
WIRE 0 496 -32 496
WIRE -32 528 -32 496
WIRE 0 576 0 496
WIRE 64 576 0 576
WIRE 112 576 112 544
WIRE 112 576 64 576
WIRE 272 576 272 496
WIRE 272 576 112 576
WIRE 400 576 400 560
WIRE 400 576 272 576
FLAG -32 528 0
FLAG 112 128 P6-RLIM
FLAG 112 416 P18-RC
FLAG 224 272 P2-OUT
FLAG 64 -16 P5-V+
FLAG 64 576 P4-V-
SYMBOL res 96 -16 R0
SYMATTR InstName R1
SYMATTR Value 39
SYMBOL LED 96 160 R0
SYMATTR InstName D1
SYMATTR Value LXHL-BW02
SYMBOL res 96 448 R0
SYMATTR InstName R2
SYMATTR Value 9.1k
SYMBOL cap 192 256 R90
WINDOW 0 0 32 VBottom 2
WINDOW 3 32 32 VTop 2
WINDOW 40 52 32 VTop 2
SYMATTR InstName C1
SYMATTR Value 1m
SYMATTR SpiceLine2 ic=0.5
SYMBOL res 256 112 R0
SYMATTR InstName R5
SYMATTR Value 390
SYMBOL res 256 -16 R0
SYMATTR InstName R4
SYMATTR Value 390
SYMBOL npn 336 400 M0
SYMATTR InstName Q3
SYMATTR Value 2N3904
SYMBOL res 384 96 R0
SYMATTR InstName R6
SYMATTR Value 20k
SYMBOL res 384 208 R0
SYMATTR InstName R7
SYMATTR Value 10k
SYMBOL res 384 464 R0
SYMATTR InstName R8
SYMATTR Value 20k
SYMBOL npn 480 48 R0
SYMATTR InstName Q4
SYMATTR Value 2N3904
SYMBOL npn 704 80 R0
SYMATTR InstName Q1
SYMATTR Value 2N3904
SYMBOL pnp 960 128 M180
SYMATTR InstName Q2a
SYMATTR Value 2N3906
SYMBOL voltage 0 192 R0
WINDOW 123 0 0 Left 0
WINDOW 39 24 116 Left 2
SYMATTR InstName V1
SYMATTR Value 1.5
SYMBOL res 896 256 R0
SYMATTR InstName R9
SYMATTR Value 100
SYMBOL pnp 1136 128 M180
SYMATTR InstName Q2b
SYMATTR Value 2N3906
SYMBOL res -16 32 R0
SYMATTR InstName PTC
SYMATTR Value 5
SYMBOL diode 800 208 R270
WINDOW 0 32 32 VTop 2
WINDOW 3 0 32 VBottom 2
SYMATTR InstName D2
SYMATTR Value 1N4148
TEXT -56 192 VRight 2 ;AA Alkaline
TEXT 512 528 Left 2 !.tran 30
TEXT 504 496 Left 2 ;Pins 3 and 7 = no connect
TEXT 760 488 Left 2 ;Sorted by Vd at If=20 mA \n \nPart # Mfg Is (A) N Iave (A) Vf@Iave (V) Vd@If (V)\nQTLP690C Fairchild 1.00E-22 1.500 0.16 1.90 1.82\nPT-121-B Luminous 4.35E-07 8.370 20.00 3.84 2.34\nLUW-W5AP OSRAM 6.57E-08 7.267 2.00 3.26 2.39\nLXHL-BW02 Lumileds 4.50E-20 2.600 0.40 2.95 2.75\nW5AP-LZMZ-5K Lumileds 3.50E-17 3.120 2.00 3.13 2.76\nLXK2-PW14 Lumileds 3.50E-17 3.120 1.60 3.11 2.76\nAOT-2015 AOT 5.96E-10 6.222 0.18 3.16 2.80\nNSSW008CT-P Nichia 2.30E-16 3.430 0.04 2.92 2.86\nNSSWS108T Nichia 1.13E-18 3.020 0.04 2.99 2.94\nNSPW500BS Nichia 2.70E-10 6.790 0.03 3.27 3.20\nNSCW100 Nichia 1.69E-08 9.626 0.03 3.60 3.50
TEXT 168 256 Left 4 ;+
TEXT 656 56 Left 2 ;3 V battery needs more VBE
Version 4
SHEET 1 1812 680
WIRE 96 -16 32 -16
WIRE 144 -16 96 -16
WIRE 304 -16 144 -16
WIRE 544 -16 304 -16
WIRE 752 -16 544 -16
WIRE 144 0 144 -16
WIRE 304 0 304 -16
WIRE 752 0 752 -16
WIRE 928 0 752 0
WIRE 752 32 752 0
WIRE 928 32 928 0
WIRE 544 48 544 -16
WIRE 832 80 816 80
WIRE 864 80 832 80
WIRE 304 96 304 80
WIRE 400 96 304 96
WIRE 480 96 400 96
WIRE 400 112 400 96
WIRE 144 128 144 80
WIRE 304 128 304 96
WIRE 752 144 752 128
WIRE 752 144 704 144
WIRE 832 144 832 80
WIRE 832 144 752 144
WIRE 144 160 144 128
WIRE 704 160 704 144
WIRE 32 208 32 -16
WIRE 400 208 400 192
WIRE 640 208 400 208
WIRE 400 224 400 208
WIRE 704 272 704 256
WIRE 144 288 144 224
WIRE 160 288 144 288
WIRE 256 288 224 288
WIRE 304 288 304 208
WIRE 304 288 256 288
WIRE 400 320 400 304
WIRE 544 320 544 144
WIRE 544 320 400 320
WIRE 144 368 144 288
WIRE 704 368 704 352
WIRE 704 368 144 368
WIRE 304 400 304 288
WIRE 144 416 144 368
WIRE 928 448 928 128
WIRE 928 448 368 448
WIRE 144 464 144 416
WIRE 400 480 400 320
WIRE 32 496 32 288
WIRE 32 496 0 496
WIRE 0 528 0 496
WIRE 32 576 32 496
WIRE 96 576 32 576
WIRE 144 576 144 544
WIRE 144 576 96 576
WIRE 304 576 304 496
WIRE 304 576 144 576
WIRE 400 576 400 560
WIRE 400 576 304 576
FLAG 0 528 0
FLAG 144 128 P6-RLIM
FLAG 144 416 P18-RC
FLAG 256 288 P2-OUT
FLAG 96 -16 P5-V+
FLAG 96 576 P4-V-
SYMBOL res 128 -16 R0
SYMATTR InstName R1
SYMATTR Value 12
SYMBOL LED 128 160 R0
SYMATTR InstName D1
SYMATTR Value PT-121-B
SYMBOL res 128 448 R0
SYMATTR InstName R2
SYMATTR Value 9.1k
SYMBOL cap 224 272 R90
WINDOW 0 0 32 VBottom 2
WINDOW 3 32 32 VTop 2
WINDOW 40 52 32 VTop 2
SYMATTR InstName C1
SYMATTR Value 470
SYMATTR SpiceLine2 ic=0.5
SYMBOL res 288 112 R0
SYMATTR InstName R5
SYMATTR Value 390
SYMBOL res 288 -16 R0
SYMATTR InstName R4
SYMATTR Value 390
SYMBOL npn 368 400 M0
SYMATTR InstName Q3
SYMATTR Value 2N3904
SYMBOL res 384 96 R0
SYMATTR InstName R6
SYMATTR Value 20k
SYMBOL res 384 208 R0
SYMATTR InstName R7
SYMATTR Value 10k
SYMBOL res 384 464 R0
SYMATTR InstName R8
SYMATTR Value 20k
SYMBOL npn 480 48 R0
SYMATTR InstName Q4
SYMATTR Value 2N3904
SYMBOL npn 640 160 R0
SYMATTR InstName Q1
SYMATTR Value 2N3904
SYMBOL pnp 816 128 R180
SYMATTR InstName Q2a
SYMATTR Value 2N3906
SYMBOL voltage 32 192 R0
WINDOW 123 0 0 Left 0
WINDOW 39 24 116 Left 2
SYMATTR InstName V1
SYMATTR Value 3
SYMBOL res 688 256 R0
SYMATTR InstName R9
SYMATTR Value 100
SYMBOL pnp 864 128 M180
SYMATTR InstName Q2b
SYMATTR Value 2N3906
TEXT -24 192 VRight 2 ;AA Alkaline
TEXT 512 528 Left 2 !.tran 10
TEXT 504 496 Left 2 ;Pins 3 and 7 = no connect
TEXT 1064 112 Left 2 ;Sorted by Vd at If=20 mA \n \nPart # Mfg Is (A) N Iave (A) Vf@Iave (V) Vd@If (V)\nQTLP690C Fairchild 1.00E-22 1.500 0.16 1.90 1.82\nPT-121-B Luminous 4.35E-07 8.370 20.00 3.84 2.34\nLUW-W5AP OSRAM 6.57E-08 7.267 2.00 3.26 2.39\nLXHL-BW02 Lumileds 4.50E-20 2.600 0.40 2.95 2.75\nW5AP-LZMZ-5K Lumileds 3.50E-17 3.120 2.00 3.13 2.76\nLXK2-PW14 Lumileds 3.50E-17 3.120 1.60 3.11 2.76\nAOT-2015 AOT 5.96E-10 6.222 0.18 3.16 2.80\nNSSW008CT-P Nichia 2.30E-16 3.430 0.04 2.92 2.86\nNSSWS108T Nichia 1.13E-18 3.020 0.04 2.99 2.94\nNSPW500BS Nichia 2.70E-10 6.790 0.03 3.27 3.20\nNSCW100 Nichia 1.69E-08 9.626 0.03 3.60 3.50
view raw Discrete LM3909.asc hosted with ❤ by GitHub

LTSpice Diode Models Sorted By Forward Voltage

LTSpice includes a bunch of LEDs I’ll never own, so finding a tabulation of their forward voltages helped match them against various LEDs on hand. The table was sorted by the forward voltage at the diode’s rated average current, which wasn’t helpful for my simple needs, so I re-sorted it on the Vf @ If = 20 mA column over on the right:

Part #       Mfg             Is         N      Iavg Vf@Iavg  Vd@If
QTLP690C     Fairchild    1.00E-22    1.500    0.16   1.90    1.82
PT-121-B     Luminous     4.35E-07    8.370   20.00   3.84    2.34
LUW-W5AP     OSRAM        6.57E-08    7.267    2.00   3.26    2.39
LXHL-BW02    Lumileds     4.50E-20    2.600    0.40   2.95    2.75
W5AP-LZMZ-5K Lumileds     3.50E-17    3.120    2.00   3.13    2.76
LXK2-PW14    Lumileds     3.50E-17    3.120    1.60   3.11    2.76
AOT-2015     AOT          5.96E-10    6.222    0.18   3.16    2.80
NSSW008CT-P  Nichia       2.30E-16    3.430    0.04   2.92    2.86
NSSWS108T    Nichia       1.13E-18    3.020    0.04   2.99    2.94
NSPW500BS    Nichia       2.70E-10    6.790    0.03   3.27    3.20
NSCW100      Nichia       1.69E-08    9.626    0.03   3.60    3.50

The currents come from plugging the various constants into the Schockley Diode Equation and turning the crank.

One could, of course, measure the constants for the diodes on hand to generate a proper Spice model, but that seems like a lot of work for what’s basically a blinking LED.

AA Alkaline Battery Holder

A battery holder for AA alkaline cells descends directly from the NP-BX1 version:

Astable Multivibrator - Alkaline Batteries - solid model - Show layout
Astable Multivibrator – Alkaline Batteries – solid model – Show layout

The square recesses fit single contact pads on the left and a “positive-to-negative conversion” plate on the right, all secured with dabs of acrylic adhesive:

Alkaline AA holder - contacts
Alkaline AA holder – contacts

Although the OpenSCAD code contains an array of battery dimensions, it only works for AA cells.

The recess on the far left is where you solder the wires onto the contact tabs, with the wires leading outward through the holes in the lid. The case needs an indexing feature to hold the lid square while gluing it down.

Alkaline cells cells do not have current-limiting circuitry, so a low-current PTC fuse seems like a Good Idea. I initially thought of hiding it in the recess, but the Brutalist nature of the astables suggests open air.

The OpenSCAD source code as a GitHub Gist:

// Astable Multivibrator
// Holder for Alkaline cells
// Ed Nisley KE4ZNU August 2020
/* [Layout options] */
CellName = "AA"; // [AA] -- does not work with anything else
NumCells = 2;
Layout = "Case"; // [Build,Show,Lid]
Struts = -1; // [0:None, -1:Dual, 1:Quad]
// Extrusion parameters - must match reality! */
/* [Hidden] */
ThreadThick = 0.25;
ThreadWidth = 0.40;
HoleWindage = 0.2;
function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
function IntegerLessMultiple(Size,Unit) = Unit * floor(Size / Unit);
Protrusion = 0.1; // make holes end cleanly
inch = 25.4;
//- Basic dimensions
WallThick = IntegerMultiple(3.0,ThreadWidth);
CornerRadius = WallThick/2;
FloorThick = IntegerMultiple(3.0,ThreadThick);
TopThick = IntegerMultiple(2.0,ThreadThick);
WireOD = 1.7; // wiring from pins to circuitry
Gap = 5.0;
// Cylindrical cell sizes
// https://en.wikipedia.org/wiki/List_of_battery_sizes#Cylindrical_batteries
CELL_NAME = 0;
CELL_OD = 1;
CELL_OAL = 2;
CellData = [
["AAAA",8.3,42.5],
["AAA",10.5,44.5],
["AA",14.5,50.5],
["C",26.2,50],
["D",34.2,61.5],
["A23",10.3,28.5],
["CR123A",17.0,34.5],
["18650",18.8,65.2], // bare 18650 with button end
["18650Prot",19.0,70.0], // protected 18650 = 19670 plus a bit
];
CellIndex = search([CellName],CellData,1,0)[0];
echo(str("Cell index: ",CellIndex," = ",CellData[CellIndex][CELL_NAME]));
//- Contact dimensions
CONTACT_NAME = 0;
CONTACT_WIDE = 1;
CONTACT_HIGH = 2;
CONTACT_THICK = 3; // plate thickness
CONTACT_TIP = 4; // tip to rear face
CONTACT_TAB = 5; // solder tab width
ContactData = [
["AA+",12.2,12.2,0.3,1.7,3.5], // pos bump
["AA-",12.2,12.2,0.3,5.0,3.5], // half-compressed neg spring
["AA+-",28.2,12.2,0.3,5.0,0], // pos-neg bridge
["Li+",18.5,16.0,0.3,2.8,5.5],
["Li-",18.5,16.0,0.3,6.0,5.5],
];
function ConDat(name,dim) = ContactData[search([name],ContactData,1,0)[0]][dim];
ContactRecess = 2*ConDat(str(CellName,"+"),CONTACT_THICK);
ContactOC = CellData[CellIndex][CELL_OD];
WireBay = 6.0; // room for wiring to contacts
//- Wire struts
StrutDia = 1.6; // AWG 14 = 1.6 mm
StrutSides = 3*4;
ID = 0;
OD = 1;
LENGTH = 2;
StrutBase = [StrutDia,StrutDia + 2*5*ThreadWidth, // ID = wire, OD = buildable
FloorThick + CellData[CellIndex][CELL_OD]]; // base is flush with cell top
//- Holder dimensions
BatterySize = [CellData[CellIndex][CELL_OAL] + // cell
ConDat(str(CellName,"+"),CONTACT_TIP) + // pos contact
ConDat(str(CellName,"-"),CONTACT_TIP) - // neg contact
2*ContactRecess, // sink into wall
NumCells*CellData[CellIndex][CELL_OD],
CellData[CellIndex][CELL_OD]
];
echo(str("Battery space: ",BatterySize));
CaseSize = [3*WallThick + // end walls + wiring partition
BatterySize.x + // cell
WireBay, // wiring bay
2*WallThick + BatterySize.y,
FloorThick + BatterySize.z
];
BatteryOffset = (CaseSize.x - (2*WallThick +
CellData[CellIndex][CELL_OAL] +
ConDat(str(CellName,"-"),CONTACT_TIP))
) /2 ;
ThumbRadius = 0.75 * CaseSize.z;
StrutOC = [IntegerLessMultiple(CaseSize.x - 2*CornerRadius -2*StrutBase[OD],5.0),
IntegerMultiple(CaseSize.y + StrutBase[OD],5.0)];
StrutAngle = atan(StrutOC.y/StrutOC.x);
echo(str("Strut OC: ",StrutOC));
LidSize = [2*WallThick + WireBay + ConDat(str(CellName,"+"),CONTACT_THICK), CaseSize.y, FloorThick/2];
//----------------------
// 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);
}
//-- Overall case with origin at battery center
module Case() {
difference() {
union() {
hull()
for (i=[-1,1], j=[-1,1])
translate([i*(CaseSize.x/2 - CornerRadius),
j*(CaseSize.y/2 - CornerRadius),
0])
cylinder(r=CornerRadius/cos(180/8),h=CaseSize.z,$fn=8); // cos() fixes undersize spheres!
if (Struts)
for (i = (Struts == 1) ? [-1,1] : -1) { // strut bases
hull()
for (j=[-1,1])
translate([i*StrutOC.x/2,j*StrutOC.y/2,0])
rotate(180/StrutSides)
cylinder(d=StrutBase[OD],h=StrutBase[LENGTH],$fn=StrutSides);
translate([i*StrutOC.x/2,0,StrutBase[LENGTH]/2])
cube([2*StrutBase[OD],StrutOC.y,StrutBase[LENGTH]],center=true); // blocks for fairing
for (j=[-1,1]) // hemisphere caps
translate([i*StrutOC.x/2,
j*StrutOC.y/2,
StrutBase[LENGTH]])
rotate(180/StrutSides)
sphere(d=StrutBase[OD]/cos(180/StrutSides),$fn=StrutSides);
}
}
translate([BatteryOffset,0,BatterySize.z/2 + FloorThick]) // cells
cube(BatterySize + [0,0,Protrusion],center=true);
translate([BatterySize.x/2 + BatteryOffset + ContactRecess/2 - Protrusion/2, // contacts
0,
BatterySize.z/2 + FloorThick])
cube([ContactRecess + Protrusion,
ConDat(str(CellName,"+-"),CONTACT_WIDE),
ConDat(str(CellName,"+-"),CONTACT_HIGH)
],center=true);
translate([-(BatterySize.x/2 - BatteryOffset + ContactRecess/2 - Protrusion/2),
ContactOC/2,
BatterySize.z/2 + FloorThick])
cube([ContactRecess + Protrusion,
ConDat(str(CellName,"+"),CONTACT_WIDE),
ConDat(str(CellName,"+"),CONTACT_HIGH)
],center=true);
translate([-(BatterySize.x/2 - BatteryOffset + ContactRecess/2 - Protrusion/2),
-ContactOC/2,
BatterySize.z/2 + FloorThick])
cube([ContactRecess + Protrusion,
ConDat(str(CellName,"-"),CONTACT_WIDE),
ConDat(str(CellName,"-"),CONTACT_HIGH)
],center=true);
translate([-CaseSize.x/2 + WireBay/2 + WallThick, // wire bay
0,
BatterySize.z/2 + FloorThick + Protrusion/2])
cube([WireBay,
BatterySize.y,
BatterySize.z + Protrusion
],center=true);
for (j=[-1,1])
translate([-(BatterySize.x/2 - BatteryOffset + WallThick/2), // contact tabs
j*ContactOC/2,
BatterySize.z + FloorThick - Protrusion])
cube([2*WallThick,
ConDat(str(CellName,"+"),CONTACT_TAB),
(BatterySize.z - ConDat(str(CellName,"+"),CONTACT_HIGH))
],center=true);
if (false)
translate([0,0,CaseSize.z]) // finger cutout
rotate([90,00,0])
cylinder(r=ThumbRadius,h=2*CaseSize.y,center=true,$fn=22);
if (Struts)
for (i2 = (Struts == 1) ? [-1,1] : -1) { // strut wire holes and fairing
for (j=[-1,1])
translate([i2*StrutOC.x/2,j*StrutOC.y/2,FloorThick])
rotate(180/StrutSides)
PolyCyl(StrutBase[ID],2*StrutBase[LENGTH],StrutSides);
for (i=[-1,1], j=[-1,1]) // fairing cutaways
translate([i*StrutBase[OD] + (i2*StrutOC.x/2),
j*StrutOC.y/2,
-Protrusion])
rotate(180/StrutSides)
PolyCyl(StrutBase[OD],StrutBase[LENGTH] + 2*Protrusion,StrutSides);
}
}
}
module Lid() {
difference() {
hull()
for (i=[-1,1], j=[-1,1], k=[-1,1])
translate([i*(LidSize.x/2 - CornerRadius),
j*(LidSize.y/2 - CornerRadius),
k*(LidSize.z - CornerRadius)]) // double thickness for flat bottom
sphere(r=CornerRadius/cos(180/8),$fn=8);
translate([0,0,-LidSize.z]) // remove bottom
cube([(LidSize.x + 2*Protrusion),(LidSize.y + 2*Protrusion),2*LidSize.z],center=true);
for (j=[-1,1]) // wire holes
translate([0,j*LidSize.y/4,-Protrusion])
PolyCyl(WireOD,2*LidSize.z,6);
}
}
//-------------------
// Build it!
if (Layout == "Case")
Case();
if (Layout == "Lid")
Lid();
if (Layout == "Build") {
rotate(-90)
translate([CaseSize.x/2 + Gap,0,0])
Case();
rotate(-90)
translate([-LidSize.x/2 - Gap,0,0])
Lid();
}
if (Layout == "Show") {
Case();
translate([-CaseSize.x/2 + LidSize.x/2,0,(CaseSize.z + Gap)])
Lid();
}