Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.
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
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?
The first pass at a holder for a Canon NB-5L battery didn’t quite work, but the failure was instructive. The overall layout was fine; the battery fit well, it’s just that the pins and springs didn’t function properly.
NB-5L Holder – Internal spring – solid model
I thought a simple straight music wire spring pushed into a hole with a pocket to limit the pin travel would suffice. Watching it build showed that the pocket came out too small and the spring hole was almost completely closed, despite a bit of HoleWindage.
Here’s a closer look at the spring arrangement, with a pocket at the bottom for an epoxy blob after it became obvious the ABS couldn’t properly anchor the pin.
NB-5L Holder – Internal spring – detail
Drilling out the holes to allow free pin movement ended up with #52 for the tip, #40 for the shaft, and #31 for the ferrule at the end; choose for an easy slip fit.
I used 0.024 inch (0.6 mm) music wire, which fit neatly into the pin’s inspection hole. Drilling that (#73 drill) into the nearly closed hole through the bottom showed that things weren’t working well: far too much resistance along what should be a half-open channel.
With the pin in place and a stub of wire pushed upward into the pin, the pins moved very stiffly and tended to not return to their rest position. Minus the spring wire, they slid freely.
After a bit of this and that, I tried 0.020 music wire, which didn’t have enough return force at all.
The end of the spring wire moved around a lot more than I think it should have, gradually turning the hole into an oval. I drilled two pockets in the bottom (and changed the solid model to match what you see above) and cast a dab of epoxy into each hole; that solved the moving-around problem, but the pins were still too stiff.
NB-5L Holder – first version – bottom
The weird orange color comes from a few layers of Safety Orange filament that melded into white in mid-flight. No reason to use fancy filament on a prototype, methinks; that’s what was in place when I started.
But I glued the cap on anyway to see if the pins would work well enough to run some early battery tests. This is what it looked like before gluing the cap:
NB-5L Holder – first version
The Powerpole connectors came from one of those packs, with the wires soldered into the ends of the pins so as to not block the inspection holes that I’m using for the music wire springs.
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
//- Spring dimensions
ExtendRelax = 1.5 + ContactRecess; // pin extension when no battery is present
ExtendOvertravel = 1.0; // ... beyond engaged position
SpringDia = 0.024 * inch; // music wire spring
SpringTravel = ExtendRelax + ExtendOvertravel;
SpringLength = 4.0 + PinShaftDia/2; // free length below pin centerline
//- 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;
SpringPlugDia = 3.0; // filament snippet holding spring wire
SpringPlugLength = IntegerMultiple(1.0,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]) { // pins
rotate([0,270,0]) {
PinShape();
translate([0,(2*ContactOC),0])
PinShape();
}
}
translate([-(PinHoleOffset - ExtendRelax + SpringTravel/2),
ContactOffset,
(CaseThickOffset + ContactHeight - SpringLength/2 + Protrusion/2)]) {
cube([SpringTravel,
(2*SpringDia),
(SpringLength + Protrusion)],
center=true); // spring deflection pocket
translate([0,(2*ContactOC),0])
cube([SpringTravel,
(2*SpringDia),
(SpringLength + Protrusion)],
center=true);
}
translate([-(PinHoleOffset - ExtendRelax),
ContactOffset,
(-Protrusion/2)]) {
PolyCyl(SpringDia,(BaseThick + ContactHeight + Protrusion)); // spring wire
PolyCyl(SpringPlugDia,(SpringPlugLength + Protrusion)); // wire holder
translate([0,(2*ContactOC),0]) {
PolyCyl(SpringDia,(BaseThick + ContactHeight + Protrusion));
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();
}
}
}
Before trotting off to college, my Shop Assistant repaired a pair of headphones she’d scrounged at the end of her Senior year; they were nice Skullcandy on-ear phones with one dead cup. The previous owner evidently wasn’t into fixing things and bequeathed them to her.
She dismantled the offending cup to find some really grody soldering that ought to have produced a cold solder joint: the common lead wasn’t making contact with the plug. Alas, the only way to proceed was to slice the injection-molded cover off the plug and see what was inside; to our surprise, everything looked fine.
We then cut an inch off the cable and pulled the conductors out:
Broken headphone cable conductors
Well, that was easy! Here’s a closer look:
Broken headphone conductor – detail
The three broken wires (only one of which was completely disconnected) failed exactly at the end of that plug covering. It had strain relief notches, but we guessed the previous owner had no qualms about bending the cable hard against the end of the plug.
She soldered it up, shrank some heat stink tubing around the plug and its sliced cover, wrapped self-vulcanizing tape around the junction for better strain relief, and it’s all good. She’ll get plenty of use out of the headphones in the dorm …
A first cut at a holder for Canon NB-5L batteries with those dimensions, with the intent of connecting them to a battery tester.
NB-5L Battery Holder Doodle
The cloud in the middle of the bottom holds pin dimensions, which I measured after I’d been doodling for a bit. They come from my heap; they’re nice heavy-gold plated male pins (yeah, I have the other gender, too) intended for a multi-pin connector. They have a convenient hole that’s normally used to verify you’ve actually soldered the wire properly. I plan to stick a music-wire spring in the hole and secure it through the bottom of the holder with a rectangular pocket below the pin that limits the travel in both directions. Drilling the hole completely through the pin to let the spring wire stick out would prevent having it fall out at the end of travel.
The spring pocket dimensions are right down around the very limit of the feature sizes my TOM can achieve. I’m not sure those blind holes will actually open up far enough.
I’m also not sure the Powerpoles will actually fit in there like that. There’s nothing wrong with pigtail leads.
It’s obviously styled after the Official Canon NB-5L charger, although they use nice bent-steel spring contacts that are trivially easy to make in mass production:
Here’s a howling 1200 Hz mechanical resonance excited by a full-step drive at 290 rpm, with the resonance running at 5 cycles for every 4 full steps.:
1 Step Res 240 Hz
[Update: top trace is voltage from big stepper-as-generator, bottom is small stepper winding current = 1 A/div.]
Switching to 1/2 microstepping at the same rotational speed pretty well tamps it down:
Half Step Res 240 Hz
However, here’s the same resonance excited by 1/2 microstepping at half the rotational speed, oscillating at 9 irregular cycles for every 8 half-steps:
Half res 120 Hz
The resonance was strong enough to back the setscrew completely out of the generator end of the shaft, leaving me wondering why the output was dead when the motor was obviously running. Yes, several times…
Conventional wisdom has it that stepper motor torque decreases as the number of microsteps per full step increases. One reasonably careful measurement trumps a kilo-opinion, so here’s a chart (clicky for bigger) of measurements to mull over:
Microstepping Mode vs Speed – 17PM-J034
Each group of like-colored dots marks the results for full, 1/2, 1/4, 1/8, and 1/16 microstepping with the same load resistance. The caret marks the full-step data point within each group. The load resistances range from a dead short (about 1 Ω due to winding resistance) on the left to 50 Ω on the right.
While I’ll grant the existence of the occasional data-collection error, it’s pretty obvious that:
Torque is reasonably constant regardless of microstepping mode
Full-step mode doesn’t produce more torque and, indeed, produces considerably less under heavy loads
Now, one can argue that the A4988 doesn’t operate in real full-step mode, because it energizes both windings at 1/√2 of the maximum current setting for each full step rather than energizing a single winding at the maximum current. That may be true, but conventional wisdom seems to not bother with such details when opining about torque, either…
As nearly as I can tell, 1/8 microstepping gives as much torque as you’re likely to get from the motor, while having reasonably smooth motion that avoids exciting mechanical resonances.
That chart (or one remarkably like it) will appear in an upcoming Circuit Cellar column. The tonnage of data supporting those dots suggests building an automated dynamometer would be a Good Idea …
Just to see what happened, I reversed the stepper dynamometer and drove the larger (480-ish mN·m) stepper in 1/8 step mode while recording the short-circuit current from the smaller (anonymous) stepper. Slowly cranking the step frequency upward produced this trace when the stepper stalled:
48 mm motor – pullout – 30 rps
The bottom trace shows 30.4 rev/s = 191 rad/s = 1824 rev/min: a pretty good speed for a loaded stepper! The rotor began slowing just before the last sync pulse, but hadn’t lost any appreciable speed.
The current scale is 0.5 A/div (set on the Tek probe amp), which makes the winding current (500 mA/div × 10.4 mVpk / 10 mV/div) = 520 mApk. The scope’s computed rms value includes the waveform after the stall, which isn’t helpful.
The small stepper has a 2.1 Ω winding resistance, so a short-circuit load dissipates (0.52 A)2 × 2.1 Ω = 567 mWpk in each winding. The rms equivalent is half of that, so the total rms power is about half a watt, essentially all internal to the motor.
The pull-out torque depends on the peak torque load, not the rms and not the sum of the two windings, so it’s 0.6 W / (191 rad/s) = 3 mN·m, which doesn’t sound a lot for a 480 mN·m motor until you consider the screaming 6000 full step/s speed: pretty much off-scale high on most of the torque-vs-speed graphs you’ll see. Not much torque left out at that end of the curve, indeed.
In order to stall the motor at lower speeds, the load stepper must generate enough voltage into the load resistor (here, the winding resistance) to push the power/speed ratio (the torque!) above the drive motor’s ability. That implies the load stepper must always be larger than the drive stepper, which means I must conjure up a bracket for that NEMA 23 motor that’s been holding down a stack of papers on my desk…
Incidentally, the voltage required to produce that load current is 0.52 mA × 2.1 Ω = 1.1 V. The 0.58 v/(rev/s) open-circuit generator constant for the smaller motor predicts 0.58 v/rev/s × 30.4 rev/s = 17.7 V. Obviously, you can’t get from the open-circuit unloaded generator constant to the short-circuit loaded voltage… Lenz’s Law gets in the way.