-
Astable Multivibrator: Monochrome Pirhana LED
The LED parts box disgorged some single-color Pirhana-style LEDs:

Astable – 2N7000 – Mono Pirhana LED Didn’t quite catch the blink, but the
Ping-Pong ballradome lights up just as you’d expect.The radome sits on a stripped-down RGB LED spider:
The circuitry is the same as the First Light version, with a 1 MΩ resistor stabilizing the LED ballast resistor:
Those are 1 µF ceramic caps in the astable section, so I’m no longer abusing electrolytics, and a stylin’ 100 nF film cap metering out the LED pulse up above.
Just for pretty, I’ve been using yellow / black wires for the battery connections and matching the LED color with its cathode lead.
The OpenSCAD source code as a GitHub Gist:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters// 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 = false; // false = 1 strut pair, true = 2 pairs Layout = "Spider"; // [Build,Show,Fit,Case,Lid,Pins,RGBSpider,Spider] /* [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_CONTACTS = 2; T_KEYS = 3; BatteryData = [ ["NP-BX1",[43.0,30.0,9.5],[[-0.75,6.0,6.2,"+"],[-0.75,16.0,6.2,"-"]],[[1.70,3.70,2.90],[1.70,3.60,2.90]]], ["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]]], ["NB-6L",[42.5,35.5,7.0],[[-0.85,5.50,3.05,"-"],[-0.85,11.90,3.05,"+"]],[[2.0,0.70,2.8],[2.0,2.00,2.8]]], ]; 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; LENGTH = 2; 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)); //- RGB / Pirhana / Neopixel-ish LEDs RGBBody = [8.0,8.0,5.0]; // Z = body height PixelPCB = [4.0,10.0,3.0]; // Neopixel-ish PCBs, ID = chip window 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() { translate([GuideOffset,-GuideRadius,0]) 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]) rotate([0,90,0]) rotate(180/6) union() { PolyCyl(PinTip[OD],PinShank[LENGTH] + PinTip[LENGTH],6); PolyCyl(PinShank[ID],PinShank[LENGTH] + Protrusion,6); // slight extension for clean cuts PolyCyl(PinFlange[OD],PinFlange[LENGTH],6); } } // Position pins to put end of shank at battery face // Does not include recess access into case module PinAssembly() { union() { for (p = Contacts) translate([0,p.y,p.z]) PinShape(); translate([-(PinShank[LENGTH] + OverTravel) + PinChannel/2, // solder space ContactCenter, Contacts[0].z]) cube([PinChannel, (Contacts[1].y – Contacts[0].y + PinFlange[OD]), PinFlange[OD]],center=true); for (j=[-1,1]) // wire channels translate([-(PinShank[LENGTH] + OverTravel – PinChannel/2), j*ContactOC/4 + ContactCenter, Contacts[0].z – PinFlange[OD]/2]) rotate(180/6) PolyCyl(WireOD,CaseSize.z,6); } } //– 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)]) hull() 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 hull() for (j=[-1,1]) translate([i*StrutOC.x/2 + StrutOffset.x,j*StrutOC.y/2 + StrutOffset.y,-BaseThick]) rotate(180/StrutSides) cylinder(d=StrutBase[OD],h=StrutBase[LENGTH],$fn=StrutSides); 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]) rotate(180/StrutSides) sphere(d=StrutBase[OD]/cos(180/StrutSides),$fn=StrutSides); } } translate([-OverTravel,-GuideRadius,0]) 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 cube([0.8*BatterySize.x,8,2*ThreadThick],center=true); translate([CaseOffset.x + CaseSize.x/2,BatterySize.y/2,-BaseThick + ThreadThick – Protrusion]) // recess around battery name cube([0.75*CaseSize.x,8,2*ThreadThick],center=true); } 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]) linear_extrude(height=2*ThreadThick,convexity=10) text(text=BatteryName,size=5,spacing=1.20,font="Arial:style:Bold",halign="center",valign="center"); translate([CaseOffset.x + CaseSize.x/2,BatterySize.y/2,-BaseThick]) linear_extrude(height=2*ThreadThick + Protrusion,convexity=10) mirror([0,1,0]) text(text="KE4ZNU",size=6,spacing=1.20,font="Arial:style:Bold",halign="center",valign="center"); } 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]) rotate(180/StrutSides) PolyCyl(StrutBase[ID],2*StrutBase[LENGTH],StrutSides); 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)]) rotate(180/StrutSides) PolyCyl(StrutBase[OD],StrutBase[LENGTH] + 2*Protrusion,StrutSides); } translate([(BatterySize.x – Protrusion), // remove thumb notch (CaseSize.y/2 + CaseOffset.y), (ThumbRadius)]) rotate([90,0,0]) rotate([0,90,0]) cylinder(r=ThumbRadius, h=(WallThick + GuideRadius + 2*Protrusion), $fn=22); PinAssembly(); // pins and wiring translate([CaseOffset.x + PinRecess + Protrusion,(Contacts[1].y + Contacts[0].y)/2,Contacts[0].z]) translate([-PinRecess,0,0]) cube([2*PinRecess, (Contacts[1].y – Contacts[0].y + PinFlange[OD]/cos(180/6) + 2*HoleWindage), 2*PinFlange[OD]],center=true); // pin insertion hole } } // Lid position offset to match case // The polarity indicator recesses are pure bodges module Lid() { union() { difference() { translate([-LidSize.x/2 + LidOffset.x + LidOverhang,LidSize.y/2 + LidOffset.y,0]) 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,$fn=8); translate([0,0,-LidSize.z/2]) // remove bottom cube([(LidSize.x + 2*Protrusion),(LidSize.y + 2*Protrusion),LidSize.z],center=true); translate([LidSize.x/8,0,0]) 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 PinAssembly(); for (n=[0,1]) // polarity recesses translate([-LidOverhang/2 – 0.40,Contacts[n].y,LidSize.z – ThreadThick/2]) cube([4,4.5,ThreadThick + Protrusion],center=true); } for (n=[0,1]) // polarity indicators translate([-LidOverhang/2,Contacts[n].y,LidSize.z – 1*ThreadThick]) // … proud of surface rotate(90) linear_extrude(height=2*ThreadThick,convexity=10) text(text=Contacts[n][3],size=5,font="Arial:style:Bold",halign="center",valign="center"); } } // Spider for RGB LED + radome atop vertical struts module RGBSpider() { difference() { union() { for (i=[-1,1], j=[-1,1]) { translate([i*StrutOC.x/2,j*StrutOC.y/2,StrutBase[OD]/2]) rotate(180/StrutSides) // doesn't quite match crosspieces; close enough sphere(d=StrutBase[OD]/cos(180/StrutSides),$fn=StrutSides); translate([i*StrutOC.x/2,j*StrutOC.y/2,0]) rotate(180/StrutSides) cylinder(d=StrutBase[OD],h=StrutBase[OD]/2,$fn=StrutSides); } for (m=[-1,1]) // connecting bars rotate(m*StrutAngle) translate([0,0,StrutBase[OD]/4]) cube([norm(StrutOC),StrutBase[OD],StrutBase[OD]/2],center=true); translate([0,0,0]) // pillar for RGB LED and ball cylinder(d=BallPillar[OD],h=BallPillar[LENGTH],$fn=BallSides); } for (i=[-1,1], j=[-1,1]) // strut wires translate([i*StrutOC.x/2,j*StrutOC.y/2,-Protrusion]) rotate(0) PolyCyl(StrutBase[ID],StrutBase[OD]/2,6); for (m=[-1,1], n=[0,1]) // RGBA wires through bars rotate(m*StrutAngle + n*180) translate([StrutOC.x/3,0,-Protrusion]) PolyCyl(StrutBase[ID],StrutBase[OD],6); translate([0,0,BallOD/2 + BallPillar[LENGTH] – BallChordM]) // ball inset sphere(d=BallOD); 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 translate([0,0,StrutBase[OD]/2]) // Neopixel recess PolyCyl(PixelPCB[OD],3*RGBBody.z,BallSides/2); for (m=[-1,1]) // RGBA wires through pillar rotate(m*StrutAngle) translate([0,0,StrutBase[OD]/2 + WireOD/2 + 0*Protrusion]) cube([norm(StrutOC)/2,WireOD,WireOD],center=true); } } // Spider for single LED atop struts, with the ball // Aligned to struts at terminal end of battery on Y axis module Spider() { difference() { union() { for (j=[-1,1]) { translate([-StrutOC.x/2,j*StrutOC.y/2,StrutBase[OD]/2]) rotate(180/StrutSides) sphere(d=StrutBase[OD]/cos(180/StrutSides),$fn=StrutSides); translate([-StrutOC.x/2,j*StrutOC.y/2,0]) rotate(180/StrutSides) cylinder(d=StrutBase[OD],h=StrutBase[OD]/2,$fn=StrutSides); } translate([-StrutOC.x/2,0,StrutBase[OD]/4]) // connecting bars cube([StrutBase[OD]*cos(180/StrutSides),StrutOC.y,StrutBase[OD]/2],center=true); translate([-StrutOC.x/2,0,0]) // pillar for RGB LED and ball cylinder(d=BallPillar[OD],h=BallPillar[LENGTH],$fn=BallSides); } for (j=[-1,1]) // strut wires translate([-StrutOC.x/2,j*StrutOC.y/2,-Protrusion]) rotate(0) PolyCyl(StrutBase[ID],StrutBase[OD]/2,6); translate([-StrutOC.x/2,0,0]) // wires through bars for (n=[-1,1]) rotate(n*90) translate([StrutOC.x/3,0,-Protrusion]) PolyCyl(StrutBase[ID],StrutBase[OD],6); translate([-StrutOC.x/2,0,-Protrusion]) // center hole for Neopixel rotate(180/6) PolyCyl(StrutBase[ID],StrutBase[OD],6); translate([-StrutOC.x/2,0,BallOD/2 + BallPillar[LENGTH] – BallChordM]) // ball inset sphere(d=BallOD); translate([-StrutOC.x/2,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 translate([-StrutOC.x/2,0,StrutBase[OD]/2]) // Neopixel recess PolyCyl(PixelPCB[OD],3*RGBBody.z,BallSides/2); translate([-StrutOC.x/2,0,StrutBase[OD]/2 + WireOD/2 + 0*Protrusion]) // wire channels cube([WireOD,StrutOC.y/2,WireOD],center=true); } } //——————- // Build it! if (Layout == "Case") Case(); if (Layout == "Lid") Lid(); if (Layout == "RGBSpider") { RGBSpider(); } if (Layout == "Spider") { Spider(); } if (Layout == "Pins") { color("Silver",0.5) PinShape(); PinAssembly(); } if (Layout == "Fit") { // reveal pin assembly difference() { Case(); translate([(CaseOffset.x – Protrusion), Contacts[1].y, Contacts[1].z]) cube([(-CaseOffset.x + Protrusion),CaseSize.y,CaseSize.z]); translate([(CaseOffset.x – Protrusion), (CaseOffset.y – Protrusion), 0]) cube([(-CaseOffset.x + Protrusion), Contacts[0].y + Protrusion – CaseOffset.y, CaseSize.z]); } translate([0,0,BatterySize.z + Gap]) Lid(); color("Silver",0.15) PinAssembly(); if (RGBCircuit) { translate([StrutOC.x/2,BatterySize.y/2,2*BatterySize.z]) difference() { RGBSpider(); rotate(180-StrutAngle) translate([0,0,-Protrusion]) cube([norm(StrutOC),StrutBase[OD],2*BallPillar.z],center=false); } color("Green",0.35) translate([StrutOC.x/2,BatterySize.y/2,2*BatterySize.z + BallOD/2 + BallPillar[LENGTH] – BallChordM]) sphere(d=BallOD); } else { difference() { translate([StrutOC.x/2,BatterySize.y/2,2*BatterySize.z]) Spider(); translate([-BallPillar[OD],BatterySize.y/2,2*BatterySize.z – Protrusion]) cube([BallPillar[OD],StrutOC.y,2*BallPillar.z],center=false); } color("Green",0.35) translate([0,BatterySize.y/2,2*BatterySize.z + BallOD/2 + BallPillar[LENGTH] – BallChordM]) sphere(d=BallOD); } } if (Layout == "Build") { rotate(90) { translate([-BatterySize.x/2,-BatterySize.y/2,BaseThick]) Case(); translate([-CaseSize.x + LidSize.x,-(LidSize.y/2 + LidOffset.y),0]) Lid(); if (RGBCircuit) translate([StrutOC.x + BatterySize.x/2,0,0]) RGBSpider(); else translate([StrutOC.x + BatterySize.x/2,0,0]) Spider(); } } if (Layout == "Show") { Case(); translate([0,0,(BatterySize.z + Gap)]) Lid(); color("Silver",0.25) PinAssembly(); if (RGBCircuit) { translate([StrutOC.x/2,BatterySize.y/2,2*BatterySize.z]) RGBSpider(); color("Green",0.35) translate([StrutOC.x/2,BatterySize.y/2,2*BatterySize.z + BallOD/2 + BallPillar[LENGTH] – BallChordM]) sphere(d=BallOD); } else { translate([StrutOC.x/2,BatterySize.y/2,2*BatterySize.z]) Spider(); color("Green",0.35) translate([0,BatterySize.y/2,2*BatterySize.z + BallOD/2 + BallPillar[LENGTH] – BallChordM]) sphere(d=BallOD); } }

