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.

Author: Ed

  • KG-UV3D GPS+Voice: Box Model

    The first pass at the box that will eventually hold the GPS+voice interface for the KG-UV3D radio looks like this, from the end that engages the alignment tabs on the bottom of the radio:

    Case Solid Model - Tab End View - Fit
    Case Solid Model – Tab End View – Fit

    The other end has the opening for the TT3’s serial connector to the GPS receiver, a probably too-small hole for the external battery pack cable / helmet cable / PTT cable, and a hole on the side for the radio mic/speaker cables.

    Case Solid Model - Connector End View - Fit
    Case Solid Model – Connector End View – Fit

    The serial connector opening has a built-in support plate that’s the shape shrunken by 5% so it’s easy to punch out. That worked surprisingly well; the line just above the right edge isn’t a break, it’s a stack of Reversal Zits. This version is rectangular; the solid model shows the proper D shape.

    KG-UV3D box - connector hole support removal
    KG-UV3D box – connector hole support removal

    The bottom has battery contact recesses and counterbores (if that’s the right term for a molded feature) for the PCB mounting  screws. In retrospect, those holes should be tapping diameter and the screws inserted from the top, through the PCB.

    Case Solid Model - Battery Contact View - Fit
    Case Solid Model – Battery Contact View – Fit

    The colors mark individual pieces that get glued together. I can probably reduce the wall thickness on the top & bottom by three threads, which is in the nature of fine tuning. The latch mechanism that holds this affair to the radio is conspicuous by its absence…

    The OpenSCAD source code:

    // Wouxun KB-UV3D Battery Pack Case
    // Ed Nisley KE4ZNU September 2011
    
    include </home/ed/Thing-O-Matic/lib/MCAD/units.scad>
    include </home/ed/Thing-O-Matic/Useful Sizes.scad>
    include </home/ed/Thing-O-Matic/lib/visibone_colors.scad>
    
    // Layout options
    
    Layout = "Fit";		// Envelope Plate Base Lid Shell Fit Buildx ScrewSupport
    								// PlugPlate
    
    //- Extrusion parameters must match reality!
    //  Print with +1 shells and 3 solid layers
    //  Use 210 C extrusion temperature to improve layer bonding
    
    ThreadThick = 0.33;
    ThreadWidth = 2.0 * ThreadThick;
    
    HoleWindage = 0.2;
    
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    
    Protrusion = 0.1;			// make holes end cleanly
    
    BuildOffset = 2.0;			// clearance for build layout
    
    //----------------------
    //- Case dimensions
    
    CaseOverallHeight = 40;
    CaseOverallWidth = 56;
    CaseOverallLength = 80.0;
    
    PlateWidthMin = 53.0;			// plate interfacing with radio contacts
    PlateWidthMax = 54.5;
    PlateLength = 75.0;
    PlateThick = IntegerMultiple(2.0,ThreadThick);
    
    ContactWidth = 7.0 + HoleWindage;
    ContactLength = 7.0 + HoleWindage;
    ContactRecess = 2*ThreadThick;	// recess for contact metal plate
    ContactGapX = 10.5;				// X space between contacts
    Contact1Y = 53.0;				// offset from base
    Contact2Y = 56.5;
    
    BaseWidthInner = PlateWidthMin;
    BaseWidthOuter = CaseOverallWidth;
    BaseLength = CaseOverallHeight;
    BaseThick = 1.0;
    BaseWidthTaper = 5.0;
    
    BaseOpeningMax = 42.0;
    BaseOpeningMin = 33.0;
    BaseOpeningY = 5.25;
    BaseOpeningDepth = 2.0;
    
    BaseTabWidth = 6.0;
    BaseTabThick = 2.0;
    BaseTabGap = 7.0;
    BaseTabOC = BaseTabWidth + BaseTabGap;
    
    BaseToothBase = 6.0;
    BaseToothTip = 3.0;
    BaseToothThick = 2.0;
    BaseToothOC = BaseTabOC;
    
    WedgeAngle = atan(BaseWidthTaper/((BaseWidthOuter - BaseWidthInner)/2));
    echo(str("Plate & Shell Wedge Angle: ",WedgeAngle));
    
    BaseEndLip = ThreadThick;			// should be 0.25 mm or so
    BaseEndWidth = (PlateWidthMin - 3*BaseToothBase - 2*BaseToothTip)/2;
    BaseEndAngle = atan((BaseOpeningDepth - BaseEndLip)/BaseOpeningY);
    
    echo(str("Plate End Angle: ",BaseEndAngle));
    
    PCBWidth = 2.00 * inch;
    PCBLength = 2.75 * inch;
    PCBMargin = Head2_56;
    PCBClearBottom = IntegerMultiple(2*Nut2_56Thick,ThreadThick);
    PCBHoleDia = Tap2_56;
    PCBHoleY = 2.50 * inch;
    PCBHoleX = 1.75 * inch;
    
    echo(str("PCB Mounting Holes OC X: ",PCBHoleX," Y: ",PCBHoleY));
    echo(str("       bottom clearance: ",PCBClearBottom));
    
    ShellHeight = CaseOverallHeight - PlateThick;
    ShellWidth = CaseOverallWidth;
    ShellLength = PlateLength;
    ShellWallX = IntegerMultiple((ShellWidth - PCBWidth)/2,ThreadWidth);
    ShellWallY = IntegerMultiple((ShellLength - PCBLength)/2,ThreadWidth);
    ShellWallMax = max(ShellWallX,ShellWallY);
    
    echo(str("Wall thick X: ",ShellWallX," Y: ",ShellWallY));
    
    LidThick = IntegerMultiple(1.0,ThreadThick);
    LidMargin = IntegerMultiple(1.0,ThreadWidth);
    LidWidth = ShellWidth - 2*LidMargin;
    LidLength = ShellLength - 2*LidMargin;
    
    LidScrewHead = Head3_48;
    LidScrewTap = Tap3_48;
    LidScrewClear = Clear3_48;
    LidScrewLength = 5.0;
    LidScrewOffsetX = ShellWidth/2 - LidMargin - 0.75*LidScrewHead;
    LidScrewOffsetY = ShellLength/2 - LidMargin - 0.75*LidScrewHead;
    
    HTCableDia = 5.0;
    HTCableAspect = 2.0;			// width of hole
    HTCableY = 65;
    HTCableZ = 10;
    
    SerialZ = ShellHeight - LidThick - 12.0;
    
    BikeCableDia = 5.0;
    BikeCableAspect = 1.5;
    BikeCableX = -20;
    BikeCableZ = 15;
    
    JackOC = 11.20;						// 14.25 OD - (3.58 + 2.58)/2
    
    JackScrewDia = 4.6;
    JackScrewOffsetX = 1.00;
    JackScrewOffsetY = 5.25;				//  mounting screw to edge of lower recess
    
    PlugBaseWidth = 9.25;				// lower section of plate
    PlugBaseLength = 22.0;
    PlugBaseThick = 2.5;
    PlugBaseRadius = 1.75;
    
    Plug3Offset = 5.25;					// edge of base recess to 3.5 mm jack
    
    Plug2BezelDia = 7.1;				// 2.5 mm plug
    Plug2BezelThick = 1.04;
    Plug2ScrewDia = 6.0;
    Plug3ScrewLength = 3.0;
    
    Plug3BezelDia = 8.13;				// 3.5 mm plug
    Plug3BezelThick = 1.6;
    Plug3ScrewDia = 7.95;
    Plug3ScrewLength = 4.0;
    
    PlugFillOffsetX = JackScrewOffsetX - 0.5;		// base recess CL to fill CL
    PlugFillOffsetY = -10.5;				//  ... to edge of fill plate
    PlugFillWidth = 11.0;
    PlugFillLength = 34.00;
    PlugFillThick = 3.0;
    PlugFillRadius1 = 1.5;
    PlugFillRadius2 = 4.5;
    
    PlugFillOffsetYTotal = 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);
    
    }
    
    //- Hack job for DB-9 (DE-9) panel opening
    //  Snug fit around the shell surrounding the male pins
    //  DB-9 should mount on outside, but if it's already soldered to the board,
    //   that doesn't work and you must mount it inside the box
    
    module DSubMin9(Height=1.0) {
    
      union() {
    
    	linear_extrude(height=Height,center=false) {
    	  hull() {
    /*
    		translate([-(19.28+0.13)/2,(10.72+0.13)/2,0])			// rectangular outline
    		  circle(r=3.05/2,$fn=8);
    		translate([-(19.28+0.13)/2,-(10.72+0.13)/2,0])
    		  circle(r=3.05/2,$fn=8);
    		translate([ (19.28+0.13)/2,(10.72+0.13)/2,0])
    		  circle(r=3.05/2,$fn=8);
    		translate([ (19.28+0.13)/2,-(10.72+0.13)/2,0])
    		  circle(r=3.05/2,$fn=8);
    */
    		translate([-(17.0+0.05)/2,-(8.48+0.05)/2,0])
    		  circle(r=0.105*inch,$fn=8);
    		translate([ (17.0+0.05)/2,-(8.48+0.05)/2,0])
    		  circle(r=0.105*inch,$fn=8);
    		translate([ (15.5+0.05)/2, (8.48+0.05)/2,0])
    		  circle(r=0.105*inch,$fn=8);
    		translate([-(15.5+0.05)/2, (8.48+0.05)/2,0])
    		  circle(r=0.105*inch,$fn=8);
    	  }
    	  hull() {
    		translate([-24.99/2,0,0])
    		  circle(r=3.05/2,$fn=8);
    		translate([ 24.99/2,0,0])
    		  circle(r=3.05/2,$fn=8);
    	  }
    	}
      }
    
    }
    
    //-------------------
    
    //- Overall case outline
    //  This defines the mating taper into the radio shell
    
    module CaseEnvelope(Length=1) {
    
    	rotate([90,0,0])
    	  linear_extrude(height=Length,center=true,convexity=5)
    		polygon(points=[
    				  [-BaseWidthOuter/2,BaseLength],
    				  [-BaseWidthOuter/2,BaseWidthTaper],
    				  [-BaseWidthInner/2,0],
    				  [-BaseOpeningMax/2,0],
    
    				  [ BaseOpeningMax/2,0],
    				  [ BaseWidthInner/2,0],
    
    				  [ BaseWidthOuter/2,BaseWidthTaper],
    
    				  [ BaseWidthOuter/2,BaseLength]
    				],
    				convexity=1
    		);
    
    }
    
    //- Battery contact plate recess
    //  This gets subtracted from the bottom plate in two places
    
    module Contact() {
    
      union() {
    	translate([0,0,-(ContactRecess - Protrusion)/2])
    	  cube([ContactWidth,ContactLength,(ContactRecess + Protrusion)],center=true);
    	translate([0,0,-(PlateThick + Protrusion)])
    	PolyCyl(Clear3_48,(PlateThick + 2*Protrusion));
    	translate([0,0,-(ContactRecess + Head3_48Thick/3)])
    	  PolyCyl(Head3_48,Head3_48Thick);				// allow for solder blob
      }
    }
    
    //- Back interface plate with battery contacts
    
    module Plate() {
    
      difference() {
    
    	translate([0,PlateLength/2,0])
    	  intersection() {
    		translate([0,0,PlateThick])
    		  rotate([180,0,0])
    			CaseEnvelope(PlateLength);
    	  translate([-PlateWidthMax/2,-PlateLength/2,0])
    		cube([PlateWidthMax,PlateLength,PlateThick],center=false);
    	  }
    
    	translate([-(ContactGapX/2 + ContactWidth/2),(Contact1Y + ContactLength/2),PlateThick])
    	  Contact();
    	translate([+(ContactGapX/2 + ContactWidth/2),(Contact2Y + ContactLength/2),PlateThick])
    	  Contact();
    
    	translate([0,PlateLength/2,0])
    	  PCBHoles(PCBHoleDia,PlateThick);
    
    	translate([0,PlateLength/2,(PlateThick - 2*Head2_56Thick/3)])
    	  PCBHoles(IntegerMultiple(Head2_56,ThreadWidth),IntegerMultiple(Head2_56Thick,ThreadThick));
      }
    
    }
    
    //- Radio bottom locating feature
    //  This polygon gets subtracted from the battery pack base
    
    module RadioBase() {
    
    linear_extrude(height=(BaseOpeningDepth + Protrusion),center=false,convexity=5)
      polygon(points=[
    			[-BaseOpeningMax/2,-Protrusion],
    
    			[-BaseOpeningMin/2,BaseOpeningY],
    			[-(BaseToothOC/2 + BaseToothBase/2),BaseOpeningY],
    
    			[-(BaseToothOC/2 + BaseToothTip/2),(BaseOpeningY - BaseToothThick)],
    			[-(BaseToothOC/2 - BaseToothTip/2),(BaseOpeningY - BaseToothThick)],
    			[-(BaseToothOC/2 - BaseToothBase/2),BaseOpeningY],
    
    			[ (BaseToothOC/2 - BaseToothBase/2),BaseOpeningY],
    			[ (BaseToothOC/2 - BaseToothTip/2),(BaseOpeningY - BaseToothThick)],
    			[ (BaseToothOC/2 + BaseToothTip/2),(BaseOpeningY - BaseToothThick)],
    			[ (BaseToothOC/2 + BaseToothBase/2),BaseOpeningY],
    			[ BaseOpeningMin/2,BaseOpeningY],
    
    			[ BaseOpeningMax/2,-Protrusion],
    
    			[ (BaseTabOC + BaseTabWidth/2),-Protrusion],
    			[ (BaseTabOC + BaseTabWidth/2),BaseTabThick],
    			[ (BaseTabOC - BaseTabWidth/2),BaseTabThick],
    			[ (BaseTabOC - BaseTabWidth/2),-Protrusion],
    
    			[ BaseTabWidth/2,-Protrusion],
    			[ BaseTabWidth/2,BaseTabThick],
    			[-BaseTabWidth/2,BaseTabThick],
    			[-BaseTabWidth/2,-Protrusion],
    
    			[-(BaseTabOC + BaseTabWidth/2),-Protrusion],
    			[-(BaseTabOC + BaseTabWidth/2),BaseTabThick],
    			[-(BaseTabOC - BaseTabWidth/2),BaseTabThick],
    			[-(BaseTabOC - BaseTabWidth/2),-Protrusion],
    		  ],
    		  convexity=5
      );
    }
    
    //- PCB Mounting Holes
    
    module PCBHoles(HoleDia=PCBHoleDia,Height=1.0) {
    
      for (x=[-1,1])
    	for (y=[-1,1])
    	  translate([(x*PCBHoleX/2),
    				(y*PCBHoleY/2),
    				-Protrusion])
    		PolyCyl(HoleDia,(Height + 2*Protrusion));
    
    }
    
    //-- Battery pack base
    
    module Base() {
    
      difference() {
    
    	translate([0,0,(BaseThick + BaseOpeningDepth)/2])
    	  rotate([-90,0,0])
    		CaseEnvelope(BaseThick + BaseOpeningDepth);
    
    	translate([0,0,BaseThick])
    	  RadioBase();
    
    	translate([(BaseToothOC + BaseTabWidth/2),
    			  -(BaseThick + BaseEndLip)/tan(BaseEndAngle),
    			  0])
    	  rotate([BaseEndAngle,0,0])
    		cube([BaseEndWidth,3*BaseOpeningY,BaseOpeningDepth],center=false);
    
    	translate([-(BaseToothOC + BaseTabWidth/2 + BaseEndWidth),
    			  -(BaseThick + BaseEndLip)/tan(BaseEndAngle),
    			  0])
    	  rotate([BaseEndAngle,0,0])
    		cube([BaseEndWidth,3*BaseOpeningY,BaseOpeningDepth],center=false);
      }
    }
    
    //- Lid
    
    module Lid(WithHoles = false) {
    
      translate([0,LidLength/2,LidThick/2])
    	difference() {
    	  cube([LidWidth,LidLength,LidThick],center=true);
    	  if (WithHoles) {
    		translate([LidScrewOffsetX,LidScrewOffsetY,-(LidThick/2 + Protrusion)])
    		  PolyCyl(LidScrewClear,(LidThick + 2*Protrusion));
    		translate([-LidScrewOffsetX,-LidScrewOffsetY,-(LidThick/2 + Protrusion)])
    		  PolyCyl(LidScrewClear,(LidThick + 2*Protrusion));
    	  }
    	}
    }
    
    //- Lid screw support shape
    
    module LidScrewSupport(WithHole = false) {
    
      SupportSize = IntegerMultiple(LidScrewHead,ThreadWidth);
    
      difference() {
    	translate([0,0,LidScrewLength/2])
    	cube([SupportSize,SupportSize,LidScrewLength],center=true);
    	if (WithHole)
    	  translate([0,0,-Protrusion])
    		PolyCyl(LidScrewTap,(LidScrewLength + 2*Protrusion));
      }
    
      translate([-SupportSize/2,SupportSize/2,-2*SupportSize])
    	rotate([90,0,0])
    	  linear_extrude(height=SupportSize,center=false)
    		polygon(points=[
    				  [0,0],[0,2*SupportSize],[SupportSize,2*SupportSize]]);
    
    }
    
    //- Battery pack shell
    
    module Shell() {
    
      union() {
    	difference() {
    
    	  translate([0,0,-PlateThick])
    		intersection() {
    		  CaseEnvelope(ShellLength);
    		  translate([0,0,(ShellHeight/2 + PlateThick)])
    			cube([ShellWidth,ShellLength,ShellHeight],center=true);
    		}
    
    	  translate([0,-LidLength/2,(ShellHeight - LidThick)])
    		scale([1,1,2])				// ensure clean cut across top
    		  Lid(false);
    
    	  translate([0,0,
    				((ShellHeight - PCBClearBottom - LidThick + Protrusion)/2 + PCBClearBottom)])
    		cube([PCBWidth,PCBLength,
    			 (ShellHeight - PCBClearBottom - LidThick + Protrusion)],
    			 center=true);
    
    	  render()
    		difference() {
    		  translate([0,0,ShellHeight/2])
    			cube([(PCBWidth - 2*PCBMargin),
    				(PCBLength - 2*PCBMargin),
    				(ShellHeight + 2*Protrusion)],
    				center=true);
    		  for (x=[-1,1])
    			for (y=[-1,1])
    			  translate([(x*PCBHoleX/2),(y*PCBHoleY/2),-Protrusion])
    				cylinder(r=PCBMargin,(ShellHeight + 2*Protrusion),$fn=4);
    		}
    
    	  PCBHoles(PCBMargin);
    
    	  translate([-(PCBWidth/2 - Protrusion),(HTCableY - PlateLength/2),HTCableZ])
    		rotate([0,-90,0])
    		  scale([1/HTCableAspect,1,1])
    			PolyCyl(HTCableDia,(ShellWallMax + 2*Protrusion),8);
    
    	  translate([BikeCableX,(PCBLength/2 - Protrusion),BikeCableZ])
    		rotate([0,90,90])
    		  scale([1/BikeCableAspect,1,1])
    			PolyCyl(BikeCableDia,(ShellWallMax + 2*Protrusion),8);
    
    	  translate([0,(PCBLength/2 - Protrusion),SerialZ])
    		rotate([-90,0,0])
    		  DSubMin9(ShellWallMax + 2*Protrusion);
    
    	}
    
      	translate([0,(PCBLength/2 + ThreadWidth/2),SerialZ])
    	  rotate([-90,0,0])
    		scale([0.95,0.95,1])
    		  DSubMin9(ShellWallY - ThreadWidth);		// thin support plug in hole
    
      }
    
    }
    
    //- Speaker-Mic plug mounting plate
    
    module PlugPlate() {
    
      BaseX = PlugBaseWidth/2 - PlugBaseRadius;
      BaseY = PlugBaseLength/2 - PlugBaseRadius;
    
      difference() {
    	union() {
    	  linear_extrude(height=PlugBaseThick,center=false,convexity=3)
    		hull() {
    		  translate([-BaseX,-BaseY,0])
    			circle(r=PlugBaseRadius,$fn=8);
    		  translate([-BaseX, BaseY,0])
    			circle(r=PlugBaseRadius,$fn=8);
    		  translate([ BaseX, BaseY,0])
    			circle(r=PlugBaseRadius,$fn=8);
    		  translate([ BaseX,-BaseY,0])
    			circle(r=PlugBaseRadius,$fn=8);
    		}
    
    	  translate([PlugFillOffsetX,
    				(PlugFillLength/2 - PlugBaseLength/2 + PlugFillOffsetY),
    				PlugBaseThick])
    		linear_extrude(height=PlugFillThick,center=false,convexity=5)
    		  hull() {
    			translate([0,-(PlugFillLength/2 - PlugFillRadius2),0])
    			  circle(r=PlugFillRadius2,$fn=10);
    			translate([-(PlugFillWidth/2 - PlugFillRadius1),-PlugBaseLength/2,0])
    			  circle(r=PlugFillRadius1,$fn=8);
    			translate([-(PlugFillWidth/2 - PlugFillRadius1),
    					  (PlugFillLength/2 - PlugFillRadius1),0])
    			  circle(r=PlugFillRadius1,$fn=8);
    			translate([(PlugFillWidth/2 - PlugFillRadius1),
    					  (PlugFillLength/2 - PlugFillRadius1),0])
    			  circle(r=PlugFillRadius1,$fn=8);
    			translate([(PlugFillWidth/2 - PlugFillRadius1),-PlugBaseLength/2,0])
    			  circle(r=PlugFillRadius1,$fn=8);
    		  }
    	}
    
    	translate([0,-JackOC/2,-Protrusion])
    	  rotate(360/16) {
    		PolyCyl(Plug3BezelDia,(Plug3BezelThick + Protrusion),8);
    		PolyCyl(Plug3ScrewDia,(PlugBaseThick + PlugFillThick + 2*Protrusion),8);
    	  }
    
    	translate([0,+JackOC/2,-Protrusion])
    	  rotate(360/16) {
    		PolyCyl(Plug2BezelDia,(Plug2BezelThick + Protrusion),8);
    		PolyCyl(Plug2ScrewDia,(PlugBaseThick + PlugFillThick + 2*Protrusion),8);
    	  }
    
    	translate([JackScrewOffsetX,-(PlugBaseLength/2 + JackScrewOffsetY),0])
    	  PolyCyl(JackScrewDia,(PlugBaseThick + PlugFillThick + Protrusion));
      }
    
    }
    
    //-------------------
    // Build it!
    
    ShowPegGrid();
    
    if (Layout == "Envelope")
      CaseEnvelope(CaseOverallLength);
    
    if (Layout == "Plate")
      Plate();
    
    if (Layout == "Base")
      Base();
    
    if (Layout == "Lid")
      Lid(true);
    
    if (Layout == "ScrewSupport")
      LidScrewSupport(true);
    
    if (Layout == "Shell")
      Shell();
    
    if (Layout == "PlugPlate")
      PlugPlate();
    
    if (Layout == "DSub")
      DSubMin9();
    
    if (Layout == "Fit") {
    
      translate([0,-PlateLength/2,0]) {
    
    	translate([0,0,PlateThick])
    	  rotate([0,180,0])
    		color(LOR) Plate();
    
    	rotate([90,0,0])
    	  color(DYO) Base();
    
    	translate([0,LidMargin,10 + (CaseOverallHeight - LidThick)])
    	  color(MOR) Lid(true);
    
    	translate([0,PlateLength/2,PlateThick])
    	    color(MFG) render() Shell();
    
    	translate([-(ShellWidth/2 +10),70,15])
    	  rotate([0,-90,0])
    		color(DDY) PlugPlate();
      }
    }
    
    if (Layout == "Build1") {
    
      translate([-20,-PlateLength/2,0])
    	Plate();
    
        translate([10,0,0])
    	rotate([0,0,-90])
    	  Base();
    
    }
    
    if (Layout == "Build2") {
    
      translate([0,-LidLength/2,0])
    	Lid(true);
    
    }
    
    if (Layout == "Build3") {
    
      translate([-20,0,0])
    	Shell();
    
    }
    
    if (Layout == "Build4") {
    
      translate([0,0,(PlugBaseThick + PlugFillThick)])
    	rotate([180,0,0])
    	  PlugPlate();
    
    }
    
  • KG-UV3D GPS+Voice: Box

    The previous iteration of GPS+voice interface boxes came from the Sherline CNC mill, with a considerable amount of huffing & puffing. I got the Thing-O-Matic to simplify that process…

    The general idea is to build a box that clips onto the radio in place of the standard battery pack. External power comes into the box and goes directly to the radio’s battery contacts; this will pose a problem with the Wouxun KG-UV3D, because it wants 7.2 V rather than the stepped-up 9 V from the Li-Ion packs I’ve been using. I think a three-wire power cord is in order: +9 V for the interface, +7.2 V for the radio, and common.

    The box also interfaces with the radio’s mic and speaker jacks. Last time around, I made a gluing fixture to keep the plugs in alignment while the epoxy cured around the plugs in the plate, but maybe I can simplify that with 3D printing. Plastic will be better in one respect: the shells of the two plugs must be electrically isolated.

    This first-pass (*) approximation shows the three tabs on the pack that engage the radio’s base:

    KG-UV3D Interface Box prototype - right side
    KG-UV3D Interface Box prototype – right side

    A detail of those tabs, as seen from the bottom:

    KG-UV3D Interface Box prototype - end tabs
    KG-UV3D Interface Box prototype – end tabs

    The ICOM IC-Z1A battery pack had a set of slip-in alignment features that held the pack on the radio, so two strips of tape sufficed to hold the interface box in place. Each Wouxun battery pack includes a spring-loaded latching mechanism that engages a pair of ramped tabs on the radio body that hold the pack against the spring-loaded battery contacts. That means I must come up with an actual latch of some sort to oppose the contact springs, but I haven’t figured that out yet.

    The solid model, with the plug mounting plate floating beside it, looks like this:

    Case Solid Model - Tab End View - Fit
    Case Solid Model – Tab End View – Fit

    Tomorrow, the solid modeling…

    * It’s actually the third printing of the bottom plate with the three tabs and the base plate with the battery contacts. That’s how I figured out the 0. 5% shrinkage thing.

    [Update: The sketch with the dimensions emerged from beneath a pile o’ stuff…]

    Wouxun KG-UV3D Battery Pack Dimensions
    Wouxun KG-UV3D Battery Pack Dimensions

     

  • GPS+Voice Interface for Wouxun KG-UV3D: Circuit Hackage

    Having had my old ICOM IC-Z1A HT stop working, most likely due to the innards finally shaking loose, I replaced it with a Wouxun KG-UV3D dual-band radio. Unfortunately, the interface box I designed to connect the Byonics TinyTrak 3+ GPS modem, the helmet earbud/mic, and the external battery pack to the Z-1A doesn’t work with the Wouxun. It’s all different:

    • Mechanical interface to the radio
    • Battery voltage
    • Power control
    • Mic level
    • PTT interface

    I modified the interface box from my bike thusly:

    GPS-HT Interface Circuit Mods for Wouxun
    GPS-HT Interface Circuit Mods for Wouxun

    Because the KG-UV3D uses the Kenwood HT interface with a single ground for mic, speaker, and PTT functions, there’s no need for galvanic isolation; all the optoisolators & the audio transformer will Go Away when I rebuild it.

    The plug connections:

    Wouxun KG-UV3D Mic & Speaker Jacks
    Tip Ring Shell
    3.5 mm +5 V Mic audio PTT
    2.5 mm Speaker audio Buttons Ground

    One distressing change: the IC-Z1A mic power was 3.5 V behind 400 Ω = 6 mA into an optoisolator LED, but the KG-UV3D puts 5 V behind 50 kΩ = 100 µA into a dead short. I think the voltage will suffice to drive a logic-gate MOSFET to switch the power through a PNP transistor, but, for the moment, I hotwired OK1 and “control” the interface power by unplugging the external battery. The radio runs from its own snap-on Li-Ion pack.

    The PTT now has a separate logic wire and is no longer multiplexed as a DC current on the audio line. The hack on OK2 was the easiest way to make that happen on the existing board, but the TT3 PTT Out line can  probably drive the PTT directly.

    I’m not happy with the audio levels; the KG-UV3D requires more mic gain (which change doesn’t appear in the mods) and more TT3 output. Having tediously calibrated the TT3 for the IC-Z1A, I’m not looking forward to doing that again. I still like using an analog multiplexer to switch the audio signal, though, because it doesn’t mix the machine noise with the voice transmissions.

    Bungied GPS Interface Box
    Bungied GPS Interface Box

    There being no way to mount the box on the radio and no way to control the interface power if I did, I simply lashed it to the side of the pack holding the radio behind the seat. Obviously, that can’t last forever…

    I think the KG-UV3D stuffs more RFI into the mic circuit, because that box is now in the only position that doesn’t result in weird voice audio dropouts. Given the precarious nature of the thing, though, I must look again after getting it in a box on the radio.

    Earth to amateur radio manufacturers: seen from out here, it’d be perfectly OK to standardize some of this stuff!

  • Thing-O-Matic: Small Features

    Strainer - knob perimeter thread
    Strainer – knob perimeter thread

    It seems most of the stuff I build with my Thing-O-Matic involves small features and thin sections that bump hard against the minimum possible sizes. I’ve found that forcing critical solid model dimensions to be integer multiples of the the extrusion width or thickness stabilizes the whole idea→model→G-Code→object chain by encouraging Skeinforge to make the choices I prefer.

    Or perhaps I’m just constraining my choices to make Skeinforge happiest. One can view reality in many ways…

    Anyhow, my OpenSCAD programs tend to have these lines up near the top:

    ThreadThick = 0.33;
    ThreadWidth = 2.0 * ThreadThick;
    
    function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
    

    The ThreadThick parameter matches the Skeinforge thread thickness parameter(s) and the 2.0 matches the w/t setting(s). Those correspond quite closely to the actual printed results, as tediously verified through many measurements. Throughout the rest of the OpenSCAD program, I compute the dimensions of key features using those sizes as building blocks.

    The IntegerMultiple function returns the next higher multiple of the basic Unit that’s greater-than-or-equal-to the desired Size. Feeding in the thread thickness or width as the Unit ensures that the result will be an integer multiple of the smallest-possible dimension and won’t be smaller. The integer limit happens automagically, because the printer can’t lay down anything else, but a less-than-possible size can cause features to (unpredictably, in my experience) vanish without warning. This way your model reflects the printed reality and Skeinforge seems more likely to produce a predictable result.

    So the parameter controlling the thickness of a flat sheet might look like:

    PlateThick = IntegerMultiple(2.0,ThreadThick);
    

    Given ThreadThick = 0.33, the sheet will be 7 layers thick = 2.31 mm. If the sheet must not exceed 2.0 mm, however, then you need a similar function with floor(), which may eradicate very small features.

    This trick seems most useful for thin wall sections, because the wall width directly affects the fill:

    • Less  than 1 thread width can’t be built
    • Exactly 1 thread width is the thinnest possible wall
    • Widths between 1 and 2 thread widths may be either, depending on surrounding features
    • Exactly 2 thread widths produces a nice wall
    • Widths between 2 and 3 thread widths can’t fill properly
    • Exactly 3 thread widths fills perfectly
    • Over 3 thread widths generally fill properly

    So making the rim around a recessed lid become an integral number of thread widths, with a minimum width of 1.0 mm, looks like this:

    LidMargin = IntegerMultiple(1.0,ThreadWidth);
    

    With a 0.66 mm thread width, the nominal wall is 1.5 threads wide and could print as either 1 or 2 threads, depending on other factors. Rather than leave the results to chance, I force the solid model wall to be exactly 2 threads wide to make the printed result come out at 1.32 mm. Because I don’t care exactly how wide the lid margin is, as long as it’s at least one thread, that’s fine with me.

    Generally, the values come from computations based on other dimensions, so quantizing the results keeps the printed result stable over small variations of those inputs.

    If I ever get around to changing the nozzle to from 0.5 mm to 0.4 mm, I’ll probably change the thread dimensions to 0.25 mm x 0.5 mm (keeping the same 2.0 w/t ratio). A 1.0 mm wall would then still be exactly 2 threads wide and come out looking exactly the same, but with a total width of 1.00 mm.

    That’s the intent, anyway.

  • The Embedded PC’s ISA Bus: Firmware, Gadgets, and Practical Tricks — Unleashed

    ISA Bus Book - Front Cover
    ISA Bus Book – Front Cover

    A long time ago, in a universe far away, I wrote a book that (barely) catapulted me into the ranks of the thousandaires. Time passes, companies get sold / fail / merge / get bought, and eventually the final owners decided to remainder the book; the last royalty check I recall was for $2.88.

    Anyhow, now that it’s discontinued and just as dead as the ISA bus, I own the copyright again and can do this:

    They’re both ZIP files, disguised as ODT files so WordPress will handle them. Just rename them to get rid of the ODT extension, unzip, and you’re good to go. Note, however, that I do retain the copyright, so if you (intend to) make money off them, be sure to tell me how that works for you.

    The big ZIP has the original pages laid out for printing, crop marks and all, so this is not as wonderful a deal as it might first appear. The little ZIP has the files from the diskette, which was unreadable right from the start.

    Words cannot begin to describe how ugly that front cover really is, but Steve’s encomium still makes me smile.

    The text and layout is firmly locked inside Adobe Framemaker files, where it may sleep soundly forever. The only way I can imagine to get it back into editable form would be to install Windows 98 in a VM, install Framemaker, load up the original files, and export them into some non-proprietary format. Yeah, like that would work, even if I had the motivation.

    If you prefer a dead-tree version, they’re dirt cheap from the usual used-book sources. Search for ISBN 1-57398-017-X (yes, X) and you’ll get pretty close.

    Or, seeing as how I just touched the carton of books I’ve been toting all these years, send me $25 (I’m easy to find; if all else fails, look up my amateur callsign in the FCC database) and get an autographed copy direct from the source. Who knows? It might be worth something some day…

    The back cover has some useful info:

    ISA Bus Book - Back Cover
    ISA Bus Book – Back Cover
  • 3D Printing Presentation for Long Island LUG

    Helmet mirror mount - 3D model - Fit layout
    Helmet mirror mount – 3D model – Fit layout

    The folks at the Long Island LUG asked me to give my DIY 3D Printing & the Makerbot Thing-O-Matic presentation, which will happen 11 October at 8 pm. Details & directions at lilug.org.

    Should you happen to be in the area that evening, drop in and pick up a tchotchke!

  • Kensington Trackball: Scroll Ring Tweakage

    Of late, something in the pile of input devices attached to my main PC has been feeding occasional bursts of upward scroll commands, to the extent that editing long documents (something I do quite a bit of, oddly enough) was becoming difficult. By process of elimination, the culprit turned out to be the Kensington trackball to the left of the keyboard: unplugging it eliminated the problem.

    Having had problems with that thing before and having gotten considerable feedback from other folks, I had a general idea of how to proceed: putz with the IR emitter-detector pair, but not very much. A side view of the pair with the trackball cup and scroll ring removed:

    Scroll ring IR emitter-detector quadrature pair
    Scroll ring IR emitter-detector quadrature pair

    Now, what’s weird about that setup is that the detector lens seems to be pointing in the wrong direction: away from the emitter’s lens. You know it’s the detector because it’s tinted: there’s no point in filtering the emitter’s output (although I have seen gray-tinted IR LEDs, which I think is just to mark them as different from visible LEDs). Here’s proof: a pure IR picture from my Sony DSC-F717 in Nightshot (a.k.a. IR) mode through a Hoya R72 visible-block filter:

    Quadrature pair in pure IR
    Quadrature pair in pure IR

    Some possibilities for why the detector is backwards:

    • It’s an assembly screwup (unlikely, but possible)
    • That’s not a lens, it’s a locating tab (different on emitter & detector?)
    • The backside uses the metal conductors as slits to enhance the signal (my favorite)

    Here’s a grossly image-enhanced blowup of the detector from that picture:

    Quadrature IR detector in pure IR - detail
    Quadrature IR detector in pure IR – detail

    The case becomes transparent in pure IR, so you can see the metal lead frame inside. I think they’re using the gaps between the leads to enhance the contrast of the scroll ring edges passing through the beam: absolutely no IR except when a gap aligns with a scroll ring opening.

    [Update: read the comments for a different interpretation; I’m probably wrong.]

    That would also explain why the pair seems so sensitive to alignment: there’s very little IR hitting the detector, because the IR illumination passes through the transparent-to-IR case and vanishes out the far side, with only a tiny bit reflected to the sensor!

    Anyhow, I pushed the pair minutely toward each other, just enough to feel the leads bend, and put everything back together. So far it seems to be working perfectly, but it’s done that before …

    [Comment: Jack found a different solution that might produce better results:

    Just got the Problem with my Scroll ring and thanks to your blog i digged a bit deeper.

    here is the Solution for my Problem:

    I checked this while connected and i found that bending worked only for a short time, so i gave a closer look to the contacts.

    all are soldered from below BUT two contacts are on the upper side.
    normaly solder should flow into but here it was as simple as just resolder the receiver with enough solder an its now working again. (btw a realigned the magnet to get a better response)

    Thanks
    Jack

    ps. the size of the cuts in the metall from the scroll ring differ, a shame for that price..

    It’s certainly worth trying, particularly when your Expert Mouse trackball isn’t working…

    Update: Nine years in the future, a real fix appears!