Astable Multivibrator: RGB LED Circuitry First Light

It lights up just like it should:

Astable RGB LED - green phase
Astable RGB LED – green phase

In colors:

Astable RGB LED - red phase
Astable RGB LED – red phase

The blue LED works, too, but I didn’t catch any of those blinks.

The spider should be done in black PETG, just like the battery holder, but I didn’t realize which filament was running until too late. Even the blue LED lights up the orange spider just fine!

The circuitry behind (well, below) the RGB LED Radome consists of three copies of the original multivibrator, with mirror image layouts to match the wire struts:

RGB LED Schematic - NPN transistors
RGB LED Schematic – NPN transistors

The solder joints adhere to exactly none of the usual good practices:

Astable RGB LED - assembled
Astable RGB LED – assembled

The simulation matches the actual blink times reasonably well:

Astable - 2N2222 cap voltages
Astable – 2N2222 cap voltages

It’s unpleasantly frenetic in real life. The next version must have much much longer time constants.

Unfortunately, the simulation also confirms my suspicion that I’ve been abusing the electrolytic capacitors with reverse-polarity waveforms. I suspect it doesn’t really matter too much, as the maximum voltage in either direction remains under a volt at very low currents, but it’s the principle of the thing.

Soooo, lengthening the time constants by increasing the capacitances seems like a Bad Idea.

Alas, increasing the resistors by an order of magnitude won’t work, either, because (despite appearances) the whole thing sits right on the hairy edge of not working. As the battery discharges toward its 2.5 V cutoff level, the currents drop and the circuitry becomes increasingly sensitive to touch. After a day or two, one of the LEDs will jam solidly on, while the others continue to blink merrily away. Removing and reinstalling the battery will sometimes resume proper operation, but it’s definitely not stable enough for production use.

Which makes a MOSFET astable multivibrator seem like a Good Idea.

One could achieve the same visible result with a few cents of microcontroller and a dab of software, but most of the charm comes from its analog nature and all those visible components.

Transistor Pricing

You can find anything on eBay (clicky for more dots):

ZVNL110A MOSFET - kilobuck eBay pricing
ZVNL110A MOSFET – kilobuck eBay pricing

The key information:

ZVNL110A MOSFET - kilobuck eBay pricing - detail
ZVNL110A MOSFET – kilobuck eBay pricing – detail

For that price, I’d expect in-person hand delivery.

Stipulated: ZVNL110A MOSFETs aren’t in production and we’re buying from diminishing inventory, but (as of late December 2018) they’re still available for under a buck apiece in small quantities.

It could be a pricing algorithm corner case, a money laundering scheme, or just a typo that could happen to anyone. As the news sites put it, the seller did not respond in time for this posting …

Astable Multivibrator: RGB LED and Radome Spider

Well, a spider with half the proper leg count:

RGB LED - radome test
RGB LED – radome test

One could argue the LED spider has an unusually large abdomen, but I’m not going there.

The solid model looks the same way:

Astable Multivibrator Battery Holder - RGB LED Spider - radome
Astable Multivibrator Battery Holder – RGB LED Spider – radome

And, yes, those are eye protection caps over the four wire struts, most useful during construction while maneuvering the radome into position.

For reasons unknown to me, they’re called “Pirhana” LEDs:

RGB LED - wiring
RGB LED – wiring

I trimmed off half of each pin, soldered on 28 AWG color-coded silicone wires, threaded wires through openings, then rammed the LED package into the recess so it sits just below the radome’s curve. The dent matching the ball comes from the chord equation, as always, and looks pretty good.

The radome is, of course, a one-star ping pong ball from the usual big box retailer’s sporting goods section. The stamped logo sits at a random position with respect to the ball’s interior structure (visible when lit, as in the top picture), so I erased it with a fine-grit sanding sponge. Hollow plastic golf balls might work just as well, with an even more interesting surface texture.

The source code includes a cutaway look at the printed parts to verify their innards:

Astable Multivibrator Battery Holder - RGB LED Spider - fit view
Astable Multivibrator Battery Holder – RGB LED Spider – fit view

The OpenSCAD source code as a GitHub Gist:

// Holder for Li-Ion battery packs
// Ed Nisley KE4ZNU January 2013
// 2018-11-15 Adapted for 1.5 mm pogo pins, battery data table
// 2018-12 RGB LED spider, general cleanups
/* [Layout options] */
BatteryName = "NP-BX1"; // [NP-BX1,NB-5L,NB-6L]
RGBCircuit = true; // false = 1 strut pair, true = 2 pairs
Layout = "Case"; // [Build,Show,Fit,Case,Lid,Pins,RGBSpider]
/* [Extrusion parameters] - must match reality! */
// Print with +2 shells and 3 solid layers
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
/* [Hidden] */
inch = 25.4;
BuildOffset = 3.0; // clearance for build layout
Gap = 2.0; // separation for Fit parts
//- Basic dimensions
WallThick = 4*ThreadWidth; // holder sidewalls
BaseThick = 6*ThreadThick; // bottom of holder to bottom of battery
TopThick = 6*ThreadThick; // top of battery to top of holder
//- Battery dimensions - rationalized from several samples
// Coordinate origin at battery corner with contacts, key openings downward
T_NAME = 0; // Name must fit recess, so don't get loquacious
T_SIZE = 1;
T_KEYS = 3;
BatteryData = [
["NB-5L", [45.0,32.0,8.0],[[-0.82,4.5,3.5],[-0.82,11.0,3.5]],[[2.2,0.75,2.0],[2.2,2.8,2.0]]],
echo(str("Battery: ",BatteryName));
BatteryIndex = search([BatteryName],BatteryData,1,0)[0];
echo(str(" Index: ",BatteryIndex));
BatterySize = BatteryData[BatteryIndex][T_SIZE]; // X = length, Y = width, Z = thickness
echo(str(" Size: ",BatterySize));
Contacts = BatteryData[BatteryIndex][T_CONTACTS]; // relative to battery edge, front, and bottom
echo(str(" Contacts: ",Contacts));
ContactOC = Contacts[1].y - Contacts[0].y; // + and - terminals for pogo pin contacts
ContactCenter = Contacts[0].y + ContactOC/2;
KeyBlocks = BatteryData[BatteryIndex][T_KEYS]; // recesses in battery face set X position
echo(str(" Keys: ",KeyBlocks));
//- Pin dimensions
ID = 0;
OD = 1;
PinShank = [1.5,2.0,6.5]; // shank, flange, compressed length
PinFlange = [1.5,2.0,0.5]; // flange, length included in PinShank
PinTip = [0.9,0.9,2.5]; // extended spring-loaded tip
WireOD = 1.7; // wiring from pins to circuitry
PinChannel = WireOD; // cut behind flange for solder overflow
PinRecess = 3.0; // recess behind pin flange end for epoxy fill
echo(str("Contact tip dia: ",PinTip[OD]));
echo(str(" .. shank dia: ",PinShank[ID]));
OverTravel = 0.5; // space beyond battery face at X origin
//- Holder dimensions
GuideRadius = ThreadWidth; // friction fit ridges
GuideOffset = 7; // from compartment corners
LidOverhang = 2.0; // atop of battery for retention
LidClearance = LidOverhang * (BatterySize.z/BatterySize.x); // … clearance above battery for tilting
echo(str("Lid clearance: ",LidClearance));
CaseSize = [BatterySize.x + PinShank[LENGTH] + OverTravel + PinRecess + GuideRadius + WallThick,
BatterySize.y + 2*WallThick + 2*GuideRadius,
BatterySize.z + BaseThick + TopThick + LidClearance];
echo(str("Case size: ",CaseSize));
CaseOffset = [-(PinShank[LENGTH] + OverTravel + PinRecess),-(WallThick + GuideRadius),0]; // position around battery
ThumbRadius = 10.0; // thumb opening at end of battery
CornerRadius = 3*ThreadThick; // nice corner rounding
LidSize = [-CaseOffset.x + LidOverhang,CaseSize.y,TopThick];
LidOffset = [0.0,CaseOffset.y,0];
//- Wire struts
StrutDia = 1.6; // AWG 14 = 1.6 mm
StrutSides = 3*4;
StrutBase = [StrutDia,StrutDia + 4*WallThick,CaseSize.z - TopThick]; // ID = wire, OD = buildable
//StrutOC = [IntegerLessMultiple(BatterySize.x - StrutBase[OD],5.0), // set easy OC wire spacing
// IntegerMultiple(CaseSize.y + StrutBase[OD],5.0)];
StrutOC = [IntegerLessMultiple(CaseSize.x - 2*CornerRadius -2*StrutBase[OD],5.0),
IntegerMultiple(CaseSize.y + StrutBase[OD],5.0)];
StrutOffset = [CaseSize.x/2 + CaseOffset.x,BatterySize.y/2]; // from case centerlines
StrutAngle = atan(StrutOC.y/StrutOC.x);
echo(str("Strut OC: ",StrutOC));
RGBBody = [8.0,8.0,5.0]; // Z = body height
RGBPin = 5.0; // pin length
RGBPinsOC = [5.0,5.0]; // pin layout
RGBRecess = RGBBody.z + RGBPin/2; // maximum LED recess depth
BallOD = 40.0; // radome sphere
BallSides = 4*StrutSides; // nice number of sides
BallPillar = [norm([RGBBody.x,RGBBody.y]),
norm([RGBBody.x,RGBBody.y]) + 4*WallThick,
StrutBase[OD] + RGBBody.z];
BallChordM = BallOD/2 - sqrt(pow(BallOD/2,2) - (pow(BallPillar[OD],2))/4);
echo(str("Ball chord depth: ",BallChordM));
// 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);
//-- Guides for tighter friction fit
module Guides() {
PolyCyl(2*GuideRadius,(BatterySize.z - Protrusion),4);
translate([GuideOffset,(BatterySize.y + GuideRadius),0])
PolyCyl(2*GuideRadius,(BatterySize.z - Protrusion),4);
translate([(BatterySize.x - GuideOffset),-GuideRadius,0])
PolyCyl(2*GuideRadius,(BatterySize.z - Protrusion),4);
translate([(BatterySize.x - GuideOffset),(BatterySize.y + GuideRadius),0])
PolyCyl(2*GuideRadius,(BatterySize.z - Protrusion),4);
translate([(BatterySize.x + GuideRadius),GuideOffset/2,0])
PolyCyl(2*GuideRadius,(BatterySize.z - Protrusion),4);
translate([(BatterySize.x + GuideRadius),(BatterySize.y - GuideOffset/2),0])
PolyCyl(2*GuideRadius,(BatterySize.z - Protrusion),4);
//-- Contact pins
// Rotated to put them in their natural oriention
// Aligned to put tip base / end of shank at Overtravel limit
module PinShape() {
translate([-(PinShank[LENGTH] + OverTravel),0,0])
union() {
PolyCyl(PinTip[OD],PinShank[LENGTH] + PinTip[LENGTH],6);
PolyCyl(PinShank[ID],PinShank[LENGTH] + Protrusion,6); // slight extension for clean cuts
// Position pins to put end of shank at battery face
// Does not include recess access into case
module PinAssembly() {
union() {
for (p = Contacts)
translate([-(PinShank[LENGTH] + OverTravel) + PinChannel/2, // solder space
(Contacts[1].y - Contacts[0].y + PinFlange[OD]),
for (j=[-1,1]) // wire channels
translate([-(PinShank[LENGTH] + OverTravel - PinChannel/2),
j*ContactOC/4 + ContactCenter,
Contacts[0].z - PinFlange[OD]/2])
//-- Case with origin at battery corner
module Case() {
difference() {
union() {
difference() {
union() {
translate([(CaseSize.x/2 + CaseOffset.x), // basic case shape
(CaseSize.y/2 + CaseOffset.y),
(CaseSize.z/2 - BaseThick)])
for (i=[-1,1], j=[-1,1], k=[-1,1])
translate([i*(CaseSize.x/2 - CornerRadius),
j*(CaseSize.y/2 - CornerRadius),
k*(CaseSize.z/2 - CornerRadius)])
sphere(r=CornerRadius/cos(180/8),$fn=8); // cos() fixes undersize spheres!
for (i= RGBCircuit ? [-1,1] : -1) { // strut bases
for (j=[-1,1])
translate([i*StrutOC.x/2 + StrutOffset.x,j*StrutOC.y/2 + StrutOffset.y,-BaseThick])
translate([i*StrutOC.x/2 + StrutOffset.x,StrutOffset.y,StrutBase[LENGTH]/2 - BaseThick])
cube([2*StrutBase[OD],StrutOC.y,StrutBase[LENGTH]],center=true); // blocks for fairing
for (j=[-1,1]) // hemisphere caps
translate([i*StrutOC.x/2 + StrutOffset.x,
j*StrutOC.y/2 + StrutOffset.y,
StrutBase[LENGTH] - BaseThick])
cube([(BatterySize.x + GuideRadius + OverTravel),
(BatterySize.y + 2*GuideRadius),
(BatterySize.z + LidClearance + Protrusion)]); // battery space
translate([BatterySize.x/2,BatterySize.y/2,0]) // recess around battery name
Guides(); // improve friction fit
translate([-OverTravel,-GuideRadius,0]) // battery keying blocks
cube(KeyBlocks[0] + [OverTravel,GuideRadius,0],center=false);
translate([-OverTravel,(BatterySize.y - KeyBlocks[1].y),0])
cube(KeyBlocks[1] + [OverTravel,GuideRadius,0],center=false);
translate([BatterySize.x/2,BatterySize.y/2,-ThreadThick]) // battery name!
translate([2*CaseOffset.x, // battery top access
(CaseOffset.y - Protrusion),
BatterySize.z + LidClearance])
cube([2*CaseSize.x,(CaseSize.y + 2*Protrusion),2*TopThick]);
for (i2 = RGBCircuit ? [-1,1] : -1) { // strut wire holes and fairing
for (j=[-1,1])
translate([i2*StrutOC.x/2 + StrutOffset.x,j*StrutOC.y/2 + StrutOffset.y,0])
for (i=[-1,1], j=[-1,1])
translate([i*StrutBase[OD] + (i2*StrutOC.x/2 + StrutOffset.x),
j*StrutOC.y/2 + StrutOffset.y,
-(BaseThick + Protrusion)])
PolyCyl(StrutBase[OD],StrutBase[LENGTH] + 2*Protrusion,StrutSides);
translate([(BatterySize.x - Protrusion), // remove thumb notch
(CaseSize.y/2 + CaseOffset.y),
h=(WallThick + GuideRadius + 2*Protrusion),
PinAssembly(); // pins and wiring
translate([CaseOffset.x + PinRecess + Protrusion,(Contacts[1].y + Contacts[0].y)/2,Contacts[0].z])
(Contacts[1].y - Contacts[0].y + PinFlange[OD]/cos(180/6) + 2*HoleWindage),
2*PinFlange[OD]],center=true); // pin insertion hole
translate([CaseOffset.x/2 + BatterySize.x/2,BatterySize.y/2,-(BaseThick + Protrusion)])
linear_extrude(height=2*ThreadThick + Protrusion,convexity=10)
// Lid position offset to match case
module Lid() {
difference() {
translate([-LidSize.x/2 + LidOffset.x + LidOverhang,LidSize.y/2 + LidOffset.y,0])
difference() {
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
translate([0,0,-LidSize.z/2]) // remove bottom
cube([(LidSize.x + 2*Protrusion),(LidSize.y + 2*Protrusion),LidSize.z],center=true);
cube([LidSize.x/4,0.75*LidSize.y,4*ThreadThick],center=true); // epoxy recess
translate([0,0,-(Contacts[0].z + PinFlange[OD])]) // punch wire holes
// Spider for RGB LED + radome atop vertical struts
module RGBSpider() {
difference() {
union() {
for (i=[-1,1], j=[-1,1]) {
rotate(180/StrutSides) // doesn't quite match crosspieces; close enough
for (m=[-1,1]) // connecting bars
translate([0,0,0]) // pillar for RGB LED and ball
for (i=[-1,1], j=[-1,1]) // strut wires
for (m=[-1,1], n=[0,1]) // RGBA wires through bars
rotate(m*StrutAngle + n*180)
# translate([0,0,BallOD/2 + BallPillar[LENGTH] - BallChordM]) // ball inset
translate([0,0,2*RGBBody.z + (BallPillar[LENGTH] - BallChordM) - RGBRecess]) // LED inset
cube(RGBBody + [HoleWindage,HoleWindage,3*RGBBody.z],center=true); // XY clearance + huge height for E-Z cut
for (m=[-1,1]) // RGBA wires through pillar
translate([0,0,StrutBase[OD]/2 + WireOD/2 + 0*Protrusion])
// Build it!
if (Layout == "Case")
if (Layout == "Lid")
if (Layout == "RGBSpider") {
if (Layout == "Pins") {
if (Layout == "Show") { // reveal pin assembly
difference() {
translate([(CaseOffset.x - Protrusion),
cube([(-CaseOffset.x + Protrusion),CaseSize.y,CaseSize.z]);
translate([(CaseOffset.x - Protrusion),
(CaseOffset.y - Protrusion),
cube([(-CaseOffset.x + Protrusion),
Contacts[0].y + Protrusion - CaseOffset.y,
translate([0,0,BatterySize.z + Gap])
if (RGBCircuit)
difference() {
if (Layout == "Build") {
translate([-CaseSize.x + LidSize.x,-(LidSize.y/2 + LidOffset.y),0])
if (RGBCircuit)
translate([StrutOC.x + BatterySize.x/2,0,0])
if (Layout == "Fit") {
translate([0,0,(BatterySize.z + Gap)])
if (RGBCircuit)

The original doodles give useful dimensions, plus some details not withstanding the test of time:

RGB LED Radome Spider - doodles
RGB LED Radome Spider – doodles

The actual center-to-center distances for the wire posts come from the battery dimensions, rounded up or down as appropriate, to the nearest multiple of 5 mm, so those are just serving suggestions.

Astable Multivibrator: RGB LED Strut Fixture

One cannot (or, perhaps, should not attempt to) solder parts to 14 AWG wires seated in a 3D printed battery holder base, so I cleaned up the edges of two polycarbonate scraps:

RGB LED Strut Fixture - flycutting setup
RGB LED Strut Fixture – flycutting setup

Then drilled holes to match the strut positions:

RGB LED Strut Fixture - drilling
RGB LED Strut Fixture – drilling

The holes fit snippets of the original wire insulation, because, after all, polycarbonate is a thermoplastic, too.

Stretch some copper wire to straighten and work-harden it, add insulation snippets, then maneuver everything in place:

RGB LED Strut Fixture - assembled
RGB LED Strut Fixture – assembled

I definitely need a third (and maybe a fourth) hand to hold each part, the solder, and the iron, but at least the wires won’t walk away in the middle of the process.

Shoe Lace Ferrules

A new pair of shoes arrived with extravagantly long laces requiring shortening. Years ago, I found heatshrink tubing completely unequal to the task, so I deployed Real Metal:

Shoelaces with crimped ferrules
Shoelaces with crimped ferrules

The ferrules come from a kit of such things, minus their plastic strain relief:

Ferrule terminals - hex crimper
Ferrule terminals – hex crimper

That’s a fancy hexagonal crimper for round-ish results. If you have a square terminal block, you should use the square crimper that comes with the kit.

Worked perfectly and produced immediate customer satisfaction.

Toy Cast Iron Stove Lid Lifter

This seemed appropriate for a day involving toys of all descriptions…

A cast iron stove (most likely a mid-last-century reproduction rather than a Genuine Antique™) emerged from a living room recess:

Toy stove with repaired lid lifter
Toy stove with repaired lid lifter

The line across the lid lifter handle shows where it broke, long ago, likely while being played with. Back then, I’d done a static-display-grade fix with a dab of clear epoxy, but a better repair seemed called for; my repair-fu has grown stronger.

I expected the handle to be pot metal, so drilling a hole in both ends for a music-wire stiffener seemed reasonable:

Toy lid lifter - laser alignment
Toy lid lifter – laser alignment

Much to my surprise, the carbide bit skittered off the surface, leaving fine swarf standing on the end. Turns out the lid lifter is cast iron, just like the rest of the stove!

Given that much of a clue, I aligned the pieces in a pair of machinist’s vises:

Toy lid lifter - alignment
Toy lid lifter – alignment

Slide apart (the vises stand on a smooth glass sheet; the nubbly side is down), dab silver solder flux on the ends, capture a snippet of 40% silver solder in the gap:

Toy lid lifter - silver solder setup
Toy lid lifter – silver solder setup

Hit it ever so gently with a propane torch and slide together:

Toy lid lifter - silver soldered
Toy lid lifter – silver soldered

The solder flows at 1200 °F = 650 °C, roughly corresponding to the blue-gray color near the joint. The nice purple (540 °C) on the left shows where I held the flame to start, with yellows (400 °C) on both sides. Good enough, sez I, it’s going to be a static-display exhibit.

Most of the solder went to the back side, so I filed it smooth and buffed off most of the heat coloration with a stainless-steel wire wheel in the Dremel:

Toy lid lifter - bottom
Toy lid lifter – bottom

A little more wire-brush action left the front side looking good:

Toy lid lifter - top
Toy lid lifter – top

As with most of the repairs around here, it simply makes me feel better …

Now, go play with your toys!