Vacuum Tube LEDs: Hard Drive Platter Base

Stainless steel socket head and button head screws add a certain techie charm to the hard drive platter mirroring the Noval tube:

Noval - Black PETG base - magenta phase
Noval – Black PETG base – magenta phase

Black PETG, rather than cyan or natural filament, suppresses the socket’s glow and emphasizes the tube’s internal lighting:

Noval tube on platter - button-head screws
Noval tube on platter – button-head screws

The base puts the USB-to-serial adapter on the floor and stands the Pro Mini against a flat on the far wall:

Noval tube socket and base - interior layout
Noval tube socket and base – interior layout

A notch for the cable seems like a useful addition subtraction to the socket, because that cable tie just doesn’t look right. I used 4 mm threaded inserts, as those button head screws looked better.

The solid model looks like you’d expect:

Vacuum Tube Lights - hard drive platter base - solid model
Vacuum Tube Lights – hard drive platter base – solid model

Those are 3 mm threaded inserts, again to get the right head size screw on the platter.

The height of the base depends on the size of the socket, with the model maintaining a bit of clearance above the USB adapter. The OD depends on the platter OD, with a fixed overhang, and the insert BCD depends on the OD / insert OD / base wall thickness.

Although I’m using an Arduino Pro Mini and a separate USB-to-serial adapter, a (knockoff) Arduino Nano would be better and cheaper, although the SMD parts on the Nano’s bottom surface make it a bit thicker and less suitable for foam-tape mounting.

I drilled the platter using manual CNC:

Hard drive platter - Noval base drilling
Hard drive platter – Noval base drilling

After centering the origin on the platter hole, the hole positions (for a 71 mm BCD) use LinuxCNC’s polar notation:

g0 @[71/2]^45
g0 @[71/2]^[45+90]
g0 @[71/2]^[45+180]
g0 @[71/2]^-45

I used the Joggy Thing for manual drilling after each move; that’s easier than figuring out the appropriate g81 feed & speed.

The 3D printed base still looks a bit chintzy compared with the platter, but it’s coming along.

The OpenSCAD source code as a GitHub Gist:

// Vacuum Tube LED Lights
// Ed Nisley KE4ZNU February ... September 2016
Layout = "PlatterBase"; // Cap LampBase USBPort Bushings
// Socket(s) (Build)FinCap Platter[Base|Fixture]
DefaultSocket = "Noval";
Section = false; // cross-section the object
Support = true;
//- Extrusion parameters must match reality!
ThreadThick = 0.25;
ThreadWidth = 0.40;
HoleWindage = 0.2;
Protrusion = 0.1; // make holes end cleanly
inch = 25.4;
function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
//----------------------
// Dimensions
// https://en.wikipedia.org/wiki/Tube_socket#Summary_of_Base_Details
// punch & screw OC modified for drive platter chassis plate
// platter = 25 mm ID
// CD = 15 mm ID with raised ring at 37 mm, needs screw head clearance
T_NAME = 0; // common name
T_NUMPINS = 1; // total, with no allowance for keying
T_PINBCD = 2; // tube pin circle diameter
T_PINOD = 3; // ... diameter
T_PINLEN = 4; // ... length (must also clear evacuation tip / spigot)
T_HOLEOD = 5; // nominal panel hole from various sources
T_PUNCHOD = 6; // panel hole optimized for inch-size Greenlee punches
T_TUBEOD = 7; // envelope or base diameter
T_PIPEOD = 8; // light pipe from LED to tube base (clear evac tip / spigot)
T_SCREWOC = 9; // mounting screw holes
// Name pins BCD dia length hole punch tube pipe screw
TubeData = [
["Mini7", 8, 9.53, 1.016, 7.0, 16.0, 25.0, 18.0, 5.0, 35.0], // punch 11/16, screw 22.5 OC
["Octal", 8, 17.45, 2.36, 10.0, 36.2, (8 + 1)/8 * inch, 32.0, 11.5, 47.0], // screw 39.0 OC
["Noval", 10, 11.89, 1.1016, 7.0, 22.0, 25.0 , 21.0, 7.5, 35.0], // punch 7/8, screw 28.0 OC
["Magnoval", 10, 17.45, 1.27, 9.0, 29.7, (4 + 1)/4 * inch, 46.0, 12.4, 38.2], // similar to Novar
["Duodecar", 13, 19.10, 1.05, 9.0, 32.0, (4 + 1)/4 * inch, 38.0, 12.5, 47.0], // screw 39.0 OC
];
ID = 0;
OD = 1;
LENGTH = 2;
Pixel = [7.0,10.0,3.0]; // ID = contact patch, OD = PCB dia, LENGTH = overall thickness
SocketNut = // socket mounting: threaded insert or nut recess
// [3.5,5.2,7.2] // 6-32 insert
[4.0,6.0,5.9] // 4 mm short insert
;
NutSides = 8;
SocketShim = 2*ThreadThick; // between pin holes and pixel top
SocketFlange = 1.5; // rim around socket below punchout
PanelThick = 1.5; // socket extension through punchout
FinCutterOD = 1/8 * inch;
FinCapSize = [(Pixel[OD] + 2*FinCutterOD),30.0,(10.0 + 2*Pixel[LENGTH])];
USBPCB =
// [28,16,6.5] // small Sparkfun knockoff
[36,18 + 1,5.8 + 0.4] // Deek-Robot fake FTDI with ISP header
;
Platter = [25.0,95.0,1.26]; // hard drive platter dimensions
//----------------------
// 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(d=(FixDia + HoleWindage),h=Height,$fn=Sides);
}
//----------------------
// Tube cap
CapTube = [4.0,3/16 * inch,10.0]; // brass tube for flying lead to cap LED
CapSize = [Pixel[ID],(Pixel[OD] + 2.0),(CapTube[OD] + 2*Pixel[LENGTH])];
CapSides = 8*4;
module Cap() {
difference() {
union() {
cylinder(d=CapSize[OD],h=(CapSize[LENGTH]),$fn=CapSides); // main cap body
translate([0,0,CapSize[LENGTH]]) // rounded top
scale([1.0,1.0,0.65])
sphere(d=CapSize[OD]/cos(180/CapSides),$fn=CapSides); // cos() fixes slight undersize vs cylinder
cylinder(d1=(CapSize[OD] + 2*3*ThreadWidth),d2=CapSize[OD],h=1.5*Pixel[LENGTH],$fn=CapSides); // skirt
}
translate([0,0,-Protrusion]) // bore for wiring to LED
PolyCyl(CapSize[ID],(CapSize[LENGTH] + 3*ThreadThick + Protrusion),CapSides);
translate([0,0,-Protrusion]) // PCB recess with clearance for tube dome
PolyCyl(Pixel[OD],(1.5*Pixel[LENGTH] + Protrusion),CapSides);
translate([0,0,(1.5*Pixel[LENGTH] - Protrusion)]) // small step + cone to retain PCB
cylinder(d1=(Pixel[OD]/cos(180/CapSides) + HoleWindage),d2=Pixel[ID],h=(Pixel[LENGTH] + Protrusion),$fn=CapSides);
translate([0,0,(CapSize[LENGTH] - CapTube[OD]/(2*cos(180/8)))]) // hole for brass tube holding wire loom
rotate([90,0,0]) rotate(180/8)
PolyCyl(CapTube[OD],CapSize[OD],8);
}
}
//----------------------
// Heatsink tube cap
module FinCap() {
CableOD = 3.5; // cable + braid diameter
BulbOD = 3.75 * inch; // bulb OD; use 10 inches for flat
echo(str("Fin Cutter: ",FinCutterOD));
FinSides = 2*4;
BulbRadius = BulbOD / 2;
BulbDepth = BulbRadius - sqrt(pow(BulbRadius,2) - pow(FinCapSize[OD],2)/4);
echo(str("Bulb OD: ",BulbOD," recess: ",BulbDepth));
NumFins = floor(PI*FinCapSize[ID] / (2*FinCutterOD));
FinAngle = 360 / NumFins;
echo(str("NumFins: ",NumFins," angle: ",FinAngle," deg"));
difference() {
union() {
cylinder(d=FinCapSize[ID],h=FinCapSize[LENGTH],$fn=2*NumFins); // main body
for (i = [0:NumFins - 1]) // fins
rotate(i * FinAngle)
hull() {
translate([FinCapSize[ID]/2,0,0])
rotate(180/FinSides)
cylinder(d=FinCutterOD,h=FinCapSize[LENGTH],$fn=FinSides);
translate([(FinCapSize[OD] - FinCutterOD)/2,0,0])
rotate(180/FinSides)
cylinder(d=FinCutterOD,h=FinCapSize[LENGTH],$fn=FinSides);
}
rotate(FinAngle/2) // cable entry boss
translate([FinCapSize[ID]/2,0,FinCapSize[LENGTH]/2])
cube([FinCapSize[OD]/4,FinCapSize[OD]/4,FinCapSize[LENGTH]],center=true);
}
for (i = [1:NumFins - 1]) // fin inner gullets, omit cable entry side
rotate(i * FinAngle + FinAngle/2) // joint isn't quite perfect, but OK
translate([FinCapSize[ID]/2,0,-Protrusion])
rotate(0*180/FinSides)
cylinder(d=FinCutterOD/cos(180/FinSides),h=(FinCapSize[LENGTH] + 2*Protrusion),$fn=FinSides);
translate([0,0,-Protrusion]) // PCB recess
PolyCyl(Pixel[OD],(1.5*Pixel[LENGTH] + Protrusion),FinSides);
PolyCyl(Pixel[ID],(FinCapSize[LENGTH] - 3*ThreadThick),FinSides); // bore for LED wiring
translate([0,0,(FinCapSize[LENGTH] - 3*ThreadThick - 2*CableOD/(2*cos(180/8)))]) // cable inlet
rotate(FinAngle/2) rotate([0,90,0]) rotate(180/8)
PolyCyl(CableOD,FinCapSize[OD],8);
if (BulbOD <= 10.0 * inch) // curve for top of bulb
translate([0,0,-(BulbRadius - BulbDepth + 2*ThreadThick)]) // ... slightly flatten tips
sphere(d=BulbOD,$fn=16*FinSides);
}
}
//----------------------
// Aperture for USB-to-serial adapter snout
// These are all magic numbers, of course
module USBPort() {
translate([0,USBPCB[0]])
rotate([90,0,0])
linear_extrude(height=USBPCB[0])
polygon(points=[
[0,0],
[USBPCB[1]/2,0],
[USBPCB[1]/2,0.5*USBPCB[2]],
[USBPCB[1]/3,USBPCB[2]],
[-USBPCB[1]/3,USBPCB[2]],
[-USBPCB[1]/2,0.5*USBPCB[2]],
[-USBPCB[1]/2,0],
]);
}
//----------------------
// Box for Leviton ceramic lamp base
module LampBase() {
Insert = [3.5,5.2,7.2]; // 6-32 brass insert to match standard electrical screws
Bottom = 3.0;
Base = [4.0*inch,4.5*inch,20.0 + Bottom];
Sides = 12*4;
Retainer = [3.5,11.0,1.0]; // flat fiber washer holding lamp base screws in place
StudSides = 8;
StudOC = 3.5 * inch;
Stud = [Insert[OD], // insert for socket screws
min(15.0,1.5*(Base[ID] - StudOC)/cos(180/StudSides)), // OD = big enough to merge with walls
(Base[LENGTH] - Retainer[LENGTH])]; // leave room for retainer
union() {
difference() {
rotate(180/Sides)
cylinder(d=Base[OD],h=Base[LENGTH],$fn=Sides);
rotate(180/Sides)
translate([0,0,Bottom])
cylinder(d=Base[ID],h=Base[LENGTH],$fn=Sides);
translate([0,-Base[OD]/2,Bottom + 1.2]) // mount on double-sided foam tape
rotate(0)
USBPort();
}
for (i = [-1,1])
translate([i*StudOC/2,0,0])
rotate(180/StudSides)
difference() {
cylinder(d=Stud[OD],h=Stud[LENGTH],$fn=StudSides);
translate([0,0,Bottom])
PolyCyl(Stud[ID],(Stud[LENGTH] - (Bottom - Protrusion)),6);
}
}
}
//----------------------
// Base for hard drive platters
module PlatterBase(TubeName = DefaultSocket) {
PCB =
[36,18,3] // Arduino Pro Mini
;
Tube = search([TubeName],TubeData,1,0)[0];
SocketHeight = Pixel[LENGTH] + SocketShim + TubeData[Tube][T_PINLEN] - PanelThick;
echo(str("Base for ",TubeData[Tube][0]," socket"));
Overhang = 5.5; // platter overhangs base by this much
Bottom = 4*ThreadThick;
Base = [(Platter[OD] - 3*Overhang), // smaller than 3.5 inch Sch 40 PVC pipe...
(Platter[OD] - 2*Overhang),
2.0 + max(PCB[1],(2.0 + SocketHeight + USBPCB[2])) + Bottom];
Sides = 24*4;
echo(str(" Height: ",Base[2]," mm"));
Insert = // platter mounting: threaded insert or nut recess
// [3.5,5.2,7.2] // 6-32 insert
[3.9,5.0,8.0] // 3 mm - long insert
;
NumStuds = 4;
StudSides = 8;
Stud = [Insert[OD], // insert for socket screws
2*Insert[OD], // OD = big enough to merge with walls
Base[LENGTH]]; // leave room for retainer
StudBCD = floor(Base[ID] - Stud[OD] + (Stud[OD] - Stud[ID])/2);
echo(str("Platter screw BCD: ",StudBCD," mm"));
PCBInset = Base[ID]/2 - sqrt(pow(Base[ID]/2,2) - pow(PCB[0],2)/4);
union() {
difference() {
rotate(180/Sides)
cylinder(d=Base[OD],h=Base[LENGTH],$fn=Sides);
rotate(180/Sides)
translate([0,0,Bottom])
cylinder(d=Base[ID],h=Base[LENGTH],$fn=Sides);
translate([0,-Base[OD]/2,Bottom + 1.2]) // mount PCB on foam tape
rotate(0)
USBPort();
}
for (a = [0:(NumStuds - 1)]) // platter mounting studs
rotate(180/NumStuds + a*360/(NumStuds))
translate([StudBCD/2,0,0])
rotate(180/StudSides)
difference() {
cylinder(d=Stud[OD],h=Stud[LENGTH],$fn=2*StudSides);
translate([0,0,Bottom])
PolyCyl(Stud[ID],(Stud[LENGTH] - (Bottom - Protrusion)),StudSides);
}
intersection() { // microcontroller PCB mounting plate
rotate(180/Sides)
cylinder(d=Base[OD],h=Base[LENGTH],$fn=Sides);
translate([-PCB[0]/2,(Base[ID]/2 - PCBInset),0])
cube([PCB[0],Base[OD]/2,Base[LENGTH]],center=false);
}
difference() {
intersection() { // totally ad-hoc bridge around USB opening
rotate(180/Sides)
cylinder(d=Base[OD],h=Base[LENGTH],$fn=Sides);
translate([-1.25*USBPCB[1]/2,-(Base[ID]/2),0])
cube([1.25*USBPCB[1],2.0,Base[LENGTH]],center=false);
}
translate([0,-Base[OD]/2,Bottom + 1.2]) // mount PCB on foam tape
rotate(0)
USBPort();
}
}
}
//----------------------
// Drilling fixture for disk platters
module PlatterFixture() {
StudOC = [1.16*inch,1.16*inch]; // Sherline tooling plate screw spacing
StudClear = 5.0;
BasePlate = [(20 + StudOC[0]*ceil(Platter[OD] / StudOC[0])),(Platter[OD] + 10),7.0];
PlateRound = 10.0; // corner radius
difference() {
hull() // basic block
for (i=[-1,1], j=[-1,1])
translate([i*(BasePlate[0]/2 - PlateRound),j*(BasePlate[1]/2 - PlateRound),0])
cylinder(r=PlateRound,h=BasePlate[2],$fn=4*4);
for (i=[-1:1], j=[-1:1]) // index marks
translate([i*100/2,j*100/2,BasePlate[2] - 2*ThreadThick])
cylinder(d=1.5,h=1,$fn=6);
for (i=[-1,1], j=[-1,0,1]) // holes for tooling plate studs
translate([i*StudOC[0]*ceil(Platter[OD] / StudOC[0])/2,j*StudOC[0],-Protrusion])
PolyCyl(StudClear,BasePlate[2] + 2*Protrusion,6);
translate([0,0,-Protrusion]) // center clamp hole
PolyCyl(StudClear,BasePlate[2] + 2*Protrusion,6);
translate([0,0,BasePlate[2] - Platter[LENGTH]]) // disk locating recess
linear_extrude(height=(Platter[LENGTH] + Protrusion),convexity=2)
difference() {
circle(d=(Platter[OD] + 1),$fn=8*4);
circle(d=Platter[ID],$fn=8*4);
}
translate([0,0,BasePlate[2] - 4.0]) // drilling recess
linear_extrude(height=(4.0 + Protrusion),convexity=2)
difference() {
circle(d=(Platter[OD] - 10),$fn=8*4);
circle(d=(Platter[ID] + 10),$fn=8*4);
}
}
}
//----------------------
// Tube Socket
module Socket(Name = DefaultSocket) {
NumSides = 6*4;
Tube = search([Name],TubeData,1,0)[0];
echo(str("Building ",TubeData[Tube][0]," socket"));
echo(str(" Punch: ",TubeData[Tube][T_PUNCHOD]," mm = ",TubeData[Tube][T_PUNCHOD]/inch," inch"));
echo(str(" Screws: ",TubeData[Tube][T_SCREWOC]," mm =",TubeData[Tube][T_SCREWOC]/inch," inch OC"));
OAH = Pixel[LENGTH] + SocketShim + TubeData[Tube][T_PINLEN];
BaseHeight = OAH - PanelThick;
difference() {
union() {
linear_extrude(height=BaseHeight) // base outline
hull() {
circle(d=(TubeData[Tube][T_PUNCHOD] + 2*SocketFlange),$fn=NumSides);
for (i=[-1,1])
translate([i*TubeData[Tube][T_SCREWOC]/2,0])
circle(d=2.0*SocketNut[OD],$fn=NumSides);
}
cylinder(d=TubeData[Tube][T_PUNCHOD],h=OAH,$fn=NumSides); // boss in chassis punch hole
}
for (i=[0:(TubeData[Tube][T_NUMPINS] - 1)]) // tube pins
rotate(i*360/TubeData[Tube][T_NUMPINS])
translate([TubeData[Tube][T_PINBCD]/2,0,(OAH - TubeData[Tube][T_PINLEN])])
rotate(180/4)
PolyCyl(TubeData[Tube][T_PINOD],(TubeData[Tube][T_PINLEN] + Protrusion),4);
for (i=[-1,1]) // mounting screw holes & nut traps / threaded inserts
translate([i*TubeData[Tube][T_SCREWOC]/2,0,-Protrusion]) {
PolyCyl(SocketNut[OD],(SocketNut[LENGTH] + Protrusion),NutSides);
PolyCyl(SocketNut[ID],(OAH + 2*Protrusion),NutSides);
}
translate([0,0,-Protrusion]) { // LED recess
PolyCyl(Pixel[OD],(Pixel[LENGTH] + Protrusion),8);
}
translate([0,0,(Pixel[LENGTH] - Protrusion)]) { // light pipe
rotate(180/TubeData[Tube][T_NUMPINS])
PolyCyl(TubeData[Tube][T_PIPEOD],(OAH + 2*Protrusion),TubeData[Tube][T_NUMPINS]);
}
}
// Totally ad-hoc support structures ...
if (Support) {
color("Yellow") {
for (i=[-1,1]) // nut traps
translate([i*TubeData[Tube][T_SCREWOC]/2,0,(SocketNut[LENGTH] - ThreadThick)/2])
for (a=[0:5])
rotate(a*30 + 15)
cube([2*ThreadWidth,0.9*SocketNut[OD],(SocketNut[LENGTH] - ThreadThick)],center=true);
if (Pixel[OD] > TubeData[Tube][T_PIPEOD]) // support pipe only if needed
translate([0,0,(Pixel[LENGTH] - ThreadThick)/2])
for (a=[0:7])
rotate(a*22.5)
cube([2*ThreadWidth,0.9*Pixel[OD],(Pixel[LENGTH] - ThreadThick)],center=true);
}
}
}
//----------------------
// Greenlee punch bushings
module PunchBushing(Name = DefaultSocket) {
PunchScrew = 9.5;
BushingThick = 3.0;
Tube = search([Name],TubeData,1,0)[0];
echo(str("Building ",TubeData[Tube][0]," bushing"));
NumSides = 6*4;
difference() {
union() {
cylinder(d=Platter[ID],h=BushingThick,$fn=NumSides);
cylinder(d=TubeData[Tube][T_PUNCHOD],h=(BushingThick - Platter[LENGTH]),$fn=NumSides);
}
translate([0,0,-Protrusion])
PolyCyl(PunchScrew,5.0,8);
}
}
//----------------------
// Build it
if (Layout == "Cap") {
if (Section)
difference() {
Cap();
translate([-CapSize[OD],0,CapSize[LENGTH]])
cube([2*CapSize[OD],2*CapSize[OD],3*CapSize[LENGTH]],center=true);
}
else
Cap();
}
if (Layout == "FinCap") {
if (Section) render(convexity=5)
difference() {
FinCap();
// translate([0,-FinCapSize[OD],FinCapSize[LENGTH]])
// cube([2*FinCapSize[OD],2*FinCapSize[OD],3*FinCapSize[LENGTH]],center=true);
translate([-FinCapSize[OD],0,FinCapSize[LENGTH]])
cube([2*FinCapSize[OD],2*FinCapSize[OD],3*FinCapSize[LENGTH]],center=true);
}
else
FinCap();
}
if (Layout == "BuildFinCap")
translate([0,0,FinCapSize[LENGTH]])
rotate([180,0,0])
FinCap();
if (Layout == "LampBase")
LampBase();
if (Layout == "PlatterBase")
PlatterBase();
if (Layout == "PlatterFixture")
PlatterFixture();
if (Layout == "USBPort")
USBPort();
if (Layout == "Bushings")
PunchBushing();
if (Layout == "Socket")
if (Section) {
difference() {
Socket();
translate([-100/2,0,-Protrusion])
cube([100,50,50],center=false);
}
}
else
Socket();
if (Layout == "Sockets") {
translate([0,50,0])
Socket("Mini7");
translate([0,20,0])
Socket("Octal");
translate([0,-15,0])
Socket("Duodecar");
translate([0,-50,0])
Socket("Noval");
translate([0,-85,0])
Socket("Magnoval");}

8 thoughts on “Vacuum Tube LEDs: Hard Drive Platter Base

    1. Would that it were so!

      I type G-Code directly into the MDI panel and use the Joggy Thing for alignment & touchoff. That process neatly combines the accuracy of CNC positioning with the impromptu “Oh, shit!” of manual milling… [sigh]

  1. How about chucking the base in the lathe and giving it a light finishing cut to get rid of layer transitions? Afterwards you could finish it with a coat of black paint from a spray can.

    1. I’ve never been satisfied with machining 3D printed objects, probably because I don’t make them rigid enough to withstand much in the way of external force. Perhaps a mandrel to support / clamp the inside?

      I should do a couple of test pieces just to see how it works, now that I have nice sharp carbide inserts…

  2. I’m unclear what that cable tie is wrapped around, it looks like it’s just floating on the wire. You could, theoretically, print the socket and the base together, in which case you’d only need one set of screws to hold the platter on as well.

    1. Half a dozen screw threads stick out beyond the brass insert: just enough for the cable tie to not quite fall off. It’s sooo half-assed that the socket model now has two slots bracketing the LED.

      I haven’t figured out how to slide the LED into the middle of a socket that grows out of the base: a slot would (probably) require impossible-to-remove support. Plus, the USB adapter sits in the air gap under the socket; the soon-to-arrive Nano boards may suggest a different arrangement.

      That poor platter has way too many screw holes, fer shure…

Comments are closed.