The Smell of Molten Projects in the Morning

Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.

Day: August 21, 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();