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.

Category: Electronics Workbench

Electrical & Electronic gadgets

  • KG-UV3D GPS+Voice Interface: APRS Bicycle Mobile

    Wouxun KG-UV3D with GPS-audio interface
    Wouxun KG-UV3D with GPS-audio interface

    Both of the GPS+voice interfaces for the Wouxun KG-UV3D radios have been working fine for a while, so I should show the whole installation in all its gory detail.

    If you haven’t been following the story, the Big Idea boils down to an amateur radio HT wearing a backpack that replaces its battery, combines the audio output of a Byonics TinyTrak3+ GPS encoder with our voice audio for transmission, and routes received audio to an earbud. Setting the radios to the APRS standard frequency (144.39 MHz) routes our GPS position points to the global packet database and, with 100 Hz tone squelch, we can use the radios as tactical intercoms without listening to all much of the data traffic.

    The local APRS network wizards approved our use of voice on the data channel, seeing as how we’re transmitting brief voice messages using low power through bad antennas from generally terrible locations. This wouldn’t work well in a dense urban environment with more APRS traffic; you’d need one of the newfangled radios that can switch frequencies for packet and voice transmissions.

    So, with that in mind, making it work required a lot of parts…

    Tour Easy - KG-UV3D GPS interface
    Tour Easy – KG-UV3D GPS interface

    A water bottle holder attaches to the seat base rail with a machined circumferential clamp. Inside the holder, a bike seat wedge pack contains the radio with its GPS+voice interface box and provides a bit of cushioning; a chunk of closed-cell foam on the bottom mostly makes me feel good.

    The flat 5 A·h Li-ion battery pack on the rack provides power for the radio; it’s intended for a DVD player and has a 9 V output that’s a trifle hot for the Wouxun radios. Some Genuine Velcro self-adhesive strips hold the packs to the racks and have survived surprisingly well.

    Just out of the picture to the left of the battery pack sits a Byonics GPS2 receiver puck atop a fender washer glued to the rack, with a black serial cable passing across the rack and down to the radio bag.

    A dual-band mobile antenna screws into the homebrew mount attached to the upper seat rail with another circumferential clamp. It’s on the left side of the rail, just barely out of the way of our helmets, and, yes, the radiating section of the antenna sits too close to our heads. The overly long coax cable has its excess coiled and strapped to the front of the rack; I pretend that’s an inductor to choke RF off the shield braid. The cable terminates in a PL-259 UHF plug, with an adapter to the radio’s reverse-polarity SMA socket.

    The push-to-talk button on the left handgrip isn’t quite visible in the picture. That cable runs down the handlebar, along the upper frame tube, under the seat, and emerges just in front of the radio bag, where it terminates in a 3.5 mm audio plug.

    The white USB cable from the helmet carries the boom mic and earbud audio over the top of the seat, knots around the top frame bar, and continues down to the radio. USB cables aren’t intended for this service and fail every few years, but they’re cheap and work well enough. The USB connector separates easily, which prevents us from being firmly secured to a dropped bike during a crash. I’d like much more supple cables, a trait that’s simply not in the USB cable repertoire. This is not a digital USB connection: I’m just using a cheap & readily available cable.

    All cables converge on the bag holding the radio:

    Tour Easy - KG-UV3D + GPS interface - detail
    Tour Easy – KG-UV3D + GPS interface – detail

    Now you can see why I put that dab of white on the top of the knob!

    The bag on my bike hasn’t accumulated quite so much crud, because it’s only a few months old, but it’s just as crowded:

    KG-UV3D + GPS interface on Tour Easy - top view
    KG-UV3D + GPS interface on Tour Easy – top view

    This whole “bicycle mobile APRS system”, to abuse a term, slowly grew from a voice-only interface for our ICOM IC-Z1A radios. Improving (and replacing!) one piece at a time occasionally produced horrible compatibility problems, while showing why commercial solutions justify owning metalworking tools, PCB design software, and a 3D printer.

    I long ago lost track of the number of Quality Shop Time hours devoted to all this, which may be the whole point…

    In other news, the 3D-printed fairing mountsblinky light mounts, and helmet mirror mounts continue to work fine; I’m absurdly proud of the mirrors. Mary likes her colorful homebrew seat cover that replaced a worn-out black OEM cover for a minute fraction of the price.

  • Vanagon iPod Interface: Minimal Edition

    My buddy Duggles, from far-off NH, restored his ’83 Vanagon to its original hippie-chick-magnet state. Late in the process, he realized that the once-fancy CD+radio widget in the dashboard lacked a line input for his iPod / iPad / iDingus. Knowing my foibles, he asked for advice.

    Fortunately, he’d already discovered the service manual, without which life is always much more difficult. Search for PIONEER DEH2850MP SERVICE MANUAL and pick the site you prefer.

    My first email went a little something like this, with a few updates:

    BEH2850MP Audio Mux
    BEH2850MP Audio Mux

    The trouble with jamming a new line input into the existing circuitry is that you must match the DC levels as well as the audio amplitude. The schematic on page 19 shows the selector IC has capacitor-coupled inputs and outputs to strip off the DC level.

    It would be very easy if the multiplexer (IC151, top of page 19, detail shown) had separate control inputs that we could override, but it uses a serial control stream from the CPU. No practical way to mess with that, alas.

    As nearly as I can tell, the best way to do this would be to hack a DPDT switch between the FM/AM tuner and the amp, upstream of the mux. You pick the Radio input, flip the DPDT switch, and the iDingus plays through the Radio inputs.

    However, an easier way is to simply inject the iDingus audio in parallel with the tuner audio, but set the tuner to an FM frequency without a radio station. The radio output should mute, leaving the field clear for the iDingus audio. This might not work, but it’ll be dead simple to try. If it’s acceptable, then you’re done.

    The obvious problem is that we don’t know if the iDingus line level matches the tuner’s line level. The mux is upstream of the volume settings, so there’s hope that this will all Just Work. If it’s way too loud, that’s fixable. If it’s too soft, that’s a problem.

    So, to begin…

    DEH2850MP PCB Radio Jumpers
    DEH2850MP PCB Radio Jumpers

    The diagram on page 36/37 shows the A side of the PCB, with all the connectors & suchlike. The FM/AM Tuner Unit is over along the right side, with the audio output on pins 23/24 near the bottom and ground on pin 22. The traces proceed upward along the edge of the PCB, cross the connector near its middle, the audio passes through caps C151/152 on the B side, go through two jumpers on the A side across a mess of traces, and then dive to the B side and wriggle into the IC151 mux.

    Quite conveniently, the ground trace follows along with them and is the lower of the three traces just to the right of the mux.

    If I interpret the part number for C151 correctly (page 45, top right):

    C 151 ... CKSRYB224K10

    it’s a 220 nF cap. Anything around that value should work. This one from Radio Shack is grossly overpriced; anything with the same or larger value is OK (voltage rating doesn’t matter): NTE MLR224K100 – 0.22MF 100V Mylar Capacitor

    Solder one lead of each cap to the top two jumpers, solder suitable wires to the other cap leads, solder the ground / shield wire to the bottom jumper, solder a suitable jack to the cable, plug iDingus into jack, fire that mother up, and see what happens.

    The right channel is on pin 24, which goes to the top jumper of the three. Don’t bother trying to figure out which pin of the iDingus corresponds to that channel; just solder the damn wires and fix it later if it’s wrong enough to be objectionable.

    I have no idea where or if you can drill hole(s) to snake the cable(s) through the housing. If the Vanagon doesn’t have a rear power amp, you could probably cut the traces under those RCA jacks (CN352, top right on page 37, above the FM/AM tuner) and repurpose them.

    Give it a go…

    We both attended Lehigh U, but Duggles realized early on that he lacked the personality flaws common to engineers and bailed out before damaging himself too badly. So his reply didn’t surprise me in the least…

    I read your instructions carefully, examined the kindly supplied circuit diagram, and pored over the circuit boards with a magnifier. Then I blew you off (!!), threw caution to the winds, hacked off an old headphone cord, snaked the wires in, and soldered right to the very convenient L/R outputs on the RF board. Fired it up ,,, shitz, tons of background hiss, no quieting on the FM signal! A skein of obscenities was loosed in the mountain air until I thought to turn the iThang on … boom, full quieting, no hiss, and a quite substantial sound. No level issues at all, quite clean and detailed, and I didn’t even use the capacitors! (What was their purpose btw?)

    After observing that a prophet is not without honor, save in his own land, I couldn’t resist going full-frontal didactic again:

    The mux has a DC bias on its signal lines, with caps on both the input and output to isolate it from the surrounding circuitry. Back in the day, analog switches were fussy about their DC bias, so you had to go overboard to make them work at all.

    I don’t know if the iDingus also has DC blocking caps on its output and figured that injecting raw DC from the mux into its guts could be a Bad Thing. But, eh, those engineers at Apple (‘s contractor) are smart folks and (probably) anticipated this sort of (mis)behavior.

    The hiss you get with the iDingus turned off probably comes from dragging the mux bias to ground. I don’t know that’s a Truly Bad Thing, but adding those caps should eliminate any future problems.

    You could even play DJ by combining radio & iDingus audio!

    Rock on…

    Seeing as how Duggles actually was a DJ for quite some years, I wouldn’t be surprised in the least to hear he does exactly that. We’ll be visiting him later this Autumn and I’ll inspect his work.

    I love it when a plan comes together…

  • Compact Fluorescent Bulb Lifetime: Another Data Point

    Each of the three chandeliers in the Poughkeepsie Train Station sports 36 bulbs in two rings. When the station opened in 1918 they installed those newfangled incandescent bulbs that were all the rage at the time. The color of the bulbs in this Wikipedia picture, dated October 2007, suggests that tungsten ruled for at least nine decades:

    Poughkeepsie Train Station Interior
    Poughkeepsie Train Station Interior

    Since then, they installed chunky compact fluorescent bulbs that probably provide the same amount of light, minus the pinpoint highlights from tungsten filaments in clear bulbs. This view from below the central chandelier shows the layout and some detail of the carving & decorative sockets:

    Pok RR Station Middle Chandelier - detail
    Pok RR Station Middle Chandelier – detail

    In addition to being decorative, those chandeliers also give useful data on the reliability of compact fluorescent bulbs. With the contrast stretched the other way to make the bulbs easier on the eye, count the number of deaders in …

    Chandelier 1:

    Pok RR Station Chandelier 1
    Pok RR Station Chandelier 1

    Chandelier 2:

    Pok RR Station Chandelier 2
    Pok RR Station Chandelier 2

    Chandelier 3:

    Pok RR Station Chandelier 3
    Pok RR Station Chandelier 3

    I took each picture from a vantage point showing all the deaders; the bulbs hidden behind the central dingus work.

    Let us assume all 108 bulbs were installed at the same time and, given the number of deaders, haven’t been touched since then (although they’re not covered in fuzz, which suggests that they’ve been dusted within living memory). I was there in mid-afternoon, so the bulbs probably burn 24 hours/day and aren’t subject to early failure from frequent starts.

    So, in no more than five years, 108 CFL bulbs have a 4.6% failure rate, which works out to 0.9%/year, more or less, ignoring any infant mortality. If they’ve been up there for the last 2.5 years, then it’s 1.8%/year.  Replacing deaders since installation, of course, makes it worse than that.

    Over the course of a decade, a compounded 0.9% failure rate will kill 9.4% of the bulbs. After 20 years, 20% will be dead. A 1.8% annual failure rate kills 20% and 43%, respectively.

    Now, I’ll grant you that tungsten bulbs burn far more energy over that time, but replacing a percent or two of those complex and somewhat eco-hostile CFL bulbs every year cuts away a big chunk of the rainbows-and-pink-unicorns delight involved in Saving The Planet.

  • Longboard Speed-Sensing Ground Effect Lighting

    After our Larval Engineer tweaked the code to track the maximum speed for the current run, so that the color always hits pure blue at top speed and red near standstill, we can prove it happened: we have a video! It’s much less awful than the First Light video, but with plenty of cinéma-vérité camera shake, lousy focus, and bloopers:

    Longboard In Action
    Longboard In Action

    That’s a frame extracted from one of the raw videos files using ffmpegthumbnailer:

    for t in `seq 0 10 100` ; do ffmpegthumbnailer -i mov07117.mpg -o Longboard-$t.jpg -t $t% -q 10 -s 640 ; done
    

    This view of the 3D printed case shows the power switch and the Hall effect sensor cable snaking out of the truck just below the near axle:

    Longboard RGB LED Electronics - right front view
    Longboard RGB LED Electronics – right front view

    She filled the case corners that pulled up from the build platform with a thin layer of epoxy, getting a plane surface by curing it atop waxed paper on the shop’s surface plate, to keep the polycarbonate sheet flat. I didn’t have any acorn nuts to top those nylon lock nuts, alas.

    The 4-cell Li-ion battery lives in the slice between the white aluminum plates, where it takes about four hours to charge from 3.0 V/cell. The Arduino Pro Mini lives behind the smoked polycarb sheet, where its red LED adds a mysterious touch. Maybe, some day, she’ll show the 1/rev pulse on the standard Arduino LED for debugging.

    A view from the other side shows the hole for the charger above the circuit board, with the Hall sensor out of sight below the far axle:

    Longboard RGB LED Electronics - left front view
    Longboard RGB LED Electronics – left front view

    Yes, the cable to the LEDs deserves better care. She learned that you must provide strain relief at cable-to-component junctions, which we achieved by pasting the wires to the board beside the LED strip with double-stick tape. The rest of the LED strip interconnections live atop similar tape strips. There’s nothing much protecting the LEDs or their delicate SMD resistors, but it works!

    Actually, one red LED in an RGB package went toes-up and wasn’t revived by resoldering its leads. So we jumpered around the package, subjecting the remaining two red LEDs in that string to a bit more current than they’d prefer, and that’s that.

    There’s a whole bunch not to like one could improve in both the mechanics and electronics, but it works! If you’ll grant it alpha prototype status, then I’d say it’s Good Enough; this is her project and she’ll learn a lot from how it works and how it fails, just like we all do.

    Not shown: crazy-proud father…

  • Wouxun KG-UV3D Battery Contact Locations

    Having gone to great pains to put the center of the contact studs on the GPS+voice case exactly at the center of the screws on the back of the radio:

    HT-GPS Case - Wouxun KG-UV3D rear view
    HT-GPS Case – Wouxun KG-UV3D rear view

    I now discover why Wouxun used 7 mm square pads on the batteries: the springy contacts hit the pack so far off-center from the studs that they very nearly miss the heads on the 4-40 brass screws I’m using as contacts. This family portrait shows the radio, the battery pack, and the GPS+voice case:

    Wouxun KG-UV3D - battery contact locations - GPS case
    Wouxun KG-UV3D – battery contact locations – GPS case

    The lines on the masking tape highlight where the spring contacts touch the case and barely kiss the screw heads:

    KG-UV3D contact marks on GPS case
    KG-UV3D contact marks on GPS case

    Squinting at the marks on the battery case contacts (you can’t see it in the pictures), the contact line is maybe 2.5 mm beyond the centerline of the square pads. How this worked on the first case I built, I have no clue. For this version, I deliberately filed the heads a bit less and recessed them into the case a bit more; obviously, that was the wrong thing to do, as the connection was intermittent at best.

    For the purposes of getting things working, I wrapped snippets of copper mesh tape (from NASA, according to the surplus blurb, with conductive adhesive) around thin chunks of conductive foam, then put them over the studs. The scars in the plastic came from an abortive attempt to get the springs far enough into the case surface to kiss the very edge of the studs:

    Copper mesh on GPS case contacts
    Copper mesh on GPS case contacts

    There’s no point in having a contact patch on the near side of the radio springs, because nothing ever touches there. So the right thing to do is simply move the contact studs to the far side by 3 mm, centering them around the actual contact point. That means changing the PCB layout by the same amount. That’s easy enough to do, but … drat!

    When I took the case apart to boost the mic gain, I replaced those neatly filed studs with unfiled pan head 4-40 brass screws from the same parts stock. The heads were tall enough to touch the radio spring contacts closer to their centers and make perfect contact. Not elegant, but better than that copper braid tape.

    The one thing I do not like about the Wouxun battery packs: the radio contact pads are flush with the pack surface, so there’s absolutely no protection against casual shorts when the pack isn’t on the radio. The packs also sport four bare round contacts on their outer surface that mate with the charger, two of which make direct contact with the battery; those sit inside a shallow molded recess that helps prevent inadvertent shorts.

    assume there’s a protective circuit inside the pack that turns off the current on a dead short, but I am most assuredly not going to test that assumption. When the packs aren’t on the radio (which they never will be, effective immediately), they sport a strip of tape across those radio contact pads.

  • PL605060 Cells: First Charge

    Five PL605060 lithium cells (prismatic, 3.7 V, 2 A·h) for the longboard project arrived and underwent incoming inspection. The lower curves show the as-received charge state (about halfway) and the upper curves show the first charge:

    PL605060 Li-Ion Cell
    PL605060 Li-Ion Cell

    The crinkly sections come from me nudging the wires (held on by the most tenuous of alligator clip connections) and don’t really affect the results. The five cells charged to within a few percent of each other, which is Good Enough.

    I hotwired each cell to the hacked battery case for the SX230HS camera and used the Canon charger:

    PL605060 cell - charging adapter
    PL605060 cell – charging adapter

    As is turns out, that charger is rated for 700 mA current, exactly the same as the 4-cell charger we’ll use for the longboard battery, so the overall charge rate should be about the same. The Canon cells run around 1.1 A·h and the PL605060 is 2.0 A·h; I figured 2.0 was approximately 1.1 for small values of 2.0. The charge cycle required only a little more time than usual, which isn’t surprising because I stopped the discharge at 3 V rather than 2.8 or whatever.

    Cell C has the highest discharge voltage of the litter, so I’ll conjure it into becoming an external battery for the camera (which prefers higher voltages over higher capacity). I’m not sure how to mount it; a case that screws into the tripod socket is obvious, but it would interfere with the macro lens adapter. Of course, I could just print another macro lens adapter with an integral battery case …

  • Longboard Lighting Case: Final Edition

    After our Larval Engineer allowed as how OpenSCAD’s learning curve was rather too steep, I punched a few holes in the solid model of the case for the Longboard Ground Effect Lighting controller:

    Longboard Case Solid Model - with holes
    Longboard Case Solid Model – with holes

    Those rounded corners sucked the Kapton tape right off the build platform as the massive shape shrank. The top layer was the worst offender, with 1.4 mm of clearance (shown with that tapered scale) under one corner:

    Longboard case - warped corner
    Longboard case – warped corner

    The warping doesn’t matter much, because the case will be compression-loaded by screws and wave washers in the corners. We may need to fill or level the warp to keep the polycarbonate cover flat, though.

    I thought about putting a support structure in the rectangular power switch opening, then decided to just try it and see what happens. It turned out fine; this view looks up toward the as-printed top of the opening (the camera’s barrel distortion makes the curve on the bottom surface look worse than it is):

    Longboard case - switch hole overhang
    Longboard case – switch hole overhang

    Four stacked lithium cells produce upwards of 14.8 V, considerably more than those poor 12 V LED strips prefer to see, so I had her take some current vs. voltage data. She figured out how to convert 10-bit ADC values into battery voltage, after which she could, if she wanted to, beat her Arduino sketch into limiting the maximum PWM duty cycle to hold the LED power dissipation down to a reasonable number. Right now, it’s set to a fixed 25% and is way bright.

    Longboard with variable RGB LED Ground Effect lighting
    Longboard with variable RGB LED Ground Effect lighting

    A truly crappy First Light video taken in the driveway is there. She’s been doing the Happy Dance all day… and promises to document the whole project in gruesome detail.

    The OpenSCAD source code:

    // Longboard Ground Effect Lighting Controller Case
    // Ed Nisley KE4ZNU
    // Karen Nisley KC2SYU
    // August 2012
    
    // Layout options
    
    Layout = "Build3";
    					// 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 = 15.0;				// for connections
    
    CellHoldWidth = 6.0;					// edge to tabs
    CellHoldLength = 4*ThreadWidth;
    
    CellCount = 4;						// cells in the battery
    
    BatteryHeight = CellCount*CellThick + 2*CellClearance;
    BatteryLength = CellLength + 2*CellClearance;
    BatteryWidth = CellWidth + 2*CellClearance;
    
    PCMWidth = 16.0;						// Battery protection module
    PCMLength = 51.0;
    PCMThick = 4.0;							// at terminal end of cells
    
    PillarOD = Washer10_32OD + 2*1.0;		// screw pillar diameter
    PillarOffset = (PillarOD/2) / sqrt(2.0);	// distance to case inside corner
    
    WallThick = 7.5;						// case wall thickness
    
    PinOD = 1.4;							// alignment pin size
    
    CaseInsideLength = BatteryLength + CellTabClearance + PCMThick;
    CaseOALength = CaseInsideLength + 2*WallThick;
    echo("Box Length outside: ",CaseOALength);
    echo("            inside: ",CaseInsideLength);
    
    WiringLength = CaseInsideLength - CellLength - CellHoldLength - PCMThick;	// wiring space at PCM
    echo("Wiring length: ",WiringLength);
    
    CaseInsideWidth = BatteryWidth;
    CaseOAWidth = CaseInsideWidth + 2*WallThick;
    echo("Box Width outside: ",CaseOAWidth);
    echo("           inside: ",CaseInsideWidth);
    echo("Screw OC length: ",CaseInsideLength + 2*PillarOffset);
    echo("          width: ",CaseInsideWidth + 2*PillarOffset);
    
    PCBThick = 2.0;							// PCB thickness
    PCBMargin = 3.0;						// clamping margin around PCB edge
    PartHeight = 17.0;						// height of components above PCB (mind the switch!)
    WiringThick = 5.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);
    
    ChargePlugOD = 11.5;					// battery charger plug
    ChargeJackHeightOC = 6.5;				// coaxial jack center pin height from PCB
    
    SwitchLength = 20.0;					// master power switch
    SwitchWidth = 13.0;
    
    WheelCableOD = 3.0;						// 3-conductor from wheel rotation sensor
    
    LEDCableWidth = 10.0;					// 6 conductor loose wires to LED strips
    LEDCableThick = 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);
    }
    
    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]) {
    	  union() {
    		translate([-(CaseInsideLength/2 - BatteryLength/2),0,0])
    		  cube([BatteryLength,
    			   BatteryWidth,
    			   BatteryHeight + 2*Protrusion],
    			   center=true);
    		cube([CaseInsideLength,
    			 (BatteryWidth - 2*CellHoldWidth),
    			 BatteryHeight + 2*Protrusion],
    			 center = true);
    		translate([(CaseInsideLength/2 - WiringLength/2),0,0])
    		  cube([WiringLength,
    				max(BatteryWidth,PCMLength),
    				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();
    	translate([25,0,(PCBThick/2 + ChargeJackHeightOC)])
    	  rotate([90,0,0])
    		PolyCyl(ChargePlugOD,CaseOAWidth);
    	translate([25,CaseOAWidth/2,PCBLayer2Thick/2])
    	  rotate([90,0,0])
    		cube([SwitchLength,SwitchWidth,CaseOAWidth],center=true);
    	translate([-CaseOALength/2,0,PCBThick/2])
    	  rotate([0,-90,0])
    		cube([2*WheelCableOD,WheelCableOD,CaseOALength],center=true);
    	translate([CaseOALength/2,0,PCBThick/2])
    	  rotate([90,0,90])
    		cube([LEDCableWidth,2*LEDCableThick,CaseOALength],center=true);
      }
    
    }
    
    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(something here!!!);
    
    if ("BatteryLayer" == Layout)
      BatteryLayer();
    
    if ("PCBEnvelope" == Layout)
      PCBEnvelope();
    
    if ("PCBLayer1" == Layout)
      PCBLayer1();
    
    if ("PCBLayer2" == Layout)
      PCBLayer2();
    
    if ("Fit" == Layout) {
      color("LightBlue") BatteryLayer();
      translate([0,0,BatteryHeight + PlateThick])
    	color("Green") PCBLayer1();
      translate([0,0,BatteryHeight + PlateThick + PCBLayer1Thick])
    	color("Cyan") PCBLayer2();
    }
    
    if ("Show" == Layout) {
      color("LightBlue") 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();