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

  • HP-48GX Calculator Disassembly: Case Rivets

    The keyboard on my trusty HP 48GX calculator finally deteriorated to the point of unusability, so I tore the thing apart following the useful instructions there. The warning about applying force to the rivets that hold the case halves together gives you not the faintest concept of how much force is actually required to pry the mumble thing apart at the battery compartment; I finally invoked force majeure with a chisel scraper

    HP-48GX case rivets
    HP-48GX case rivets

    I expected the calculator would not survive this operation and I wasn’t disappointed.

    An HP 50g is now in hand. Here in late 2011 I’d expect HP’s top-of-the-line RPN calculator to sport a crisp high-resolution display, but noooo the low-contrast 131×80 LCD seems teleported directly from the latter part of the last millennium. The manuals are PDFs, which is OK, but their content is far inferior to the HP 48GX manuals. In particular, the editing / proofreading is terrible. I infer that the HP calculator division can barely fog a mirror and is on advanced life support; HP’s diverting all their money to, uh, executive buyouts or some other non-productive purpose.

    The fact that HP sells new-manufacture HP 15C calculators doesn’t crank my tractor, even though I lived and died by one for many years. A one-line 7-segment display doesn’t cut it any more, even if the new machinery inside allegedly runs like a bat out of hell.

    My HP 16C, now, that one you’ll pry out of my cold, dead hands. At one point in the dim past, I’d programmed the Mandelbrot iteration into it to provide bit-for-bit verification of the 8051 firmware for the Mandelbrot Engine array processor I did for Circuit Cellar: slow, but perfect. That calculator has a low duty cycle these days, but when I need it, I need it bad.

  • 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!

  • 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
  • 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!

  • NB-5L Holder: Coil Springs

    Having twice failed to make music-wire springs work, I rummaged around in the Big Box o’ Small Springs with more diligence and unearthed a pair of coil compression springs that exactly match the pin ferrule OD. Twiddling the solid model produced this longer & flatter version with in-line springs and cylindrical plugs holding them in place:

    NB-5L Holder - Coil spring - solid model
    NB-5L Holder – Coil spring – solid model

    A closeup of the pin arrangement, which now looks very clean and easy to build:

    NB-5L Holder - Coil spring - detail
    NB-5L Holder – Coil spring – detail

    The OpenSCAD code will print out a quartet of plugs (pick the best two), but having thought of that too late, I turned a pair from a random acrylic rod:

    Turning spring plugs
    Turning spring plugs

    I did remember to solder the wires before assembling the pins this time…

    Pin assemblies
    Pin assemblies

    Because the pins now index on their shoulder with the springs at partial extension, I set the drills into the pin vice vise [Update: One can probably be arrested for pin vice] to produce depths displayed by the OpenSCAD program before reaming out the  printed holes:

    ECHO: "Depth to taper end: 24.72"
    ECHO: "         ferrule end: 15.62"
    ECHO: "         plug end: 4.62"
    

    Then glue the pin plugs into the holder and the flat lid atop the case to capture the battery, clamping everything to the corner of the Sherline’s countertop:

    Gluing pin assemblies
    Gluing pin assemblies

    And it Just Worked: nice travel between the limits, smooth operation, it’s the way I should have done it from the beginning*. You knew that all along, right?

    Here are the three NB-5L Battery Holder versions, all snuggled up together. The longer and flatter coil-spring version sits on the right:

    Variations on an NB-5L holder theme
    Variations on an NB-5L holder theme

    Now I can take some data…

    The OpenSCAD source code:

    // Holder for Canon NB-5L Li-Ion battery
    // Ed Nisley KE4ZNU August 2011
    
    include </home/ed/Thing-O-Matic/lib/MCAD/units.scad>
    include </home/ed/Thing-O-Matic/lib/MCAD/boxes.scad>
    include </home/ed/Thing-O-Matic/Useful Sizes.scad>
    
    // Layout options
    
    Layout = "Build";					// Case Lid Plugs Show Build Fit
    
    //- Extrusion parameters - must match reality!
    //  Print with +2 shells and 3 solid layers
    
    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 = 3.0;			// clearance for build layout
    
    //- Battery dimensions - rationalized from several samples
    //  Coordinate origin at battery corner by contact plates on bottom surface
    
    BatteryLength = 45.25;
    BatteryWidth = 32.17;
    BatteryThick =  7.85;
    
    ContactWidth = 2.10;
    ContactLength = 4.10;
    ContactRecess = 0.85;
    
    ContactOC = 3.18;			// center-to-center across contact face
    ContactOffset = 4.45;		// offset from battery edge
    ContactHeight = 3.05;		// offset from battery bottom plane
    
    AlignThick = 2.2;			// alignment recesses on contact face
    AlignDepth = 2.0;			// into face
    AlignWidth1 = 0.7;			// across face at contacts
    AlignWidth2 = 2.8;			//  ... other edge
    
    //- Pin dimensions
    
    PinTipDia = 1.6;
    PinTipLength = 10.0;
    
    PinTaperLength = 2.3;
    
    PinShaftDia = 2.4;
    PinShaftLength = 6.8;
    
    PinFerruleDia = 3.1;
    PinFerruleLength = 2.0;
    
    PinLength = PinTipLength + PinTaperLength + PinShaftLength + PinFerruleLength;
    
    ExtendRelax = 1.5 + ContactRecess;		// pin extension when no battery is present
    ExtendOvertravel = 1.0;					//  ... beyond engaged position
    
    //- Spring dimensions
    
    SpringDia = 3.1;						// coil OD
    SpringMax = 9.3;
    SpringLength = SpringMax - 0.3;			// slightly compressed
    SpringMin = 4.5;
    
    SpringPlugDia = 5.0;					// plug retaining the spring
    SpringPlugLength = IntegerMultiple(4.0,ThreadWidth);
    SpringPlugSides = 12;
    
    SpringTravel = ExtendRelax + ExtendOvertravel;
    
    //- Holder dimensions
    
    GuideRadius = ThreadWidth;						// friction fit ridges
    GuideOffset = 10;
    WallThick = 4*ThreadWidth;						// holder sidewalls
    
    BaseThick = 6*ThreadThick;			// bottom of holder to bottom of battery
    TopThick = 4*ThreadThick;			// top of battery to top of holder
    
    ThumbRadius = 10.0;			// thumb opening at end of battery
    
    CornerRadius = 3*ThreadThick;			// nice corner rounding
    
    CaseLength = SpringPlugLength + SpringLength + PinLength - ExtendRelax
    			+ BatteryLength + GuideRadius + WallThick;
    CaseWidth = 2*WallThick + 2*GuideRadius + BatteryWidth;
    CaseThick = BaseThick + BatteryThick + TopThick;
    
    //- XY origin at front left battery corner, Z on platform below that
    
    CaseLengthOffset = -(SpringPlugLength + SpringLength + PinLength - ExtendRelax);
    CaseWidthOffset = -(WallThick + GuideRadius);
    CaseThickOffset = BaseThick;
    
    LidLength = ExtendRelax - CaseLengthOffset;
    
    echo(str("Depth to taper end: ",
    		 (SpringPlugLength + SpringLength + PinFerruleLength + PinShaftLength + PinTaperLength)));
    echo(str("         ferrule end: ",
    		  (SpringPlugLength + SpringLength + PinFerruleLength)));
    echo(str("         plug end: ",SpringPlugLength));
    
    //----------------------
    // 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);
    
    }
    
    //-------------------
    
    //-- Guides for tighter friction fit
    
    module Guides() {
      	  translate([GuideOffset,-GuideRadius,CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    	  translate([GuideOffset,(BatteryWidth + GuideRadius),CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    	  translate([(BatteryLength - GuideOffset),-GuideRadius,CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    	  translate([(BatteryLength - GuideOffset),(BatteryWidth + GuideRadius),CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    	  translate([(BatteryLength + GuideRadius),GuideOffset/2,CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    	  translate([(BatteryLength + GuideRadius),(BatteryWidth - GuideOffset/2),CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    
    }
    
    //-- Contact pins (holes therefore)
    
    module PinShape() {
    
      union() {
    	cylinder(r=(PinTipDia + HoleWindage)/2,h=(PinTipLength + Protrusion),$fn=6);
    
    	translate([0,0,PinTipLength])
    	  cylinder(r=(PinShaftDia + HoleWindage)/2,
    			   h=(PinTaperLength + PinShaftLength + Protrusion),$fn=6);
    
    	translate([0,0,(PinLength - PinFerruleLength)])
    	  cylinder(r=(PinFerruleDia + HoleWindage)/2,
    				h=(PinFerruleLength + Protrusion),$fn=6);
    
    	translate([0,0,(PinLength)])
    	  cylinder(r=(SpringDia + HoleWindage)/2,
    				h=(SpringLength + Protrusion),$fn=6);
    
    	translate([0,0,(PinLength + SpringLength)])
    	  cylinder(r=(SpringPlugDia + HoleWindage)/2,h=(SpringPlugLength + Protrusion),$fn=SpringPlugSides);
    
    	  translate([0,0,(PinLength + SpringLength + SpringPlugLength)])
    	  cylinder(r=(SpringPlugDia + HoleWindage)/2,h=2*SpringPlugLength,$fn=SpringPlugSides);	// extend hole
      }
    
    }
    
    module PinAssembly() {
    
      translate([ExtendRelax,ContactOffset,CaseThickOffset + ContactHeight]) {
    	rotate([0,270,0]) {
    	  PinShape();												// pins
    	  translate([0,(2*ContactOC),0])
    		PinShape();
    	}
      }
    
    }
    
    //-- Case with origin at battery corner
    
    module Case() {
    
      difference() {
    
    	union() {
    
    	  difference() {
    		translate([(CaseLength/2 + CaseLengthOffset),
    				  (CaseWidth/2 + CaseWidthOffset),
    				  (CaseThick/2)])
    		  roundedBox([CaseLength,CaseWidth,CaseThick],CornerRadius); 	// basic case shape
    
    		translate([-ExtendOvertravel,-GuideRadius,CaseThickOffset])
    		  cube([(BatteryLength + GuideRadius + ExtendOvertravel),
    				(BatteryWidth + 2* GuideRadius),
    				(BatteryThick + Protrusion)]);						// battery space
    
    	  }
    
    	  Guides();
    
    	  translate([-ExtendOvertravel,-GuideRadius,BaseThick])
    		cube([(AlignDepth + ExtendOvertravel),
    			  (AlignWidth1 + GuideRadius),
    			  AlignThick]);											// alignment blocks
    	  translate([-ExtendOvertravel,
    				 (BatteryWidth - AlignWidth2),
    				 BaseThick])
    		cube([(AlignDepth + ExtendOvertravel),
    			  (AlignWidth2 + GuideRadius),
    			  AlignThick]);
    
    	}
    
    	translate([(-ExtendOvertravel),
    			   (CaseWidthOffset - Protrusion),
    			   (CaseThickOffset + BatteryThick)])
    	  cube([CaseLength,
    		    (CaseWidth + 2*Protrusion),
    		    (TopThick + Protrusion)]);								// battery access
    
    	translate([(CaseLengthOffset - Protrusion),
    			   (CaseWidthOffset - Protrusion),
    			   (CaseThickOffset + BatteryThick)])
    	  cube([(CaseLength + 2*Protrusion),
    		    (CaseWidth + 2*Protrusion),
    		    (TopThick + Protrusion)]);								// battery insertion allowance
    
    	translate([(BatteryLength - Protrusion),
    			    (CaseWidth/2 + CaseWidthOffset),
    			    (CaseThickOffset + ThumbRadius)])
    	  rotate([90,0,0])
    		rotate([0,90,0])
    		  cylinder(r=ThumbRadius,
    				   h=(WallThick + GuideRadius + 2*Protrusion),
    				   $fn=22);											// remove thumb notch
    
    	PinAssembly();
    
      }
    
    }
    
    module Lid() {
    
      difference() {
    	translate([0,0,(CaseThick/2 - BaseThick - BatteryThick)])
    	  roundedBox([LidLength,
    				 CaseWidth,CaseThick],CornerRadius);
    
    	translate([0,0,-(CaseThick/2)])
    	  cube([(LidLength + 2*Protrusion),
    		    (CaseWidth + 2*Protrusion),
    		    (CaseThick)],center=true);
      }
    
    }
    
    module PlugShape() {
    
      difference() {
    	cylinder(r=SpringPlugDia/2,h=SpringPlugLength,$fn=SpringPlugSides);
    	translate([0,0,-Protrusion])
    	  PolyCyl(PinShaftDia,(SpringPlugLength + 2*Protrusion),SpringPlugSides/2);
      }
    }
    
    module Plugs() {
      translate([0,ContactOC,0])
    	PlugShape();
      translate([0,-ContactOC,0])
    	PlugShape();
    }
    
    //-------------------
    // Build it!
    
    ShowPegGrid();
    
    if (Layout == "Case")
      Case();
    
    if (Layout == "Lid")
      Lid();
    
    if (Layout == "Plugs")
      Plugs();
    
    if (Layout == "Show") {								// reveal pin assembly
      difference() {
    	Case();
    
    	translate([(CaseLengthOffset - Protrusion),
    			   (CaseWidthOffset - Protrusion + WallThick + ContactOffset + ContactOC),
    			   (BaseThick + ContactHeight)])
    	  cube([(-CaseLengthOffset + Protrusion),
    			 (CaseWidth + 2*Protrusion),
    			 CaseThick + BaseThick - ContactHeight + Protrusion]);
    
    	translate([(CaseLengthOffset - Protrusion),
    			   (CaseWidthOffset - Protrusion),
    			   -Protrusion])
    	  cube([(-CaseLengthOffset + Protrusion),
    			 (WallThick + GuideRadius + ContactOffset + Protrusion),
    			 CaseThick]);
      }
    
      translate([ExtendRelax,ContactOffset,(CaseThickOffset + ContactHeight)]) {	// pins
    	rotate([0,270,0]) {
    	  %PinShape();
    //	  translate([0,(2*ContactOC),0])
    //		%PinShape();
    	}
      }
    
      translate([CaseLengthOffset,ContactOffset,(CaseThickOffset + ContactHeight)])
    	rotate([0,90,0])
    	  PlugShape();
    }
    
    if (Layout == "Build") {
      translate([-(CaseLength/2 + CaseLengthOffset),-(CaseWidthOffset - BuildOffset),0])
    	Case();
      translate([0,(CaseLengthOffset/2 - BuildOffset),0])
    	rotate([0,0,90])
    	  Lid();
      translate([CaseLengthOffset - SpringPlugDia,-CaseWidth/2,0])
    	Plugs();
      translate([(CaseLengthOffset + SpringPlugDia),-CaseWidth/2,0])	// extra set of plugs
    	Plugs();
    }
    
    if (Layout == "Fit") {
      Case();
      translate([(-LidLength/2 + ExtendRelax),
    			(CaseWidth/2 + CaseWidthOffset),
    			(BaseThick + BatteryThick)])
    	  Lid();
      translate([ExtendRelax,ContactOffset,CaseThickOffset + ContactHeight]) {	// pins
    	rotate([0,270,0]) {
    	  %PinShape();
    	  translate([0,(2*ContactOC),0])
    		%PinShape();
    	}
      }
    
      translate([CaseLengthOffset,
    			(ContactOffset + ContactOC),
    			(CaseThickOffset + ContactHeight)])
      rotate([0,90,0])
    	Plugs();
    
    }
    

    (*) Modulo, of course, simply buying a $5 charger from eBay and gutting it. What’s the fun in that?

  • NB-5L Holder: Plug Spring Holder

    After that failure, I thought maybe making the spring guide pocket a bit wider and seating the spring wire in a solid plug would work. A tweak to the OpenSCAD script produced this, along with slightly larger locating ribs around the battery compartment:

    Plug spring - solid model
    NB-5L Holder – Plug spring – solid model

    A closer look at the plug spring assembly:

    NB-5L Holder - Plug spring - detail
    NB-5L Holder – Plug spring – detail

    The hole is now slightly larger, distinct from the side of the pocket, and the partition between the pocket and plug (although something of a formality) seats the plug during assembly. The plug started out at 3 mm in diameter, as I intended to try ramming a heated wire into a length of filament. That worked, mmmm, somewhat poorly, so I drilled a hole in a length of filament:

    Music wire in filament plug
    Music wire in filament plug

    Unfortunately, that whole bodge didn’t work any better than the spring in the first pass at a holder, so I gave up and cast the springs in epoxy. The OpenSCAD code produces a 5 mm diameter hole that should provide a larger epoxy plate with better grip than the 3 mm holes in this picture, but it probably won’t make much difference:

    Music wire in epoxy plug
    Music wire in epoxy plug

    The alert reader will note a complete faceplant: yeah, I forgot to solder the wires into the pins before blobbing the springs in place. Fortunately, the epoxy cures slowly enough that I could:

    • Take the picture
    • Immediately see the obvious problem
    • Ease the music wire springs out just a tidge
    • Extract the pins
    • Quick-like-a-bunny solder wires to pins
    • Insert pins with proper polarity
    • Ease springs back in place

    I hate it when that happens…

    With springs & wires properly in place and the epoxy cured overnight, the pins had considerably better springiness and free motion than before, although they didn’t have quite the range of travel I wanted. I think the spring wire bent slightly on the first push, as the pins never came quite as far out after that.

    So this was a qualified success, but not a solid win. Time for round three…

    The OpenSCAD source code:

    // Holder for Canon NB-5L Li-Ion battery
    // Ed Nisley KE4ZNU August 2011
    
    include </home/ed/Thing-O-Matic/lib/MCAD/units.scad>
    include </home/ed/Thing-O-Matic/lib/MCAD/boxes.scad>
    include </home/ed/Thing-O-Matic/Useful Sizes.scad>
    
    // Layout options
    
    Layout = "Show";					// Case Lid Show Build Fit
    
    //- Extrusion parameters - must match reality!
    //  Print with +2 shells and 3 solid layers
    
    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 = 3.0;			// clearance for build layout
    
    //- Battery dimensions - rationalized from several samples
    //  Coordinate origin at battery corner by contact plates on bottom surface
    
    BatteryLength = 45.25;
    BatteryWidth = 32.17;
    BatteryThick =  7.85;
    
    ContactWidth = 2.10;
    ContactLength = 4.10;
    ContactRecess = 0.85;
    
    ContactOC = 3.18;			// center-to-center across contact face
    ContactOffset = 4.45;		// offset from battery edge
    ContactHeight = 3.05;		// offset from battery bottom plane
    
    AlignThick = 2.2;			// alignment recesses on contact face
    AlignDepth = 2.0;			// into face
    AlignWidth1 = 0.7;			// across face at contacts
    AlignWidth2 = 2.8;			//  ... other edge
    
    //- Pin dimensions
    
    PinTipDia = 1.6;
    PinTipLength = 10.0;
    
    PinTaperLength = 2.3;
    
    PinShaftDia = 2.4;
    PinShaftLength = 6.8;
    
    PinFerruleDia = 3.0;
    PinFerruleLength = 2.0;
    
    PinLength = PinTipLength + PinTaperLength + PinShaftLength + PinFerruleLength;
    
    PinHoleOffset = 13.9;			// tip to spring hole
    
    ExtendRelax = 1.5 + ContactRecess;		// pin extension when no battery is present
    ExtendOvertravel = 1.0;					//  ... beyond engaged position
    
    //- Holder dimensions
    
    GuideRadius = ThreadWidth;						// friction fit ridges
    GuideOffset = 10;
    WallThick = 4*ThreadWidth;						// holder sidewalls
    
    BaseThick = IntegerMultiple(6.0,ThreadThick);	// bottom of holder to bottom of battery
    TopThick = 6*ThreadThick;	// top of battery to top of holder
    
    ThumbRadius = 10.0;			// thumb opening at end of battery
    
    CornerRadius = 3*ThreadThick;			// nice corner rounding
    
    CaseLength = 2*WallThick + PinLength - ExtendRelax + ExtendOvertravel + BatteryLength + GuideRadius;
    CaseWidth = 2*WallThick + 2*GuideRadius + BatteryWidth;
    CaseThick = BaseThick + BatteryThick + TopThick;
    
    //- XY origin at front left battery corner, Z on platform below that
    
    CaseLengthOffset = -(WallThick + PinLength - ExtendRelax + ExtendOvertravel);
    CaseWidthOffset = -(WallThick + GuideRadius);
    CaseThickOffset = BaseThick;
    
    LidLength = ExtendRelax - CaseLengthOffset;
    
    //- Spring dimensions
    
    SpringPlugDia = 5.0;					// epoxy plug holding spring wire
    SpringPlugLength = IntegerMultiple(1.5,ThreadThick);
    
    SpringDia = 0.024 * inch;	// music wire spring
    SpringTravel = ExtendRelax + ExtendOvertravel;
    SpringLength = BaseThick + ContactHeight - SpringPlugLength - 2*ThreadThick;
    
    echo(str("Spring wire from end: ",WallThick + PinLength - PinHoleOffset));
    echo(str("            from side: ",WallThick + GuideRadius + ContactOffset));
    echo(str("Pin spacing on centers: ",ContactOC));
    
    //----------------------
    // 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);
    
    }
    
    //-------------------
    
    //-- Guides for tighter friction fit
    
    module Guides() {
      	  translate([GuideOffset,-GuideRadius,CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    	  translate([GuideOffset,(BatteryWidth + GuideRadius),CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    	  translate([(BatteryLength - GuideOffset),-GuideRadius,CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    	  translate([(BatteryLength - GuideOffset),(BatteryWidth + GuideRadius),CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    	  translate([(BatteryLength + GuideRadius),GuideOffset/2,CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    	  translate([(BatteryLength + GuideRadius),(BatteryWidth - GuideOffset/2),CaseThickOffset])
    		PolyCyl(2*GuideRadius,(BatteryThick - Protrusion),4);
    
    }
    
    //-- Contact pins (holes therefore)
    
    module PinShape() {
    
    PolyPin = false;
    
      union() {
    	if (PolyPin)
    	  PolyCyl(PinTipDia,(PinTipLength + Protrusion));
    	else
    	  cylinder(r=(PinTipDia + HoleWindage)/2,h=(PinTipLength + Protrusion),$fn=6);
    
    	translate([0,0,PinTipLength])
    	  if (PolyPin)
    		PolyCyl(PinShaftDia,(PinTaperLength + PinShaftLength + Protrusion));
    	  else
    		cylinder(r=(PinShaftDia + HoleWindage)/2,
    				 h=(PinTaperLength + PinShaftLength + Protrusion),$fn=6);
    
    	translate([0,0,(PinLength - PinFerruleLength)])
    	  if (PolyPin)
    		PolyCyl(PinFerruleDia,(PinFerruleLength + Protrusion));
    	  else
    		cylinder(r=(PinFerruleDia + HoleWindage)/2,
    				 h=(PinFerruleLength + Protrusion),$fn=6);
    
    	translate([0,0,PinLength])
    	  if (PolyPin)
    		PolyCyl(PinFerruleDia,PinLength);			// very long holes to punch case
    	  else
    		cylinder(r=(PinFerruleDia + HoleWindage)/2,h=PinLength,$fn=6);
      }
    
    }
    
    module PinAssembly() {
    
      translate([ExtendRelax,ContactOffset,CaseThickOffset + ContactHeight]) {
    	rotate([0,270,0]) {
    	  PinShape();												// pins
    	  translate([0,(2*ContactOC),0])
    		PinShape();
    	}
      }
    
      translate([-(PinHoleOffset - ExtendRelax + SpringTravel/2 - SpringDia/2 - HoleWindage/2),
    			 ContactOffset,
    			 (CaseThickOffset + ContactHeight - SpringLength/2 - Protrusion)]) {
    	  cube([(SpringTravel + SpringDia/2 + HoleWindage),
    		    PinShaftDia,
    			(SpringLength + 2*Protrusion)],
    		   center=true);										// spring deflection pocket
    	  translate([0,(2*ContactOC),0])
    		cube([(SpringTravel + SpringDia/2 + HoleWindage),
    			 PinShaftDia,
    			 (SpringLength + 2*Protrusion)],
    			 center=true);
      }
    
      translate([-(PinHoleOffset - ExtendRelax),
    			 ContactOffset,
    			 (-Protrusion/2)]) {
    	PolyCyl(SpringDia,(BaseThick + ContactHeight + Protrusion),4);		// spring wire
    	PolyCyl(SpringPlugDia,(SpringPlugLength + Protrusion));				// wire holder
    	translate([0,(2*ContactOC),0]) {
    	  PolyCyl(SpringDia,(BaseThick + ContactHeight + Protrusion),4);
    	  PolyCyl(SpringPlugDia,(SpringPlugLength + Protrusion));
    	}
      }
    
    }
    
    //-- Case with origin at battery corner
    
    module Case() {
    
      difference() {
    
    	union() {
    
    	  difference() {
    		translate([(CaseLength/2 + CaseLengthOffset),
    				  (CaseWidth/2 + CaseWidthOffset),
    				  (CaseThick/2)])
    		  roundedBox([CaseLength,CaseWidth,CaseThick],CornerRadius); 	// basic case shape
    
    		translate([-ExtendOvertravel,-GuideRadius,CaseThickOffset])
    		  cube([(BatteryLength + GuideRadius + ExtendOvertravel),
    				(BatteryWidth + 2* GuideRadius),
    				(BatteryThick + Protrusion)]);						// battery space
    
    	  }
    
    	  Guides();
    
    	  translate([-ExtendOvertravel,-GuideRadius,BaseThick])
    		cube([(AlignDepth + ExtendOvertravel),
    			  (AlignWidth1 + GuideRadius),
    			  AlignThick]);											// alignment blocks
    	  translate([-ExtendOvertravel,
    				 (BatteryWidth - AlignWidth2),
    				 BaseThick])
    		cube([(AlignDepth + ExtendOvertravel),
    			  (AlignWidth2 + GuideRadius),
    			  AlignThick]);
    
    	}
    
    	translate([(-ExtendOvertravel),
    			   (CaseWidthOffset - Protrusion),
    			   (CaseThickOffset + BatteryThick)])
    	  cube([CaseLength,
    		    (CaseWidth + 2*Protrusion),
    		    (TopThick + Protrusion)]);								// battery access
    
    	translate([(CaseLengthOffset - Protrusion),
    			   (CaseWidthOffset - Protrusion),
    			   (CaseThickOffset + BatteryThick)])
    	  cube([(CaseLength + 2*Protrusion),
    		    (CaseWidth + 2*Protrusion),
    		    (TopThick + Protrusion)]);								// battery insertion allowance
    
    	translate([(BatteryLength - Protrusion),
    			    (CaseWidth/2 + CaseWidthOffset),
    			    (CaseThickOffset + ThumbRadius)])
    	  rotate([90,0,0])
    		rotate([0,90,0])
    		  cylinder(r=ThumbRadius,
    				   h=(WallThick + GuideRadius + 2*Protrusion),
    				   $fn=22);											// remove thumb notch
    
    	PinAssembly();
    
      }
    
    }
    
    module Lid() {
    
      difference() {
    	translate([0,0,(CaseThick/2 - BaseThick - BatteryThick)])
    	  roundedBox([LidLength,
    				 CaseWidth,CaseThick],CornerRadius);
    
    	translate([0,0,-(CaseThick/2)])
    	  cube([(LidLength + 2*Protrusion),
    		    (CaseWidth + 2*Protrusion),
    		    (CaseThick)],center=true);
      }
    
    }
    
    //-------------------
    // Build it!
    
    ShowPegGrid();
    
    if (Layout == "Case")
      Case();
    
    if (Layout == "Lid")
      Lid();
    
    if (Layout == "Show") {								// reveal pin assembly
      difference() {
    	Case();
    
    	translate([(CaseLengthOffset - Protrusion),
    			   (CaseWidthOffset - Protrusion + WallThick + ContactOffset + ContactOC),
    			   (BaseThick + ContactHeight)])
    	  cube([(-CaseLengthOffset + Protrusion),
    			 (CaseWidth + 2*Protrusion),
    			 CaseThick + BaseThick - ContactHeight + Protrusion]);
    
    	translate([(CaseLengthOffset - Protrusion),
    			   (CaseWidthOffset - Protrusion),
    			   -Protrusion])
    	  cube([(-CaseLengthOffset + Protrusion),
    			 (WallThick + GuideRadius + ContactOffset + Protrusion),
    			 CaseThick]);
      }
    
      translate([ExtendRelax,ContactOffset,CaseThickOffset + ContactHeight]) {	// pins
    	rotate([0,270,0]) {
    	  %PinShape();
    //	  translate([0,(2*ContactOC),0])
    //		%PinShape();
    	}
      }
    
    }
    
    if (Layout == "Build") {
      translate([-(CaseLength/2 + CaseLengthOffset),-(CaseWidthOffset - BuildOffset),0])
    	Case();
      translate([0,(CaseLengthOffset/2 - BuildOffset),0])
    	rotate([0,0,90])
    	  Lid();
    
    }
    
    if (Layout == "Fit") {
      Case();
      translate([(-LidLength/2 + ExtendRelax),
    			(CaseWidth/2 + CaseWidthOffset),
    			(BaseThick + BatteryThick)])
    	  Lid();
      translate([ExtendRelax,ContactOffset,CaseThickOffset + ContactHeight]) {	// pins
    	rotate([0,270,0]) {
    	  %PinShape();
    	  translate([0,(2*ContactOC),0])
    		%PinShape();
    	}
      }
    
    }
    
  • Generic NB-5L Battery Performance: FAIL

    The first version of the NB-5L battery holder worked well enough to get some initial performance curves from the assortment of eBay batteries. I bought one apiece from four different vendors for around $3 each; an order of magnitude less than OEM Canon NB-5L camera batteries. Based on past experience, I didn’t expect much and, lo-and-behold, I wasn’t disappointed in the least! Clicky for more dots:

    Canon NB-5L - first tests
    Canon NB-5L – first tests

    Using a 500 mA discharge current (roughly C/2) seemed reasonable, but I have no idea what the camera actually draws and the Canon manual isn’t forthcoming. These are all hot off the Canon charger.

    That nice long curve on the top is the OEM Canon NB-5L that came with the camera and delivers pretty much its rated 1050 mAh.

    The generic batteries have two faults:

    • Low discharge voltage (high internal resistance?)
    • Much less than their claimed capacity (they lie!)

    The one labeled D Group was advertised as 1500 mAh, which seemed unreasonable on the face of it. The battery case says 1050 mAh and the vendor said their manufacturer “must have shipped them the wrong batteries”. Yeah, right, like they hadn’t noticed up. They wanted me to return it (on their dime, by “refusing” the shipment, which is, AFAICT, prohibited after you open the package), which says that they didn’t have any batteries with “1500 mAh” printed on the side for an exchange. Of course, their advertising for the other NB-5L batteries they offer on eBay hasn’t changed, so … they lie!

    The Anonymous Gray battery is particularly feeble; I may harvest the frame and connector and battery protection circuit to build an external battery pack with far more capacity.

    The crinkly black trace comes from testing that battery with wires taped in place before I got the first version of the holder up & running well enough to take the rest of the measurements.

    Only one generic battery has a manufacturer’s name, two lack regulatory agency markings (not that I expect any to comply with the requirements implied by those markings), and all four are obviously junk. I’ll use them for around-the-house pix with the charger close at hand, but … now we all know that you don’t get something for nothing. No surprise there, eh?