Advertisements

Archive for November, 2015

Kenmore Vacuum Cleaner Tool Adapters

After donating the neversufficiently-to-be-damned Samsung vacuum cleaner (and all its remaining bags & doodads) to a nonprofit’s tag sale, we picked up a Sears Kenmore Progressive vacuum cleaner that seemed to be the least awful of the current offerings. Unlike all previous vacuum cleaners, its tools & doodads have complex plastic fittings with latches and keyways and all manner of gimcrackery. The designers seem to have hands and legs of far-above-average size, but that’s another rant.

All this came to a head when I attempted to vacuum the fuzz out of the refrigerator’s evaporator coils, because the long snout that reaches the back of the refrigerator doesn’t fit the aperture in the giant handle.

Well, at least I can fix that

The first step involved modeling the plastic fitting that snaps into the handle:

Kenmore Male Fitting - Solid model

Kenmore Male Fitting – Solid model

The latch on the handle snaps into an opening that took some tinkering to reproduce. Stand back, I’m going to use trigonometry:

            translate([0,-11.5/2,23.0 - 5.0])                                    // latch opening
                cube(Latch);
                
            translate([OEMTube[ID1]/2 + EntryHeight/tan(90-EntryAngle),0,0])    // latch ramp
                translate([(Latch[1]/cos(180/EntrySides))*cos(EntryAngle)/2,0,(Latch[1]/cos(180/EntrySides))*sin(EntryAngle)/2])
                    rotate([0,-EntryAngle,0])
                        intersection() {
                            rotate(180/EntrySides)
                                PolyCyl(Latch[1],Latch[0],EntrySides);
                            translate([-(2*Latch[0])/2,0,-Protrusion])
                                cube(2*Latch[0],center=true);
                        }

Which spits out two suitable shapes with the proper positions and alignments:

Kenmore Male Fitting - Latch detail - Solid model

Kenmore Male Fitting – Latch detail – Solid model

The magic wand for the refrigerator originally slid into the Samsung’s metal pipe, so I put a slightly tapered cylinder inside a somewhat more tapered exterior (which seems chunky enough to withstand my flailing around under the refrigerator), then topped it off with the male fitting:

Refrigerator Coil Wand Adapter

Refrigerator Coil Wand Adapter

The Kenmore crevice tool snaps under the gargantuan plastic handle, which limits it to being 6.5 inches long, totally unable to reach into any of the nontrivial crevices around here, and in the way when it’s not being used. Some rummaging turned up a longer crevice tool from the Electrolux That Came With The House™, an old-school tool that slipped over its pipe. Modeling a straight cylinder inside a tapered cylinder that fits the tool didn’t take long:

Crevice Tool Adapter

Crevice Tool Adapter

Flushed with success, I found a smaller floor brush than the new Kenmore, with dimensions similar to the Electrolux snout, so another module appeared:

Floor Brush Adapter

Floor Brush Adapter

All of them build with the latch end upward to avoid needing support structure, with a 5 mm brim for good platform adhesion:

Floor Brush Adapter - Slic3r preview

Floor Brush Adapter – Slic3r preview

I printed them during the PDS Mini Maker Faire as examples of Useful Things You Can Do With a 3D Printer:

Kenmore Vacuum Cleaner - Tool Adapters

Kenmore Vacuum Cleaner – Tool Adapters

As I pointed out to nearly everybody, the Big Lie about 3D printing is that you’ll just download somebody else’s model to solve your problem. In general, that won’t work, because nobody else has your problem; if you can’t do solid modeling, there’s no point in you having a 3D printer. There’s also no point in going to Kinko’s to get a standardized 3D printed doodad, because you can just order a better-looking injection-molded part directly from Sears (or an aftermarket source) and be done with it.

I loves me some good OpenSCAD action on my Makergear M2, though…

The OpenSCAD source code:

// Kenmore vacuum cleaner nozzle adapters
// Ed Nisley KE4ZNU November 2015

// Layout options

Layout = "CreviceTool";        // MaleFitting CoilWand FloorBrush CreviceTool

//- Extrusion parameters must match reality!
//  Print with +1 shells and 3 solid layers

ThreadThick = 0.25;
ThreadWidth = 0.40;

HoleWindage = 0.2;

function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);

Protrusion = 0.1;           // make holes end cleanly

//----------------------
// Dimensions

ID1 = 0;                                                // for tapered tubes
ID2 = 1;
OD1 = 2;
OD2 = 3;
LENGTH = 4;

OEMTube = [35.0,35.0,41.7,40.5,30.0];                    // main fitting tube
EndStop = [OEMTube[ID1],OEMTube[ID2],47.5,47.5,6.5];    // flange at end of main tube

FittingOAL = OEMTube[LENGTH] + EndStop[LENGTH];

$fn = 12*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);
}


//-------------------
// Male fitting on end of Kenmore tools
// This slides into the end of the handle or wand and latches firmly in place

module MaleFitting() {
    
Latch = [40,11.5,5.0];                    // rectangle latch opening
EntryAngle = 45;                        // latch entry ramp
EntrySides = 16;
EntryHeight = 15.0;                        // lower edge on *inside* of fitting

KeyRadius = 1.0;
        
    translate([0,0,6.5])
        difference() {
            union() {
                cylinder(d1=OEMTube[OD1],d2=OEMTube[OD2],h=OEMTube[LENGTH]);            // main tube
                
                hull()                                                                    // insertion guide
                    for (i=[-(6.0/2 - KeyRadius),(6.0/2 - KeyRadius)], 
                        j=[-(28.0/2 - KeyRadius),(28.0/2 - KeyRadius)], 
                        k=[-(26.0/2 - KeyRadius),(26.0/2 - KeyRadius)])
                        translate([(i - (OEMTube[ID1]/2 + OEMTube[OD1]/2)/2 + 6.0/2),j,(k + 26.0/2 - 1.0)])
                            sphere(r=KeyRadius,$fn=8);
                
                translate([0,0,-EndStop[LENGTH]])                                // wand tube butts against this
                    cylinder(d=EndStop[OD1],h=EndStop[LENGTH] + Protrusion);
            }
            
            translate([0,0,-OEMTube[LENGTH]])                                    // main bore
                cylinder(d=OEMTube[ID1],h=2*OEMTube[LENGTH] + 2*Protrusion);
                
            translate([0,-11.5/2,23.0 - 5.0])                                    // latch opening
                cube(Latch);
                
            translate([OEMTube[ID1]/2 + EntryHeight/tan(90-EntryAngle),0,0])    // latch ramp
                translate([(Latch[1]/cos(180/EntrySides))*cos(EntryAngle)/2,0,(Latch[1]/cos(180/EntrySides))*sin(EntryAngle)/2])
                    rotate([0,-EntryAngle,0])
                        intersection() {
                            rotate(180/EntrySides)
                                PolyCyl(Latch[1],Latch[0],EntrySides);
                            translate([-(2*Latch[0])/2,0,-Protrusion])
                                cube(2*Latch[0],center=true);
                        }
        }
}

//-------------------
// Refrigerator evaporator coil wand

module CoilWand() {
    
    union() {
        translate([0,0,50.0])
            rotate([180,0,0])
                difference() {
                    cylinder(d1=EndStop[OD1],d2=42.0,h=50.0);
                    translate([0,0,-Protrusion])
                        cylinder(d1=35.0,d2=35.8,h=100);
                }
        translate([0,0,50.0 - Protrusion])
            MaleFitting();
    }
}


//-------------------
// Refrigerator evaporator coil wand

module FloorBrush() {
    
    union() {
        translate([0,0,60.0])
            rotate([180,0,0])
                difference() {
                    union() {
                        cylinder(d1=EndStop[OD1],d2=32.4,h=10.0);
                        translate([0,0,10.0 - Protrusion])
                            cylinder(d1=32.4,d2=30.7,h=50.0 + Protrusion);
                    }
                    translate([0,0,-Protrusion])
                        cylinder(d1=28.0,d2=24.0,h=100);
                }
        translate([0,0,60.0 - Protrusion])
            MaleFitting();
    }
}


//-------------------
// Crevice tool

module CreviceTool() {
    
    union() {
        translate([0,0,60.0])
            rotate([180,0,0])
                difference() {
                    union() {
                        cylinder(d1=EndStop[OD1],d2=32.0,h=10.0);
                        translate([0,0,10.0 - Protrusion])
                            cylinder(d1=32.0,d2=30.4,h=50.0 + Protrusion);
                    }
                    translate([0,0,-Protrusion])
                        cylinder(d1=28.0,d2=24.0,h=100);
                }
        translate([0,0,60.0 - Protrusion])
            MaleFitting();
    }
}




//----------------------
// Build it!

if (Layout == "MaleFitting")
    MaleFitting();

if (Layout == "CoilWand")
    CoilWand();

if (Layout == "FloorBrush")
    FloorBrush();

if (Layout == "CreviceTool")
    CreviceTool();

Advertisements

, ,

9 Comments

Provincetown Pilgrim Memorial: Lighting Ticky-tacky

This imposing memorial plaque stands in a small park in Provincetown MA, at the foot of the hill from which the Pilgrim Monument emerges:

Provincetown Pilgrim Memorial

Provincetown Pilgrim Memorial

It’s one of those 1920-ish things with the impeccable stonework and bronze casting that you couldn’t possibly duplicate nowadays. But, at least twice between then and now, somebody thought it’d be a Good Idea to decorate it with what look to be Genuine Christmas Tree Lights:

Provincetown Pilgrim Memorial - detail

Provincetown Pilgrim Memorial – detail

The most recent lamps and wires seem to be restrained by plastic clips glued onto the face of the stone:

Provincetown Pilgrim Memorial - lamp detail

Provincetown Pilgrim Memorial – lamp detail

A previous generation drilled small holes and inserted metal pins that didn’t survive in a salt-spray environment, so I guess plastic seemed like the right answer.

Words fail me…

Leave a comment

New Countertop vs. Old Strut

Spotted this in a restaurant near Lowell, MA on our road trip:

Countertop trash cutout vs support strut

Countertop trash cutout vs support strut

Somehow, it’s very hard to coordinate sinks, supports, and plumbing these days…

Leave a comment

Hard Drive Platter Mood Light: First Light!

Disassembling the (unglued!) platter stack simplified wiring the the Neopixels:

Hard Drive Mood Light - test light

Hard Drive Mood Light – test light

Orienting the strips in alternate directions kept the white data connections between adjacent strips on the top and bottom level. If they sat in the same direction, the data wires would run from top to bottom.

Each Neopixel draw 60 mA max, so each side of the pillar can draw 180 mA and lighting up all four sides in full-throttle white draws a bit over 720 mA. That’s more than those little Wire-Wrap wires should be forced to carry, but the tiny Neopixel solder pads aren’t good for much more than that. The revised column model has wiring channels behind both strip ends to provide access to the slightly larger pads on the rear surface; the fact that all the end pads get cut in half doesn’t help matters.

The red and blue power wires connect adjacent strips, with two opposite strips wired in parallel at the bottom of the column. There’s a 100 µF cap across the incoming power leads: as much capacitor as would fit in the somewhat undersized base.

A knockoff Arduino Pro Mini sits inline between a 5.2 VDC wall wart and the Mood Light with three connections: VCC, GND, and D6. It’s flapping around in mid-air with no protection whatsoever, so I’ll let your imagination draw that picture. I want to hide it in the base, along with a power jack, as part of the fine tuning.

Anyhow, restacking the platters produced this pleasant effect:

Hard Drive Mood Light - low angle

Hard Drive Mood Light – low angle

You’re seeing each LEDs both directly and through a reflection in the platter below it. Despite having handled the platters for a few days, the reflection’s clarity surprised me; the multiple reflections required to bounce the LED image to the edge of the platter work perfectly:

Hard Drive Mood Light - high angle

Hard Drive Mood Light – high angle

Running the original firmware (which, as noted in the comments, will eventually fall off its rails), the colors change slowly enough to be always the same while you’re watching and always different after you look away:

Hard Drive Mood Light - red

Hard Drive Mood Light – red

The platters stack sufficiently parallel to each other that the LED images still have the right spacing after multiple reflections. It’s not quite an infinite house of mirrors.

With the LEDs running at half intensity (PWM limited to 128/255), the stack lights up a dark living room just fine. At full throttle, it’d probably be too bright…

All in all, it looks suprisingly good!

1 Comment

Hard Drive Platter Mood Light: 3D Printed Structure

Harvesting a stack of hard drive platters and discovering that four Neopixel strips could stand vertically inside the central hole suggested this overall structure:

Hard Drive Mood Light - solid model - Show view

Hard Drive Mood Light – solid model – Show view

The model includes a parameter for the number of strips, but not everything respects that. I’m not sure I’ll ever make a three-LED column and five strips won’t fit, so it probably doesn’t matter.

The central pillar holds everything together:

Hard Drive Mood Light - solid model - Pillar

Hard Drive Mood Light – solid model – Pillar

The Neopixel strips slide into those slots, which turned out to be too small to actually print, because the molten plastic pretty much squeezed the slots closed. Some deft pull saw action enlarged them enough to pass the strips, at the cost of tedious hand-fitting and considerable hidden ugliness. Printing the slots slightly larger bangs against the (lack of) printer resolution, because there’s not much wiggle room between the tiny slots and the outer diameter of the column:

Hard Drive Mood Light - Pillar - Slic3r preview

Hard Drive Mood Light – Pillar – Slic3r preview

The three alignment pin holes along each edge sit 6.944 mm on center, which is what you get when you divide the nominal 1 meter strip length by 144 Neopixels. I’m using knockoff Neopixels from halfway around the planet, but they’re probably pretty close to the real thing (also from halfway around the planet, I’m sure).

All those parts laid out on the platform, along with a fourth set of spacers in case I drop one:

Hard Drive Mood Light - solid model - Build view

Hard Drive Mood Light – solid model – Build view

And they print in cyan PETG just like you’d expect:

Hard Drive Mood Light - parts on platform

Hard Drive Mood Light – parts on platform

The round base (on the right) prints bottom-side-up, with bridging from the rim to the central pillar, and came out looking just fine. The top doesn’t have the central post and the pillar doesn’t have the top recess shown in the model: those tweaks will appear in the next iteration.

Each tiny triangular spacer gets an alignment pin glued into its inner surface, then four of them get glued to the pillar. This crash test dummy pillar worked out the dimensions, so it’s squat and ugly:

Hard Drive Platter Mood Light - pillar gluing

Hard Drive Platter Mood Light – pillar gluing

It’s clamped to a glass plate (smooth side up!) to force the spacers onto on a plane, with the other clamps smashing them against the pillar. All the other spacers get glued in situ atop each platter as it’s installed, which is a definite downside.

Installing the Neopixels before assembling the platters seemed to be the right way to go:

Hard Drive Mood Light - first platter assembly

Hard Drive Mood Light – first platter assembly

After that, just stack ’em up:

Hard Drive Mood Light - top Neopixels

Hard Drive Mood Light – top Neopixels

I dry-assembled the upper two spacer sets, so I could pull it apart in case that seemed necessary. Turned out to be a good idea.

And then screw the lid on top to see what it looks like:

Hard Drive Mood Light - trial assembly

Hard Drive Mood Light – trial assembly

That top screw should be a pan-head or something similarly smooth, rather than a random PC case screw. The sacrificial hard drives provided a bunch of Torx screws that would surely look better; most are far too small.

I thought a taller stack would be appropriate, but I kinda like the short, squat aspect ratio.

Now for some wiring…

The OpenSCAD source code:

// Hard Drive Platter Mood Light
// Ed Nisley KE4ZNU November 2015

Layout = "Show";					// Build Show Pixel LEDString Platters Pillar Spacers TopCap Base

ShowDisks = 2;						// number of disks in Show layout

//- Extrusion parameters must match reality!

ThreadThick = 0.20;
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

ID = 0;
OD = 1;
LENGTH = 2;

Platter = [25.0,95.0,1.27];						// hard drive platters

LEDStringCount = 3;								// number of LEDs on each strip (Show mode looks odd for less than 3)
LEDStripCount = 4;								// number of strips (verify locating pin holes & suchlike)

WireSpace = 1.0;								// allowance for wiring along strip ends

BaseSize = [40,14,3.0];							// overall base plate outside engine controller slot

Pixel = [13.0, 1000 / 144, 0.5];				// smallest indivisible unit of LED strip
PixelMargin = [1.0, 1.0, 2.0];					// LED and circuitry atop the strip

BeamAngle = 120;								// LED viewing angle
BeamShape = [
	[0,0],
	[Platter[OD]*cos(BeamAngle/2),-Platter[OD]*sin(BeamAngle/2)],
	[Platter[OD]*cos(BeamAngle/2), Platter[OD]*sin(BeamAngle/2)]
];

PillarSides = 12*4;

PillarCore = Platter[ID] - 2*(Pixel[2] + PixelMargin[2] + 2.0);		// LED channel distance across pillar centerline
PillarLength = LEDStringCount*Pixel[1] + Platter[LENGTH];
echo(str("Pillar core size: ",PillarCore));
echo(str("      ... length:"),PillarLength);

Cap = [Platter[ID] + 4.0,Platter[ID] + 4.0 + 10*2*ThreadWidth,2*WireSpace + 6*ThreadThick];		// cap over top of pillar
CapSides = 16;

Base = [Platter[ID] + 10.0,0.5*Platter[OD],8.0];
BaseSides = 16;

Screw = [2.0,3.0,20.0];							// screws used to secure cap & pillar

Spacer = [Platter[ID],(Platter[ID] + 2*8),(Pixel[1] - Platter[LENGTH])];
echo(str("Spacer  OD: ",Spacer[OD]));
echo(str(" ... thick:",Spacer[LENGTH]));

LEDStripProfile = [
	[0,0],
	[Pixel[0]/2,0],
	[Pixel[0]/2,Pixel[2]],
	[(Pixel[0]/2 - PixelMargin[0]),Pixel[2]],
	[(Pixel[0]/2 - PixelMargin[0]),(Pixel[2] + PixelMargin[2])],
	[-(Pixel[0]/2 - PixelMargin[0]),(Pixel[2] + PixelMargin[2])],
	[-(Pixel[0]/2 - PixelMargin[0]),Pixel[2]],
	[-Pixel[0]/2,Pixel[2]],
	[-Pixel[0]/2,0]
];

//----------------------
// 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);
}

//- Locating pin hole with glue recess
//  Default length is two pin diameters on each side of the split

PinOD = 1.70;

module LocatingPin(Dia=PinOD,Len=0.0) {
	
	PinLen = (Len != 0.0) ? Len : (4*Dia);
	
	translate([0,0,-ThreadThick])
		PolyCyl((Dia + 2*ThreadWidth),2*ThreadThick,4);

	translate([0,0,-2*ThreadThick])
		PolyCyl((Dia + 1*ThreadWidth),4*ThreadThick,4);
		
	translate([0,0,-(PinLen/2 + ThreadThick)])
		PolyCyl(Dia,(PinLen + 2*ThreadThick),4);

}
//----------------------
// Pieces

//-- LED strips

module OnePixel() {
	
	render()
		rotate([-90,0,0]) rotate(180)				// align result the way you'd expect from the dimensions
			difference() {
				linear_extrude(height=Pixel[1],convexity=3)
					polygon(points=LEDStripProfile);
				translate([-Pixel[0]/2,Pixel[2],-PixelMargin[0]])
					cube([Pixel[0],2*PixelMargin[2],2*PixelMargin[0]]);
				translate([-Pixel[0]/2,Pixel[2],Pixel[1]-PixelMargin[0]])
					cube([Pixel[0],2*PixelMargin[2],2*PixelMargin[0]]);
			}
}

module LEDString(n = LEDStringCount) {
	
	for (i=[0:n-1])
		translate([0,i*Pixel[1]])
//			resize([0,Pixel[1] + 2*Protrusion,0])
				OnePixel();
}

//-- Stack of hard drive platters

module Platters(n = LEDStringCount + 1) {
	
	color("gold",0.4)
	for (i=[0:n-1]) {
		translate([0,0,i*Pixel[1]])
			difference() {
				cylinder(d=Platter[OD],h=Platter[LENGTH],center=false,$fn=PillarSides);
				cylinder(d=Platter[ID],h=3*Platter[LENGTH],center=true,$fn=PillarSides);
			}
	}
}

//-- Pillar holding the LED strips

module Pillar() {
	
	difflen = PillarLength + 2*Protrusion;
	
//	render(convexity=5)
	difference() {
		linear_extrude(height=PillarLength,convexity=4)
			difference() {
				rotate(180/(12*4))
					circle(d=Platter[ID] - 1*ThreadWidth,$fn=PillarSides);
				
				for (i=[0:LEDStripCount-1]) 					// clearance for LED beamwidth, may not actually cut surface
					rotate(i*360/LEDStripCount)
						translate([PillarCore/2,0,0])
							polygon(points=BeamShape);
							
				for (i=[0:LEDStripCount-1])						// LED front clearance
					rotate(i*360/LEDStripCount)
						translate([(PillarCore/2 + Pixel[2]),(Pixel[0] - 2*PixelMargin[0])/2])
							rotate(-90)
								square([Pixel[0] - 2*PixelMargin[0],Platter[ID]]);

			}
			
		for (i=[0:LEDStripCount-1])								// LED strip slots
			rotate(i*360/LEDStripCount)
				translate([PillarCore/2,0,-Protrusion])
					linear_extrude(height=difflen,convexity=2)
						rotate(-90)
							polygon(points=LEDStripProfile);
		
		for (i=[0,90])											// wiring recess on top surface
			rotate(i)
				translate([0,0,(PillarLength - (WireSpace/2 - Protrusion))])
					cube([(PillarCore + 2*Protrusion),Pixel[0] - 2*PixelMargin[0],WireSpace],center=true);
							
		for (i=[0:LEDStripCount-1])								// wiring recess on bottom surface
			rotate(i*90)
				translate([PillarCore/2 - (WireSpace - Protrusion)/2,0,WireSpace/2 - Protrusion])
					cube([WireSpace + Protrusion,Pixel[0] - 2*PixelMargin[0],WireSpace],center=true);
							
		for (j=[0:LEDStringCount-1])							// platter spacer alignment pins
			for (i=[0:LEDStripCount-1])
				rotate(i*360/LEDStripCount + 180/LEDStripCount)
					translate([(Platter[ID] - 1*ThreadWidth)/2,0,(j*Pixel[1] + Pixel[1]/2 + Platter[LENGTH]/2)])
						rotate([0,90,0])
							rotate(45)
								LocatingPin();
								
		translate([0,0,-Protrusion])							// central screw hole
			rotate(180/4)
				PolyCyl(Screw[ID],difflen,4);
		
		if (false)
		for (i=[-1,1])											// vertical wire channels
			rotate(i*360/LEDStripCount + 180/LEDStripCount)
				translate([PillarCore/2 - 2.0,0,-Protrusion])
					PolyCyl(2.0,difflen,4);
					
		for (i=[-1,1])											// locating pins
			rotate(i*360/LEDStripCount - 180/LEDStripCount)
				translate([PillarCore/2 - 2.0,0,0])
					LocatingPin();
	}
}

//-- Spacers to separate platters

module Spacers() {

	difference() {
		linear_extrude(height=Spacer[LENGTH],convexity=4)
			difference() {
				rotate(180/PillarSides)
					circle(d=Spacer[OD],$fn=PillarSides);
				
				for (i=[0:LEDStripCount-1]) 					// clearance for LED beamwidth, may not actually cut surface
					rotate(i*360/LEDStripCount)
						translate([PillarCore/2,0,0])
							polygon(points=BeamShape);
							
				for (i=[0:LEDStripCount-1])						// LED front clearance
					rotate(i*360/LEDStripCount)
						translate([(PillarCore/2 + Pixel[2]),(Pixel[0] - 2*PixelMargin[0])/2])
							rotate(-90)
								square([Pixel[0] - 2*PixelMargin[0],Platter[ID]]);

							
				rotate(180/PillarSides)
					circle(d=Spacer[ID],$fn=PillarSides);		// central pillar fits in the hole
			}
			
		for (i=[0:LEDStripCount-1])
			rotate(i*360/LEDStripCount + 180/LEDStripCount)
				translate([Platter[ID]/2,0,(Pixel[1] - Platter[LENGTH])/2])
					rotate([0,90,0])
						rotate(45)
							LocatingPin();

	}
}

//-- Cap over top of pillar

module TopCap() {
	
	difference() {
		cylinder(d1=(Cap[OD] + Cap[ID])/2,d2=Cap[OD],h=Cap[LENGTH],$fn=CapSides);		// outer lid
		
		translate([0,0,-Protrusion])
			PolyCyl(Screw[ID],Cap[LENGTH] + WireSpace + Protrusion,4);					// screw hole
		
		translate([0,0,Cap[LENGTH] - 2*WireSpace])
			difference() {
				cylinder(d=Cap[ID],h=2*Cap[LENGTH],$fn=CapSides);						// cutout
				cylinder(d=2*Screw[OD],h=Cap[LENGTH],$fn=CapSides);						// boss
			}
		
		translate([0,0,Cap[LENGTH] - ThreadThick])
			cylinder(d=Cap[ID]/2,h=ThreadThick + Protrusion,$fn=CapSides);				// recess boss
	}
}

//-- Base below pillar

module Base() {
	
	SideWidth = 0.5*Base[OD]*sin(180/BaseSides);						// close enough
	
	difference() {
		union() {
			difference() {
				cylinder(d=Base[OD],h=Base[LENGTH],$fn=BaseSides);			// outer base

				translate([0,0,6*ThreadThick])								// main cutout
					cylinder(d=Base[ID],h=Base[LENGTH],$fn=BaseSides);
					
				translate([-SideWidth/2,0,6*ThreadThick]) 					// cable port
					cube([SideWidth,Base[OD],Base[LENGTH]]);
			}
			
			translate([0,0,Base[LENGTH]/2])									// pillar support is recessed below rim
				cube([PillarCore,PillarCore,Base[LENGTH] - ThreadThick],center=true);
		}

		for (i=[0:LEDStripCount-1])											// wiring recesses
			rotate(i*90)
				translate([PillarCore/2 - (WireSpace - Protrusion)/2,0,Base[LENGTH] - WireSpace/2])
					cube([WireSpace + Protrusion,PillarCore - 4*WireSpace,WireSpace],center=true);
		
		translate([0,0,-Protrusion])
			PolyCyl(Screw[ID],2*Base[LENGTH],4);						// screw hole
			
		translate([0,0,-Protrusion])									// screw head recess
			PolyCyl(8.5,5.0 + Protrusion,$fn=6);
			
		for (i=[-1,1])													// locating pins
			rotate(i*360/LEDStripCount - 180/LEDStripCount)
				translate([PillarCore/2 - 2.0,0,Base[LENGTH] - ThreadThick])
					LocatingPin();

	}
		
}

//----------------------
// Build it

if (Layout == "Pixel")
	OnePixel();
	
if (Layout == "LEDString")
	LEDString(LEDStringCount);
	
if (Layout == "Platters")
	Platters(LEDStringCount + 1);
	
if (Layout == "Pillar")
	Pillar(LEDStringCount);
	
if (Layout == "TopCap")
	TopCap();
		
if (Layout == "Base")
	Base();

if (Layout == "Spacers")
	Spacers();
	
if (Layout == "Show") {
	Pillar();

	for (i=[0:LEDStripCount-1])											// LED strips
		rotate(i*360/LEDStripCount)
			translate([PillarCore/2,0,Platter[LENGTH]/2])
				rotate([90,0,90])
					color("lightblue") LEDString();
	if (true)	
	for (j=[0:max(1,ShowDisks - 2)])									// spacers
		translate([0,0,(j*Pixel[1] + Platter[LENGTH])])
			color("cyan") Spacers();
							
	for (j=[0:max(2,ShowDisks - 2)])										// spacer alignment pins
		for (i=[0:LEDStripCount-1])
			rotate(i*360/LEDStripCount + 180/LEDStripCount)
				translate([(Platter[ID] - 1*ThreadWidth)/2,0,(j*Pixel[1] + Pixel[1]/2 + Platter[LENGTH]/2)])
					rotate([0,90,0])
						rotate(45)
							 color("Yellow",0.25) LocatingPin(Len=4);
	translate([0,0,PillarLength + 3*Cap[LENGTH]])
		rotate([180,0,0])
			TopCap();
	
	translate([0,0,-3*Base[LENGTH]])
		Base();
		
	if (ShowDisks > 0)	
		Platters(ShowDisks);
	
}

// Ad-hoc build layout

if (Layout == "Build") {
	Pillar();
	
	translate([0,Cap[OD],0])
		TopCap();
	
	translate([0,-Base[OD],Base[LENGTH]])
		rotate([0,180,0])
			Base();
	
	Ybase = Spacer[OD] * (LEDStringCount%2 ? (LEDStringCount - 1) : (LEDStringCount - 2)) / 4;
	for (i=[0:LEDStringCount])										// build one extra set of spacers!
		translate([(i%2 ? 1 : -1)*(Spacer[OD] + Base[OD])/2,		// alternate X sides to shrink Y space
				   (i%2 ? i-1 : i)*Spacer[OD]/2 - Ybase,			// same Y for even-odd pairs in X
				   0])
			Spacers();
}

The original doodles showing this might work, along with some ideas that wouldn’t:

Hard Drive Mood Light - Doodles 1

Hard Drive Mood Light – Doodles 1

Hard Drive Mood Light - Doodles 2

Hard Drive Mood Light – Doodles 2

Hard Drive Mood Light - Doodles 3

Hard Drive Mood Light – Doodles 3

,

5 Comments

Neopixel Current

Adafruit’s Neopixels are RGB LEDs with a built-in current-limiting 400 Hz PWM controller and a serial data link. Successive Neopixels aren’t synchronized, so their PWM cycles can produce serious current spikes.

Lighting up just the red LED in two Neopixels at PWM 16/255 produces this current waveform (at 10 mA/div):

Neopixel current 10 mA - 16-0-0 0-1

Neopixel current 10 mA – 16-0-0 0-1

Each red LED draws about 20 mA, so when the two Neopixel PWM cycles coincide, you get a nasty 40 mA spike. When they don’t coincide, you get a pair of 20 mA pulses. Those pulses walk with respect to each other at a pretty good clip; the oscillators aren’t trimmed to precision.

Lighting up three Neopixels with PWM 16/255 on the red does exactly what you’d expect. The horizontal scale  is now 100 µs/div, making the PWM pulses five times wider:

Neopixel current 10 mA - 16-0-0 0-1-2

Neopixel current 10 mA – 16-0-0 0-1-2

The narrow spike comes from the brief shining instant when all three Neopixels were on at the same time. Now you have three PWM pulses, each with slightly different periods.

Remember that these are PWM 16/255 pulses. When they’re at full brightness, PWM 255/255, there’s only a brief downtime between pulses that last nearly 2.5 ms and they’ll overlap like crazy.

Obviously, the more Neopixels and the lower the average PWM setting, the more the average current will tend toward the, uh, average. However, it will have brutal spikes, so the correct way to size the power supply is to multiply the number of Neopixels in the string by the maximum possible 60 mA/Neopixel… which gets really big, really fast.

A 1 meter strip of 144 knockoff Neopixels from the usual eBay supplier will draw 144 x 60 mA = 8.6 A when all the pulses coincide. Worse, the supply must be able to cope with full-scale transients and all the fractions in between. A husky filter cap would be your friend, but you need one with a low ESR and very high capacity to support the transients.

No wonder people have trouble with their Neopixel strings; you really shouldn’t (try to) run more than one or two directly from an Arduino’s on-board regulator…

5 Comments

Ed’s Fireball Hot Cocoa Recipe

The hot chocolate recipe on the back of the cocoa container tastes like bland liquid candy.

This tastes the way hot cocoa should:

Ingredients

  • 1 generous cup milk (full-fat is where it’s at)
  • 1 tbsp white sugar (just do it)
  • 3 tbsp cocoa powder (not chocolate drink mix)
  • 1/4 tsp Vietnamese cinnamon
  • 1 tbsp milk for mixing
  • few drops peppermint extract
  • 1/4 tsp vanilla extract
  • 20 oz Starbucks City Mug (got ’em cheap at a tag sale)

Preparation

  • Microwave the generous cup o’ milk for 1 minute
  • Mix dry ingredients in the giant mug
  • Stir in just enough cold milk to make a thick mud (*)
  • Add peppermint drops using 1/4 tsp measure
  • Rinse 1/4 tsp measure with vanilla
  • Blend the extracts into the mud (*)
  • Stir in warm milk, scraping mud off the mug
  • Microwave for another 45 s or so
  • Stir to blend

What’s going on:

  • More cocoa = more flavor, pure & simple
  • Less sugar = more cocoa bite
  • Vietnamese cinnamon adds the aroma & zip of those old Atomic Fireballs
  • Vanilla smooths the taste
  • Peppermint reminds you it’s winter

Sipping a cup in the afternoon banishes the urge to power-nosh anything else until suppertime…

* Update: non-alkalized / non-Dutch-process cocoa doesn’t blend well. Mix up the mud, let it set for 15 minutes, blend again, pause for 5 minutes, then proceed. Wonderfully smooth with no powder bombs.

13 Comments