Advertisements

Archive for August 21st, 2012

Longboard Ground Effect Lighting Case

Our Larval Engineer has been diligently procrastinating on her summer project to add ground effect lighting to her longboard. I’m hereby depriving her of the opportunity to learn enough OpenSCAD to build a case from scratch:

Longboard Ground Effect Lighting Case - exploded view

Longboard Ground Effect Lighting Case – exploded view

This is upside-down from its in-use position, but she’ll have it in this orientation on the bench. Four 10-32 screws clamp the whole affair together and hold it to a bottom aluminum plate with threads to suit; that plate also gets bolted between the longboard and the rear truck.

The general idea is that four 2 A·h lithium prismatic cells live in the bottom slice with their protection circuit, sandwiched between two aluminum plates that should protect them from all but catastrophic impact. The circuit board (which ought to be a PCB, but we’ll go with hand wiring for the first iteration) gets clamped in the recess between the two upper slices, above the upper aluminum plate. A polycarbonate sheet on top provides visibility for the Arduino blinky LED inside and shows off the circuitry to one and all.

I think a ridge on each long wall should suffice to hold the cells against the end wall; we don’t have the cells in hand to figure that out yet. She gets to add internal partitions, cable cutouts, and suchlike.

Oh. “Ground effect lighting” means ten RGB LED strips glued under the longboard deck. Her innovation is to make the LED color depend on the speed, which can range upward to scary-fast. It’s a simple matter of software, using a Hall effect sensor for input. This will look much better after dark, but she’s pretty much nocturnal anyway.

The OpenSCAD source code:

// Longboard Ground Effect Lighting Controller Case
// Ed Nisley KE4ZNU
// Karen Nisley KC2SYU
// July 2012

// Layout options

Layout = "Show";
 // Overall layout: Fit Show
 // Printing plates: Build1 .. Buildn (see bottom!)
 // Parts: BatteryLayer PCBLayer1 PCBLayer2
 // Shapes: CaseShell PCBEnvelope

ShowGap = 5; // spacing between parts in Show layout

//-----
// Extrusion parameters must match reality!

ThreadThick = 0.25;
ThreadWidth = 2.0 * ThreadThick;

HoleWindage = 0.2;

//-- Handy stuff

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

Protrusion = 0.1; // make holes end cleanly

inch = 25.4;

Tap10_32 = 0.159 * inch;
Clear10_32 = 0.190 * inch;
Head10_32 = 0.373 * inch;
Head10_32Thick = 0.110 * inch;
Nut10_32Dia = 0.433 * inch;
Nut10_32Thick = 0.130 * inch;
Washer10_32OD = 0.381 * inch;
Washer10_32ID = 0.204 * inch;

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

CellWidth = 50.0; // Lithium-ion cell dimensions
CellLength = 60.0;
CellThick = 6.0;
CellClearance = 1.5; // on all sides
CellTabClearance = 10.0; // for connections

BatteryCount = 4; // cells in the battery
BatteryHeight = BatteryCount*CellThick + 2*CellClearance;

PCMWidth = 16.0; // Battery protection board
PCMLength = 50.0;
PCMThick = 4.0; // at terminal end of cells

PillarOD = Washer10_32OD + 2*0.5; // screw pillar diameter
PillarOffset = (PillarOD/2) / sqrt(2.0); // distance to case inside corner

WallThick = 6.0; // case wall thickness

PinOD = 1.4; // alignment pin size

CaseOALength = CellLength + CellClearance + CellTabClearance + PCMThick + 2*WallThick;
CaseInsideLength = CaseOALength - 2*WallThick;
echo("Box Length outside: ",CaseOALength);
echo(" inside: ",CaseInsideLength);

CaseOAWidth = CellWidth + 2*CellClearance + 2*WallThick;
CaseInsideWidth = CaseOAWidth - 2*WallThick;
echo("Box Width outside: ",CaseOAWidth);
echo(" inside: ",CaseInsideWidth);

CaseOAHeight = BatteryCount * CellThick + CellClearance;

PCBThick = 1.0; // PCB thickness
PCBMargin = 3.0; // clamping margin around PCB edge
PartHeight = 10.0; // height of components above PCB
WiringThick = 4.0; // wiring below PCB

echo("PCB thickness:",PCBThick);
echo(" clamp margin: ",PCBMargin);
echo(" wiring: ",WiringThick);
echo(" components: ",PartHeight);

PCBLayer1Thick = IntegerMultiple(WiringThick + PCBThick/2,ThreadThick);
PCBLayer2Thick = IntegerMultiple(PartHeight + PCBThick/2,ThreadThick);

echo("Battery compartment height: ",BatteryHeight);
echo("PCB Layer 1 height: ",PCBLayer1Thick);
echo("PCB Layer 2 height: ",PCBLayer2Thick);

PlateThick = 1/16 * inch; // aluminum mount / armor plates

echo("Total height: ",2*PlateThick + BatteryHeight + PCBLayer1Thick + PCBLayer2Thick);

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

module ShowPegGrid(Space = 10.0,Size = 1.0) {

Range = floor(50 / Space);

for (x=[-Range:Range])
 for (y=[-Range:Range])
 translate([x*Space,y*Space,Size/2])
 %cube(Size,center=true);

}
//-------------------
// Shapes

module CaseShell(h=1.0) {

difference() {
 union() {
 translate([0,0,h/2])
 cube([CaseOALength,CaseOAWidth,h],center=true);

for (x=[-1,1])
 for (y=[-1,1])
 translate([x*(PillarOffset + CaseInsideLength/2),
 y*(PillarOffset + CaseInsideWidth/2),
 h/2])
 cylinder(r=PillarOD/2,h,center=true,$fn=4*6);
 }

for (x=[-1,1]) // screw holes on corners
 for (y=[-1,1])
 translate([x*(PillarOffset + CaseInsideLength/2),
 y*(PillarOffset + CaseInsideWidth/2),
 -Protrusion])
 PolyCyl(Clear10_32,(h + 2*Protrusion),8);

for (x=[-1,1]) // alignment pins in width walls
 translate([x*(CaseOALength - WallThick)/2,0,-Protrusion])
 rotate(45)
 PolyCyl(PinOD,(h + 2*Protrusion));
 for (y=[-1,1]) // alignment pins in length walls
 translate([0,y*(CaseOAWidth - WallThick)/2,-Protrusion])
 rotate(45)
 PolyCyl(PinOD,(h + 2*Protrusion));

}
}

module BatteryLayer() {

difference() {
 CaseShell(BatteryHeight);

translate([0,0,BatteryHeight/2])
 cube([CaseOALength - 2*WallThick,
 CaseOAWidth - 2*WallThick,
 BatteryHeight + 2*Protrusion],
 center=true);
 }
}

module PCBEnvelope() {

union() {
 translate([0,0,WiringThick + PCBThick + PartHeight/2])
 cube([CaseInsideLength - 2*PCBMargin,
 CaseInsideWidth - 2*PCBMargin,
 PartHeight + 2*Protrusion],
 center=true);

translate([0,0,WiringThick + PCBThick/2])
 cube([CaseInsideLength,CaseInsideWidth,PCBThick],center=true);

translate([0,0,WiringThick/2])
 cube([CaseInsideLength - 2*PCBMargin,
 CaseInsideWidth - 2*PCBMargin,
 WiringThick + 2*Protrusion],
 center=true);
 }
}

module PCBLayer1() {

difference() {
 CaseShell(PCBLayer1Thick);
 PCBEnvelope();
 }

}

module PCBLayer2() {

difference() {
 CaseShell(PCBLayer2Thick);
 translate([0,0,-(WiringThick + PCBThick/2)])
 PCBEnvelope();
 }

}

module Aluminum() {
 translate([0,0,PlateThick/2])
 cube([1.1*CaseOALength,1.1*CaseOAWidth,PlateThick - Protrusion],center=true);
}

//-------------------
// Build things...

ShowPegGrid();

if ("Battery" == Layout)
 Battery();

if ("CaseShell" == Layout)
 CaseShell(CaseOAHeight);

if ("BatteryLayer" == Layout)
 BatteryLayer();

if ("PCBEnvelope" == Layout)
 PCBEnvelope();

if ("PCBLayer1" == Layout)
 PCBLayer1();

if ("PCBLayer2" == Layout)
 PCBLayer2();

if ("Fit" == Layout) {
 BatteryLayer();
 translate([0,0,BatteryHeight + PlateThick])
 color("Green") PCBLayer1();
 translate([0,0,BatteryHeight + PlateThick + PCBLayer1Thick])
 color("Cyan") PCBLayer2();
}

if ("Show" == Layout) {
 BatteryLayer();
 translate([0,0,BatteryHeight + PlateThick + ShowGap])
 color("Green") PCBLayer1();
 translate([0,0,BatteryHeight + PlateThick + PCBLayer1Thick + 2*ShowGap])
 color("Cyan") PCBLayer2();
}

if ("Build1" == Layout)
 rotate(90) BatteryLayer();

if ("Build2" == Layout)
 rotate(90) PCBLayer1();

if ("Build3" == Layout)
 translate([0,0,PCBLayer2Thick])
 rotate([0,180,90])
 PCBLayer2();
Advertisements

5 Comments