Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.
Squirting it with circuit cooler brought it back to life, albeit briefly, so it’s a real thermal failure. OK, after I get smacked upside the head twice, I can recognize a problem when I see it.
I removed the top cover and jammed a themocouple into the screw hole in the middle of the pillar:
Mood Light – thermocouple location
A folded tissue weighted down with random desktop junk kept the breeze out of the interior:
Mood Light – PWM 128 temperature measurement
If the middle of the column hits 50 °C, what’s it like inside the 5050 packages with all those LEDs blazing away? Looks like I’ve been cooking those poor knockoff Neopixels to death.
The temperature is 50 °C with the LEDs running at maximum PWM = 128. Reducing the maximum PWM to 64 reduces the core to 30 °C and that dead blue LED springs back to life.
Figuring each LED package dissipate 250-ish mW at full throttle, that’s 120 mW at PWM 128 / 60 mW at PWM 64. The set of 12 packages dissipates 1.4 W / 750 mW, so, in a 22 °C room, the thermal coefficient is up around 10 to 20 °C/W, which is somewhere between bad and awful. Running the LEDs at full throttle obviously isn’t an option and even half-throttle really doesn’t work.
So, OK, mounting LED strips on a clever 3D printed plastic column with zero air circulation isn’t nearly as smart an idea as I thought: barely more than a watt burns right through the redline.
The Neopixel specs have nothing to say about the thermal coefficient from the LED junctions to the package leads, but cooling the copper conductors in the flex PCB can’t possibly hurt.
No, I do not want to CNC machine an aluminum pillar with little tabs on the platter for better heatsinking. It would be an interesting design project, though.
The Power Wheels Racer rules limit the motor to 1440 W, a tidy 60 A at 24 V. Let’s call it 70 A, which lines up neatly with the second major division up from the bottom: the orange current line hits 70 A with torque = 2.6 N·m.
Draw a vertical line at that point and read off all the other parameters from the scales on the left.
The motor will produce 2.6 N·m at just shy of 4500 RPM; call it 4400 RPM.
The SqWr Racer has 9:40 chain-drive gearing, so the rear wheels turn at:
990 RPM = 4400 RPM x (9/40)
With 13 inch diameter wheels, the racer moves at:
38 mph = 990 RPM x (π x 13 inch) x (60 min/hr) x (1 mile / 63.36x103 inch)
Which is scary fast if you ask me. A higher ratio may be in order.
At that speed the motor delivers: 1.6 HP = 1180 W = 2.6 N·m x 4400 RPM x 2π rad/rev / (60 s/min)
… to the shaft and, minus mechanical losses, to the tires.
If the racer doesn’t require that much power to roll at breakneck speed, it’ll go even faster, until the motor’s (falling) power output matches the (rising) mechanical load at some higher speed with correspondingly lower current.
With a current of 70 A and a winding resistance of 0.089 Ω (let’s say 0.10 Ω), the motor dissipates 490 W. That’s probably too much for long-term running, even with a 70% (= 1150 / (1150 + 490)) efficiency.
The mandated Littelfuse 60 A fuse has a bit under 1 mΩ of cold resistance and will dissipate 3.6 W at 60 A. The specs say it will blow within 6 minutes at rated current.
The resistance of the wiring / connectors / switches / whatever should be on that same order. Figuring the racer needs 2 m of stranded copper wire, that calls for 2 AWG or larger (0.5 mΩ/m). Right now, the racer uses 8 AWG (2 mΩ/m) and might have 4 mΩ total resistance, although I think it has less than 2 m of wire. Empirically, the motor conductors get really hot at 40 A for about ten seconds, but that’s with a severely defunct motor.
If the conductors + connectors between the battery and the motor introduce, say, 10 mΩ of resistance, they’ll dissipate 36 W at 60 A. That scales linearly with resistance, so a high-resistance connection will incinerate itself.
Using a PWM controller to reduce the speed will reduce the available horsepower, so the racer will accelerate slowly. With the torque limited to 2.6 N·m, the horsepower will vary linearly with the PWM duty cycle: nearly zero for small PWM, up to 1.5 HP for large PWM at 60 A, then upward as the RPM increases with decreasing load. Yeah, you get more torque when you need it least.
I could make a case for a three-speed transmission in addition to higher gear ratio, although that seems overly complex.
A less beefy motor will be in order and The Mighty Thor suggests a torque converter as a low-budget transmission. Sounds good to me; I should learn more about electric traction motors…
The Power Wheels Racer taking shape at SquidWrench let out The Big Stink at the Mini Maker Faire a few weeks ago, so I brought some test equipment to the regular Weekly Doing and helped with the autopsy.
The PWM motor controller purports to do 60 A at up to 50 V, but removing the cover showed it wasn’t going to do any more controlling:
Motor Controller – smoked housing
That smudge came from a rank of detonated MOSFETs:
Motor Controller – exploded MOSFET
Other MOSFETs had unsoldered themselves:
Motor Controller – unsoldered MOSFETs
Explosively:
Motor Controller – solder ejecta
I brought along an ancient Sears starter-motor ammeter to measure the motor current:
Sears 244-2145 Starter Ammeter – front
The magnetic field around the wire directly drives the meter movement, with two guides for the 75 A and 400 A ranges, and none of that newfangled Hall effect nonsense to contend with:
Sears 244-2145 Starter Ammeter – wire guides
Yeah, that says FEB 79; I’ve been collecting tools for quite a while…
I slapped the motor connectors directly on the battery terminals, holding them with small locking pliers after discovering that the wires got way too hot, way too fast. A snippet of retroreflective tape on the motor sprocket and a laser tach gave us the speed:
12 V: 1600 RPM @ 40 A
24 V: 2400 RPM @ > 100 A
The AmpFlow E30-400 motor data sheet confirmed that those numbers were grossly wrong. Unloaded, it should spin at 5700 RPM at 24 V while drawing 3.2 A (thus, 2800 RPM at 12 V & 1.6 A).
Diassembling the motor showed it hadn’t escaped the carnage:
Motor – charred windings
Those windings should be the usual amber enamel-over-copper, not charred black. The excessive current and reduced speed suggests many shorted turns inside the rotor.
Protip: never disassemble a working DC motor, because you’ll demagnetize the stator. The motor should still run when you put it back together, but the reduced magnetic field will wreck the performance.
As nearly as we could tell, one of the motor wires shorted to the frame when it got pinched under the seat; that’s an easy mistake to make and shows why compulsive wire neatness pays off big time. Shorting the controller output blew the transistors and, after raising the seat to look underneath, the motor would cook itself without generating much torque while you figure out what happened.
As far as I’m concerned, if you’ve never blown up anything that severely, you’re not building interesting stuff and definitely not trying hard enough.
The next iteration should work better!
Thanks to Dragorn of Kismet for stepping into the stench with phone camera in hand…
So this device showed up in an envelope with a letter telling us we’d won a contest if, of course, the number on the device matched the number in the letter:
CodeKase device
I wonder if anybody else had second thoughts about pulling what’s obviously an insulating sheet holding two contacts apart? In this day and age, getting the victim to blow his own fingers off probably counts as a win.
Maybe it comes from having read The White Plague at an impressionable age. Who could resist getting a Nice Thing in the mail?
The number matched, of course, but the letter’s finer print said the prize would be one of:
A new car of some sort
A flat screen TV
A cheap electronic trinket
A three-day / two night vacation
In order to claim your prize, you had to call an 800 number. The much finer print revealed the odds of winning the first three of those prizes was somewhere around 300,000:1. “Winning” the vacation was essentially a slam-dunk proposition, of course, and probably tells you everything you need to know about the course of the phone call.
It’s apparently economical to send out this much hardware to reel in new “customers”:
CodeKase device – parts
Using the metal disk from a membrane switch as a spring to push the coin cell against the contact wires is a nice touch. This is apparently the optimized version that uses a single lithium cell in place of two alkaline buttons; the cell “socket” on the other end consists of vestigial lumps.
I harvested the lithium cell and the blue LED, of course…
More about CodeKase, direct from the source. I like “Step Five: Recycle Your CodeKase”…
About a week after First Light, one of the knockoff Neopixels (not a Genuine Adafruit Product) suffered an intermittent failure: it worked fine after being off for an hour or two, but eventually stalled at a fixed color, with all downstream pixels equally dead. Of course, it was the middle package in the string of three, buried in the hub (this is before the failure):
Hard Drive Mood Light – low angle
Spraying circuit cooler on the package brought it back to life for a few minutes, confirming the diagnosis. Reducing the maximum intensity to PWM 32 reduced the average power dissipation enough to let it run for as long as I was willing to let it, although it might not survive a hot summer day.
Not having glued the spacers onto the hub simplified extracting the strip, although warranty repair is always a nuisance. I daubed red Sharpie on the failing LED to avoid losing track of it, then resoldered the LED and capacitor connections to no avail:
Knockoff Neopixel Failure – overview
There’s nothing obviously wrong inside:
Knockoff Neopixel Failure – detail
The fine details of the WS2812B controller produce a horrible Moiré blur with the camera’s low-res image, but you get the general idea.
Most likely, one of those flying wires isn’t quite bonded, but we’ll never know…
An improved version of the 3D printed plastic bits going into the Hard Drive Platter Mood Light:
Hard Drive Mood Light – improved – solid model – Show view
The central pillar now has cutouts behind the Neopixel strips so you (well, I) can solder directly to the larger half-pads on the back, plus a boss on the top for better wire management:
Hard Drive Mood Light – improved – Pillar – solid model
I’m not entirely satisfied with the little slots for the strip edges; the resolution limits of 3D printing call for larger openings, but there’s not much meat around those pins up the edge.
The base becomes much larger to hold the Arduino Pro Mini and gains an optional slot to let the programming cable reach the outside:
Hard Drive Mood Light – improved – Base – solid model
The cap has a boss matching the one atop the pillar:
Hard Drive Mood Light – improved – Cap – solid model
Both the cap & base have center features recessed by two thread thicknesses to let their rims apply a slight clamping force on the platters.
Our Larval Engineer says it really needs an internal battery with maybe four hours of runtime, a charging base station (ideally with inductive power transfer), buttons (or, better, a tilt switch / accelerometer) for mode selection, and perhaps a microphone to synchronize lighting effects with music. To my horror, her co-op job seems to have exposed her to Marketeers…
We do, however, agree that the Cap would look better in lathe-turned brass with a non-tarnish clearcoat.
The OpenSCAD source code:
// Hard Drive Platter Mood Light
// Ed Nisley KE4ZNU November 2015
Layout = "Spacers"; // Build Show Pixel LEDString Platters Pillar Spacers TopCap Base
CablePort = true;
ShowDisks = 2; // number of disks in Show layout
//- 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
ID = 0;
OD = 1;
LENGTH = 2;
Platter = [25.0,95.0,1.27]; // hard drive platters - must match actual thickness!
LEDStringCount = 3; // number of LEDs on each strip
LEDStripCount = 4; // number of strips (verify locating pin holes & suchlike)
WireSpace = 1.0; // allowance for wiring along strip ends
Pixel = [13.0, 1000 / 144, 0.6]; // 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);
PCB = [34.5,17.5,1.6]; // Arduino Pro Mini (or whatever) PCB size
PCBClearTop = 5.0;
PCBClearBot = 5.0;
PCBHeight = PCB[2] + PCBClearBot + PCBClearTop;
PCBRadius = sqrt(pow(Platter[ID]/2 + PCB[1],2) + pow(PCB[0]/2,2));
echo(str("PCB Corner radius: ",PCBRadius));
CoaxConn = [7.8,11.2,5.0]; // power connector
Cap = [Platter[ID] + 4.0,Platter[ID] + 4.0 + 10*2*ThreadWidth,2*WireSpace + 6*ThreadThick]; // cap over top of pillar
CapSides = 8*4;
BaseClearHeight = max(PCBHeight,CoaxConn[OD]);
Base = [2.0 + 2*PCBRadius,2.0 + 2*PCBRadius + CoaxConn[LENGTH],BaseClearHeight + 6*ThreadThick];
BaseSides = 8*4;
Screw = [1.5,2.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);
difference() { // wiring recess on top surface, minus boss
for (i=[0,90])
rotate(i)
translate([0,0,(PillarLength - (WireSpace/2 - Protrusion))])
cube([(PillarCore + 2*Protrusion),Pixel[0] - 2*PixelMargin[0],WireSpace],center=true);
cylinder(d=3*Screw[OD],h=PillarLength + Protrusion,$fn=CapSides);
}
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=3*Screw[OD],h=Cap[LENGTH],$fn=CapSides); // boss
}
translate([0,0,Cap[LENGTH] - 2*ThreadThick])
cylinder(d=Cap[ID]/2,h=2*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);
rotate(180/BaseSides)
translate([0,0,Base[LENGTH] - BaseClearHeight/2]) // power connector hole
rotate([90,0,0]) rotate(180/8)
PolyCyl(CoaxConn[ID],Base[OD],8);
}
translate([0,0,Base[LENGTH]/2]) // recess pillar support below rim
cube([PillarCore,PillarCore,Base[LENGTH] - 2*ThreadThick],center=true);
}
for (i=[0:LEDStripCount-1]) // wiring recesses
rotate(i*90)
translate([PillarCore/2 - (WireSpace - Protrusion)/2,0,Base[LENGTH] - 4*WireSpace/2])
cube([WireSpace + Protrusion,PillarCore - 4*WireSpace,4*WireSpace],center=true);
translate([0,0,-Protrusion])
PolyCyl(Screw[ID],2*Base[LENGTH],4); // screw hole
translate([0,0,-Protrusion]) // screw head recess
rotate(180/8)
PolyCyl(8.5,Base[LENGTH] - 3.0 + Protrusion,8);
for (i=[-1,1]) // locating pins
rotate(i*360/LEDStripCount - 180/LEDStripCount)
translate([PillarCore/2 - 2.0,0,Base[LENGTH] - ThreadThick])
LocatingPin();
if (CablePort)
translate([0,Platter[ID]/2 + PCB[1],Base[LENGTH] - 3.0 + Protrusion])
rotate(-90)
cube([PCB[1],Base[OD],3.0]);
}
}
//----------------------
// 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,-2*Base[LENGTH]])
Base();
if (ShowDisks > 0)
Platters(ShowDisks);
}
// Ad-hoc build layout
if (Layout == "Build") {
if (true)
Pillar();
if (true)
translate([0,(Platter[ID] + Cap[OD])/2,0])
TopCap();
if (true)
translate([0,-(Platter[ID] + Base[OD])/2,0])
Base();
Ybase = Spacer[OD] * (LEDStringCount%2 ? (LEDStringCount - 1) : (LEDStringCount - 2)) / 4;
if (true)
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();
}
Just to show why powering Neopixels directly from an Arduino is a Bad Idea, I wired up an Adafruit Jewel thusly (and, BTW, exactly like their lead illustration shows):
Makes your skin crawl just to look at it, right?
With all seven Neopixels set to a gray PWM (64,64,64), the average current should be around 90 mA: 21 * 18 mA * 64/255, with another 6% knocked off because the WS2812B controller imposes that much mandatory dark time at PWM 255.
Eyeballometrically, this looks pretty close at 100 mA/div:
Neopixel current 100 mA – 64-64-64 0-7 200 mA peak
But those seven asynchronous PWM oscillators guarantee this will happen every now & again:
Neopixel current 100 mA – 64-64-64 0-7 400 mA peak
The 400 mA peaks happen when all seven Neopixels turn on at once. The broad flat floor means they’re off most of the time and the power supply sees a hefty 400 Hz pulsating load.
The bottom trace shows the effect of those peaks in the top trace (at 200 mA/div) on the Arduino’s VCC pin:
Neopixel current 200 mA – 64-64-64 0-7 400 mA pk w VCC
That’s at 200 mV/div and AC coupled to remove the 5 VDC supply. Because the board runs from USB power, the on-board regulator doesn’t contribute to the problem, but there’s plenty of problem to go around.
Always use an external power supply and a 5 VDC regulator with Neopixels!